mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 15:34:30 -04:00
Merge pull request #20 from M-E-Development-Design/v5-develop
V5 develop
This commit is contained in:
commit
08f33ac3e7
2
.env.ci
2
.env.ci
@ -24,4 +24,4 @@ PHANTOMJS_PDF_GENERATION=false
|
||||
CACHE_DRIVER=redis
|
||||
QUEUE_CONNECTION=redis
|
||||
SESSION_DRIVER=redis
|
||||
PDF_GENERATOR=hosted_ninja
|
||||
PDF_GENERATOR=snappdf
|
6
.github/workflows/react_release.yml
vendored
6
.github/workflows/react_release.yml
vendored
@ -38,6 +38,9 @@ jobs:
|
||||
sudo php artisan cache:clear
|
||||
sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
|
||||
sudo find ./ -type d -exec chmod 755 {} \;
|
||||
- name: Set current date to variable
|
||||
id: set_date
|
||||
run: echo "current_date=$(date '+%Y-%m-%d')" >> $GITHUB_ENV
|
||||
|
||||
- name: Prepare React FrontEnd
|
||||
run: |
|
||||
@ -46,10 +49,11 @@ jobs:
|
||||
git checkout develop
|
||||
cp .env.example .env
|
||||
cp ../vite.config.ts.react ./vite.config.js
|
||||
sed -i '/"version"/c\ "version": " Latest Build - ${{ env.current_date }}",' package.json
|
||||
npm i
|
||||
npm run build
|
||||
cp -r dist/* ../public/
|
||||
mv dist/index.html ../resources/views/react/index.blade.php
|
||||
mv ../public/index.html ../resources/views/react/index.blade.php
|
||||
|
||||
- name: Prepare JS/CSS assets
|
||||
run: |
|
||||
|
43
README.md
43
README.md
@ -1,5 +1,5 @@
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/>
|
||||
<a href ="https://www.youtube.com/watch?v=CxGxXiotv0I" target="_blank" title="Invoice Ninja Overview Video"><img src="https://raw.githubusercontent.com/hillelcoren/invoice-ninja/master/public/images/round_logo.png" alt="Sublime's custom image"/></a>
|
||||
</p>
|
||||
|
||||

|
||||
@ -8,25 +8,30 @@
|
||||
|
||||
# Invoice Ninja 5
|
||||
|
||||
## [Hosted](https://www.invoiceninja.com) | [Self-Hosted](https://www.invoiceninja.org)
|
||||
Invoice Ninja Version 5 is here! We've taken the best parts of version 4 and added the most requested features to create an invoicing application like no other. Check the [Invoice Ninja YouTube Channel](https://www.youtube.com/@appinvoiceninja) to get up to speed, or try the [Demo](https://react.invoicing.co/demo) now.
|
||||
|
||||
Join us on [Slack](http://slack.invoiceninja.com), [Discord](https://discord.gg/ZwEdtfCwXA), [Support Forum](https://forum.invoiceninja.com)
|
||||
**Choose your setup**
|
||||
|
||||
## Introduction
|
||||
- [Hosted](https://www.invoiceninja.com): Our hosted version is a Software as a Service (SaaS) solution. You're up and running in under 5 minutes, with no need to worry about hosting or server infrastructure.
|
||||
- [Self-Hosted](https://www.invoiceninja.org): For those who prefer to manage their own hosting and server infrastructure. This version gives you full control and flexibility.
|
||||
|
||||
Version 5 of Invoice Ninja is here!
|
||||
We took the best parts of version 4 and add the most requested features
|
||||
to produce a invoicing application like no other.
|
||||
All Pro and Enterprise features from the hosted app are included in the open-source code. We offer a $30 per year white-label license to remove the Invoice Ninja branding from client-facing parts of the app.
|
||||
|
||||
All Pro and Enterprise features from the hosted app are included in the open code.
|
||||
We offer a $30 per year white-label license to remove the Invoice Ninja branding from client facing parts of the app.
|
||||
#### Get social with us
|
||||
|
||||
* [Videos](https://www.youtube.com/@appinvoiceninja)
|
||||
* [API Documentation](https://api-docs.invoicing.co/)
|
||||
* [APP Documentation](https://invoiceninja.github.io/)
|
||||
* [Support Forum](https://forum.invoiceninja.com)
|
||||
* [Slack](http://slack.invoiceninja.com)
|
||||
* [Discord](https://discord.gg/ZwEdtfCwXA)
|
||||
* [Instagram](https://www.instagram.com/appinvoiceninja)
|
||||
|
||||
## Setup
|
||||
#### Documentation
|
||||
|
||||
* [Invoice Ninja - API](https://api-docs.invoicing.co/)
|
||||
* [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/)
|
||||
* [Invoice Ninja - User Guide](https://invoiceninja.github.io/en/user-guide/)
|
||||
* [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/)
|
||||
|
||||
## Installation Options and Clients
|
||||
|
||||
### Mobile Apps
|
||||
* [iPhone](https://apps.apple.com/app/id1503970375?platform=iphone)
|
||||
@ -39,16 +44,21 @@ We offer a $30 per year white-label license to remove the Invoice Ninja branding
|
||||
* [Linux - Snap](https://snapcraft.io/invoiceninja)
|
||||
* [Linux - Flatpak](https://flathub.org/apps/com.invoiceninja.InvoiceNinja)
|
||||
|
||||
### Installation Options
|
||||
### Self-Hosted Server Installation
|
||||
**Note:** The self-hosted options do support the desktop and mobile apps.
|
||||
|
||||
* [Server or VM](https://invoiceninja.github.io/en/self-host-installation/)
|
||||
* [Docker File](https://hub.docker.com/r/invoiceninja/invoiceninja/)
|
||||
* [Cloudron](https://cloudron.io/store/com.invoiceninja.cloudronapp.html)
|
||||
* [Cloudron](https://www.cloudron.io/store/com.invoiceninja.cloudronapp2.html)
|
||||
* [Softaculous](https://www.softaculous.com/apps/ecommerce/Invoice_Ninja)
|
||||
|
||||
### Recommended Providers
|
||||
* [Stripe](https://stripe.com/)
|
||||
* [Postmark](https://postmarkapp.com/)
|
||||
|
||||
## Quick Hosting Setup
|
||||
## [Advanced] Quick Hosting Setup
|
||||
|
||||
In addition to the official [Invoice Ninja - Self-Hosted Installation Guide](https://invoiceninja.github.io/en/self-host-installation/) we have a few commands for you.
|
||||
|
||||
```sh
|
||||
git clone --single-branch --branch v5-stable https://github.com/invoiceninja/invoiceninja.git
|
||||
@ -84,6 +94,7 @@ pass: password
|
||||
```
|
||||
## Developers Guide
|
||||
|
||||
In addition to the official [Invoice Ninja - Developer Guide](https://invoiceninja.github.io/en/developer-guide/) we've got your back with some insights.
|
||||
|
||||
### App Design
|
||||
|
||||
|
@ -1 +1 @@
|
||||
5.10.13
|
||||
5.10.16
|
@ -177,7 +177,6 @@ class BackupUpdate extends Command
|
||||
$doc_bin = $document->getFile();
|
||||
} catch(\Exception $e) {
|
||||
nlog("Exception:: BackupUpdate::" . $e->getMessage());
|
||||
nlog($e->getMessage());
|
||||
}
|
||||
|
||||
if ($doc_bin) {
|
||||
|
44
app/DataMapper/Tax/PL/Rule.php
Normal file
44
app/DataMapper/Tax/PL/Rule.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
/**
|
||||
* Invoice Ninja (https://invoiceninja.com).
|
||||
*
|
||||
* @link https://github.com/invoiceninja/invoiceninja source repository
|
||||
*
|
||||
* @copyright Copyright (c) 2024. Invoice Ninja LLC (https://invoiceninja.com)
|
||||
*
|
||||
* @license https://www.elastic.co/licensing/elastic-license
|
||||
*/
|
||||
|
||||
namespace App\DataMapper\Tax\PL;
|
||||
|
||||
use App\DataMapper\Tax\DE\Rule as DERule;
|
||||
|
||||
class Rule extends DERule
|
||||
{
|
||||
/** @var string $seller_region */
|
||||
public string $seller_region = 'EU';
|
||||
|
||||
/** @var bool $consumer_tax_exempt */
|
||||
public bool $consumer_tax_exempt = false;
|
||||
|
||||
/** @var bool $business_tax_exempt */
|
||||
public bool $business_tax_exempt = false;
|
||||
|
||||
/** @var bool $eu_business_tax_exempt */
|
||||
public bool $eu_business_tax_exempt = true;
|
||||
|
||||
/** @var bool $foreign_business_tax_exempt */
|
||||
public bool $foreign_business_tax_exempt = false;
|
||||
|
||||
/** @var bool $foreign_consumer_tax_exempt */
|
||||
public bool $foreign_consumer_tax_exempt = false;
|
||||
|
||||
/** @var float $tax_rate */
|
||||
public float $tax_rate = 0;
|
||||
|
||||
/** @var float $reduced_tax_rate */
|
||||
public float $reduced_tax_rate = 0;
|
||||
|
||||
public string $tax_name1 = 'VAT';
|
||||
|
||||
}
|
@ -17,7 +17,7 @@ class TaxModel
|
||||
public string $seller_subregion = 'CA';
|
||||
|
||||
/** @var string $version */
|
||||
public string $version = 'alpha';
|
||||
public string $version = 'beta';
|
||||
|
||||
/** @var object $regions */
|
||||
public object $regions;
|
||||
@ -28,15 +28,38 @@ class TaxModel
|
||||
* @param TaxModel $model
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(public ?TaxModel $model = null)
|
||||
public function __construct(public mixed $model = null)
|
||||
{
|
||||
|
||||
if(!$this->model) {
|
||||
if(!$model) {
|
||||
$this->regions = $this->init();
|
||||
} else {
|
||||
$this->regions = $model;
|
||||
|
||||
//@phpstan-ignore-next-line
|
||||
foreach($model as $key => $value) {
|
||||
$this->{$key} = $value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->migrate();
|
||||
}
|
||||
|
||||
public function migrate(): self
|
||||
{
|
||||
|
||||
if($this->version == 'alpha')
|
||||
{
|
||||
$this->regions->EU->subregions->PL = new \stdClass();
|
||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
|
||||
$this->regions->EU->subregions->PL->apply_tax = false;
|
||||
|
||||
$this->version = 'beta';
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -474,6 +497,12 @@ class TaxModel
|
||||
$this->regions->EU->subregions->NL->reduced_tax_rate = 9;
|
||||
$this->regions->EU->subregions->NL->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->PL = new \stdClass();
|
||||
$this->regions->EU->subregions->PL->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PL->tax_name = 'VAT';
|
||||
$this->regions->EU->subregions->PL->reduced_tax_rate = 8;
|
||||
$this->regions->EU->subregions->PL->apply_tax = false;
|
||||
|
||||
$this->regions->EU->subregions->PT = new \stdClass();
|
||||
$this->regions->EU->subregions->PT->tax_rate = 23;
|
||||
$this->regions->EU->subregions->PT->tax_name = 'IVA';
|
||||
|
@ -197,6 +197,10 @@ region:
|
||||
vat: 21
|
||||
reduced_vat: 9
|
||||
apply_tax: false
|
||||
PL:
|
||||
vat: 23
|
||||
reduced_vat: 8
|
||||
apply_tax: false
|
||||
PT:
|
||||
vat: 23
|
||||
reduced_vat: 6
|
||||
|
@ -13,15 +13,21 @@ namespace App\Events\Client;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Company;
|
||||
use League\Fractal\Manager;
|
||||
use League\Fractal\Resource\Item;
|
||||
use Illuminate\Broadcasting\Channel;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use App\Transformers\ArraySerializer;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\Transformers\ClientTransformer;
|
||||
use Illuminate\Broadcasting\PrivateChannel;
|
||||
use Illuminate\Foundation\Events\Dispatchable;
|
||||
use Illuminate\Broadcasting\InteractsWithSockets;
|
||||
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
|
||||
|
||||
/**
|
||||
* Class ClientWasArchived.
|
||||
*/
|
||||
class ClientWasArchived
|
||||
class ClientWasArchived implements ShouldBroadcast
|
||||
{
|
||||
use Dispatchable;
|
||||
use InteractsWithSockets;
|
||||
@ -50,13 +56,34 @@ class ClientWasArchived
|
||||
$this->event_vars = $event_vars;
|
||||
}
|
||||
|
||||
// /**
|
||||
// * Get the channels the event should broadcast on.
|
||||
// *
|
||||
// * @return Channel|array
|
||||
// */
|
||||
public function broadcastWith()
|
||||
{
|
||||
|
||||
$manager = new Manager();
|
||||
$manager->setSerializer(new ArraySerializer());
|
||||
$class = sprintf('App\\Transformers\\%sTransformer', class_basename($this->client));
|
||||
|
||||
$transformer = new $class();
|
||||
|
||||
$resource = new Item($this->client, $transformer, $this->client->getEntityType());
|
||||
$data = $manager->createData($resource)->toArray();
|
||||
|
||||
return $data;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the channels the event should broadcast on.
|
||||
*
|
||||
* @return Channel|array
|
||||
*/
|
||||
public function broadcastOn()
|
||||
{
|
||||
return [];
|
||||
|
||||
return [
|
||||
new PrivateChannel("company-{$this->company->company_key}"),
|
||||
];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -172,6 +172,7 @@ class BaseExport
|
||||
'tax_rate3' => 'invoice.tax_rate3',
|
||||
'recurring_invoice' => 'invoice.recurring_id',
|
||||
'auto_bill' => 'invoice.auto_bill_enabled',
|
||||
'project' => 'invoice.project',
|
||||
];
|
||||
|
||||
protected array $recurring_invoice_report_keys = [
|
||||
@ -449,6 +450,7 @@ class BaseExport
|
||||
'status' => 'task.status_id',
|
||||
'project' => 'task.project_id',
|
||||
'billable' => 'task.billable',
|
||||
'item_notes' => 'task.item_notes',
|
||||
];
|
||||
|
||||
protected array $forced_client_fields = [
|
||||
@ -1038,6 +1040,10 @@ class BaseExport
|
||||
|
||||
$recurring_filters = [];
|
||||
|
||||
if($this->company->getSetting('report_include_drafts')){
|
||||
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
|
||||
}
|
||||
|
||||
if (in_array('active', $status_parameters)) {
|
||||
$recurring_filters[] = RecurringInvoice::STATUS_ACTIVE;
|
||||
}
|
||||
@ -1252,7 +1258,7 @@ class BaseExport
|
||||
|
||||
$date_range = $this->input['date_range'];
|
||||
|
||||
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key']) > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
|
||||
if (array_key_exists('date_key', $this->input) && strlen($this->input['date_key'] ?? '') > 1 && ($table_name && $this->columnExists($table_name, $this->input['date_key']))) {
|
||||
$this->date_key = $this->input['date_key'];
|
||||
}
|
||||
|
||||
@ -1263,7 +1269,7 @@ class BaseExport
|
||||
$custom_start_date = now()->startOfYear();
|
||||
$custom_end_date = now();
|
||||
}
|
||||
|
||||
|
||||
switch ($date_range) {
|
||||
case 'all':
|
||||
$this->start_date = 'All available data';
|
||||
|
@ -153,9 +153,9 @@ class InvoiceExport extends BaseExport
|
||||
private function decorateAdvancedFields(Invoice $invoice, array $entity): array
|
||||
{
|
||||
|
||||
// if (in_array('invoice.status', $this->input['report_keys'])) {
|
||||
// $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id);
|
||||
// }
|
||||
if (in_array('invoice.project', $this->input['report_keys'])) {
|
||||
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';
|
||||
}
|
||||
|
||||
if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
|
||||
$entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';
|
||||
|
@ -265,6 +265,10 @@ class InvoiceItemExport extends BaseExport
|
||||
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line
|
||||
}
|
||||
|
||||
if (in_array('invoice.project', $this->input['report_keys'])) {
|
||||
$entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';// @phpstan-ignore-line
|
||||
}
|
||||
|
||||
return $entity;
|
||||
}
|
||||
|
||||
|
@ -29,7 +29,7 @@ class TaskExport extends BaseExport
|
||||
{
|
||||
private $entity_transformer;
|
||||
|
||||
public string $date_key = 'created_at';
|
||||
public string $date_key = 'calculated_start_date';
|
||||
|
||||
private string $date_format = 'Y-m-d';
|
||||
|
||||
@ -156,7 +156,7 @@ class TaskExport extends BaseExport
|
||||
$entity[$key] = $transformed_entity[$parts[1]];
|
||||
} elseif (array_key_exists($key, $transformed_entity)) {
|
||||
$entity[$key] = $transformed_entity[$key];
|
||||
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration'])) {
|
||||
} elseif (in_array($key, ['task.start_date', 'task.end_date', 'task.duration', 'task.billable', 'task.item_notes'])) {
|
||||
$entity[$key] = '';
|
||||
} else {
|
||||
$entity[$key] = $this->decorator->transform($key, $task);
|
||||
@ -175,7 +175,7 @@ class TaskExport extends BaseExport
|
||||
private function iterateLogs(Task $task, array $entity)
|
||||
{
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
@ -209,6 +209,14 @@ class TaskExport extends BaseExport
|
||||
$entity['task.duration_words'] = $seconds > 86400 ? CarbonInterval::seconds($seconds)->locale($this->company->locale())->cascade()->forHumans() : now()->startOfDay()->addSeconds($seconds)->format('H:i:s');
|
||||
}
|
||||
|
||||
if (in_array('task.billable', $this->input['report_keys']) || in_array('billable', $this->input['report_keys'])) {
|
||||
$entity['task.billable'] = isset($item[3]) && $item[3] == 'true' ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
}
|
||||
|
||||
if (in_array('task.item_notes', $this->input['report_keys']) || in_array('item_notes', $this->input['report_keys'])) {
|
||||
$entity['task.item_notes'] = isset($item[2]) ? (string)$item[2] : '';
|
||||
}
|
||||
|
||||
$entity = $this->decorateAdvancedFields($task, $entity);
|
||||
|
||||
$this->storage_array[] = $entity;
|
||||
@ -219,6 +227,8 @@ class TaskExport extends BaseExport
|
||||
$entity['task.end_time'] = '';
|
||||
$entity['task.duration'] = '';
|
||||
$entity['task.duration_words'] = '';
|
||||
$entity['task.billable'] = '';
|
||||
$entity['task.item_notes'] = '';
|
||||
|
||||
}
|
||||
|
||||
|
@ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : '';
|
||||
}
|
||||
|
||||
public function auto_bill_enabled(Invoice $invoice)
|
||||
{
|
||||
return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
|
@ -18,6 +18,7 @@ use Carbon\Carbon;
|
||||
|
||||
class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
//@todo - we do not handle iterating through the timelog here.
|
||||
public function transform(string $key, mixed $entity): mixed
|
||||
{
|
||||
$task = false;
|
||||
@ -42,7 +43,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
@ -71,7 +72,7 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
{
|
||||
|
||||
$timezone = Timezone::find($task->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
@ -95,6 +96,26 @@ class TaskDecorator extends Decorator implements DecoratorInterface
|
||||
return '';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* billable
|
||||
*
|
||||
* @todo
|
||||
*/
|
||||
public function billable(Task $task)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* items_notes
|
||||
* @todo
|
||||
*/
|
||||
public function items_notes(Task $task)
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function duration(Task $task)
|
||||
{
|
||||
return $task->calcDuration();
|
||||
|
@ -155,11 +155,13 @@ class BankTransactionFilters extends QueryFilters
|
||||
$dir = ($sort_col[1] == 'asc') ? 'asc' : 'desc';
|
||||
|
||||
if ($sort_col[0] == 'deposit') {
|
||||
return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
|
||||
return $this->builder->orderByRaw("(CASE WHEN base_type = 'CREDIT' THEN amount END) $dir")->orderBy('amount', $dir);
|
||||
// return $this->builder->where('base_type', 'CREDIT')->orderBy('amount', $dir);
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'withdrawal') {
|
||||
return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
|
||||
return $this->builder->orderByRaw("(CASE WHEN base_type = 'DEBIT' THEN amount END) $dir")->orderBy('amount', $dir);
|
||||
// return $this->builder->where('base_type', 'DEBIT')->orderBy('amount', $dir);
|
||||
}
|
||||
|
||||
if ($sort_col[0] == 'status') {
|
||||
|
@ -41,7 +41,7 @@ class ClientFilters extends QueryFilters
|
||||
*/
|
||||
public function balance(string $balance = ''): Builder
|
||||
{
|
||||
if (strlen($balance) == 0) {
|
||||
if (strlen($balance) == 0 || count(explode(":", $balance)) < 2) {
|
||||
return $this->builder;
|
||||
}
|
||||
|
||||
|
@ -153,22 +153,22 @@ class InvoiceFilters extends QueryFilters
|
||||
{
|
||||
|
||||
return $this->builder->where(function ($query) {
|
||||
$query->whereIn('invoices.status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
|
||||
->where('invoices.is_deleted', 0)
|
||||
->where('invoices.balance', '>', 0)
|
||||
->orWhere(function ($query) {
|
||||
$query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
|
||||
->where('is_deleted', 0)
|
||||
->where('balance', '>', 0)
|
||||
->where(function ($query) {
|
||||
|
||||
$query->whereNull('invoices.due_date')
|
||||
$query->whereNull('due_date')
|
||||
->orWhere(function ($q) {
|
||||
$q->where('invoices.due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', 0);
|
||||
$q->where('due_date', '>=', now()->startOfDay()->subSecond())->where('partial', 0);
|
||||
})
|
||||
->orWhere(function ($q) {
|
||||
$q->where('invoices.partial_due_date', '>=', now()->startOfDay()->subSecond())->where('invoices.partial', '>', 0);
|
||||
$q->where('partial_due_date', '>=', now()->startOfDay()->subSecond())->where('partial', '>', 0);
|
||||
});
|
||||
|
||||
})
|
||||
->orderByRaw('ISNULL(invoices.due_date), invoices.due_date ' . 'desc')
|
||||
->orderByRaw('ISNULL(invoices.partial_due_date), invoices.partial_due_date ' . 'desc');
|
||||
->orderByRaw('ISNULL(due_date), due_date ' . 'desc')
|
||||
->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc');
|
||||
});
|
||||
|
||||
}
|
||||
|
@ -171,7 +171,7 @@ class TransactionTransformer implements BankRevenueInterface
|
||||
private function formatDate(string $input)
|
||||
{
|
||||
$timezone = Timezone::find($this->company->settings->timezone_id);
|
||||
$timezone_name = 'US/Eastern';
|
||||
$timezone_name = 'America/New_York';
|
||||
|
||||
if ($timezone) {
|
||||
$timezone_name = $timezone->name;
|
||||
|
@ -11,16 +11,18 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\DataMapper\BaseSettings;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\DataMapper\InvoiceItem;
|
||||
use App\DataMapper\BaseSettings;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
|
||||
class InvoiceItemSum
|
||||
@ -120,7 +122,7 @@ class InvoiceItemSum
|
||||
|
||||
private $tax_collection;
|
||||
|
||||
private ?Client $client;
|
||||
private Client | Vendor $client;
|
||||
|
||||
private bool $calc_tax = false;
|
||||
|
||||
@ -131,10 +133,10 @@ class InvoiceItemSum
|
||||
$this->tax_collection = collect([]);
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
$this->client = $this->invoice->client;
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
@ -313,7 +315,7 @@ class InvoiceItemSum
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.floatval($tax_rate).'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
|
@ -11,14 +11,16 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\DataMapper\Tax\RuleInterface;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
|
||||
class InvoiceItemSumInclusive
|
||||
@ -109,7 +111,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
private bool $calc_tax = false;
|
||||
|
||||
private ?Client $client;
|
||||
private Client | Vendor $client;
|
||||
|
||||
private RuleInterface $rule;
|
||||
|
||||
@ -118,10 +120,10 @@ class InvoiceItemSumInclusive
|
||||
$this->tax_collection = collect([]);
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->currency = $this->invoice->client->currency();
|
||||
$this->client = $this->invoice->client;
|
||||
$this->shouldCalculateTax();
|
||||
} else {
|
||||
$this->currency = $this->invoice->vendor->currency();
|
||||
@ -265,7 +267,7 @@ class InvoiceItemSumInclusive
|
||||
|
||||
$key = str_replace(' ', '', $tax_name.$tax_rate);
|
||||
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.$tax_rate.'%'];
|
||||
$group_tax = ['key' => $key, 'total' => $tax_total, 'tax_name' => $tax_name.' '.Number::formatValueNoTrailingZeroes(floatval($tax_rate), $this->client).'%'];
|
||||
|
||||
$this->tax_collection->push(collect($group_tax));
|
||||
}
|
||||
|
@ -11,12 +11,14 @@
|
||||
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\Quote;
|
||||
use App\Models\RecurringInvoice;
|
||||
use App\Models\RecurringQuote;
|
||||
use App\Models\Vendor;
|
||||
use App\Utils\Number;
|
||||
use App\Utils\Traits\NumberFormatter;
|
||||
use Illuminate\Support\Collection;
|
||||
@ -50,6 +52,8 @@ class InvoiceSum
|
||||
|
||||
private $precision;
|
||||
|
||||
private Client | Vendor $client;
|
||||
|
||||
public InvoiceItemSum $invoice_items;
|
||||
|
||||
private $rappen_rounding = false;
|
||||
@ -60,18 +64,15 @@ class InvoiceSum
|
||||
*/
|
||||
public function __construct($invoice)
|
||||
{
|
||||
|
||||
$this->invoice = $invoice;
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
|
||||
} else {
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
|
||||
|
||||
}
|
||||
$this->precision = $this->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
|
||||
|
||||
$this->tax_map = new Collection();
|
||||
|
||||
}
|
||||
|
||||
public function build()
|
||||
@ -131,7 +132,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name1, $this->invoice->tax_rate1);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) >= 2) {
|
||||
@ -139,7 +140,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name2, $this->invoice->tax_rate2);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) >= 2) {
|
||||
@ -147,7 +148,7 @@ class InvoiceSum
|
||||
$tax += $this->getSurchargeTaxTotalForKey($this->invoice->tax_name3, $this->invoice->tax_rate3);
|
||||
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -12,7 +12,10 @@
|
||||
namespace App\Helpers\Invoice;
|
||||
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Number;
|
||||
use App\Models\Client;
|
||||
use App\Models\Credit;
|
||||
use App\Models\Vendor;
|
||||
use App\Models\Invoice;
|
||||
use App\Models\PurchaseOrder;
|
||||
use App\Models\RecurringQuote;
|
||||
@ -49,6 +52,8 @@ class InvoiceSumInclusive
|
||||
|
||||
private $rappen_rounding = false;
|
||||
|
||||
private Client | Vendor $client;
|
||||
|
||||
public InvoiceItemSumInclusive $invoice_items;
|
||||
/**
|
||||
* Constructs the object with Invoice and Settings object.
|
||||
@ -58,14 +63,10 @@ class InvoiceSumInclusive
|
||||
public function __construct($invoice)
|
||||
{
|
||||
$this->invoice = $invoice;
|
||||
|
||||
if ($this->invoice->client) {
|
||||
$this->precision = $this->invoice->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->client->getSetting('enable_rappen_rounding');
|
||||
} else {
|
||||
$this->precision = $this->invoice->vendor->currency()->precision;
|
||||
$this->rappen_rounding = $this->invoice->vendor->getSetting('enable_rappen_rounding');
|
||||
}
|
||||
$this->client = $invoice->client ?? $invoice->vendor;
|
||||
|
||||
$this->precision = $this->client->currency()->precision;
|
||||
$this->rappen_rounding = $this->client->getSetting('enable_rappen_rounding');
|
||||
|
||||
$this->tax_map = new Collection();
|
||||
}
|
||||
@ -157,19 +158,19 @@ class InvoiceSumInclusive
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate1, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.floatval($this->invoice->tax_rate1).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name1.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate1), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name2) && strlen($this->invoice->tax_name2) > 1) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate2, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.floatval($this->invoice->tax_rate2).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name2.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate2), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
if (is_string($this->invoice->tax_name3) && strlen($this->invoice->tax_name3) > 1) {
|
||||
$tax = $this->calcInclusiveLineTax($this->invoice->tax_rate3, $amount);
|
||||
$this->total_taxes += $tax;
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.floatval($this->invoice->tax_rate3).'%', 'total' => $tax];
|
||||
$this->total_tax_map[] = ['name' => $this->invoice->tax_name3.' '.Number::formatValueNoTrailingZeroes(floatval($this->invoice->tax_rate3), $this->client).'%', 'total' => $tax];
|
||||
}
|
||||
|
||||
return $this;
|
||||
|
@ -66,7 +66,7 @@ class ChartController extends BaseController
|
||||
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200);
|
||||
}
|
||||
|
||||
public function calculatedField(ShowCalculatedFieldRequest $request)
|
||||
public function calculatedFields(ShowCalculatedFieldRequest $request)
|
||||
{
|
||||
|
||||
/** @var \App\Models\User auth()->user() */
|
||||
|
@ -300,7 +300,9 @@ class InvitationController extends Controller
|
||||
'signature' => false,
|
||||
'contact_first_name' => $invitation->contact->first_name ?? '',
|
||||
'contact_last_name' => $invitation->contact->last_name ?? '',
|
||||
'contact_email' => $invitation->contact->email ?? ''
|
||||
'contact_email' => $invitation->contact->email ?? '',
|
||||
'client_city' => $invitation->client->city ?? '',
|
||||
'client_postal_code' => $invitation->client->postal_code ?? '',
|
||||
];
|
||||
|
||||
$request->replace($data);
|
||||
|
@ -108,11 +108,11 @@ class PaymentController extends Controller
|
||||
*/
|
||||
public function process(Request $request)
|
||||
{
|
||||
$request->validate([
|
||||
'contact_first_name' => ['required'],
|
||||
'contact_last_name' => ['required'],
|
||||
'contact_email' => ['required', 'email'],
|
||||
]);
|
||||
// $request->validate([
|
||||
// 'contact_first_name' => ['required'],
|
||||
// 'contact_last_name' => ['required'],
|
||||
// 'contact_email' => ['required', 'email'],
|
||||
// ]);
|
||||
|
||||
return (new InstantPayment($request))->run();
|
||||
}
|
||||
|
@ -85,7 +85,7 @@ class ImportController extends Controller
|
||||
$contents = $this->convertEncoding($contents);
|
||||
|
||||
// Store the csv in cache with an expiry of 10 minutes
|
||||
Cache::put($hash.'-'.$entityType, base64_encode($contents), 600);
|
||||
Cache::put($hash.'-'.$entityType, base64_encode($contents), 1200);
|
||||
|
||||
// Parse CSV
|
||||
$csv_array = $this->getCsvData($contents);
|
||||
|
@ -35,7 +35,7 @@ class MailgunWebhookController extends BaseController
|
||||
}
|
||||
|
||||
if(\hash_equals(\hash_hmac('sha256', $input['signature']['timestamp'] . $input['signature']['token'], config('services.mailgun.webhook_signing_key')), $input['signature']['signature'])) {
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(10);
|
||||
ProcessMailgunWebhook::dispatch($request->all())->delay(rand(2,10));
|
||||
}
|
||||
|
||||
return response()->json(['message' => 'Success.'], 200);
|
||||
|
@ -64,6 +64,9 @@ class StoreCreditRequest extends Request
|
||||
$user = auth()->user();
|
||||
|
||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
// $rules['number'] = new UniqueCreditNumberRule($this->all());
|
||||
$rules['number'] = ['nullable', Rule::unique('credits')->where('company_id', $user->company()->id)];
|
||||
|
@ -65,6 +65,9 @@ class UpdateCreditRequest extends Request
|
||||
$rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('credits')->where('company_id', $user->company()->id)->ignore($this->credit->id)];
|
||||
|
||||
$rules['client_id'] = ['bail', 'sometimes',Rule::in([$this->credit->client_id])];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
|
@ -38,11 +38,14 @@ class StoreInvoiceRequest extends Request
|
||||
|
||||
public function rules()
|
||||
{
|
||||
$rules = [];
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted', 0)];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
} elseif ($this->file('documents')) {
|
||||
@ -57,16 +60,16 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['client_id'] = 'bail|required|exists:clients,id,company_id,'.$user->company()->id.',is_deleted,0';
|
||||
|
||||
$rules['invitations.*.client_contact_id'] = 'distinct';
|
||||
|
||||
$rules['number'] = ['bail', 'nullable', Rule::unique('invoices')->where('company_id', $user->company()->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
@ -79,18 +82,17 @@ class StoreInvoiceRequest extends Request
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
// $rules['amount'] = ['sometimes', 'bail', 'max:99999999999999'];
|
||||
// $rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date) > 1), 'date'];
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function prepareForValidation()
|
||||
{
|
||||
|
||||
/** @var \App\Models\User $user */
|
||||
$user = auth()->user();
|
||||
|
||||
$input = $this->all();
|
||||
|
||||
$input = $this->decodePrimaryKeys($input);
|
||||
@ -102,24 +104,24 @@ class StoreInvoiceRequest extends Request
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = $this->entityTotalAmount($input['line_items']);
|
||||
}
|
||||
|
||||
if(isset($input['partial']) && $input['partial'] == 0) {
|
||||
$input['partial_due_date'] = null;
|
||||
}
|
||||
|
||||
if (array_key_exists('tax_rate1', $input) && is_null($input['tax_rate1'])) {
|
||||
}
|
||||
if (!isset($input['tax_rate1'])) {
|
||||
$input['tax_rate1'] = 0;
|
||||
}
|
||||
if (array_key_exists('tax_rate2', $input) && is_null($input['tax_rate2'])) {
|
||||
if (!isset($input['tax_rate2'])) {
|
||||
$input['tax_rate2'] = 0;
|
||||
}
|
||||
if (array_key_exists('tax_rate3', $input) && is_null($input['tax_rate3'])) {
|
||||
if (!isset($input['tax_rate3'])) {
|
||||
$input['tax_rate3'] = 0;
|
||||
}
|
||||
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
|
||||
if(!isset($input['date'])) {
|
||||
$input['date'] = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
|
||||
}
|
||||
//handles edge case where we need for force set the due date of the invoice.
|
||||
if((isset($input['partial_due_date']) && strlen($input['partial_due_date']) > 1) && (!array_key_exists('due_date', $input) || (empty($input['due_date']) && empty($this->invoice->due_date)))) {
|
||||
$client = \App\Models\Client::withTrashed()->find($input['client_id']);
|
||||
|
@ -67,6 +67,9 @@ class UpdateInvoiceRequest extends Request
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->invoice->client_id])];
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['tax_rate1'] = 'bail|sometimes|numeric';
|
||||
|
@ -50,6 +50,10 @@ class StorePurchaseOrderRequest extends Request
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('purchase_orders')->where('company_id', $user->company()->id)];
|
||||
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.vendor_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['line_items'] = 'array';
|
||||
|
@ -53,6 +53,9 @@ class UpdatePurchaseOrderRequest extends Request
|
||||
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.vendor_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
|
||||
|
@ -11,12 +11,13 @@
|
||||
|
||||
namespace App\Http\Requests\Quote;
|
||||
|
||||
use App\Http\Requests\Request;
|
||||
use App\Http\ValidationRules\Quote\UniqueQuoteNumberRule;
|
||||
use App\Models\Quote;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Illuminate\Validation\Rule;
|
||||
use App\Utils\Traits\CleanLineItems;
|
||||
use App\Http\ValidationRules\Quote\UniqueQuoteNumberRule;
|
||||
use App\Http\ValidationRules\Project\ValidProjectForClient;
|
||||
|
||||
class StoreQuoteRequest extends Request
|
||||
{
|
||||
@ -43,7 +44,7 @@ class StoreQuoteRequest extends Request
|
||||
|
||||
$rules = [];
|
||||
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)];
|
||||
$rules['client_id'] = ['required', 'bail', Rule::exists('clients', 'id')->where('company_id', $user->company()->id)->where('is_deleted',0)];
|
||||
|
||||
if ($this->file('documents') && is_array($this->file('documents'))) {
|
||||
$rules['documents.*'] = $this->fileValidation();
|
||||
@ -59,15 +60,28 @@ class StoreQuoteRequest extends Request
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['number'] = ['nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
|
||||
$rules['number'] = ['bail','nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['project_id'] = ['bail', 'sometimes', new ValidProjectForClient($this->all())];
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
$rules['line_items'] = 'array';
|
||||
|
||||
$rules['discount'] = 'sometimes|numeric|max:99999999999999';
|
||||
$rules['is_amount_discount'] = ['boolean'];
|
||||
$rules['tax_rate1'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_rate2'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_rate3'] = 'bail|sometimes|numeric';
|
||||
$rules['tax_name1'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name2'] = 'bail|sometimes|string|nullable';
|
||||
$rules['tax_name3'] = 'bail|sometimes|string|nullable';
|
||||
$rules['exchange_rate'] = 'bail|sometimes|numeric';
|
||||
$rules['line_items'] = 'array';
|
||||
$rules['date'] = 'bail|sometimes|date:Y-m-d';
|
||||
|
||||
$rules['partial'] = 'bail|sometimes|nullable|numeric|gte:0';
|
||||
$rules['partial_due_date'] = ['bail', 'sometimes', 'nullable', 'exclude_if:partial,0', 'date', 'before:due_date', 'after_or_equal:date'];
|
||||
$rules['due_date'] = ['bail', 'sometimes', 'nullable', 'after:partial_due_date', Rule::requiredIf(fn () => strlen($this->partial_due_date ?? '') > 1), 'date'];
|
||||
$rules['amount'] = ['sometimes', 'bail', 'numeric', 'max:99999999999999'];
|
||||
|
||||
return $rules;
|
||||
@ -89,19 +103,24 @@ class StoreQuoteRequest extends Request
|
||||
$input['line_items'] = isset($input['line_items']) ? $this->cleanItems($input['line_items']) : [];
|
||||
$input['amount'] = $this->entityTotalAmount($input['line_items']);
|
||||
}
|
||||
|
||||
if (array_key_exists('exchange_rate', $input) && is_null($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
|
||||
if(isset($input['partial']) && $input['partial'] == 0) {
|
||||
$input['partial_due_date'] = null;
|
||||
}
|
||||
|
||||
if (!isset($input['tax_rate1'])) {
|
||||
$input['tax_rate1'] = 0;
|
||||
}
|
||||
if (!isset($input['tax_rate2'])) {
|
||||
$input['tax_rate2'] = 0;
|
||||
}
|
||||
if (!isset($input['tax_rate3'])) {
|
||||
$input['tax_rate3'] = 0;
|
||||
}
|
||||
if (!isset($input['exchange_rate'])) {
|
||||
$input['exchange_rate'] = 1;
|
||||
}
|
||||
if(!isset($input['date'])) {
|
||||
$input['date'] = now()->addSeconds($user->company()->utc_offset())->format('Y-m-d');
|
||||
}
|
||||
|
||||
if(isset($input['partial_due_date']) && (!isset($input['due_date']) || strlen($input['due_date']) <= 1)) {
|
||||
$client = \App\Models\Client::withTrashed()->find($input['client_id']);
|
||||
$valid_days = ($client && strlen($client->getSetting('valid_until')) >= 1) ? $client->getSetting('valid_until') : 7;
|
||||
|
@ -55,6 +55,9 @@ class UpdateQuoteRequest extends Request
|
||||
} elseif ($this->file('file')) {
|
||||
$rules['file'] = $this->fileValidation();
|
||||
}
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['number'] = ['bail', 'sometimes', 'nullable', Rule::unique('quotes')->where('company_id', $user->company()->id)->ignore($this->quote->id)];
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->quote->client_id])];
|
||||
|
@ -61,7 +61,8 @@ class StoreRecurringInvoiceRequest extends Request
|
||||
|
||||
$rules['client_id'] = 'required|exists:clients,id,company_id,'.$user->company()->id;
|
||||
|
||||
$rules['invitations.*.client_contact_id'] = 'distinct';
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['frequency_id'] = 'required|integer|digits_between:1,12';
|
||||
|
||||
|
@ -60,6 +60,8 @@ class UpdateRecurringInvoiceRequest extends Request
|
||||
|
||||
$rules['number'] = ['bail', 'sometimes', Rule::unique('recurring_invoices')->where('company_id', $user->company()->id)->ignore($this->recurring_invoice->id)];
|
||||
|
||||
$rules['invitations'] = 'sometimes|bail|array';
|
||||
$rules['invitations.*.client_contact_id'] = 'bail|required|distinct';
|
||||
|
||||
$rules['client_id'] = ['bail', 'sometimes', Rule::in([$this->recurring_invoice->client_id])];
|
||||
|
||||
|
@ -19,8 +19,9 @@ use Illuminate\Contracts\Validation\ValidationRule;
|
||||
*/
|
||||
class BlackListRule implements ValidationRule
|
||||
{
|
||||
/** Bad domains +/- dispoable email domains */
|
||||
/** Bad domains +/- disposable email domains */
|
||||
private array $blacklist = [
|
||||
'padvn.com',
|
||||
'anonaddy.me',
|
||||
'nqmo.com',
|
||||
'wireconnected.com',
|
||||
|
@ -42,6 +42,7 @@ class AccountComponent extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.account', array_merge($this->attributes->getAttributes(), $this->defaults) );
|
||||
|
||||
return render('gateways.rotessa.components.account', $this->attributes->getAttributes() + $this->defaults);
|
||||
}
|
||||
}
|
||||
|
@ -43,6 +43,6 @@ class AddressComponent extends Component
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.address',array_merge( $this->defaults, $this->attributes->getAttributes() ) );
|
||||
return render('gateways.rotessa.components.address', $this->attributes->getAttributes() + $this->defaults );
|
||||
}
|
||||
}
|
||||
|
@ -15,11 +15,12 @@ class ContactComponent extends Component
|
||||
{
|
||||
|
||||
public function __construct(ClientContact $contact) {
|
||||
|
||||
$contact = collect($contact->client->contacts->firstWhere('is_primary', 1)->toArray())->merge([
|
||||
'home_phone' =>$contact->client->phone,
|
||||
'custom_identifier' => $contact->client->number,
|
||||
'name' =>$contact->client->name,
|
||||
'id' => null
|
||||
'id' => $contact->client->contact_key,
|
||||
] )->all();
|
||||
|
||||
$this->attributes = $this->newAttributeBag(Arr::only($contact, $this->fields) );
|
||||
@ -37,12 +38,13 @@ class ContactComponent extends Component
|
||||
|
||||
private $defaults = [
|
||||
'customer_type' => "Business",
|
||||
'customer_identifier' => null,
|
||||
'id' => null
|
||||
'custom_identifier' => null,
|
||||
'customer_id' => null
|
||||
];
|
||||
|
||||
public function render()
|
||||
{
|
||||
return render('gateways.rotessa.components.contact', array_merge($this->defaults, $this->attributes->getAttributes() ) );
|
||||
\Debugbar::debug($this->attributes->getAttributes() + $this->defaults);
|
||||
return render('gateways.rotessa.components.contact', $this->attributes->getAttributes() + $this->defaults );
|
||||
}
|
||||
}
|
||||
|
@ -98,7 +98,7 @@ class BaseImport
|
||||
}
|
||||
|
||||
/** @var string $base64_encoded_csv */
|
||||
$base64_encoded_csv = Cache::pull($this->hash.'-'.$entity_type);
|
||||
$base64_encoded_csv = Cache::get($this->hash.'-'.$entity_type);
|
||||
|
||||
if (empty($base64_encoded_csv)) {
|
||||
return null;
|
||||
@ -473,6 +473,8 @@ class BaseImport
|
||||
|
||||
$tasks = $this->groupTasks($tasks, $task_number_key);
|
||||
|
||||
nlog($tasks);
|
||||
|
||||
foreach ($tasks as $raw_task) {
|
||||
$task_data = [];
|
||||
|
||||
@ -702,16 +704,16 @@ class BaseImport
|
||||
->save();
|
||||
}
|
||||
|
||||
if ($invoice->status_id === Invoice::STATUS_DRAFT) {
|
||||
} elseif ($invoice->status_id === Invoice::STATUS_SENT) {
|
||||
$invoice = $invoice
|
||||
->service()
|
||||
->markSent()
|
||||
->save();
|
||||
} elseif (
|
||||
$invoice->status_id <= Invoice::STATUS_SENT &&
|
||||
$invoice->amount > 0
|
||||
) {
|
||||
if ($invoice->status_id == Invoice::STATUS_DRAFT) {
|
||||
return $invoice;
|
||||
}
|
||||
|
||||
$invoice = $invoice
|
||||
->service()
|
||||
->markSent()
|
||||
->save();
|
||||
|
||||
if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0) {
|
||||
if ($invoice->balance <= 0) {
|
||||
$invoice->status_id = Invoice::STATUS_PAID;
|
||||
$invoice->save();
|
||||
|
@ -172,7 +172,7 @@ class Wave extends BaseImport implements ImportInterface
|
||||
{
|
||||
$entity_type = 'expense';
|
||||
|
||||
$data = $this->getCsvData($entity_type);
|
||||
$data = $this->getCsvData('invoice');
|
||||
|
||||
if (!$data) {
|
||||
$this->entity_count['expense'] = 0;
|
||||
@ -244,14 +244,17 @@ class Wave extends BaseImport implements ImportInterface
|
||||
if (empty($expense_data['vendor_id'])) {
|
||||
$vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
|
||||
|
||||
$vendor_repository->save(
|
||||
['name' => $raw_expense['Vendor Name']],
|
||||
$vendor = VendorFactory::create(
|
||||
$this->company->id,
|
||||
$vendor_data['user_id']
|
||||
)
|
||||
);
|
||||
$expense_data['vendor_id'] = $vendor->id;
|
||||
if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor']))
|
||||
{
|
||||
$vendor_repository->save(
|
||||
['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
|
||||
$vendor = VendorFactory::create(
|
||||
$this->company->id,
|
||||
$vendor_data['user_id']
|
||||
)
|
||||
);
|
||||
$expense_data['vendor_id'] = $vendor->id;
|
||||
}
|
||||
}
|
||||
|
||||
$validator = Validator::make(
|
||||
|
@ -46,6 +46,7 @@ class TaskTransformer extends BaseTransformer
|
||||
'company_id' => $this->company->id,
|
||||
'number' => $this->getString($task_data, 'task.number'),
|
||||
'user_id' => $this->getString($task_data, 'task.user_id'),
|
||||
'rate' => $this->getFloat($task_data, 'task.rate'),
|
||||
'client_id' => $clientId,
|
||||
'project_id' => $this->getProjectId($projectId, $clientId),
|
||||
'description' => $this->getString($task_data, 'task.description'),
|
||||
@ -87,8 +88,7 @@ class TaskTransformer extends BaseTransformer
|
||||
$is_billable = true;
|
||||
}
|
||||
|
||||
if(isset($item['task.start_date']) &&
|
||||
isset($item['task.end_date'])) {
|
||||
if(isset($item['task.start_date'])) {
|
||||
$start_date = $this->resolveStartDate($item);
|
||||
$end_date = $this->resolveEndDate($item);
|
||||
} elseif(isset($item['task.duration'])) {
|
||||
@ -136,7 +136,7 @@ class TaskTransformer extends BaseTransformer
|
||||
private function resolveEndDate($item)
|
||||
{
|
||||
|
||||
$stub_end_date = $item['task.end_date'];
|
||||
$stub_end_date = isset($item['task.end_date']) ? $item['task.end_date'] : $item['task.start_date'];
|
||||
$stub_end_date .= isset($item['task.end_time']) ? " ".$item['task.end_time'] : '';
|
||||
|
||||
try {
|
||||
|
@ -36,18 +36,26 @@ class ExpenseTransformer extends BaseTransformer
|
||||
$total_tax += floatval($record['Sales Tax Amount']);
|
||||
}
|
||||
|
||||
$tax_rate = round(($total_tax / $amount) * 100, 3);
|
||||
$tax_rate = $total_tax > 0 ? round(($total_tax / $amount) * 100, 3) : 0;
|
||||
|
||||
if(isset($data['Notes / Memo']) && strlen($data['Notes / Memo']) > 1)
|
||||
$public_notes = $data['Notes / Memo'];
|
||||
elseif (isset($data['Transaction Description']) && strlen($data['Transaction Description']) > 1)
|
||||
$public_notes = $data['Transaction Description'];
|
||||
else
|
||||
$public_notes = '';
|
||||
|
||||
|
||||
$transformed = [
|
||||
'company_id' => $this->company->id,
|
||||
'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')),
|
||||
'number' => $this->getString($data, 'Bill Number'),
|
||||
'public_notes' => $this->getString($data, 'Notes / Memo'),
|
||||
'public_notes' => $public_notes,
|
||||
'date' => $this->parseDate($data['Transaction Date Added']) ?: now()->format('Y-m-d'), //27-01-2022
|
||||
'currency_id' => $this->company->settings->currency_id,
|
||||
'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']),
|
||||
'amount' => $amount,
|
||||
'tax_name1' => $data['Sales Tax Name'],
|
||||
'tax_name1' => isset($data['Sales Tax Name']) ? $data['Sales Tax Name'] : '',
|
||||
'tax_rate1' => $tax_rate,
|
||||
];
|
||||
|
||||
|
@ -11,7 +11,6 @@
|
||||
|
||||
namespace App\Jobs\EDocument;
|
||||
|
||||
use App\Services\EDocument\Standards\RoEInvoice;
|
||||
use App\Utils\Ninja;
|
||||
use App\Models\Quote;
|
||||
use App\Models\Credit;
|
||||
@ -23,10 +22,12 @@ use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use App\Services\EDocument\Standards\Peppol;
|
||||
use horstoeko\zugferd\ZugferdDocumentBuilder;
|
||||
use App\Services\EDocument\Standards\FatturaPA;
|
||||
use App\Services\EDocument\Standards\RoEInvoice;
|
||||
use App\Services\EDocument\Standards\OrderXDocument;
|
||||
use App\Services\EDocument\Standards\FacturaEInvoice;
|
||||
use App\Services\EDocument\Standards\FatturaPA;
|
||||
use App\Services\EDocument\Standards\ZugferdEDokument;
|
||||
|
||||
class CreateEDocument implements ShouldQueue
|
||||
@ -68,6 +69,8 @@ class CreateEDocument implements ShouldQueue
|
||||
|
||||
if ($this->document instanceof Invoice) {
|
||||
switch ($e_document_type) {
|
||||
case "PEPPOL":
|
||||
return (new Peppol($this->document))->toXml();
|
||||
case "FACT1":
|
||||
return (new RoEInvoice($this->document))->generateXml();
|
||||
case "FatturaPA":
|
||||
|
@ -297,12 +297,13 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$t->replace(Ninja::transformTranslations($this->nmo->settings));
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
// if(Ninja::isHosted() && !$this->company->account->isPaid())
|
||||
// {
|
||||
// $this->mailer = 'mailgun';
|
||||
// $this->setHostedMailgunMailer();
|
||||
// return $this;
|
||||
// }
|
||||
|
||||
if($this->mailer == 'default' && $this->company->account->isNewHostedAccount()) {
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
if (Ninja::isHosted() && $this->company->account->isPaid() && $this->nmo->settings->email_sending_method == 'default') {
|
||||
//check if outlook.
|
||||
@ -391,7 +392,7 @@ class NinjaMailerJob implements ShouldQueue
|
||||
$smtp_username = $company->smtp_username ?? '';
|
||||
$smtp_password = $company->smtp_password ?? '';
|
||||
$smtp_encryption = $company->smtp_encryption ?? 'tls';
|
||||
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
|
||||
$smtp_local_domain = strlen($company->smtp_local_domain ?? '') > 2 ? $company->smtp_local_domain : null;
|
||||
$smtp_verify_peer = $company->smtp_verify_peer ?? true;
|
||||
|
||||
if(strlen($smtp_host) <= 1 ||
|
||||
|
@ -181,7 +181,7 @@ class ProcessMailgunWebhook implements ShouldQueue
|
||||
$sl = $this->getSystemLog($this->request['MessageID']);
|
||||
|
||||
/** Prevents Gmail tracking from firing inappropriately */
|
||||
if($this->request['signature']['timestamp'] < $sl->log['signature']['timestamp'] + 3) {
|
||||
if(!$sl || $this->request['signature']['timestamp'] < $sl->log['signature']['timestamp'] + 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ class CleanStaleInvoiceOrder implements ShouldQueue
|
||||
Invoice::query()
|
||||
->withTrashed()
|
||||
->where('is_proforma', 1)
|
||||
->whereBetween('created_at', [now()->subHours(1), now()->subMinutes(10)])
|
||||
->where('created_at', '<', now()->subHour())
|
||||
->cursor()
|
||||
->each(function ($invoice) use ($repo) {
|
||||
$invoice->is_proforma = false;
|
||||
|
@ -188,6 +188,10 @@ class BillingPortalPurchase extends Component
|
||||
|
||||
public ?string $contact_email;
|
||||
|
||||
public ?string $client_city;
|
||||
|
||||
public ?string $client_postal_code;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
MultiDB::setDb($this->db);
|
||||
@ -203,7 +207,7 @@ class BillingPortalPurchase extends Component
|
||||
if (request()->query('coupon')) {
|
||||
$this->coupon = request()->query('coupon');
|
||||
$this->handleCoupon();
|
||||
} elseif (strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
} elseif (strlen($this->subscription->promo_code ?? '') == 0 && $this->subscription->promo_discount > 0) {
|
||||
$this->price = $this->subscription->promo_price;
|
||||
}
|
||||
|
||||
@ -335,10 +339,6 @@ class BillingPortalPurchase extends Component
|
||||
{
|
||||
$this->contact = $contact;
|
||||
|
||||
if ($contact->showRff()) {
|
||||
return $this->rff();
|
||||
}
|
||||
|
||||
Auth::guard('contact')->loginUsingId($contact->id, true);
|
||||
|
||||
if ($this->subscription->trial_enabled) {
|
||||
@ -351,11 +351,20 @@ class BillingPortalPurchase extends Component
|
||||
if ((int)$this->price == 0) {
|
||||
$this->steps['payment_required'] = false;
|
||||
} else {
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
// $this->steps['fetched_payment_methods'] = true;
|
||||
}
|
||||
|
||||
$this->methods = $contact->client->service()->getPaymentMethods($this->price);
|
||||
|
||||
foreach($this->methods as $method){
|
||||
|
||||
if($method['is_paypal'] == '1' && !$this->steps['check_rff']){
|
||||
$this->rff();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
$this->heading_text = ctrans('texts.payment_methods');
|
||||
|
||||
return $this;
|
||||
@ -366,6 +375,8 @@ class BillingPortalPurchase extends Component
|
||||
$this->contact_first_name = $this->contact->first_name;
|
||||
$this->contact_last_name = $this->contact->last_name;
|
||||
$this->contact_email = $this->contact->email;
|
||||
$this->client_city = $this->contact->client->city;
|
||||
$this->client_postal_code = $this->contact->client->postal_code;
|
||||
|
||||
$this->steps['check_rff'] = true;
|
||||
|
||||
@ -377,13 +388,20 @@ class BillingPortalPurchase extends Component
|
||||
$validated = $this->validate([
|
||||
'contact_first_name' => ['required'],
|
||||
'contact_last_name' => ['required'],
|
||||
'client_city' => ['required'],
|
||||
'client_postal_code' => ['required'],
|
||||
'contact_email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
$this->contact->first_name = $validated['contact_first_name'];
|
||||
$this->contact->last_name = $validated['contact_last_name'];
|
||||
$this->contact->email = $validated['contact_email'];
|
||||
$this->contact->save();
|
||||
$this->contact->client->postal_code = $validated['client_postal_code'];
|
||||
$this->contact->client->city = $validated['client_city'];
|
||||
|
||||
$this->contact->pushQuietly();
|
||||
|
||||
$this->steps['fetched_payment_methods'] = true;
|
||||
|
||||
return $this->getPaymentMethods($this->contact);
|
||||
}
|
||||
@ -395,13 +413,13 @@ class BillingPortalPurchase extends Component
|
||||
* @param $company_gateway_id
|
||||
* @param $gateway_type_id
|
||||
*/
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id)
|
||||
public function handleMethodSelectingEvent($company_gateway_id, $gateway_type_id, $is_paypal = false)
|
||||
{
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
$this->payment_method_id = $gateway_type_id;
|
||||
|
||||
$this->handleBeforePaymentEvents();
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,6 +164,13 @@ class BillingPortalPurchasev2 extends Component
|
||||
public $payment_confirmed = false;
|
||||
public $is_eligible = true;
|
||||
public $not_eligible_message = '';
|
||||
public $check_rff = false;
|
||||
|
||||
public ?string $contact_first_name;
|
||||
public ?string $contact_last_name;
|
||||
public ?string $contact_email;
|
||||
public ?string $client_city;
|
||||
public ?string $client_postal_code;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@ -472,7 +479,6 @@ class BillingPortalPurchasev2 extends Component
|
||||
*/
|
||||
protected function getPaymentMethods(): self
|
||||
{
|
||||
nlog("total amount = {$this->float_amount_total}");
|
||||
|
||||
if ($this->float_amount_total == 0) {
|
||||
$this->methods = [];
|
||||
@ -481,10 +487,73 @@ class BillingPortalPurchasev2 extends Component
|
||||
if ($this->contact && $this->float_amount_total >= 1) {
|
||||
$this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total);
|
||||
}
|
||||
|
||||
foreach($this->methods as $method) {
|
||||
|
||||
if($method['is_paypal'] == '1' && !$this->check_rff) {
|
||||
$this->rff();
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function rff()
|
||||
{
|
||||
|
||||
$this->contact_first_name = $this->contact->first_name;
|
||||
$this->contact_last_name = $this->contact->last_name;
|
||||
$this->contact_email = $this->contact->email;
|
||||
$this->client_city = $this->contact->client->city;
|
||||
$this->client_postal_code = $this->contact->client->postal_code;
|
||||
|
||||
if(
|
||||
strlen($this->contact_first_name ?? '') == 0 ||
|
||||
strlen($this->contact_last_name ?? '') == 0 ||
|
||||
strlen($this->contact_email ?? '') == 0 ||
|
||||
strlen($this->client_city ?? '') == 0 ||
|
||||
strlen($this->client_postal_code ?? '') == 0
|
||||
)
|
||||
{
|
||||
$this->check_rff = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function handleRff()
|
||||
{
|
||||
|
||||
$validated = $this->validate([
|
||||
'contact_first_name' => ['required'],
|
||||
'contact_last_name' => ['required'],
|
||||
'client_city' => ['required'],
|
||||
'client_postal_code' => ['required'],
|
||||
'contact_email' => ['required', 'email'],
|
||||
]);
|
||||
|
||||
$this->check_rff = false;
|
||||
|
||||
$this->contact->first_name = $validated['contact_first_name'];
|
||||
$this->contact->last_name = $validated['contact_last_name'];
|
||||
$this->contact->email = $validated['contact_email'];
|
||||
$this->contact->client->postal_code = $validated['client_postal_code'];
|
||||
$this->contact->client->city = $validated['client_city'];
|
||||
|
||||
$this->contact->pushQuietly();
|
||||
|
||||
$this->refreshComponent();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
protected function refreshComponent()
|
||||
{
|
||||
$this->dispatch('$refresh');
|
||||
}
|
||||
|
||||
/**
|
||||
* Middle method between selecting payment method &
|
||||
* submitting the from to the backend.
|
||||
|
@ -364,16 +364,19 @@ class Account extends BaseModel
|
||||
return $this->isProClient() && $this->isPaid();
|
||||
}
|
||||
|
||||
public function isNewHostedAccount()
|
||||
{
|
||||
return Ninja::isHosted() && Carbon::createFromTimestamp($this->created_at)->diffInWeeks() <= 2;
|
||||
}
|
||||
|
||||
public function isTrial(): bool
|
||||
{
|
||||
if (!Ninja::isNinja()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
//@27-01-2024 - updates for logic around trials
|
||||
return !$this->plan_paid && $this->trial_started && Carbon::parse($this->trial_started)->addDays(14)->gte(now()->subHours(12));
|
||||
// $plan_details = $this->getPlanDetails();
|
||||
// return $plan_details && $plan_details['trial'];
|
||||
|
||||
}
|
||||
|
||||
public function startTrial($plan): void
|
||||
|
@ -27,7 +27,6 @@ use Illuminate\Contracts\Translation\HasLocalePreference;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
/**
|
||||
@ -124,7 +123,7 @@ class Client extends BaseModel implements HasLocalePreference
|
||||
use AppSetup;
|
||||
use ClientGroupSettingsSaver;
|
||||
use Excludable;
|
||||
|
||||
|
||||
protected $presenter = ClientPresenter::class;
|
||||
|
||||
protected $hidden = [
|
||||
|
@ -351,9 +351,9 @@ class ClientContact extends Authenticatable implements HasLocalePreference
|
||||
|
||||
public function showRff(): bool
|
||||
{
|
||||
if (\strlen($this->first_name) === 0 || \strlen($this->last_name) === 0 || \strlen($this->email) === 0) {
|
||||
return true;
|
||||
}
|
||||
// if (\strlen($this->first_name ?? '') === 0 || \strlen($this->last_name ?? '') === 0 || \strlen($this->email ?? '') === 0) {
|
||||
// return true;
|
||||
// }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
@ -159,6 +159,11 @@ class CompanyGateway extends BaseModel
|
||||
|
||||
protected $touches = [];
|
||||
|
||||
public function isPayPal()
|
||||
{
|
||||
return in_array($this->gateway_key, ['80af24a6a691230bbec33e930ab40666','80af24a6a691230bbec33e930ab40665']);
|
||||
}
|
||||
|
||||
public function getEntityType()
|
||||
{
|
||||
return self::class;
|
||||
|
@ -105,7 +105,7 @@ class Gateway extends StaticModel
|
||||
$link = 'https://www.forte.net/';
|
||||
} elseif ($this->id == 62) {
|
||||
$link = 'https://docs.btcpayserver.org/InvoiceNinja/';
|
||||
} elseif ($this->id == 4002) {
|
||||
} elseif ($this->id == 63) {
|
||||
$link = 'https://rotessa.com';
|
||||
}
|
||||
|
||||
@ -141,23 +141,23 @@ class Gateway extends StaticModel
|
||||
case 20:
|
||||
case 56:
|
||||
return [
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'customer.source.updated', 'payment_intent.processing', 'payment_intent.payment_failed', 'charge.failed']],
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing', 'payment_intent.succeeded', 'payment_intent.partially_funded', 'payment_intent.payment_failed']],
|
||||
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded', 'charge.refunded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BANK_TRANSFER => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.refunded','charge.succeeded', 'customer.source.updated', 'payment_intent.processing', 'payment_intent.payment_failed', 'charge.failed']],
|
||||
GatewayType::DIRECT_DEBIT => ['refund' => false, 'token_billing' => false, 'webhooks' => ['payment_intent.processing', 'charge.refunded', 'payment_intent.succeeded', 'payment_intent.partially_funded', 'payment_intent.payment_failed']],
|
||||
GatewayType::ALIPAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::APPLE_PAY => ['refund' => false, 'token_billing' => false],
|
||||
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated', 'payment_intent.payment_failed']],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.failed',]],
|
||||
GatewayType::BACS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.processing', 'payment_intent.succeeded', 'mandate.updated', 'payment_intent.payment_failed']],
|
||||
GatewayType::SOFORT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::KLARNA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::SEPA => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::PRZELEWY24 => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::GIROPAY => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::EPS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BANCONTACT => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::BECS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::IDEAL => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::ACSS => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed', 'payment_intent.succeeded', 'payment_intent.payment_failed']],
|
||||
GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed',]],
|
||||
];
|
||||
case 39:
|
||||
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout
|
||||
@ -226,7 +226,7 @@ class Gateway extends StaticModel
|
||||
return [
|
||||
GatewayType::CRYPTO => ['refund' => true, 'token_billing' => false, 'webhooks' => ['confirmed', 'paid_out', 'failed', 'fulfilled']],
|
||||
]; //BTCPay
|
||||
case 4002:
|
||||
case 63:
|
||||
return [
|
||||
GatewayType::BANK_TRANSFER => [
|
||||
'refund' => false,
|
||||
|
@ -95,4 +95,9 @@ class UserPresenter extends EntityPresenter
|
||||
{
|
||||
return $this->entity->phone ?? ' ';
|
||||
}
|
||||
|
||||
public function email(): string
|
||||
{
|
||||
return $this->entity->email ?? ' ';
|
||||
}
|
||||
}
|
||||
|
@ -129,7 +129,7 @@ class Project extends BaseModel
|
||||
|
||||
public function invoices(): HasMany
|
||||
{
|
||||
return $this->hasMany(Invoice::class);
|
||||
return $this->hasMany(Invoice::class)->withTrashed();
|
||||
}
|
||||
|
||||
public function quotes(): HasMany
|
||||
|
@ -152,6 +152,8 @@ class SystemLog extends Model
|
||||
|
||||
public const TYPE_BTC_PAY = 324;
|
||||
|
||||
public const TYPE_ROTESSA = 325;
|
||||
|
||||
public const TYPE_QUOTA_EXCEEDED = 400;
|
||||
|
||||
public const TYPE_UPSTREAM_FAILURE = 401;
|
||||
|
@ -170,6 +170,9 @@ class ACH
|
||||
];
|
||||
|
||||
$payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
// return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -187,6 +187,8 @@ class CreditCard
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
];
|
||||
$payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED);
|
||||
return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
// return redirect('client/invoices')->withSuccess('Invoice paid.');
|
||||
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -251,11 +251,11 @@ class PayPalBasePaymentDriver extends BaseDriver
|
||||
[
|
||||
"address" =>
|
||||
[
|
||||
"address_line_1" => strlen($this->client->shipping_address1) > 1 ? $this->client->shipping_address1 : $this->client->address1,
|
||||
"address_line_1" => strlen($this->client->shipping_address1 ?? '') > 1 ? $this->client->shipping_address1 : $this->client->address1,
|
||||
"address_line_2" => $this->client->shipping_address2,
|
||||
"admin_area_2" => strlen($this->client->shipping_city) > 1 ? $this->client->shipping_city : $this->client->city,
|
||||
"admin_area_1" => strlen($this->client->shipping_state) > 1 ? $this->client->shipping_state : $this->client->state,
|
||||
"postal_code" => strlen($this->client->shipping_postal_code) > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
|
||||
"admin_area_2" => strlen($this->client->shipping_city ?? '') > 1 ? $this->client->shipping_city : $this->client->city,
|
||||
"admin_area_1" => strlen($this->client->shipping_state ?? '') > 1 ? $this->client->shipping_state : $this->client->state,
|
||||
"postal_code" => strlen($this->client->shipping_postal_code ?? '') > 1 ? $this->client->shipping_postal_code : $this->client->postal_code,
|
||||
"country_code" => $this->client->present()->shipping_country_code(),
|
||||
],
|
||||
]
|
||||
|
@ -128,7 +128,7 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
|
||||
|
||||
nlog($response);
|
||||
|
||||
if($request->has('token') && strlen($request->input('token')) > 2) {
|
||||
if($request->has('token') && strlen($request->input('token','')) > 2) {
|
||||
return $this->processTokenPayment($request, $response);
|
||||
}
|
||||
|
||||
@ -273,14 +273,14 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
|
||||
]
|
||||
];
|
||||
|
||||
|
||||
if($shipping = $this->getShippingAddress()) {
|
||||
if($shipping = $this->getShippingAddress())
|
||||
$order['purchase_units'][0]["shipping"] = $shipping;
|
||||
}
|
||||
|
||||
if(isset($data['payment_source'])) {
|
||||
if(isset($data['payment_source']))
|
||||
$order['payment_source'] = $data['payment_source'];
|
||||
}
|
||||
|
||||
if(isset($data['payer']))
|
||||
$order['payer'] = $data['payer'];
|
||||
|
||||
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
|
||||
|
||||
@ -316,8 +316,17 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
|
||||
->firstOrFail();
|
||||
|
||||
$orderId = $response['orderID'];
|
||||
|
||||
$r = $this->gatewayRequest("/v1/checkout/orders/{$orderId}/", 'delete', ['body' => '']);
|
||||
|
||||
$data["payer"] = [
|
||||
"name" => [
|
||||
"given_name" => $this->client->present()->first_name(),
|
||||
"surname" => $this->client->present()->last_name()
|
||||
],
|
||||
"email_address" => $this->client->present()->email(),
|
||||
];
|
||||
|
||||
$data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
|
||||
$data["payment_source"] = [
|
||||
"card" => [
|
||||
@ -332,8 +341,6 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
|
||||
|
||||
$orderId = $this->createOrder($data);
|
||||
|
||||
// $r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
|
||||
|
||||
try {
|
||||
|
||||
$r = $this->gatewayRequest("/v2/checkout/orders/{$orderId}", 'get', ['body' => '']);
|
||||
@ -395,6 +402,14 @@ class PayPalPPCPPaymentDriver extends PayPalBasePaymentDriver
|
||||
$data = [];
|
||||
$this->payment_hash = $payment_hash;
|
||||
|
||||
$data["payer"] = [
|
||||
"name" => [
|
||||
"given_name" => $this->client->present()->first_name(),
|
||||
"surname" => $this->client->present()->last_name()
|
||||
],
|
||||
"email_address" => $this->client->present()->email(),
|
||||
];
|
||||
|
||||
$data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
|
||||
$data["payment_source"] = [
|
||||
"card" => [
|
||||
|
@ -157,10 +157,6 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public function createOrder(array $data): string
|
||||
{
|
||||
|
||||
@ -213,6 +209,10 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
|
||||
$order['payment_source'] = $data['payment_source'];
|
||||
}
|
||||
|
||||
if(isset($data["payer"])){
|
||||
$order['payer'] = $data["payer"];
|
||||
}
|
||||
|
||||
$r = $this->gatewayRequest('/v2/checkout/orders', 'post', $order);
|
||||
|
||||
nlog($r->json());
|
||||
@ -274,6 +274,13 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
|
||||
|
||||
nlog($r->body());
|
||||
|
||||
$data["payer"] = [
|
||||
"name" => [
|
||||
"given_name" => $this->client->present()->first_name(),
|
||||
"surname" => $this->client->present()->last_name()
|
||||
],
|
||||
"email_address" => $this->client->present()->email(),
|
||||
];
|
||||
$data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
|
||||
$data["payment_source"] = [
|
||||
"card" => [
|
||||
@ -349,6 +356,14 @@ class PayPalRestPaymentDriver extends PayPalBasePaymentDriver
|
||||
$data = [];
|
||||
$this->payment_hash = $payment_hash;
|
||||
|
||||
$data['payer'] = [
|
||||
"name" => [
|
||||
"given_name" => $this->client->present()->first_name(),
|
||||
"surname" => $this->client->present()->last_name()
|
||||
],
|
||||
"email_address" => $this->client->present()->email(),
|
||||
];
|
||||
|
||||
$data['amount_with_fee'] = $this->payment_hash->data->amount_with_fee;
|
||||
$data["payment_source"] = [
|
||||
"card" => [
|
||||
|
@ -60,7 +60,10 @@ class PaymentMethod implements MethodInterface
|
||||
'id' => null
|
||||
] )->all();
|
||||
$data['gateway'] = $this->rotessa;
|
||||
$data['gateway_type_id'] = $data['client']->country->iso_3166_2 == 'US' ? GatewayType::BANK_TRANSFER : ( $data['client']->country->iso_3166_2 == 'CA' ? GatewayType::ACSS : (int) request('method'));
|
||||
// Set gateway type according to client country
|
||||
// $data['gateway_type_id'] = $data['client']->country->iso_3166_2 == 'US' ? GatewayType::BANK_TRANSFER : ( $data['client']->country->iso_3166_2 == 'CA' ? GatewayType::ACSS : (int) request('method'));
|
||||
// TODO: detect GatewayType based on client country USA vs CAN
|
||||
$data['gateway_type_id'] = GatewayType::ACSS ;
|
||||
$data['account'] = [
|
||||
'routing_number' => $data['client']->routing_id,
|
||||
'country' => $data['client']->country->iso_3166_2
|
||||
@ -145,18 +148,16 @@ class PaymentMethod implements MethodInterface
|
||||
$request->validate([
|
||||
'source' => ['required','string','exists:client_gateway_tokens,token'],
|
||||
'amount' => ['required','numeric'],
|
||||
'token_id' => ['required','integer','exists:client_gateway_tokens,id'],
|
||||
'process_date'=> ['required','date','after_or_equal:today'],
|
||||
]);
|
||||
$customer = ClientGatewayToken::query()
|
||||
->where('company_gateway_id', $this->rotessa->company_gateway->id)
|
||||
->where('client_id', $this->rotessa->client->id)
|
||||
->where('id', (int) $request->input('token_id'))
|
||||
->where('token', $request->input('source'))
|
||||
->first();
|
||||
if(!$customer) throw new \Exception('Client gateway token not found!', 605);
|
||||
if(!$customer) throw new \Exception('Client gateway token not found!', SystemLog::TYPE_ROTESSA);
|
||||
|
||||
$transaction = new Transaction($request->only('frequency' ,'installments','amount','process_date','comment'));
|
||||
$transaction = new Transaction($request->only('frequency' ,'installments','amount','process_date') + ['comment' => $this->rotessa->getDescription(false) ]);
|
||||
$transaction->additional(['customer_id' => $customer->gateway_customer_reference]);
|
||||
$transaction = array_filter( $transaction->resolve());
|
||||
$response = $this->rotessa->gateway->capture($transaction)->send();
|
||||
@ -182,12 +183,12 @@ class PaymentMethod implements MethodInterface
|
||||
[ 'data' => $data ],
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_SUCCESS,
|
||||
880,
|
||||
SystemLog::TYPE_ROTESSA,
|
||||
$this->rotessa->client,
|
||||
$this->rotessa->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', [ 'payment' => $this->rotessa->encodePrimaryKey($payment->id) ]);
|
||||
return redirect()->route('client.payments.show', [ 'payment' => $payment->hashed_id ]);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -205,7 +206,7 @@ class PaymentMethod implements MethodInterface
|
||||
$exception->getMessage(),
|
||||
SystemLog::CATEGORY_GATEWAY_RESPONSE,
|
||||
SystemLog::EVENT_GATEWAY_FAILURE,
|
||||
880,
|
||||
SystemLog::TYPE_ROTESSA,
|
||||
$this->rotessa->client,
|
||||
$this->rotessa->client->company,
|
||||
);
|
||||
|
@ -24,6 +24,7 @@ use App\Utils\Traits\MakesHash;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\PaymentDrivers\BaseDriver;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use App\PaymentDrivers\Rotessa\Resources\Customer;
|
||||
use App\PaymentDrivers\Rotessa\PaymentMethod as Acss;
|
||||
@ -64,13 +65,15 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
{
|
||||
$types = [];
|
||||
|
||||
if ($this->client
|
||||
/*
|
||||
// TODO: needs to test with US test account
|
||||
if ($this->client
|
||||
&& $this->client->currency()
|
||||
&& in_array($this->client->currency()->code, ['USD'])
|
||||
&& isset($this->client->country)
|
||||
&& in_array($this->client->country->iso_3166_2, ['US'])) {
|
||||
$types[] = GatewayType::BANK_TRANSFER;
|
||||
}
|
||||
}*/
|
||||
|
||||
if ($this->client
|
||||
&& $this->client->currency()
|
||||
@ -115,39 +118,108 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
public function importCustomers() {
|
||||
$this->init();
|
||||
try {
|
||||
$result = $this->gateway->getCustomers()->send();
|
||||
if(!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
|
||||
$customers = collect($result->getData())->unique('email');
|
||||
if(!$result = Cache::has("rotessa-import_customers-{$this->company_gateway->company->company_key}")) {
|
||||
$result = $this->gateway->getCustomers()->send();
|
||||
if(!$result->isSuccessful()) throw new \Exception($result->getMessage(), (int) $result->getCode());
|
||||
// cache results
|
||||
Cache::put("rotessa-import_customers-{$this->company_gateway->company->company_key}", $result->getData(), 60 * 60 * 24);
|
||||
}
|
||||
|
||||
$result = Cache::get("rotessa-import_customers-{$this->company_gateway->company->company_key}");
|
||||
$customers = collect($result)->unique('email');
|
||||
$client_emails = $customers->pluck('email')->all();
|
||||
$company_id = $this->company_gateway->company->id;
|
||||
// get existing customers
|
||||
$client_contacts = ClientContact::where('company_id', $company_id)->whereIn('email', $client_emails )->whereNull('deleted_at')->get();
|
||||
$client_contacts = $client_contacts->map(function($item, $key) use ($customers) {
|
||||
return array_merge([], (array) $customers->firstWhere("email", $item->email) , ['custom_identifier' => $item->client->number, 'identifier' => $item->client->number ]);
|
||||
return array_merge([], (array) $customers->firstWhere("email", $item->email) , ['custom_identifier' => $item->client->number, 'identifier' => $item->client->number, 'client_id' => $item->client->id ]);
|
||||
} );
|
||||
|
||||
// create payment methods
|
||||
$client_contacts->each(
|
||||
function($contact) use ($customers) {
|
||||
sleep(10);
|
||||
$result = $this->gateway->getCustomersId(['id' => ($contact = (object) $contact)->id])->send();
|
||||
$this->client = Client::find($contact->custom_identifier);
|
||||
$this->client = Client::find($contact->client_id);
|
||||
$customer = (new Customer($result->getData()))->additional(['id' => $contact->id, 'custom_identifier' => $contact->custom_identifier ] );
|
||||
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
// create new clients from rotessa customers
|
||||
$client_emails = $client_contacts->pluck('email')->all();
|
||||
$client_contacts = $customers->filter(function ($value, $key) use ($client_emails) {
|
||||
return !in_array(((object) $value)->email, $client_emails);
|
||||
})->each( function($customer) use ($company_id) {
|
||||
// create new client contact from rotess customer
|
||||
$customer = (object) $this->gateway->getCustomersId(['id' => ($customer = (object) $customer)->id])->send()->getData();
|
||||
/**
|
||||
{
|
||||
"account_number": "11111111"
|
||||
"active": true,
|
||||
"address": {
|
||||
"address_1": "123 Main Street",
|
||||
"address_2": "Unit 4",
|
||||
"city": "Birmingham",
|
||||
"id": 114397,
|
||||
"postal_code": "36016",
|
||||
"province_code": "AL"
|
||||
},
|
||||
"authorization_type": "Online",
|
||||
"bank_account_type": "Checking",
|
||||
"bank_name": "Scotiabank",
|
||||
"created_at": "2015-02-10T23:50:45.000-06:00",
|
||||
"custom_identifier": "Mikey",
|
||||
"customer_type": "Personal",
|
||||
"email": "mikesmith@test.com",
|
||||
"financial_transactions": [],
|
||||
"home_phone": "(204) 555 5555",
|
||||
"id": 1,
|
||||
"identifier": "Mikey",
|
||||
"institution_number": "",
|
||||
"name": "Mike Smith",
|
||||
"phone": "(204) 555 4444",
|
||||
"routing_number": "111111111",
|
||||
"transaction_schedules": [],
|
||||
"transit_number": "",
|
||||
"updated_at": "2015-02-10T23:50:45.000-06:00"
|
||||
}
|
||||
*/
|
||||
$client = (\App\Factory\ClientFactory::create($this->company_gateway->company_id, $this->company_gateway->user_id))->fill(
|
||||
[
|
||||
'address1' => $customer->address['address_1'] ?? '',
|
||||
'address2' =>$customer->address['address_2'] ?? '',
|
||||
'city' => $customer->address['city'] ?? '',
|
||||
'postal_code' => $customer->address['postal_code'] ?? '',
|
||||
'state' => $customer->address['province_code'] ?? '',
|
||||
'country_id' => empty($customer->transit_number) ? 840 : 124,
|
||||
'routing_id' => empty(($r = $customer->routing_number))? null : $r,
|
||||
"number" => str_pad($customer->account_number,3,'0',STR_PAD_LEFT)
|
||||
]
|
||||
);
|
||||
$client->saveQuietly();
|
||||
$contact = (\App\Factory\ClientContactFactory::create($company_id, $this->company_gateway->user_id))->fill([
|
||||
"first_name" => substr($customer->name, 0, stripos($customer->name, " ")),
|
||||
"last_name" => substr($customer->name, stripos($customer->name, " ")),
|
||||
"email" => $customer->email,
|
||||
"phone" => $customer->phone,
|
||||
"is_primary" => true,
|
||||
"send_email" => true,
|
||||
]);
|
||||
$client->contacts()->saveMany([$contact]);
|
||||
$contact = $client->contacts()->first();
|
||||
$this->client = $client;
|
||||
$customer = (new Customer((array) $customer))->additional(['id' => $customer->id, 'custom_identifier' => $customer->custom_identifier ?? $contact->id ] );
|
||||
$this->findOrCreateCustomer($customer->additional + $customer->jsonSerialize());
|
||||
});
|
||||
} catch (\Throwable $th) {
|
||||
$data = [
|
||||
$data = [
|
||||
'transaction_reference' => null,
|
||||
'transaction_response' => $th->getMessage(),
|
||||
'success' => false,
|
||||
'description' => $th->getMessage(),
|
||||
'code' =>(int) $th->getCode()
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(['server_response' => $th->getMessage(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, 880 , $this->client , $this->company_gateway->company);
|
||||
SystemLogger::dispatch(['server_response' => $th->getMessage(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, SystemLog::TYPE_ROTESSA , $this->company_gateway->client , $this->company_gateway->company);
|
||||
|
||||
|
||||
throw $th;
|
||||
}
|
||||
|
||||
@ -176,9 +248,11 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
$data = array_filter($customer->resolve());
|
||||
}
|
||||
|
||||
$payment_method_id = Arr::has($data,'address.postal_code') && ((int) $data['address']['postal_code'])? GatewayType::BANK_TRANSFER: GatewayType::ACSS;
|
||||
// $payment_method_id = Arr::has($data,'address.postal_code') && ((int) $data['address']['postal_code'])? GatewayType::BANK_TRANSFER: GatewayType::ACSS;
|
||||
// TODO: Check/ Validate postal code between USA vs CAN
|
||||
$payment_method_id = GatewayType::ACSS;
|
||||
$gateway_token = $this->storeGatewayToken( [
|
||||
'payment_meta' => $data + ['brand' => 'Rotessa'],
|
||||
'payment_meta' => $data + ['brand' => 'Rotessa', 'last4' => $data['bank_name'], 'type' => $data['bank_account_type'] ],
|
||||
'token' => encrypt(join(".", Arr::only($data, 'id','custom_identifier'))),
|
||||
'payment_method_id' => $payment_method_id ,
|
||||
], ['gateway_customer_reference' =>
|
||||
@ -198,7 +272,7 @@ class RotessaPaymentDriver extends BaseDriver
|
||||
'code' =>(int) $th->getCode()
|
||||
];
|
||||
|
||||
SystemLogger::dispatch(['server_response' => is_null($result) ? '' : $result->getData(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, 880 , $this->client, $this->client->company);
|
||||
SystemLogger::dispatch(['server_response' => is_null($result) ? '' : $result->getMessage(), 'data' => $data], SystemLog::CATEGORY_GATEWAY_RESPONSE, SystemLog::EVENT_GATEWAY_FAILURE, 880 , $this->client, $this->company_gateway->company);
|
||||
|
||||
throw $th;
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ class BrowserPay implements MethodInterface
|
||||
$this->stripe->client->company,
|
||||
);
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
|
||||
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -160,7 +160,7 @@ class CreditCard
|
||||
}
|
||||
}
|
||||
|
||||
return redirect()->route('client.payments.show', ['payment' => $this->stripe->encodePrimaryKey($payment->id)]);
|
||||
return redirect()->route('client.payments.show', ['payment' => $payment->hashed_id]);
|
||||
}
|
||||
|
||||
public function processUnsuccessfulPayment($server_response)
|
||||
|
@ -11,18 +11,22 @@
|
||||
|
||||
namespace App\PaymentDrivers\Stripe\Jobs;
|
||||
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\Company;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Models\Payment;
|
||||
use App\Libraries\MultiDB;
|
||||
use App\Models\PaymentHash;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\Services\Email\Email;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use App\Models\CompanyGateway;
|
||||
use App\Services\Email\EmailObject;
|
||||
use Illuminate\Support\Facades\App;
|
||||
use Illuminate\Mail\Mailables\Address;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\Middleware\WithoutOverlapping;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
|
||||
class ChargeRefunded implements ShouldQueue
|
||||
{
|
||||
@ -36,19 +40,10 @@ class ChargeRefunded implements ShouldQueue
|
||||
|
||||
public $deleteWhenMissingModels = true;
|
||||
|
||||
public $stripe_request;
|
||||
|
||||
public $company_key;
|
||||
|
||||
private $company_gateway_id;
|
||||
|
||||
public $payment_completed = false;
|
||||
|
||||
public function __construct($stripe_request, $company_key, $company_gateway_id)
|
||||
public function __construct(public array $stripe_request, private string $company_key)
|
||||
{
|
||||
$this->stripe_request = $stripe_request;
|
||||
$this->company_key = $company_key;
|
||||
$this->company_gateway_id = $company_gateway_id;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
@ -64,8 +59,8 @@ class ChargeRefunded implements ShouldQueue
|
||||
|
||||
$payment_hash_key = $source['metadata']['payment_hash'] ?? null;
|
||||
|
||||
$company_gateway = CompanyGateway::query()->find($this->company_gateway_id);
|
||||
$payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first();
|
||||
$company_gateway = $payment_hash->payment->company_gateway;
|
||||
|
||||
$stripe_driver = $company_gateway->driver()->init();
|
||||
|
||||
@ -79,7 +74,7 @@ class ChargeRefunded implements ShouldQueue
|
||||
->first();
|
||||
|
||||
//don't touch if already refunded
|
||||
if(!$payment || in_array($payment->status_id, [Payment::STATUS_PARTIALLY_REFUNDED, Payment::STATUS_REFUNDED])) {
|
||||
if(!$payment || $payment->status_id == Payment::STATUS_REFUNDED || $payment->is_deleted){
|
||||
return;
|
||||
}
|
||||
|
||||
@ -94,8 +89,19 @@ class ChargeRefunded implements ShouldQueue
|
||||
return;
|
||||
}
|
||||
|
||||
if($payment->status_id == Payment::STATUS_COMPLETED) {
|
||||
usleep(rand(200000,300000));
|
||||
$payment = $payment->fresh();
|
||||
|
||||
if($payment->status_id == Payment::STATUS_PARTIALLY_REFUNDED){
|
||||
//determine the delta in the refunded amount - how much has already been refunded and only apply the delta.
|
||||
|
||||
if(floatval($payment->refunded) >= floatval($amount_refunded))
|
||||
return;
|
||||
|
||||
$amount_refunded -= $payment->refunded;
|
||||
|
||||
}
|
||||
|
||||
$invoice_collection = $payment->paymentables
|
||||
->where('paymentable_type', 'invoices')
|
||||
->map(function ($pivot) {
|
||||
@ -117,9 +123,24 @@ class ChargeRefunded implements ShouldQueue
|
||||
];
|
||||
});
|
||||
|
||||
} elseif($invoice_collection->sum('amount') != $amount_refunded) {
|
||||
//too many edges cases at this point, return early
|
||||
}
|
||||
elseif($invoice_collection->sum('amount') != $amount_refunded) {
|
||||
|
||||
$refund_text = "A partial refund was processed for Payment #{$payment_hash->payment->number}. <br><br> This payment is associated with multiple invoices, so you will need to manually apply the refund to the correct invoice/s.";
|
||||
|
||||
App::setLocale($payment_hash->payment->company->getLocale());
|
||||
|
||||
$mo = new EmailObject();
|
||||
$mo->subject = "Refund processed in Stripe for multiple invoices, action required.";
|
||||
$mo->body = $refund_text;
|
||||
$mo->text_body = $refund_text;
|
||||
$mo->company_key = $payment_hash->payment->company->company_key;
|
||||
$mo->html_template = 'email.template.generic';
|
||||
$mo->to = [new Address($payment_hash->payment->company->owner()->email, $payment_hash->payment->company->owner()->present()->name())];
|
||||
|
||||
Email::dispatch($mo, $payment_hash->payment->company);
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
$invoices = $invoice_collection->toArray();
|
||||
@ -131,20 +152,21 @@ class ChargeRefunded implements ShouldQueue
|
||||
'date' => now()->format('Y-m-d'),
|
||||
'gateway_refund' => false,
|
||||
'email_receipt' => false,
|
||||
'via_webhook' => true,
|
||||
];
|
||||
|
||||
nlog($data);
|
||||
|
||||
$payment->refund($data);
|
||||
|
||||
$payment->private_notes .= 'Refunded via Stripe';
|
||||
return;
|
||||
}
|
||||
$payment->private_notes .= 'Refunded via Stripe ';
|
||||
|
||||
$payment->saveQuietly();
|
||||
|
||||
}
|
||||
|
||||
public function middleware()
|
||||
{
|
||||
return [new WithoutOverlapping($this->company_gateway_id)];
|
||||
return [new WithoutOverlapping($this->company_key)];
|
||||
}
|
||||
}
|
||||
|
@ -12,54 +12,55 @@
|
||||
|
||||
namespace App\PaymentDrivers;
|
||||
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Exceptions\StripeConnectFailure;
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Models\Client;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\Payment;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Models\SystemLog;
|
||||
use App\PaymentDrivers\Stripe\ACH;
|
||||
use App\PaymentDrivers\Stripe\ACSS;
|
||||
use App\PaymentDrivers\Stripe\Alipay;
|
||||
use App\PaymentDrivers\Stripe\BACS;
|
||||
use App\PaymentDrivers\Stripe\Bancontact;
|
||||
use App\PaymentDrivers\Stripe\BankTransfer;
|
||||
use App\PaymentDrivers\Stripe\BECS;
|
||||
use App\PaymentDrivers\Stripe\BrowserPay;
|
||||
use App\PaymentDrivers\Stripe\Charge;
|
||||
use App\PaymentDrivers\Stripe\Connect\Verify;
|
||||
use App\PaymentDrivers\Stripe\CreditCard;
|
||||
use App\PaymentDrivers\Stripe\EPS;
|
||||
use App\PaymentDrivers\Stripe\FPX;
|
||||
use App\PaymentDrivers\Stripe\GIROPAY;
|
||||
use App\PaymentDrivers\Stripe\iDeal;
|
||||
use App\PaymentDrivers\Stripe\ImportCustomers;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook;
|
||||
use App\PaymentDrivers\Stripe\Klarna;
|
||||
use App\PaymentDrivers\Stripe\PRZELEWY24;
|
||||
use App\PaymentDrivers\Stripe\SEPA;
|
||||
use App\PaymentDrivers\Stripe\SOFORT;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use Exception;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Laracasts\Presenter\Exceptions\PresenterException;
|
||||
use Stripe\Stripe;
|
||||
use Stripe\Account;
|
||||
use Stripe\Customer;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
use App\Models\Client;
|
||||
use App\Models\Payment;
|
||||
use Stripe\SetupIntent;
|
||||
use Stripe\StripeClient;
|
||||
use App\Models\SystemLog;
|
||||
use Stripe\PaymentIntent;
|
||||
use Stripe\PaymentMethod;
|
||||
use Stripe\SetupIntent;
|
||||
use Stripe\Stripe;
|
||||
use Stripe\StripeClient;
|
||||
use App\Models\GatewayType;
|
||||
use App\Models\PaymentHash;
|
||||
use App\Http\Requests\Request;
|
||||
use App\Jobs\Util\SystemLogger;
|
||||
use App\Utils\Traits\MakesHash;
|
||||
use App\Exceptions\PaymentFailed;
|
||||
use App\Models\ClientGatewayToken;
|
||||
use App\PaymentDrivers\Stripe\ACH;
|
||||
use App\PaymentDrivers\Stripe\EPS;
|
||||
use App\PaymentDrivers\Stripe\FPX;
|
||||
use App\PaymentDrivers\Stripe\ACSS;
|
||||
use App\PaymentDrivers\Stripe\BACS;
|
||||
use App\PaymentDrivers\Stripe\BECS;
|
||||
use App\PaymentDrivers\Stripe\SEPA;
|
||||
use App\PaymentDrivers\Stripe\iDeal;
|
||||
use App\PaymentDrivers\Stripe\Alipay;
|
||||
use App\PaymentDrivers\Stripe\Charge;
|
||||
use App\PaymentDrivers\Stripe\Klarna;
|
||||
use App\PaymentDrivers\Stripe\SOFORT;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use App\PaymentDrivers\Stripe\GIROPAY;
|
||||
use Stripe\Exception\ApiErrorException;
|
||||
use App\Exceptions\StripeConnectFailure;
|
||||
use App\PaymentDrivers\Stripe\Utilities;
|
||||
use App\PaymentDrivers\Stripe\Bancontact;
|
||||
use App\PaymentDrivers\Stripe\BrowserPay;
|
||||
use App\PaymentDrivers\Stripe\CreditCard;
|
||||
use App\PaymentDrivers\Stripe\PRZELEWY24;
|
||||
use App\PaymentDrivers\Stripe\BankTransfer;
|
||||
use App\PaymentDrivers\Stripe\Connect\Verify;
|
||||
use App\PaymentDrivers\Stripe\ImportCustomers;
|
||||
use App\PaymentDrivers\Stripe\Jobs\ChargeRefunded;
|
||||
use App\Http\Requests\Payments\PaymentWebhookRequest;
|
||||
use Laracasts\Presenter\Exceptions\PresenterException;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentFailureWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentProcessingWebhook;
|
||||
use App\PaymentDrivers\Stripe\Jobs\PaymentIntentPartiallyFundedWebhook;
|
||||
|
||||
class StripePaymentDriver extends BaseDriver
|
||||
{
|
||||
@ -670,31 +671,39 @@ class StripePaymentDriver extends BaseDriver
|
||||
|
||||
public function processWebhookRequest(PaymentWebhookRequest $request)
|
||||
{
|
||||
nlog($request->all());
|
||||
|
||||
if ($request->type === 'customer.source.updated') {
|
||||
$ach = new ACH($this);
|
||||
$ach->updateBankAccount($request->all());
|
||||
}
|
||||
|
||||
if ($request->type === 'payment_intent.processing') {
|
||||
PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 12)));
|
||||
PaymentIntentProcessingWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5));
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
//payment_intent.succeeded - this will confirm or cancel the payment
|
||||
if ($request->type === 'payment_intent.succeeded') {
|
||||
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 15)));
|
||||
PaymentIntentWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5));
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
if ($request->type === 'payment_intent.partially_funded') {
|
||||
PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(10, 15)));
|
||||
PaymentIntentPartiallyFundedWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(5));
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
if (in_array($request->type, ['payment_intent.payment_failed', 'charge.failed'])) {
|
||||
PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(rand(5, 10)));
|
||||
PaymentIntentFailureWebhook::dispatch($request->data, $request->company_key, $this->company_gateway->id)->delay(now()->addSeconds(2));
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
|
||||
if ($request->type === 'charge.refunded' && $request->data['object']['status'] == 'succeeded') {
|
||||
ChargeRefunded::dispatch($request->data, $request->company_key)->delay(now()->addSeconds(5));
|
||||
|
||||
return response()->json([], 200);
|
||||
}
|
||||
@ -702,7 +711,6 @@ class StripePaymentDriver extends BaseDriver
|
||||
if ($request->type === 'charge.succeeded') {
|
||||
foreach ($request->data as $transaction) {
|
||||
|
||||
|
||||
$payment = Payment::query()
|
||||
->where('company_id', $this->company_gateway->company_id)
|
||||
->where(function ($query) use ($transaction) {
|
||||
|
@ -192,6 +192,7 @@ class PaymentMethod
|
||||
'label' => ctrans('texts.apply_credit'),
|
||||
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,
|
||||
'gateway_type_id' => GatewayType::CREDIT,
|
||||
'is_paypal' => false,
|
||||
];
|
||||
}
|
||||
|
||||
@ -210,12 +211,14 @@ class PaymentMethod
|
||||
'label' => $gateway->getConfigField('name').$fee_label,
|
||||
'company_gateway_id' => $gateway->id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
'is_paypal' => $gateway->isPayPal(),
|
||||
];
|
||||
} else {
|
||||
$this->payment_urls[] = [
|
||||
'label' => $gateway->getTypeAlias($type).$fee_label,
|
||||
'company_gateway_id' => $gateway->id,
|
||||
'gateway_type_id' => $type,
|
||||
'is_paypal' => $gateway->isPayPal(),
|
||||
];
|
||||
}
|
||||
|
||||
@ -236,12 +239,14 @@ class PaymentMethod
|
||||
'label' => $gateway->getConfigField('name').$fee_label,
|
||||
'company_gateway_id' => $gateway_id,
|
||||
'gateway_type_id' => GatewayType::CREDIT_CARD,
|
||||
'is_paypal' => $gateway->isPayPal(),
|
||||
];
|
||||
} else {
|
||||
$this->payment_urls[] = [
|
||||
'label' => $gateway->getTypeAlias($gateway_type_id).$fee_label,
|
||||
'company_gateway_id' => $gateway_id,
|
||||
'gateway_type_id' => $gateway_type_id,
|
||||
'is_paypal' => $gateway->isPayPal(),
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -259,6 +264,7 @@ class PaymentMethod
|
||||
'label' => ctrans('texts.apply_credit'),
|
||||
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,
|
||||
'gateway_type_id' => GatewayType::CREDIT,
|
||||
'is_paypal' => false,
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -44,17 +44,16 @@ class InstantPayment
|
||||
|
||||
public function run()
|
||||
{
|
||||
nlog($this->request->all());
|
||||
|
||||
/** @var \App\Models\ClientContact $cc */
|
||||
|
||||
$cc = auth()->guard('contact')->user();
|
||||
|
||||
$cc->first_name = $this->request->contact_first_name;
|
||||
$cc->last_name = $this->request->contact_last_name;
|
||||
$cc->email = $this->request->contact_email;
|
||||
|
||||
$cc->save();
|
||||
$cc->client->postal_code = strlen($cc->client->postal_code ?? '') > 1 ? $cc->client->postal_code : $this->request->client_postal_code;
|
||||
$cc->client->city = strlen($cc->client->city ?? '') > 1 ? $cc->client->city : $this->request->client_city;
|
||||
$cc->client->shipping_postal_code = strlen($cc->client->shipping_postal_code ?? '') > 1 ? $cc->client->shipping_postal_code : $cc->client->postal_code;
|
||||
$cc->client->shipping_city = strlen($cc->client->shipping_city ?? '') > 1 ? $cc->client->shipping_city : $cc->client->city;
|
||||
$cc->pushQuietly();
|
||||
|
||||
$is_credit_payment = false;
|
||||
|
||||
@ -73,8 +72,6 @@ class InstantPayment
|
||||
*/
|
||||
$payable_invoices = collect($this->request->payable_invoices);
|
||||
|
||||
nlog($payable_invoices);
|
||||
|
||||
$invoices = Invoice::query()->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
|
||||
|
||||
$invoices->each(function ($invoice) {
|
||||
|
@ -95,6 +95,48 @@ class Storecove {
|
||||
// parseStrategy: ubl
|
||||
// }
|
||||
*/
|
||||
public function sendJsonDocument($document)
|
||||
{
|
||||
|
||||
|
||||
$payload = [
|
||||
"legalEntityId" => 290868,
|
||||
"idempotencyGuid" => \Illuminate\Support\Str::uuid(),
|
||||
"routing" => [
|
||||
"eIdentifiers" => [],
|
||||
"emails" => ["david@invoiceninja.com"]
|
||||
],
|
||||
// "document" => [
|
||||
// 'documentType' => 'invoice',
|
||||
// "rawDocumentData" => [
|
||||
// "document" => base64_encode($document),
|
||||
// "parse" => true,
|
||||
// "parseStrategy" => "ubl",
|
||||
// ],
|
||||
// ],
|
||||
"document"=> [
|
||||
"documentType" => "invoice",
|
||||
"invoice" => $document,
|
||||
],
|
||||
];
|
||||
|
||||
$uri = "document_submissions";
|
||||
|
||||
nlog($payload);
|
||||
|
||||
$r = $this->httpClient($uri, (HttpVerb::POST)->value, $payload, $this->getHeaders());
|
||||
|
||||
nlog($r->body());
|
||||
nlog($r->json());
|
||||
|
||||
if($r->successful()) {
|
||||
return $r->json()['guid'];
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
public function sendDocument($document)
|
||||
{
|
||||
|
||||
@ -256,8 +298,6 @@ class Storecove {
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function addIdentifier(int $legal_entity_id, string $identifier, string $scheme)
|
||||
{
|
||||
$uri = "legal_entities/{$legal_entity_id}/peppol_identifiers";
|
||||
@ -278,7 +318,6 @@ class Storecove {
|
||||
|
||||
}
|
||||
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
private function getHeaders(array $headers = [])
|
||||
{
|
||||
@ -300,5 +339,4 @@ class Storecove {
|
||||
return $r;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -48,6 +48,7 @@ use InvoiceNinja\EInvoice\Models\Peppol\CustomerPartyType\AccountingCustomerPart
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\SupplierPartyType\AccountingSupplierParty;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\FinancialAccountType\PayeeFinancialAccount;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\IdentifierType\ID;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\Party as PeppolParty;
|
||||
use InvoiceNinja\EInvoice\Models\Peppol\PartyIdentification;
|
||||
|
||||
class Peppol extends AbstractService
|
||||
@ -150,14 +151,36 @@ class Peppol extends AbstractService
|
||||
|
||||
private InvoiceSum | InvoiceSumInclusive $calc;
|
||||
|
||||
private \InvoiceNinja\EInvoice\Models\Peppol\Invoice $p_invoice;
|
||||
/**
|
||||
* @param Invoice $invoice
|
||||
*/
|
||||
public function __construct(public Invoice $invoice, public ?\InvoiceNinja\EInvoice\Models\Peppol\Invoice $p_invoice = null)
|
||||
* @param Invoice $invoice
|
||||
*/
|
||||
public function __construct(public Invoice $invoice)
|
||||
{
|
||||
$this->p_invoice = $p_invoice ?? new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||
$this->company = $invoice->company;
|
||||
$this->calc = $this->invoice->calc();
|
||||
$this->setInvoice();
|
||||
}
|
||||
|
||||
private function setInvoice(): self
|
||||
{
|
||||
|
||||
|
||||
if($this->invoice->e_invoice){
|
||||
|
||||
|
||||
$e = new EInvoice();
|
||||
$this->p_invoice = $e->decode('Peppol', json_encode($this->invoice->e_invoice->Invoice), 'json');
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
$this->p_invoice = new \InvoiceNinja\EInvoice\Models\Peppol\Invoice();
|
||||
|
||||
$this->setInvoiceDefaults();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getInvoice(): \InvoiceNinja\EInvoice\Models\Peppol\Invoice
|
||||
@ -170,7 +193,31 @@ class Peppol extends AbstractService
|
||||
public function toXml(): string
|
||||
{
|
||||
$e = new EInvoice();
|
||||
return $e->encode($this->p_invoice, 'xml');
|
||||
$xml = $e->encode($this->p_invoice, 'xml');
|
||||
|
||||
$prefix = '<?xml version="1.0" encoding="utf-8"?>
|
||||
<Invoice
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
|
||||
xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">';
|
||||
|
||||
return str_ireplace(['\n','<?xml version="1.0"?>'], ['', $prefix], $xml);
|
||||
|
||||
}
|
||||
|
||||
public function toJson(): string
|
||||
{
|
||||
$e = new EInvoice();
|
||||
$json = $e->encode($this->p_invoice, 'json');
|
||||
|
||||
return $json;
|
||||
// $prefixes = str_ireplace(["cac:","cbc:"], "", $json);
|
||||
// return str_ireplace(["InvoiceLine", "PostalAddress", "PartyName"], ["invoiceLines","address", "companyName"], $prefixes);
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return json_decode($this->toJson(), true);
|
||||
}
|
||||
|
||||
public function run()
|
||||
@ -185,31 +232,12 @@ class Peppol extends AbstractService
|
||||
$this->p_invoice->TaxTotal = $this->getTotalTaxes();
|
||||
$this->p_invoice->LegalMonetaryTotal = $this->getLegalMonetaryTotal();
|
||||
|
||||
// $this->p_invoice->PaymentMeans = $this->getPaymentMeans();
|
||||
|
||||
// $payeeFinancialAccount = (new PayeeFinancialAccount())
|
||||
// ->setBankId($company->settings->custom_value1)
|
||||
// ->setBankName($company->settings->custom_value2);
|
||||
|
||||
// $paymentMeans = (new PaymentMeans())
|
||||
// ->setPaymentMeansCode($invoice->custom_value1)
|
||||
// ->setPayeeFinancialAccount($payeeFinancialAccount);
|
||||
// $ubl_invoice->setPaymentMeans($paymentMeans);
|
||||
$this->countryLevelMutators();
|
||||
|
||||
return $this;
|
||||
|
||||
}
|
||||
|
||||
// private function getPaymentMeans(): PaymentMeans
|
||||
// {
|
||||
// $payeeFinancialAccount = new PayeeFinancialAccount()
|
||||
// $payeeFinancialAccount->
|
||||
|
||||
// $ppm = new PaymentMeans();
|
||||
// $ppm->PayeeFinancialAccount = $payeeFinancialAccount;
|
||||
|
||||
// return $ppm;
|
||||
// }
|
||||
|
||||
private function getLegalMonetaryTotal(): LegalMonetaryTotal
|
||||
{
|
||||
$taxable = $this->getTaxable();
|
||||
@ -482,7 +510,7 @@ class Peppol extends AbstractService
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate2, $item->line_total) : $this->calcAmountLineTax($item->tax_rate2, $item->line_total);
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate2, $item->line_total) : $this->calcAmountLineTax($item->tax_rate2, $item->line_total);
|
||||
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
$tax_subtotal->TaxAmount = $tax_amount;
|
||||
@ -516,7 +544,7 @@ $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiv
|
||||
$tax_amount = new TaxAmount();
|
||||
$tax_amount->currencyID = $this->invoice->client->currency()->code;
|
||||
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate3, $item->line_total) : $this->calcAmountLineTax($item->tax_rate3, $item->line_total);
|
||||
$tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiveLineTax($item->tax_rate3, $item->line_total) : $this->calcAmountLineTax($item->tax_rate3, $item->line_total);
|
||||
|
||||
$tax_subtotal = new TaxSubtotal();
|
||||
$tax_subtotal->TaxAmount = $tax_amount;
|
||||
@ -572,7 +600,9 @@ $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiv
|
||||
$party->PhysicalLocation = $address;
|
||||
|
||||
$contact = new Contact();
|
||||
$contact->ElectronicMail = $this->invoice->company->owner()->email ?? 'owner@gmail.com';
|
||||
$contact->ElectronicMail = $this->getSetting('Invoice.AccountingSupplierParty.Party.Contact') ?? $this->invoice->company->owner()->present()->email();
|
||||
$contact->Telephone = $this->getSetting('Invoice.AccountingSupplierParty.Party.Telephone') ?? $this->invoice->company->getSetting('phone');
|
||||
$contact->Name = $this->getSetting('Invoice.AccountingSupplierParty.Party.Name') ?? $this->invoice->company->owner()->present()->name();
|
||||
|
||||
$party->Contact = $contact;
|
||||
|
||||
@ -695,16 +725,283 @@ $tax_amount->amount = $this->invoice->uses_inclusive_taxes ? $this->calcInclusiv
|
||||
'PaymentTerms' => 7,
|
||||
];
|
||||
|
||||
//only scans for top level props
|
||||
foreach($settings as $prop => $visibility){
|
||||
|
||||
if($prop_value = PropertyResolver::resolve($this->invoice->client->e_invoice, $prop))
|
||||
if($prop_value = $this->getSetting($prop))
|
||||
$this->p_invoice->{$prop} = $prop_value;
|
||||
elseif($prop_value = PropertyResolver::resolve($this->invoice->company->e_invoice, $prop)) {
|
||||
$this->p_invoice->{$prop} = $prop_value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSetting(string $property_path): mixed
|
||||
{
|
||||
|
||||
if($prop_value = PropertyResolver::resolve($this->invoice->e_invoice, $property_path))
|
||||
return $prop_value;
|
||||
elseif($prop_value = PropertyResolver::resolve($this->invoice->client->e_invoice, $property_path))
|
||||
return $prop_value;
|
||||
elseif($prop_value = PropertyResolver::resolve($this->invoice->company->e_invoice, $property_path))
|
||||
return $prop_value;
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
public function countryLevelMutators():self
|
||||
{
|
||||
|
||||
if(method_exists($this, $this->invoice->company->country()->iso_3166_2))
|
||||
$this->{$this->invoice->company->country()->iso_3166_2}();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function setPaymentMeans(bool $required = false): self
|
||||
{
|
||||
|
||||
if($this->p_invoice->PaymentMeans)
|
||||
return $this;
|
||||
elseif(!isset($this->p_invoice->PaymentMeans) && $paymentMeans = $this->getSetting('Invoice.PaymentMeans')){
|
||||
$this->p_invoice->PaymentMeans = is_array($paymentMeans) ? $paymentMeans : [$paymentMeans];
|
||||
return $this;
|
||||
}
|
||||
|
||||
if($required)
|
||||
throw new \Exception('e-invoice generation halted:: Payment Means required');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function DE(): self
|
||||
{
|
||||
// accountingsupplierparty.party.contact MUST be set - Name / Telephone / Electronic Mail
|
||||
// this is forced by default.
|
||||
|
||||
$this->setPaymentMeans(true);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function CH(): self
|
||||
{
|
||||
//if QR-Bill support required - then special flow required.... optional.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function AT(): self
|
||||
{
|
||||
//special fields for sending to AT:GOV
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function AU(): self
|
||||
{
|
||||
|
||||
//if payment means are included, they must be the same `type`
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function ES(): self
|
||||
{
|
||||
|
||||
// For B2B, provide an ES:DIRE routing identifier and an ES:VAT tax identifier.
|
||||
// both sender and receiver must be an ES company;
|
||||
// you must have a "credit_transfer" PaymentMean;
|
||||
// the "dueDate" property is mandatory.
|
||||
|
||||
// For B2G, provide three ES:FACE identifiers in the routing object,
|
||||
// as well as the ES:VAT tax identifier in the accountingCustomerParty.publicIdentifiers.
|
||||
// The invoice will then be routed through the FACe network. The three required ES:FACE identifiers are as follows:
|
||||
// "routing": {
|
||||
// "eIdentifiers":[
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-01-FISCAL"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-02-RECEPTOR"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "ES:FACE",
|
||||
// "id": "L01234567",
|
||||
// "role": "ES-03-PAGADOR"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function FI(): self
|
||||
{
|
||||
|
||||
// For Finvoice, provide an FI:OPID routing identifier and an FI:OVT legal identifier.
|
||||
// An FI:VAT is recommended. In many cases (depending on the sender/receiver country and the type of service/goods)
|
||||
// an FI:VAT is required. So we recommend always including this.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function FR(): self
|
||||
{
|
||||
// When sending invoices to the French government (Chorus Pro):
|
||||
|
||||
// All invoices have to be routed to SIRET 0009:11000201100044. There is no test environment for sending to public entities.
|
||||
|
||||
// The SIRET / 0009 identifier of the final recipient is to be included in the invoice.accountingCustomerParty.publicIdentifiers array.
|
||||
|
||||
// The service code must be sent in invoice.buyerReference (deprecated) or the invoice.references array (documentType buyer_reference)
|
||||
|
||||
// The commitment number must be sent in the invoice.orderReference (deprecated) or the invoice.references array (documentType purchase_order).
|
||||
|
||||
// Invoices to companies (SIRET / 0009 or SIRENE / 0002) are routed directly to that identifier.
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function IT(): self
|
||||
{
|
||||
// IT Sender, IT Receiver, B2B/B2G
|
||||
// Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario)
|
||||
|
||||
// IT Sender, IT Receiver, B2C
|
||||
// Provide the receiver IT:CF and the receiver IT:CUUO (codice destinatario)
|
||||
|
||||
// IT Sender, non-IT Receiver
|
||||
// Provide the receiver tax identifier and any routing identifier applicable to the receiving country (see Receiver Identifiers).
|
||||
|
||||
// non-IT Sender, IT Receiver, B2B/B2G
|
||||
// Provide the receiver IT:VAT and the receiver IT:CUUO (codice destinatario)
|
||||
|
||||
// non-IT Sender, IT Receiver, B2C
|
||||
// Provide the receiver IT:CF and an optional email. The invoice will be eReported and sent via email. Note that this cannot be a PEC email address.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function MY(): self
|
||||
{
|
||||
//way too much to digest here, delayed.
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function NL(): self
|
||||
{
|
||||
|
||||
// When sending to public entities, the invoice.accountingSupplierParty.party.contact.email is mandatory.
|
||||
|
||||
// Dutch senders and receivers require a legal identifier. For companies, this is NL:KVK, for public entities this is NL:OINO.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function NZ(): self
|
||||
{
|
||||
// New Zealand uses a GLN to identify businesses. In addition, when sending invoices to a New Zealand customer, make sure you include the pseudo identifier NZ:GST as their tax identifier.
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function PL(): self
|
||||
{
|
||||
|
||||
// Because using this network is not yet mandatory, the default workflow is to not use this network. Therefore, you have to force its use, as follows:
|
||||
|
||||
// "routing": {
|
||||
// "eIdentifiers": [
|
||||
// {
|
||||
// "scheme": "PL:VAT",
|
||||
// "id": "PL0101010101"
|
||||
// }
|
||||
// ],
|
||||
// "networks": [
|
||||
// {
|
||||
// "application": "pl-ksef",
|
||||
// "settings": {
|
||||
// "enabled": true
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// Note this will only work if your LegalEntity has been setup for this network.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function RO(): self
|
||||
{
|
||||
// Because using this network is not yet mandatory, the default workflow is to not use this network. Therefore, you have to force its use, as follows:
|
||||
|
||||
// "routing": {
|
||||
// "eIdentifiers": [
|
||||
// {
|
||||
// "scheme": "RO:VAT",
|
||||
// "id": "RO010101010"
|
||||
// }
|
||||
// ],
|
||||
// "networks": [
|
||||
// {
|
||||
// "application": "ro-anaf",
|
||||
// "settings": {
|
||||
// "enabled": true
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// Note this will only work if your LegalEntity has been setup for this network.
|
||||
// The county field for a Romania address must use the ISO3166-2:RO codes, e.g. "RO-AB, RO-AR". Don’t omit the country prefix!
|
||||
// The city field for county RO-B must be SECTOR1 - SECTOR6.
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function SG(): self
|
||||
{
|
||||
//delayed - stage 2
|
||||
return $this;
|
||||
}
|
||||
|
||||
//Sweden
|
||||
private function SE(): self
|
||||
{
|
||||
// Deliver invoices to the "Svefaktura" co-operation of local Swedish service providers.
|
||||
// Routing is through the SE:ORGNR together with a network specification:
|
||||
|
||||
// "routing": {
|
||||
// "eIdentifiers": [
|
||||
// {
|
||||
// "scheme": "SE:ORGNR",
|
||||
// "id": "0012345678"
|
||||
// }
|
||||
// ],
|
||||
// "networks": [
|
||||
// {
|
||||
// "application": "svefaktura",
|
||||
// "settings": {
|
||||
// "enabled": true
|
||||
// }
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
// Use of the "Svefaktura" co-operation can also be induced by specifying an operator id, as follows:
|
||||
|
||||
// "routing": {
|
||||
// "eIdentifiers": [
|
||||
// {
|
||||
// "scheme": "SE:ORGNR",
|
||||
// "id": "0012345678"
|
||||
// },
|
||||
// {
|
||||
// "scheme": "SE:OPID",
|
||||
// "id": "1234567890"
|
||||
// }
|
||||
// ]
|
||||
// }
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -526,11 +526,11 @@ class Email implements ShouldQueue
|
||||
{
|
||||
|
||||
/** Force free/trials onto specific mail driver */
|
||||
// if(Ninja::isHosted() && !$this->company->account->isPaid()) {
|
||||
// $this->mailer = 'mailgun';
|
||||
// $this->setHostedMailgunMailer();
|
||||
// return $this;
|
||||
// }
|
||||
if($this->mailer == 'default' && $this->company->account->isNewHostedAccount()) {
|
||||
$this->mailer = 'mailgun';
|
||||
$this->setHostedMailgunMailer();
|
||||
return $this;
|
||||
}
|
||||
|
||||
if (Ninja::isHosted() && $this->company->account->isPaid() && $this->email_object->settings->email_sending_method == 'default') {
|
||||
|
||||
@ -619,7 +619,7 @@ class Email implements ShouldQueue
|
||||
$smtp_username = $company->smtp_username ?? '';
|
||||
$smtp_password = $company->smtp_password ?? '';
|
||||
$smtp_encryption = $company->smtp_encryption ?? 'tls';
|
||||
$smtp_local_domain = strlen($company->smtp_local_domain) > 2 ? $company->smtp_local_domain : null;
|
||||
$smtp_local_domain = strlen($company->smtp_local_domain ?? '') > 2 ? $company->smtp_local_domain : null;
|
||||
$smtp_verify_peer = $company->smtp_verify_peer ?? true;
|
||||
|
||||
if(strlen($smtp_host) <= 1 ||
|
||||
|
@ -42,6 +42,9 @@ class AutoBillInvoice extends AbstractService
|
||||
|
||||
public function __construct(private Invoice $invoice, protected string $db)
|
||||
{
|
||||
|
||||
$this->client = $this->invoice->client;
|
||||
|
||||
}
|
||||
|
||||
public function run()
|
||||
@ -49,8 +52,7 @@ class AutoBillInvoice extends AbstractService
|
||||
MultiDB::setDb($this->db);
|
||||
|
||||
/* @var \App\Modesl\Client $client */
|
||||
$this->client = $this->invoice->client;
|
||||
|
||||
|
||||
$is_partial = false;
|
||||
|
||||
/* Is the invoice payable? */
|
||||
@ -272,7 +274,7 @@ class AutoBillInvoice extends AbstractService
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
private function applyUnappliedPayment(): self
|
||||
public function applyUnappliedPayment(): self
|
||||
{
|
||||
$unapplied_payments = Payment::query()
|
||||
->where('client_id', $this->client->id)
|
||||
@ -284,6 +286,11 @@ class AutoBillInvoice extends AbstractService
|
||||
->get();
|
||||
|
||||
$available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied');
|
||||
|
||||
nlog($this->client->id);
|
||||
nlog($this->invoice->id);
|
||||
nlog($unapplied_payments->sum('amount'));
|
||||
nlog($unapplied_payments->sum('applied'));
|
||||
|
||||
nlog("available unapplied balance = {$available_unapplied_balance}");
|
||||
|
||||
@ -347,7 +354,7 @@ class AutoBillInvoice extends AbstractService
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
private function applyCreditPayment(): self
|
||||
public function applyCreditPayment(): self
|
||||
{
|
||||
$available_credits = Credit::query()->where('client_id', $this->client->id)
|
||||
->where('is_deleted', false)
|
||||
|
@ -44,7 +44,6 @@ class RefundPayment
|
||||
->setStatus() //sets status of payment
|
||||
->updatePaymentables() //update the paymentable items
|
||||
->adjustInvoices()
|
||||
->finalize()
|
||||
->save();
|
||||
|
||||
if (array_key_exists('email_receipt', $this->refund_data) && $this->refund_data['email_receipt'] == 'true') {
|
||||
@ -52,10 +51,11 @@ class RefundPayment
|
||||
EmailRefundPayment::dispatch($this->payment, $this->payment->company, $contact);
|
||||
}
|
||||
|
||||
$notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : ";
|
||||
$notes .= $this->refund_data['gateway_refund'] !== false ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
|
||||
$is_gateway_refund = ($this->refund_data['gateway_refund'] !== false || $this->refund_failed || (isset($this->refund_data['via_webhook']) && $this->refund_data['via_webhook'] !== false)) ? ctrans('texts.yes') : ctrans('texts.no');
|
||||
$notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : " . $is_gateway_refund;
|
||||
|
||||
$this->createActivity($notes);
|
||||
$this->finalize();
|
||||
|
||||
return $this->payment;
|
||||
}
|
||||
@ -178,7 +178,7 @@ class RefundPayment
|
||||
*/
|
||||
private function setStatus()
|
||||
{
|
||||
if ($this->total_refund == $this->payment->amount) {
|
||||
if ($this->total_refund == $this->payment->amount || floatval($this->payment->amount) == floatval($this->payment->refunded)) {
|
||||
$this->payment->status_id = Payment::STATUS_REFUNDED;
|
||||
} else {
|
||||
$this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED;
|
||||
|
@ -739,7 +739,7 @@ class PdfBuilder
|
||||
if ($item->is_amount_discount) {
|
||||
$data[$key][$table_type.'.discount'] = $this->service->config->formatMoney($item->discount);
|
||||
} else {
|
||||
$data[$key][$table_type.'.discount'] = floatval($item->discount).'%';
|
||||
$data[$key][$table_type.'.discount'] = $this->service->config->formatValueNoTrailingZeroes(floatval($item->discount)).'%';
|
||||
}
|
||||
} else {
|
||||
$data[$key][$table_type.'.discount'] = '';
|
||||
@ -749,17 +749,17 @@ class PdfBuilder
|
||||
// but that's no longer necessary.
|
||||
|
||||
if (isset($item->tax_rate1)) {
|
||||
$data[$key][$table_type.'.tax_rate1'] = floatval($item->tax_rate1).'%';
|
||||
$data[$key][$table_type.'.tax_rate1'] = $this->service->config->formatValueNoTrailingZeroes(floatval($item->tax_rate1)).'%';
|
||||
$data[$key][$table_type.'.tax1'] = &$data[$key][$table_type.'.tax_rate1'];
|
||||
}
|
||||
|
||||
if (isset($item->tax_rate2)) {
|
||||
$data[$key][$table_type.'.tax_rate2'] = floatval($item->tax_rate2).'%';
|
||||
$data[$key][$table_type.'.tax_rate2'] = $this->service->config->formatValueNoTrailingZeroes(floatval($item->tax_rate2)).'%';
|
||||
$data[$key][$table_type.'.tax2'] = &$data[$key][$table_type.'.tax_rate2'];
|
||||
}
|
||||
|
||||
if (isset($item->tax_rate3)) {
|
||||
$data[$key][$table_type.'.tax_rate3'] = floatval($item->tax_rate3).'%';
|
||||
$data[$key][$table_type.'.tax_rate3'] = $this->service->config->formatValueNoTrailingZeroes(floatval($item->tax_rate3)).'%';
|
||||
$data[$key][$table_type.'.tax3'] = &$data[$key][$table_type.'.tax_rate3'];
|
||||
}
|
||||
|
||||
|
@ -88,7 +88,7 @@ class SubscriptionService
|
||||
|
||||
|
||||
// if we have a recurring product - then generate a recurring invoice
|
||||
if (strlen($this->subscription->recurring_product_ids) >= 1) {
|
||||
if (strlen($this->subscription->recurring_product_ids ?? '') >= 1) {
|
||||
if (isset($payment_hash->data->billing_context->bundle)) {
|
||||
$recurring_invoice = $this->convertInvoiceToRecurringBundle($payment_hash->payment->client_id, $payment_hash->data->billing_context->bundle);
|
||||
} else {
|
||||
@ -1024,10 +1024,10 @@ class SubscriptionService
|
||||
$invoice->subscription_id = $this->subscription->id;
|
||||
$invoice->is_proforma = true;
|
||||
|
||||
if (strlen($data['coupon']) >= 1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0) {
|
||||
if (strlen($data['coupon'] ?? '') >= 1 && ($data['coupon'] == $this->subscription->promo_code) && $this->subscription->promo_discount > 0) {
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
} elseif (strlen($this->subscription->promo_code) == 0 && $this->subscription->promo_discount > 0) {
|
||||
} elseif (strlen($this->subscription->promo_code ?? '') == 0 && $this->subscription->promo_discount > 0) {
|
||||
$invoice->discount = $this->subscription->promo_discount;
|
||||
$invoice->is_amount_discount = $this->subscription->is_amount_discount;
|
||||
}
|
||||
@ -1118,7 +1118,7 @@ class SubscriptionService
|
||||
*/
|
||||
public function triggerWebhook($context)
|
||||
{
|
||||
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url']) < 1) { //@phpstan-ignore-line
|
||||
if (empty($this->subscription->webhook_configuration['post_purchase_url']) || is_null($this->subscription->webhook_configuration['post_purchase_url']) || strlen($this->subscription->webhook_configuration['post_purchase_url'] ?? '') < 1) { //@phpstan-ignore-line
|
||||
return ["message" => "Success", "status_code" => 200];
|
||||
}
|
||||
|
||||
@ -1436,7 +1436,7 @@ class SubscriptionService
|
||||
*/
|
||||
public function handleNoPaymentFlow(Invoice $invoice, $bundle, ClientContact $contact)
|
||||
{
|
||||
if (strlen($this->subscription->recurring_product_ids) >= 1) {
|
||||
if (strlen($this->subscription->recurring_product_ids ?? '') >= 1) {
|
||||
$recurring_invoice = $this->convertInvoiceToRecurringBundle($contact->client_id, collect($bundle)->map(function ($bund) {
|
||||
return (object) $bund;
|
||||
}));
|
||||
@ -1492,7 +1492,7 @@ class SubscriptionService
|
||||
*/
|
||||
private function handleRedirect($default_redirect)
|
||||
{
|
||||
if (array_key_exists('return_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['return_url']) >= 1) {
|
||||
if (array_key_exists('return_url', $this->subscription->webhook_configuration) && strlen($this->subscription->webhook_configuration['return_url'] ?? '') >= 1) {
|
||||
return method_exists(redirect(), "send") ? redirect($this->subscription->webhook_configuration['return_url'])->send() : redirect($this->subscription->webhook_configuration['return_url']);
|
||||
}
|
||||
|
||||
|
@ -557,15 +557,7 @@ class TemplateService
|
||||
'reminder_last_sent' => $this->translateDate($invoice->reminder_last_sent, $invoice->client->date_format(), $invoice->client->locale()),
|
||||
'paid_to_date' => Number::formatMoney($invoice->paid_to_date, $invoice->client),
|
||||
'auto_bill_enabled' => (bool) $invoice->auto_bill_enabled,
|
||||
'client' => [
|
||||
'name' => $invoice->client->present()->name(),
|
||||
'balance' => $invoice->client->balance,
|
||||
'payment_balance' => $invoice->client->payment_balance,
|
||||
'credit_balance' => $invoice->client->credit_balance,
|
||||
'vat_number' => $invoice->client->vat_number ?? '',
|
||||
'currency' => $invoice->client->currency()->code ?? 'USD',
|
||||
'locale' => substr($invoice->client->locale(), 0, 2),
|
||||
],
|
||||
'client' => $this->getClient($invoice),
|
||||
'payments' => $payments,
|
||||
'total_tax_map' => $invoice->calc()->getTotalTaxMap(),
|
||||
'line_tax_map' => $invoice->calc()->getTaxMap(),
|
||||
@ -680,14 +672,7 @@ class TemplateService
|
||||
'custom_value4' => $payment->custom_value4 ?? '',
|
||||
'created_at' => $this->translateDate($payment->created_at, $payment->client->date_format(), $payment->client->locale()),
|
||||
'updated_at' => $this->translateDate($payment->updated_at, $payment->client->date_format(), $payment->client->locale()),
|
||||
'client' => [
|
||||
'name' => $payment->client->present()->name(),
|
||||
'balance' => $payment->client->balance,
|
||||
'payment_balance' => $payment->client->payment_balance,
|
||||
'credit_balance' => $payment->client->credit_balance,
|
||||
'vat_number' => $payment->client->vat_number ?? '',
|
||||
'currency' => $payment->client->currency()->code ?? 'USD',
|
||||
],
|
||||
'client' => $this->getClient($payment),
|
||||
'paymentables' => $pivot,
|
||||
'refund_activity' => $this->getPaymentRefundActivity($payment),
|
||||
];
|
||||
@ -760,14 +745,7 @@ class TemplateService
|
||||
'amount' => Number::formatMoney($quote->amount, $quote->client),
|
||||
'balance' => Number::formatMoney($quote->balance, $quote->client),
|
||||
'balance_raw' => (float) $quote->balance,
|
||||
'client' => [
|
||||
'name' => $quote->client->present()->name(),
|
||||
'balance' => $quote->client->balance,
|
||||
'payment_balance' => $quote->client->payment_balance,
|
||||
'credit_balance' => $quote->client->credit_balance,
|
||||
'vat_number' => $quote->client->vat_number ?? '',
|
||||
'currency' => $quote->client->currency()->code ?? 'USD',
|
||||
],
|
||||
'client' => $this->getClient($quote),
|
||||
'status_id' => $quote->status_id,
|
||||
'status' => Quote::stringStatus($quote->status_id),
|
||||
'number' => $quote->number ?: '',
|
||||
@ -888,14 +866,7 @@ class TemplateService
|
||||
'reminder_last_sent' => $this->translateDate($credit->reminder_last_sent, $credit->client->date_format(), $credit->client->locale()),
|
||||
'paid_to_date' => Number::formatMoney($credit->paid_to_date, $credit->client),
|
||||
'auto_bill_enabled' => (bool) $credit->auto_bill_enabled,
|
||||
'client' => [
|
||||
'name' => $credit->client->present()->name(),
|
||||
'balance' => $credit->client->balance,
|
||||
'payment_balance' => $credit->client->payment_balance,
|
||||
'credit_balance' => $credit->client->credit_balance,
|
||||
'vat_number' => $credit->client->vat_number ?? '',
|
||||
'currency' => $credit->client->currency()->code ?? 'USD',
|
||||
],
|
||||
'client' => $this->getClient($credit),
|
||||
'payments' => $payments,
|
||||
'total_tax_map' => $credit->calc()->getTotalTaxMap(),
|
||||
'line_tax_map' => $credit->calc()->getTaxMap(),
|
||||
@ -924,6 +895,25 @@ class TemplateService
|
||||
|
||||
}
|
||||
|
||||
private function getClient($entity): array
|
||||
{
|
||||
|
||||
return $entity->client ? [
|
||||
'name' => $entity->client->present()->name(),
|
||||
'balance' => $entity->client->balance,
|
||||
'payment_balance' => $entity->client->payment_balance,
|
||||
'credit_balance' => $entity->client->credit_balance,
|
||||
'vat_number' => $entity->client->vat_number ?? '',
|
||||
'currency' => $entity->client->currency()->code ?? 'USD',
|
||||
'custom_value1' => $entity->client->custom_value1 ?? '',
|
||||
'custom_value2' => $entity->client->custom_value2 ?? '',
|
||||
'custom_value3' => $entity->client->custom_value3 ?? '',
|
||||
'custom_value4' => $entity->client->custom_value4 ?? '',
|
||||
'address' => $entity->client->present()->address(),
|
||||
'shipping_address' => $entity->client->present()->shipping_address(),
|
||||
'locale' => substr($entity->client->locale(), 0, 2),
|
||||
] : [];
|
||||
}
|
||||
/**
|
||||
* @todo refactor
|
||||
*
|
||||
@ -953,14 +943,7 @@ class TemplateService
|
||||
'custom_value4' => $task->custom_value4 ?: '',
|
||||
'status' => $task->status ? $task->status->name : '',
|
||||
'user' => $this->userInfo($task->user),
|
||||
'client' => $task->client ? [
|
||||
'name' => $task->client->present()->name(),
|
||||
'balance' => $task->client->balance,
|
||||
'payment_balance' => $task->client->payment_balance,
|
||||
'credit_balance' => $task->client->credit_balance,
|
||||
'vat_number' => $task->client->vat_number ?? '',
|
||||
'currency' => $task->client->currency()->code ?? 'USD',
|
||||
] : [],
|
||||
'client' => $this->getClient($task),
|
||||
];
|
||||
|
||||
|
||||
@ -1015,15 +998,9 @@ class TemplateService
|
||||
'color' => (string) $project->color ?: '',
|
||||
'current_hours' => (int) $project->current_hours ?: 0,
|
||||
'tasks' => ($project->tasks && !$nested) ? $this->processTasks($project->tasks, true) : [], //@phpstan-ignore-line
|
||||
'client' => $project->client ? [
|
||||
'name' => $project->client->present()->name(),
|
||||
'balance' => $project->client->balance,
|
||||
'payment_balance' => $project->client->payment_balance,
|
||||
'credit_balance' => $project->client->credit_balance,
|
||||
'vat_number' => $project->client->vat_number ?? '',
|
||||
'currency' => $project->client->currency()->code ?? 'USD',
|
||||
] : [],
|
||||
'user' => $this->userInfo($project->user)
|
||||
'client' => $this->getClient($project),
|
||||
'user' => $this->userInfo($project->user),
|
||||
'invoices' => $this->processInvoices($project->invoices)
|
||||
];
|
||||
|
||||
}
|
||||
@ -1046,16 +1023,7 @@ class TemplateService
|
||||
] : [],
|
||||
'amount' => (float)$purchase_order->amount,
|
||||
'balance' => (float)$purchase_order->balance,
|
||||
'client' => $purchase_order->client ? [
|
||||
'name' => $purchase_order->client->present()->name(),
|
||||
'balance' => $purchase_order->client->balance,
|
||||
'payment_balance' => $purchase_order->client->payment_balance,
|
||||
'credit_balance' => $purchase_order->client->credit_balance,
|
||||
'vat_number' => $purchase_order->client->vat_number ?? '',
|
||||
'address' => $purchase_order->client->present()->address(),
|
||||
'shipping_address' => $purchase_order->client->present()->shipping_address(),
|
||||
'currency' => $purchase_order->client->currency()->code ?? 'USD',
|
||||
] : [],
|
||||
'client' => $this->getClient($purchase_order),
|
||||
'status_id' => (string)($purchase_order->status_id ?: 1),
|
||||
'status' => PurchaseOrder::stringStatus($purchase_order->status_id ?? 1),
|
||||
'is_deleted' => (bool)$purchase_order->is_deleted,
|
||||
|
436
composer.lock
generated
436
composer.lock
generated
File diff suppressed because it is too large
Load Diff
@ -193,7 +193,7 @@ return [
|
||||
*/
|
||||
App\Providers\AppServiceProvider::class,
|
||||
App\Providers\AuthServiceProvider::class,
|
||||
// App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\BroadcastServiceProvider::class,
|
||||
App\Providers\EventServiceProvider::class,
|
||||
App\Providers\RouteServiceProvider::class,
|
||||
App\Providers\ComposerServiceProvider::class,
|
||||
|
@ -39,7 +39,7 @@ return [
|
||||
'host' => env('PUSHER_HOST', 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com'),
|
||||
'port' => env('PUSHER_PORT', 443),
|
||||
'scheme' => env('PUSHER_SCHEME', 'https'),
|
||||
'encrypted' => true,
|
||||
'encrypted' => false,
|
||||
'useTLS' => env('PUSHER_SCHEME', 'https') === 'https',
|
||||
],
|
||||
'client_options' => [
|
||||
|
@ -17,8 +17,8 @@ return [
|
||||
'require_https' => env('REQUIRE_HTTPS', true),
|
||||
'app_url' => rtrim(env('APP_URL', ''), '/'),
|
||||
'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
|
||||
'app_version' => env('APP_VERSION', '5.10.13'),
|
||||
'app_tag' => env('APP_TAG', '5.10.13'),
|
||||
'app_version' => env('APP_VERSION', '5.10.16'),
|
||||
'app_tag' => env('APP_TAG', '5.10.16'),
|
||||
'minimum_client_version' => '5.0.16',
|
||||
'terms_version' => '1.0.1',
|
||||
'api_secret' => env('API_SECRET', false),
|
||||
|
@ -15,42 +15,23 @@ return new class extends Migration
|
||||
public function up(): void
|
||||
{
|
||||
Model::unguard();
|
||||
\DB::statement('SET FOREIGN_KEY_CHECKS=0;');
|
||||
|
||||
$record = Gateway::where('name', '=', 'Rotessa')->first();
|
||||
$count = (int) Gateway::count();
|
||||
if(!Gateway::find(63)) {
|
||||
$configuration = new \stdClass;
|
||||
$configuration->apiKey = '';
|
||||
$configuration->testMode = true;
|
||||
|
||||
$configuration = new \stdClass;
|
||||
$configuration->api_key = '';
|
||||
$configuration->test_mode = true;
|
||||
|
||||
if (!$record) {
|
||||
$gateway = new Gateway;
|
||||
} else {
|
||||
$gateway = $record;
|
||||
$gateway = new Gateway();
|
||||
$gateway->id = 63;
|
||||
$gateway->name = 'Rotessa';
|
||||
$gateway->key = '91be24c7b792230bced33e930ac61676';
|
||||
$gateway->provider = 'Rotessa';
|
||||
$gateway->is_offsite = true;
|
||||
$gateway->fields = \json_encode($configuration);
|
||||
$gateway->visible = 1;
|
||||
$gateway->site_url = "https://rotessa.com";
|
||||
$gateway->default_gateway_type_id = 2;
|
||||
$gateway->save();
|
||||
}
|
||||
|
||||
$gateway->id = 4002;
|
||||
$gateway->name = 'Rotessa';
|
||||
$gateway->key = Str::lower(Str::random(32));
|
||||
$gateway->provider = 'Rotessa';
|
||||
$gateway->is_offsite = true;
|
||||
$gateway->fields = \json_encode($configuration);
|
||||
$gateway->visible = 1;
|
||||
$gateway->site_url = "https://rotessa.com";
|
||||
$gateway->default_gateway_type_id = 2;
|
||||
$gateway->save();
|
||||
|
||||
Gateway::query()->where('name','=', 'Rotessa')->update(['visible' => 1]);
|
||||
|
||||
\DB::statement('SET FOREIGN_KEY_CHECKS=1;');
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Gateway::where('name', '=', 'Rotessa')->delete();
|
||||
}
|
||||
};
|
||||
|
@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
use App\Models\Company;
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Company::whereNotNull('tax_data')
|
||||
->cursor()
|
||||
->each(function($company){
|
||||
|
||||
if($company->tax_data?->version == 'alpha')
|
||||
{
|
||||
|
||||
$company->update(['tax_data' => new \App\DataMapper\Tax\TaxModel($company->tax_data)]);
|
||||
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
//
|
||||
}
|
||||
};
|
@ -88,6 +88,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
['id' => 60, 'name' => 'PayPal REST', 'provider' => 'PayPal_Rest', 'key' => '80af24a6a691230bbec33e930ab40665', 'fields' => '{"clientId":"","secret":"","signature":"","testMode":false}'],
|
||||
['id' => 61, 'name' => 'PayPal Platform', 'provider' => 'PayPal_PPCP', 'key' => '80af24a6a691230bbec33e930ab40666', 'fields' => '{"testMode":false}'],
|
||||
['id' => 62, 'name' => 'BTCPay', 'provider' => 'BTCPay', 'key' => 'vpyfbmdrkqcicpkjqdusgjfluebftuva', 'fields' => '{"btcpayUrl":"", "apiKey":"", "storeId":"", "webhookSecret":""}'],
|
||||
['id' => 63, 'name' => 'Rotessa', 'is_offsite' => false, 'sort_order' => 22, 'provider' => 'Rotessa', 'key' => '91be24c7b792230bced33e930ac61676', 'fields' => '{"apiKey":"", "testMode":""}'],
|
||||
];
|
||||
|
||||
foreach ($gateways as $gateway) {
|
||||
@ -104,7 +105,7 @@ class PaymentLibrariesSeeder extends Seeder
|
||||
|
||||
Gateway::query()->update(['visible' => 0]);
|
||||
|
||||
Gateway::whereIn('id', [1, 3, 7, 11, 15, 20, 39, 46, 55, 50, 57, 52, 58, 59, 60, 62])->update(['visible' => 1]);
|
||||
Gateway::whereIn('id', [1, 3, 7, 11, 15, 20, 39, 46, 55, 50, 57, 52, 58, 59, 60, 62, 63])->update(['visible' => 1]);
|
||||
|
||||
if (Ninja::isHosted()) {
|
||||
Gateway::whereIn('id', [20, 49])->update(['visible' => 0]);
|
||||
|
@ -5300,7 +5300,7 @@ $lang = array(
|
||||
'merge_to_pdf' => 'Merge to PDF',
|
||||
'latest_requires_php_version' => 'Note: the latest version requires PHP :version',
|
||||
'auto_expand_product_table_notes' => 'Automatically expand products table notes',
|
||||
'auto_expand_product_table_notes_help' => 'Automatically expands the notes section within the products table to display more lines.',
|
||||
'auto_expand_product_table_notes_help' => 'Automatically expands the notes section within the products table to display more lines.'
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
@ -5124,7 +5124,7 @@ $lang = array(
|
||||
'all_contacts' => 'All Contacts',
|
||||
'insert_below' => 'Insert Below',
|
||||
'nordigen_handler_subtitle' => 'Bank account authentication. Selecting your institution to complete the request with your account credentials.',
|
||||
'nordigen_handler_error_heading_unknown' => 'An error has occured',
|
||||
'nordigen_handler_error_heading_unknown' => 'An error has occurred',
|
||||
'nordigen_handler_error_contents_unknown' => 'An unknown error has occurred! Reason:',
|
||||
'nordigen_handler_error_heading_token_invalid' => 'Invalid Token',
|
||||
'nordigen_handler_error_contents_token_invalid' => 'The provided token was invalid. Contact support for help, if this issue persists.',
|
||||
@ -5301,6 +5301,15 @@ $lang = array(
|
||||
'latest_requires_php_version' => 'Note: the latest version requires PHP :version',
|
||||
'auto_expand_product_table_notes' => 'Automatically expand products table notes',
|
||||
'auto_expand_product_table_notes_help' => 'Automatically expands the notes section within the products table to display more lines.',
|
||||
'institution_number' => 'Institution Number',
|
||||
'transit_number' => 'Transit Number',
|
||||
'personal' => 'Personal',
|
||||
'address_information' => 'Address Information',
|
||||
'enter_the_information_for_the_bank_account' => 'Enter the Information for the Bank Account',
|
||||
'account_holder_information' => 'Account Holder Information',
|
||||
'enter_information_for_the_account_holder' => 'Enter Information for the Account Holder',
|
||||
'customer_type' => 'Customer Type',
|
||||
'process_date' => 'Process Date'
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
@ -5298,7 +5298,7 @@ Lorsque les montant apparaîtront sur votre relevé, veuillez revenir sur cette
|
||||
'latest_requires_php_version' => 'Note: La dernière version requiert PHP :version',
|
||||
'auto_expand_product_table_notes' => 'Développer automatiquement les notes du tableau de produits',
|
||||
'auto_expand_product_table_notes_help' => '
|
||||
Développe automatiquement la section des notes dans le tableau de produits pour afficher plus de lignes.',
|
||||
Développe automatiquement la section des notes dans le tableau de produits pour afficher plus de lignes.'
|
||||
);
|
||||
|
||||
return $lang;
|
||||
|
File diff suppressed because one or more lines are too long
1
public/build/assets/app-039bd735.css
vendored
Normal file
1
public/build/assets/app-039bd735.css
vendored
Normal file
File diff suppressed because one or more lines are too long
109
public/build/assets/app-234e3402.js
vendored
109
public/build/assets/app-234e3402.js
vendored
File diff suppressed because one or more lines are too long
109
public/build/assets/app-e0713224.js
vendored
Normal file
109
public/build/assets/app-e0713224.js
vendored
Normal file
File diff suppressed because one or more lines are too long
9
public/build/assets/payment-1bdbd169.js
vendored
9
public/build/assets/payment-1bdbd169.js
vendored
@ -1,9 +0,0 @@
|
||||
/**
|
||||
* 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 s{constructor(t,e,a){this.shouldDisplayTerms=t,this.shouldDisplaySignature=e,this.shouldDisplayRff=a,this.submitting=!1,this.steps=new Map,this.shouldDisplayRff&&this.steps.set("rff",{element:document.getElementById("displayRequiredFieldsModal"),nextButton:document.getElementById("rff-next-step"),callback:()=>{const n={firstName:document.querySelector('input[name="rff_first_name"]'),lastName:document.querySelector('input[name="rff_last_name"]'),email:document.querySelector('input[name="rff_email"]')};n.firstName&&(document.querySelector('input[name="contact_first_name"]').value=n.firstName.value),n.lastName&&(document.querySelector('input[name="contact_last_name"]').value=n.lastName.value),n.email&&(document.querySelector('input[name="contact_email"]').value=n.email.value)}}),this.shouldDisplaySignature&&this.steps.set("signature",{element:document.getElementById("displaySignatureModal"),nextButton:document.getElementById("signature-next-step"),boot:()=>this.signaturePad=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"}),callback:()=>document.querySelector('input[name="signature"').value=this.signaturePad.toDataURL()}),this.shouldDisplayTerms&&this.steps.set("terms",{element:document.getElementById("displayTermsModal"),nextButton:document.getElementById("accept-terms-button")})}handleMethodSelect(t){if(document.getElementById("company_gateway_id").value=t.dataset.companyGatewayId,document.getElementById("payment_method_id").value=t.dataset.gatewayTypeId,this.steps.size===0)return this.submitForm();const e=this.steps.values().next().value;e.element.removeAttribute("style"),e.boot&&e.boot(),console.log(e),e.nextButton.addEventListener("click",()=>{e.element.setAttribute("style","display: none;"),this.steps=new Map(Array.from(this.steps.entries()).slice(1)),e.callback&&e.callback(),this.handleMethodSelect(t)})}submitForm(){this.submitting=!0,document.getElementById("payment-form").submit()}handle(){document.querySelectorAll(".dropdown-gateway-button").forEach(t=>{t.addEventListener("click",()=>{this.submitting||this.handleMethodSelect(t)})})}}const i=document.querySelector('meta[name="require-invoice-signature"]').content,o=document.querySelector('meta[name="show-invoice-terms"]').content,l=document.querySelector('meta[name="show-required-fields-form"]').content;new s(!!+o,!!+i,!!+l).handle();
|
9
public/build/assets/payment-292ee4d0.js
vendored
Normal file
9
public/build/assets/payment-292ee4d0.js
vendored
Normal 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
|
||||
*/class a{constructor(t,n){this.shouldDisplayTerms=t,this.shouldDisplaySignature=n,this.submitting=!1,this.steps=new Map,this.steps.set("rff",{element:document.getElementById("displayRequiredFieldsModal"),nextButton:document.getElementById("rff-next-step"),callback:()=>{const e={firstName:document.querySelector('input[name="rff_first_name"]'),lastName:document.querySelector('input[name="rff_last_name"]'),email:document.querySelector('input[name="rff_email"]'),city:document.querySelector('input[name="rff_city"]'),postalCode:document.querySelector('input[name="rff_postal_code"]')};e.firstName&&(document.querySelector('input[name="contact_first_name"]').value=e.firstName.value),e.lastName&&(document.querySelector('input[name="contact_last_name"]').value=e.lastName.value),e.email&&(document.querySelector('input[name="contact_email"]').value=e.email.value),e.city&&(document.querySelector('input[name="client_city"]').value=e.city.value),e.postalCode&&(document.querySelector('input[name="client_postal_code"]').value=e.postalCode.value)}}),this.shouldDisplaySignature&&this.steps.set("signature",{element:document.getElementById("displaySignatureModal"),nextButton:document.getElementById("signature-next-step"),boot:()=>this.signaturePad=new SignaturePad(document.getElementById("signature-pad"),{penColor:"rgb(0, 0, 0)"}),callback:()=>document.querySelector('input[name="signature"').value=this.signaturePad.toDataURL()}),this.shouldDisplayTerms&&this.steps.set("terms",{element:document.getElementById("displayTermsModal"),nextButton:document.getElementById("accept-terms-button")})}handleMethodSelect(t){document.getElementById("company_gateway_id").value=t.dataset.companyGatewayId,document.getElementById("payment_method_id").value=t.dataset.gatewayTypeId;const n=document.querySelector('input[name="contact_first_name"').value.length>=1&&document.querySelector('input[name="contact_last_name"').value.length>=1&&document.querySelector('input[name="contact_email"').value.length>=1&&document.querySelector('input[name="client_city"').value.length>=1&&document.querySelector('input[name="client_postal_code"').value.length>=1;if((t.dataset.isPaypal!="1"||n)&&this.steps.delete("rff"),this.steps.size===0)return this.submitForm();const e=this.steps.values().next().value;e.element.removeAttribute("style"),e.boot&&e.boot(),console.log(e),e.nextButton.addEventListener("click",()=>{e.element.setAttribute("style","display: none;"),this.steps=new Map(Array.from(this.steps.entries()).slice(1)),e.callback&&e.callback(),this.handleMethodSelect(t)})}submitForm(){this.submitting=!0,document.getElementById("payment-form").submit()}handle(){document.querySelectorAll(".dropdown-gateway-button").forEach(t=>{t.addEventListener("click",()=>{this.submitting||this.handleMethodSelect(t)})})}}const l=document.querySelector('meta[name="require-invoice-signature"]').content,o=document.querySelector('meta[name="show-invoice-terms"]').content;new a(!!+o,!!+l).handle();
|
@ -9,7 +9,7 @@
|
||||
]
|
||||
},
|
||||
"resources/js/app.js": {
|
||||
"file": "assets/app-234e3402.js",
|
||||
"file": "assets/app-e0713224.js",
|
||||
"imports": [
|
||||
"_index-08e160a7.js",
|
||||
"__commonjsHelpers-725317a4.js"
|
||||
@ -23,7 +23,7 @@
|
||||
"src": "resources/js/clients/invoices/action-selectors.js"
|
||||
},
|
||||
"resources/js/clients/invoices/payment.js": {
|
||||
"file": "assets/payment-1bdbd169.js",
|
||||
"file": "assets/payment-292ee4d0.js",
|
||||
"isEntry": true,
|
||||
"src": "resources/js/clients/invoices/payment.js"
|
||||
},
|
||||
@ -240,7 +240,7 @@
|
||||
"src": "resources/js/setup/setup.js"
|
||||
},
|
||||
"resources/sass/app.scss": {
|
||||
"file": "assets/app-f3b33400.css",
|
||||
"file": "assets/app-039bd735.css",
|
||||
"isEntry": true,
|
||||
"src": "resources/sass/app.scss"
|
||||
}
|
||||
|
77
resources/js/clients/invoices/payment.js
vendored
77
resources/js/clients/invoices/payment.js
vendored
@ -9,39 +9,47 @@
|
||||
*/
|
||||
|
||||
class Payment {
|
||||
constructor(displayTerms, displaySignature, displayRff) {
|
||||
constructor(displayTerms, displaySignature) {
|
||||
this.shouldDisplayTerms = displayTerms;
|
||||
this.shouldDisplaySignature = displaySignature;
|
||||
this.shouldDisplayRff = displayRff;
|
||||
|
||||
|
||||
this.submitting = false;
|
||||
this.steps = new Map()
|
||||
|
||||
if (this.shouldDisplayRff) {
|
||||
this.steps.set("rff", {
|
||||
element: document.getElementById('displayRequiredFieldsModal'),
|
||||
nextButton: document.getElementById('rff-next-step'),
|
||||
callback: () => {
|
||||
const fields = {
|
||||
firstName: document.querySelector('input[name="rff_first_name"]'),
|
||||
lastName: document.querySelector('input[name="rff_last_name"]'),
|
||||
email: document.querySelector('input[name="rff_email"]'),
|
||||
}
|
||||
|
||||
if (fields.firstName) {
|
||||
document.querySelector('input[name="contact_first_name"]').value = fields.firstName.value;
|
||||
}
|
||||
|
||||
if (fields.lastName) {
|
||||
document.querySelector('input[name="contact_last_name"]').value = fields.lastName.value;
|
||||
}
|
||||
|
||||
if (fields.email) {
|
||||
document.querySelector('input[name="contact_email"]').value = fields.email.value;
|
||||
}
|
||||
this.steps.set("rff", {
|
||||
element: document.getElementById('displayRequiredFieldsModal'),
|
||||
nextButton: document.getElementById('rff-next-step'),
|
||||
callback: () => {
|
||||
const fields = {
|
||||
firstName: document.querySelector('input[name="rff_first_name"]'),
|
||||
lastName: document.querySelector('input[name="rff_last_name"]'),
|
||||
email: document.querySelector('input[name="rff_email"]'),
|
||||
city: document.querySelector('input[name="rff_city"]'),
|
||||
postalCode: document.querySelector('input[name="rff_postal_code"]'),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (fields.firstName) {
|
||||
document.querySelector('input[name="contact_first_name"]').value = fields.firstName.value;
|
||||
}
|
||||
|
||||
if (fields.lastName) {
|
||||
document.querySelector('input[name="contact_last_name"]').value = fields.lastName.value;
|
||||
}
|
||||
|
||||
if (fields.email) {
|
||||
document.querySelector('input[name="contact_email"]').value = fields.email.value;
|
||||
}
|
||||
|
||||
if (fields.city) {
|
||||
document.querySelector('input[name="client_city"]').value = fields.city.value;
|
||||
}
|
||||
|
||||
if (fields.postalCode) {
|
||||
document.querySelector('input[name="client_postal_code"]').value = fields.postalCode.value;
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
if (this.shouldDisplaySignature) {
|
||||
this.steps.set("signature", {
|
||||
@ -71,7 +79,17 @@ class Payment {
|
||||
element.dataset.companyGatewayId;
|
||||
document.getElementById("payment_method_id").value =
|
||||
element.dataset.gatewayTypeId;
|
||||
|
||||
|
||||
const filledRff = document.querySelector('input[name="contact_first_name"').value.length >=1 &&
|
||||
document.querySelector('input[name="contact_last_name"').value.length >= 1 &&
|
||||
document.querySelector('input[name="contact_email"').value.length >= 1 &&
|
||||
document.querySelector('input[name="client_city"').value.length >= 1 &&
|
||||
document.querySelector('input[name="client_postal_code"').value.length >= 1;
|
||||
|
||||
if (element.dataset.isPaypal != '1' || filledRff) {
|
||||
this.steps.delete("rff");
|
||||
}
|
||||
|
||||
if (this.steps.size === 0) {
|
||||
return this.submitForm();
|
||||
}
|
||||
@ -124,6 +142,5 @@ const signature = document.querySelector(
|
||||
).content;
|
||||
|
||||
const terms = document.querySelector('meta[name="show-invoice-terms"]').content;
|
||||
const rff = document.querySelector('meta[name="show-required-fields-form"]').content;
|
||||
|
||||
new Payment(Boolean(+terms), Boolean(+signature), Boolean(+rff)).handle();
|
||||
new Payment(Boolean(+terms), Boolean(+signature)).handle();
|
||||
|
@ -141,12 +141,15 @@
|
||||
<input type="hidden" name="contact_first_name" value="{{ $contact->first_name }}">
|
||||
<input type="hidden" name="contact_last_name" value="{{ $contact->last_name }}">
|
||||
<input type="hidden" name="contact_email" value="{{ $contact->email }}">
|
||||
<input type="hidden" name="client_city" value="{{ $contact->client->city }}">
|
||||
<input type="hidden" name="client_postal_code" value="{{ $contact->client->postal_code }}">
|
||||
|
||||
</form>
|
||||
|
||||
@if($steps['started_payment'] == false)
|
||||
@foreach($this->methods as $method)
|
||||
<button
|
||||
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}'); $wire.$refresh(); "
|
||||
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}', '{{ $method['is_paypal'] }}'); $wire.$refresh(); "
|
||||
class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
|
||||
{{ $method['label'] }}
|
||||
</button>
|
||||
@ -189,27 +192,41 @@
|
||||
<form wire:submit="handleRff">
|
||||
@csrf
|
||||
|
||||
@if(strlen($contact->first_name) === 0)
|
||||
@if(strlen($contact->first_name ?? '') === 0)
|
||||
<div class="col-auto mt-3">
|
||||
<label for="first_name" class="input-label">{{ ctrans('texts.first_name') }}</label>
|
||||
<input id="first_name" class="input w-full" wire:model="contact_first_name" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($contact->last_name) === 0)
|
||||
@if(strlen($contact->last_name ?? '') === 0)
|
||||
<div class="col-auto mt-3 @if($contact->last_name) !== 0) hidden @endif">
|
||||
<label for="last_name" class="input-label">{{ ctrans('texts.last_name') }}</label>
|
||||
<input id="last_name" class="input w-full" wire:model="contact_last_name" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($contact->email) === 0)
|
||||
@if(strlen($contact->email ?? '') === 0)
|
||||
<div class="col-auto mt-3 @if($contact->email) !== 0) hidden @endif">
|
||||
<label for="email" class="input-label">{{ ctrans('texts.email') }}</label>
|
||||
<input id="email" class="input w-full" wire:model="contact_email" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($client_postal_code ?? '') === 0)
|
||||
<div class="col-auto mt-3 @if($client_postal_code) !== 0) hidden @endif">
|
||||
<label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label>
|
||||
<input id="postal_code" class="input w-full" wire:model="client_postal_code" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(strlen($client_city ?? '') === 0)
|
||||
<div class="col-auto mt-3 @if($client_city) !== 0) hidden @endif">
|
||||
<label for="city" class="input-label">{{ ctrans('texts.city') }}</label>
|
||||
<input id="city" class="input w-full" wire:model="client_city" />
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="button button-block bg-primary text-white mt-4">
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user