Merge branch 'invoiceninja:v5-develop' into v5-develop

This commit is contained in:
Kendall Arneaud 2024-07-29 18:53:21 -04:00 committed by GitHub
commit 37c6ba4d69
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
53 changed files with 926 additions and 527 deletions

View File

@ -38,6 +38,9 @@ jobs:
sudo php artisan cache:clear sudo php artisan cache:clear
sudo find ./vendor/bin/ -type f -exec chmod +x {} \; sudo find ./vendor/bin/ -type f -exec chmod +x {} \;
sudo find ./ -type d -exec chmod 755 {} \; 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 - name: Prepare React FrontEnd
run: | run: |
@ -46,10 +49,11 @@ jobs:
git checkout develop git checkout develop
cp .env.example .env cp .env.example .env
cp ../vite.config.ts.react ./vite.config.js cp ../vite.config.ts.react ./vite.config.js
sed -i '/"version"/c\ "version": " Latest Build - ${{ env.current_date }}",' package.json
npm i npm i
npm run build npm run build
cp -r dist/* ../public/ 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 - name: Prepare JS/CSS assets
run: | run: |

View File

@ -1 +1 @@
5.10.13 5.10.16

View File

@ -177,7 +177,6 @@ class BackupUpdate extends Command
$doc_bin = $document->getFile(); $doc_bin = $document->getFile();
} catch(\Exception $e) { } catch(\Exception $e) {
nlog("Exception:: BackupUpdate::" . $e->getMessage()); nlog("Exception:: BackupUpdate::" . $e->getMessage());
nlog($e->getMessage());
} }
if ($doc_bin) { if ($doc_bin) {

View File

@ -172,6 +172,7 @@ class BaseExport
'tax_rate3' => 'invoice.tax_rate3', 'tax_rate3' => 'invoice.tax_rate3',
'recurring_invoice' => 'invoice.recurring_id', 'recurring_invoice' => 'invoice.recurring_id',
'auto_bill' => 'invoice.auto_bill_enabled', 'auto_bill' => 'invoice.auto_bill_enabled',
'project' => 'invoice.project',
]; ];
protected array $recurring_invoice_report_keys = [ protected array $recurring_invoice_report_keys = [
@ -1038,6 +1039,10 @@ class BaseExport
$recurring_filters = []; $recurring_filters = [];
if($this->company->getSetting('report_include_drafts')){
$recurring_filters[] = RecurringInvoice::STATUS_DRAFT;
}
if (in_array('active', $status_parameters)) { if (in_array('active', $status_parameters)) {
$recurring_filters[] = RecurringInvoice::STATUS_ACTIVE; $recurring_filters[] = RecurringInvoice::STATUS_ACTIVE;
} }

View File

@ -153,9 +153,9 @@ class InvoiceExport extends BaseExport
private function decorateAdvancedFields(Invoice $invoice, array $entity): array private function decorateAdvancedFields(Invoice $invoice, array $entity): array
{ {
// if (in_array('invoice.status', $this->input['report_keys'])) { if (in_array('invoice.project', $this->input['report_keys'])) {
// $entity['invoice.status'] = $invoice->stringStatus($invoice->status_id); $entity['invoice.project'] = $invoice->project ? $invoice->project->name : '';
// } }
if (in_array('invoice.recurring_id', $this->input['report_keys'])) { if (in_array('invoice.recurring_id', $this->input['report_keys'])) {
$entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? ''; $entity['invoice.recurring_id'] = $invoice->recurring_invoice->number ?? '';

View File

@ -265,6 +265,10 @@ class InvoiceItemExport extends BaseExport
$entity['invoice.user_id'] = $invoice->user ? $invoice->user->present()->name() : '';// @phpstan-ignore-line $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; return $entity;
} }

View File

@ -92,6 +92,7 @@ class InvoiceDecorator extends Decorator implements DecoratorInterface
{ {
return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : ''; return $invoice->recurring_invoice ? $invoice->recurring_invoice->number : '';
} }
public function auto_bill_enabled(Invoice $invoice) public function auto_bill_enabled(Invoice $invoice)
{ {
return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no'); return $invoice->auto_bill_enabled ? ctrans('texts.yes') : ctrans('texts.no');

View File

@ -153,22 +153,22 @@ class InvoiceFilters extends QueryFilters
{ {
return $this->builder->where(function ($query) { return $this->builder->where(function ($query) {
$query->whereIn('invoices.status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT]) $query->whereIn('status_id', [Invoice::STATUS_PARTIAL, Invoice::STATUS_SENT])
->where('invoices.is_deleted', 0) ->where('is_deleted', 0)
->where('invoices.balance', '>', 0) ->where('balance', '>', 0)
->orWhere(function ($query) { ->where(function ($query) {
$query->whereNull('invoices.due_date') $query->whereNull('due_date')
->orWhere(function ($q) { ->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) { ->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(due_date), due_date ' . 'desc')
->orderByRaw('ISNULL(invoices.partial_due_date), invoices.partial_due_date ' . 'desc'); ->orderByRaw('ISNULL(partial_due_date), partial_due_date ' . 'desc');
}); });
} }

View File

@ -66,7 +66,7 @@ class ChartController extends BaseController
return response()->json($cs->chart_summary($request->input('start_date'), $request->input('end_date')), 200); 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() */ /** @var \App\Models\User auth()->user() */

View File

@ -300,7 +300,9 @@ class InvitationController extends Controller
'signature' => false, 'signature' => false,
'contact_first_name' => $invitation->contact->first_name ?? '', 'contact_first_name' => $invitation->contact->first_name ?? '',
'contact_last_name' => $invitation->contact->last_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); $request->replace($data);

View File

@ -108,11 +108,11 @@ class PaymentController extends Controller
*/ */
public function process(Request $request) public function process(Request $request)
{ {
$request->validate([ // $request->validate([
'contact_first_name' => ['required'], // 'contact_first_name' => ['required'],
'contact_last_name' => ['required'], // 'contact_last_name' => ['required'],
'contact_email' => ['required', 'email'], // 'contact_email' => ['required', 'email'],
]); // ]);
return (new InstantPayment($request))->run(); return (new InstantPayment($request))->run();
} }

View File

@ -85,7 +85,7 @@ class ImportController extends Controller
$contents = $this->convertEncoding($contents); $contents = $this->convertEncoding($contents);
// Store the csv in cache with an expiry of 10 minutes // 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 // Parse CSV
$csv_array = $this->getCsvData($contents); $csv_array = $this->getCsvData($contents);

View File

@ -19,8 +19,9 @@ use Illuminate\Contracts\Validation\ValidationRule;
*/ */
class BlackListRule implements ValidationRule class BlackListRule implements ValidationRule
{ {
/** Bad domains +/- dispoable email domains */ /** Bad domains +/- disposable email domains */
private array $blacklist = [ private array $blacklist = [
'padvn.com',
'anonaddy.me', 'anonaddy.me',
'nqmo.com', 'nqmo.com',
'wireconnected.com', 'wireconnected.com',

View File

@ -98,7 +98,7 @@ class BaseImport
} }
/** @var string $base64_encoded_csv */ /** @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)) { if (empty($base64_encoded_csv)) {
return null; return null;
@ -473,6 +473,8 @@ class BaseImport
$tasks = $this->groupTasks($tasks, $task_number_key); $tasks = $this->groupTasks($tasks, $task_number_key);
nlog($tasks);
foreach ($tasks as $raw_task) { foreach ($tasks as $raw_task) {
$task_data = []; $task_data = [];
@ -702,16 +704,16 @@ class BaseImport
->save(); ->save();
} }
if ($invoice->status_id === Invoice::STATUS_DRAFT) { if ($invoice->status_id == Invoice::STATUS_DRAFT) {
} elseif ($invoice->status_id === Invoice::STATUS_SENT) { return $invoice;
$invoice = $invoice }
->service()
->markSent() $invoice = $invoice
->save(); ->service()
} elseif ( ->markSent()
$invoice->status_id <= Invoice::STATUS_SENT && ->save();
$invoice->amount > 0
) { if ($invoice->status_id <= Invoice::STATUS_SENT && $invoice->amount > 0) {
if ($invoice->balance <= 0) { if ($invoice->balance <= 0) {
$invoice->status_id = Invoice::STATUS_PAID; $invoice->status_id = Invoice::STATUS_PAID;
$invoice->save(); $invoice->save();

View File

@ -172,7 +172,7 @@ class Wave extends BaseImport implements ImportInterface
{ {
$entity_type = 'expense'; $entity_type = 'expense';
$data = $this->getCsvData($entity_type); $data = $this->getCsvData('invoice');
if (!$data) { if (!$data) {
$this->entity_count['expense'] = 0; $this->entity_count['expense'] = 0;
@ -244,14 +244,17 @@ class Wave extends BaseImport implements ImportInterface
if (empty($expense_data['vendor_id'])) { if (empty($expense_data['vendor_id'])) {
$vendor_data['user_id'] = $this->getUserIDForRecord($expense_data); $vendor_data['user_id'] = $this->getUserIDForRecord($expense_data);
$vendor_repository->save( if(isset($raw_expense['Vendor Name']) || isset($raw_expense['Vendor']))
['name' => $raw_expense['Vendor Name']], {
$vendor = VendorFactory::create( $vendor_repository->save(
$this->company->id, ['name' => isset($raw_expense['Vendor Name']) ? $raw_expense['Vendor Name'] : isset($raw_expense['Vendor'])],
$vendor_data['user_id'] $vendor = VendorFactory::create(
) $this->company->id,
); $vendor_data['user_id']
$expense_data['vendor_id'] = $vendor->id; )
);
$expense_data['vendor_id'] = $vendor->id;
}
} }
$validator = Validator::make( $validator = Validator::make(

View File

@ -46,6 +46,7 @@ class TaskTransformer extends BaseTransformer
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'number' => $this->getString($task_data, 'task.number'), 'number' => $this->getString($task_data, 'task.number'),
'user_id' => $this->getString($task_data, 'task.user_id'), 'user_id' => $this->getString($task_data, 'task.user_id'),
'rate' => $this->getFloat($task_data, 'task.rate'),
'client_id' => $clientId, 'client_id' => $clientId,
'project_id' => $this->getProjectId($projectId, $clientId), 'project_id' => $this->getProjectId($projectId, $clientId),
'description' => $this->getString($task_data, 'task.description'), 'description' => $this->getString($task_data, 'task.description'),
@ -87,8 +88,7 @@ class TaskTransformer extends BaseTransformer
$is_billable = true; $is_billable = true;
} }
if(isset($item['task.start_date']) && if(isset($item['task.start_date'])) {
isset($item['task.end_date'])) {
$start_date = $this->resolveStartDate($item); $start_date = $this->resolveStartDate($item);
$end_date = $this->resolveEndDate($item); $end_date = $this->resolveEndDate($item);
} elseif(isset($item['task.duration'])) { } elseif(isset($item['task.duration'])) {
@ -136,7 +136,7 @@ class TaskTransformer extends BaseTransformer
private function resolveEndDate($item) 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'] : ''; $stub_end_date .= isset($item['task.end_time']) ? " ".$item['task.end_time'] : '';
try { try {

View File

@ -36,18 +36,26 @@ class ExpenseTransformer extends BaseTransformer
$total_tax += floatval($record['Sales Tax Amount']); $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 = [ $transformed = [
'company_id' => $this->company->id, 'company_id' => $this->company->id,
'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')), 'vendor_id' => $this->getVendorIdOrCreate($this->getString($data, 'Vendor')),
'number' => $this->getString($data, 'Bill Number'), '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 'date' => $this->parseDate($data['Transaction Date Added']) ?: now()->format('Y-m-d'), //27-01-2022
'currency_id' => $this->company->settings->currency_id, 'currency_id' => $this->company->settings->currency_id,
'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']), 'category_id' => $this->getOrCreateExpenseCategry($data['Account Name']),
'amount' => $amount, 'amount' => $amount,
'tax_name1' => $data['Sales Tax Name'], 'tax_name1' => isset($data['Sales Tax Name']) ? $data['Sales Tax Name'] : '',
'tax_rate1' => $tax_rate, 'tax_rate1' => $tax_rate,
]; ];

View File

@ -188,6 +188,10 @@ class BillingPortalPurchase extends Component
public ?string $contact_email; public ?string $contact_email;
public ?string $client_city;
public ?string $client_postal_code;
public function mount() public function mount()
{ {
MultiDB::setDb($this->db); MultiDB::setDb($this->db);
@ -203,7 +207,7 @@ class BillingPortalPurchase extends Component
if (request()->query('coupon')) { if (request()->query('coupon')) {
$this->coupon = request()->query('coupon'); $this->coupon = request()->query('coupon');
$this->handleCoupon(); $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; $this->price = $this->subscription->promo_price;
} }
@ -335,10 +339,6 @@ class BillingPortalPurchase extends Component
{ {
$this->contact = $contact; $this->contact = $contact;
if ($contact->showRff()) {
return $this->rff();
}
Auth::guard('contact')->loginUsingId($contact->id, true); Auth::guard('contact')->loginUsingId($contact->id, true);
if ($this->subscription->trial_enabled) { if ($this->subscription->trial_enabled) {
@ -351,11 +351,20 @@ class BillingPortalPurchase extends Component
if ((int)$this->price == 0) { if ((int)$this->price == 0) {
$this->steps['payment_required'] = false; $this->steps['payment_required'] = false;
} else { } else {
$this->steps['fetched_payment_methods'] = true; // $this->steps['fetched_payment_methods'] = true;
} }
$this->methods = $contact->client->service()->getPaymentMethods($this->price); $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'); $this->heading_text = ctrans('texts.payment_methods');
return $this; return $this;
@ -366,6 +375,8 @@ class BillingPortalPurchase extends Component
$this->contact_first_name = $this->contact->first_name; $this->contact_first_name = $this->contact->first_name;
$this->contact_last_name = $this->contact->last_name; $this->contact_last_name = $this->contact->last_name;
$this->contact_email = $this->contact->email; $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; $this->steps['check_rff'] = true;
@ -377,13 +388,20 @@ class BillingPortalPurchase extends Component
$validated = $this->validate([ $validated = $this->validate([
'contact_first_name' => ['required'], 'contact_first_name' => ['required'],
'contact_last_name' => ['required'], 'contact_last_name' => ['required'],
'client_city' => ['required'],
'client_postal_code' => ['required'],
'contact_email' => ['required', 'email'], 'contact_email' => ['required', 'email'],
]); ]);
$this->contact->first_name = $validated['contact_first_name']; $this->contact->first_name = $validated['contact_first_name'];
$this->contact->last_name = $validated['contact_last_name']; $this->contact->last_name = $validated['contact_last_name'];
$this->contact->email = $validated['contact_email']; $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); return $this->getPaymentMethods($this->contact);
} }
@ -395,13 +413,13 @@ class BillingPortalPurchase extends Component
* @param $company_gateway_id * @param $company_gateway_id
* @param $gateway_type_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->company_gateway_id = $company_gateway_id;
$this->payment_method_id = $gateway_type_id; $this->payment_method_id = $gateway_type_id;
$this->handleBeforePaymentEvents(); $this->handleBeforePaymentEvents();
} }
/** /**

View File

@ -164,6 +164,13 @@ class BillingPortalPurchasev2 extends Component
public $payment_confirmed = false; public $payment_confirmed = false;
public $is_eligible = true; public $is_eligible = true;
public $not_eligible_message = ''; 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() public function mount()
{ {
@ -472,7 +479,6 @@ class BillingPortalPurchasev2 extends Component
*/ */
protected function getPaymentMethods(): self protected function getPaymentMethods(): self
{ {
nlog("total amount = {$this->float_amount_total}");
if ($this->float_amount_total == 0) { if ($this->float_amount_total == 0) {
$this->methods = []; $this->methods = [];
@ -481,10 +487,73 @@ class BillingPortalPurchasev2 extends Component
if ($this->contact && $this->float_amount_total >= 1) { if ($this->contact && $this->float_amount_total >= 1) {
$this->methods = $this->contact->client->service()->getPaymentMethods($this->float_amount_total); $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; 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 & * Middle method between selecting payment method &
* submitting the from to the backend. * submitting the from to the backend.

View File

@ -351,9 +351,9 @@ class ClientContact extends Authenticatable implements HasLocalePreference
public function showRff(): bool public function showRff(): bool
{ {
if (\strlen($this->first_name) === 0 || \strlen($this->last_name) === 0 || \strlen($this->email) === 0) { // if (\strlen($this->first_name ?? '') === 0 || \strlen($this->last_name ?? '') === 0 || \strlen($this->email ?? '') === 0) {
return true; // return true;
} // }
return false; return false;
} }

View File

@ -159,6 +159,11 @@ class CompanyGateway extends BaseModel
protected $touches = []; protected $touches = [];
public function isPayPal()
{
return in_array($this->gateway_key, ['80af24a6a691230bbec33e930ab40666','80af24a6a691230bbec33e930ab40665']);
}
public function getEntityType() public function getEntityType()
{ {
return self::class; return self::class;

View File

@ -141,23 +141,23 @@ class Gateway extends StaticModel
case 20: case 20:
case 56: case 56:
return [ return [
GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => ['payment_intent.succeeded', '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.succeeded', 'customer.source.updated', 'payment_intent.processing', 'payment_intent.payment_failed', 'charge.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', 'payment_intent.succeeded', 'payment_intent.partially_funded', 'payment_intent.payment_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::ALIPAY => ['refund' => false, 'token_billing' => false],
GatewayType::APPLE_PAY => ['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::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.failed', 'payment_intent.succeeded', '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.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.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.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.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.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.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.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.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.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.failed',]], GatewayType::FPX => ['refund' => true, 'token_billing' => true, 'webhooks' => ['source.chargeable', 'charge.succeeded', 'charge.refunded', 'charge.failed',]],
]; ];
case 39: case 39:
return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout return [GatewayType::CREDIT_CARD => ['refund' => true, 'token_billing' => true, 'webhooks' => [' ']]]; //Checkout

View File

@ -129,7 +129,7 @@ class Project extends BaseModel
public function invoices(): HasMany public function invoices(): HasMany
{ {
return $this->hasMany(Invoice::class); return $this->hasMany(Invoice::class)->withTrashed();
} }
public function quotes(): HasMany public function quotes(): HasMany

View File

@ -170,6 +170,9 @@ class ACH
]; ];
$payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED); $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]);
} }
} }

View File

@ -187,6 +187,8 @@ class CreditCard
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
]; ];
$payment = $this->forte->createPayment($data, Payment::STATUS_COMPLETED); $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]);
} }
} }

View File

@ -251,11 +251,11 @@ class PayPalBasePaymentDriver extends BaseDriver
[ [
"address" => "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, "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_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, "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, "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(), "country_code" => $this->client->present()->shipping_country_code(),
], ],
] ]

View File

@ -11,18 +11,22 @@
namespace App\PaymentDrivers\Stripe\Jobs; namespace App\PaymentDrivers\Stripe\Jobs;
use App\Libraries\MultiDB;
use App\Models\Company; use App\Models\Company;
use App\Models\CompanyGateway;
use App\Models\Payment; use App\Models\Payment;
use App\Libraries\MultiDB;
use App\Models\PaymentHash; use App\Models\PaymentHash;
use App\PaymentDrivers\Stripe\Utilities; use App\Services\Email\Email;
use Illuminate\Bus\Queueable; 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\Contracts\Queue\ShouldQueue;
use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Foundation\Bus\Dispatchable;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels;
class ChargeRefunded implements ShouldQueue class ChargeRefunded implements ShouldQueue
{ {
@ -36,19 +40,10 @@ class ChargeRefunded implements ShouldQueue
public $deleteWhenMissingModels = true; public $deleteWhenMissingModels = true;
public $stripe_request;
public $company_key;
private $company_gateway_id;
public $payment_completed = false; 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() public function handle()
@ -64,8 +59,8 @@ class ChargeRefunded implements ShouldQueue
$payment_hash_key = $source['metadata']['payment_hash'] ?? null; $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(); $payment_hash = PaymentHash::query()->where('hash', $payment_hash_key)->first();
$company_gateway = $payment_hash->payment->company_gateway;
$stripe_driver = $company_gateway->driver()->init(); $stripe_driver = $company_gateway->driver()->init();
@ -79,7 +74,7 @@ class ChargeRefunded implements ShouldQueue
->first(); ->first();
//don't touch if already refunded //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; return;
} }
@ -94,8 +89,19 @@ class ChargeRefunded implements ShouldQueue
return; 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 $invoice_collection = $payment->paymentables
->where('paymentable_type', 'invoices') ->where('paymentable_type', 'invoices')
->map(function ($pivot) { ->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; return;
} }
$invoices = $invoice_collection->toArray(); $invoices = $invoice_collection->toArray();
@ -131,20 +152,21 @@ class ChargeRefunded implements ShouldQueue
'date' => now()->format('Y-m-d'), 'date' => now()->format('Y-m-d'),
'gateway_refund' => false, 'gateway_refund' => false,
'email_receipt' => false, 'email_receipt' => false,
'via_webhook' => true,
]; ];
nlog($data); nlog($data);
$payment->refund($data); $payment->refund($data);
$payment->private_notes .= 'Refunded via Stripe'; $payment->private_notes .= 'Refunded via Stripe ';
return;
} $payment->saveQuietly();
} }
public function middleware() public function middleware()
{ {
return [new WithoutOverlapping($this->company_gateway_id)]; return [new WithoutOverlapping($this->company_key)];
} }
} }

View File

@ -12,54 +12,55 @@
namespace App\PaymentDrivers; 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 Exception;
use Illuminate\Http\RedirectResponse; use Stripe\Stripe;
use Laracasts\Presenter\Exceptions\PresenterException;
use Stripe\Account; use Stripe\Account;
use Stripe\Customer; 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\PaymentIntent;
use Stripe\PaymentMethod; use Stripe\PaymentMethod;
use Stripe\SetupIntent; use App\Models\GatewayType;
use Stripe\Stripe; use App\Models\PaymentHash;
use Stripe\StripeClient; 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 class StripePaymentDriver extends BaseDriver
{ {
@ -670,31 +671,39 @@ class StripePaymentDriver extends BaseDriver
public function processWebhookRequest(PaymentWebhookRequest $request) public function processWebhookRequest(PaymentWebhookRequest $request)
{ {
nlog($request->all());
if ($request->type === 'customer.source.updated') { if ($request->type === 'customer.source.updated') {
$ach = new ACH($this); $ach = new ACH($this);
$ach->updateBankAccount($request->all()); $ach->updateBankAccount($request->all());
} }
if ($request->type === 'payment_intent.processing') { 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); return response()->json([], 200);
} }
//payment_intent.succeeded - this will confirm or cancel the payment //payment_intent.succeeded - this will confirm or cancel the payment
if ($request->type === 'payment_intent.succeeded') { 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); return response()->json([], 200);
} }
if ($request->type === 'payment_intent.partially_funded') { 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); return response()->json([], 200);
} }
if (in_array($request->type, ['payment_intent.payment_failed', 'charge.failed'])) { 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); return response()->json([], 200);
} }
@ -702,7 +711,6 @@ class StripePaymentDriver extends BaseDriver
if ($request->type === 'charge.succeeded') { if ($request->type === 'charge.succeeded') {
foreach ($request->data as $transaction) { foreach ($request->data as $transaction) {
$payment = Payment::query() $payment = Payment::query()
->where('company_id', $this->company_gateway->company_id) ->where('company_id', $this->company_gateway->company_id)
->where(function ($query) use ($transaction) { ->where(function ($query) use ($transaction) {

View File

@ -192,6 +192,7 @@ class PaymentMethod
'label' => ctrans('texts.apply_credit'), 'label' => ctrans('texts.apply_credit'),
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT, 'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,
'gateway_type_id' => GatewayType::CREDIT, 'gateway_type_id' => GatewayType::CREDIT,
'is_paypal' => false,
]; ];
} }
@ -210,12 +211,14 @@ class PaymentMethod
'label' => $gateway->getConfigField('name').$fee_label, 'label' => $gateway->getConfigField('name').$fee_label,
'company_gateway_id' => $gateway->id, 'company_gateway_id' => $gateway->id,
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
'is_paypal' => $gateway->isPayPal(),
]; ];
} else { } else {
$this->payment_urls[] = [ $this->payment_urls[] = [
'label' => $gateway->getTypeAlias($type).$fee_label, 'label' => $gateway->getTypeAlias($type).$fee_label,
'company_gateway_id' => $gateway->id, 'company_gateway_id' => $gateway->id,
'gateway_type_id' => $type, 'gateway_type_id' => $type,
'is_paypal' => $gateway->isPayPal(),
]; ];
} }
@ -236,12 +239,14 @@ class PaymentMethod
'label' => $gateway->getConfigField('name').$fee_label, 'label' => $gateway->getConfigField('name').$fee_label,
'company_gateway_id' => $gateway_id, 'company_gateway_id' => $gateway_id,
'gateway_type_id' => GatewayType::CREDIT_CARD, 'gateway_type_id' => GatewayType::CREDIT_CARD,
'is_paypal' => $gateway->isPayPal(),
]; ];
} else { } else {
$this->payment_urls[] = [ $this->payment_urls[] = [
'label' => $gateway->getTypeAlias($gateway_type_id).$fee_label, 'label' => $gateway->getTypeAlias($gateway_type_id).$fee_label,
'company_gateway_id' => $gateway_id, 'company_gateway_id' => $gateway_id,
'gateway_type_id' => $gateway_type_id, 'gateway_type_id' => $gateway_type_id,
'is_paypal' => $gateway->isPayPal(),
]; ];
} }
} }
@ -259,6 +264,7 @@ class PaymentMethod
'label' => ctrans('texts.apply_credit'), 'label' => ctrans('texts.apply_credit'),
'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT, 'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT,
'gateway_type_id' => GatewayType::CREDIT, 'gateway_type_id' => GatewayType::CREDIT,
'is_paypal' => false,
]; ];
} }

View File

@ -44,17 +44,16 @@ class InstantPayment
public function run() public function run()
{ {
nlog($this->request->all());
/** @var \App\Models\ClientContact $cc */ /** @var \App\Models\ClientContact $cc */
$cc = auth()->guard('contact')->user(); $cc = auth()->guard('contact')->user();
$cc->first_name = $this->request->contact_first_name; $cc->first_name = $this->request->contact_first_name;
$cc->last_name = $this->request->contact_last_name; $cc->last_name = $this->request->contact_last_name;
$cc->email = $this->request->contact_email; $cc->email = $this->request->contact_email;
$cc->client->postal_code = strlen($cc->client->postal_code ?? '') > 1 ? $cc->client->postal_code : $this->request->client_postal_code;
$cc->save(); $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; $is_credit_payment = false;
@ -73,8 +72,6 @@ class InstantPayment
*/ */
$payable_invoices = collect($this->request->payable_invoices); $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 = Invoice::query()->whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get();
$invoices->each(function ($invoice) { $invoices->each(function ($invoice) {

View File

@ -42,6 +42,9 @@ class AutoBillInvoice extends AbstractService
public function __construct(private Invoice $invoice, protected string $db) public function __construct(private Invoice $invoice, protected string $db)
{ {
$this->client = $this->invoice->client;
} }
public function run() public function run()
@ -49,8 +52,7 @@ class AutoBillInvoice extends AbstractService
MultiDB::setDb($this->db); MultiDB::setDb($this->db);
/* @var \App\Modesl\Client $client */ /* @var \App\Modesl\Client $client */
$this->client = $this->invoice->client;
$is_partial = false; $is_partial = false;
/* Is the invoice payable? */ /* Is the invoice payable? */
@ -272,7 +274,7 @@ class AutoBillInvoice extends AbstractService
* *
* @return self * @return self
*/ */
private function applyUnappliedPayment(): self public function applyUnappliedPayment(): self
{ {
$unapplied_payments = Payment::query() $unapplied_payments = Payment::query()
->where('client_id', $this->client->id) ->where('client_id', $this->client->id)
@ -284,6 +286,11 @@ class AutoBillInvoice extends AbstractService
->get(); ->get();
$available_unapplied_balance = $unapplied_payments->sum('amount') - $unapplied_payments->sum('applied'); $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}"); nlog("available unapplied balance = {$available_unapplied_balance}");
@ -347,7 +354,7 @@ class AutoBillInvoice extends AbstractService
* *
* @return $this * @return $this
*/ */
private function applyCreditPayment(): self public function applyCreditPayment(): self
{ {
$available_credits = Credit::query()->where('client_id', $this->client->id) $available_credits = Credit::query()->where('client_id', $this->client->id)
->where('is_deleted', false) ->where('is_deleted', false)

View File

@ -44,7 +44,6 @@ class RefundPayment
->setStatus() //sets status of payment ->setStatus() //sets status of payment
->updatePaymentables() //update the paymentable items ->updatePaymentables() //update the paymentable items
->adjustInvoices() ->adjustInvoices()
->finalize()
->save(); ->save();
if (array_key_exists('email_receipt', $this->refund_data) && $this->refund_data['email_receipt'] == 'true') { 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); EmailRefundPayment::dispatch($this->payment, $this->payment->company, $contact);
} }
$notes = ctrans('texts.refunded') . " : {$this->total_refund} - " . ctrans('texts.gateway_refund') . " : "; $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 .= $this->refund_data['gateway_refund'] !== 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->createActivity($notes);
$this->finalize();
return $this->payment; return $this->payment;
} }
@ -178,7 +178,7 @@ class RefundPayment
*/ */
private function setStatus() 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; $this->payment->status_id = Payment::STATUS_REFUNDED;
} else { } else {
$this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED; $this->payment->status_id = Payment::STATUS_PARTIALLY_REFUNDED;

View File

@ -88,7 +88,7 @@ class SubscriptionService
// if we have a recurring product - then generate a recurring invoice // 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)) { if (isset($payment_hash->data->billing_context->bundle)) {
$recurring_invoice = $this->convertInvoiceToRecurringBundle($payment_hash->payment->client_id, $payment_hash->data->billing_context->bundle); $recurring_invoice = $this->convertInvoiceToRecurringBundle($payment_hash->payment->client_id, $payment_hash->data->billing_context->bundle);
} else { } else {
@ -1024,10 +1024,10 @@ class SubscriptionService
$invoice->subscription_id = $this->subscription->id; $invoice->subscription_id = $this->subscription->id;
$invoice->is_proforma = true; $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->discount = $this->subscription->promo_discount;
$invoice->is_amount_discount = $this->subscription->is_amount_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->discount = $this->subscription->promo_discount;
$invoice->is_amount_discount = $this->subscription->is_amount_discount; $invoice->is_amount_discount = $this->subscription->is_amount_discount;
} }
@ -1118,7 +1118,7 @@ class SubscriptionService
*/ */
public function triggerWebhook($context) 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]; return ["message" => "Success", "status_code" => 200];
} }
@ -1436,7 +1436,7 @@ class SubscriptionService
*/ */
public function handleNoPaymentFlow(Invoice $invoice, $bundle, ClientContact $contact) 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) { $recurring_invoice = $this->convertInvoiceToRecurringBundle($contact->client_id, collect($bundle)->map(function ($bund) {
return (object) $bund; return (object) $bund;
})); }));
@ -1492,7 +1492,7 @@ class SubscriptionService
*/ */
private function handleRedirect($default_redirect) 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']); return method_exists(redirect(), "send") ? redirect($this->subscription->webhook_configuration['return_url'])->send() : redirect($this->subscription->webhook_configuration['return_url']);
} }

View File

@ -1023,7 +1023,8 @@ class TemplateService
'vat_number' => $project->client->vat_number ?? '', 'vat_number' => $project->client->vat_number ?? '',
'currency' => $project->client->currency()->code ?? 'USD', 'currency' => $project->client->currency()->code ?? 'USD',
] : [], ] : [],
'user' => $this->userInfo($project->user) 'user' => $this->userInfo($project->user),
'invoices' => $this->processInvoices($project->invoices)
]; ];
} }

344
composer.lock generated
View File

@ -740,16 +740,16 @@
}, },
{ {
"name": "braintree/braintree_php", "name": "braintree/braintree_php",
"version": "6.18.0", "version": "6.19.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/braintree/braintree_php.git", "url": "https://github.com/braintree/braintree_php.git",
"reference": "8ca67004fe2405ef0b6b33a5897594fdcf417e0e" "reference": "f3178632ca098d1f96a429d665aabc4e95346c03"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/braintree/braintree_php/zipball/8ca67004fe2405ef0b6b33a5897594fdcf417e0e", "url": "https://api.github.com/repos/braintree/braintree_php/zipball/f3178632ca098d1f96a429d665aabc4e95346c03",
"reference": "8ca67004fe2405ef0b6b33a5897594fdcf417e0e", "reference": "f3178632ca098d1f96a429d665aabc4e95346c03",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -783,9 +783,9 @@
"description": "Braintree PHP Client Library", "description": "Braintree PHP Client Library",
"support": { "support": {
"issues": "https://github.com/braintree/braintree_php/issues", "issues": "https://github.com/braintree/braintree_php/issues",
"source": "https://github.com/braintree/braintree_php/tree/6.18.0" "source": "https://github.com/braintree/braintree_php/tree/6.19.0"
}, },
"time": "2024-03-26T21:08:13+00:00" "time": "2024-07-23T20:09:58+00:00"
}, },
{ {
"name": "brick/math", "name": "brick/math",
@ -2804,16 +2804,16 @@
}, },
{ {
"name": "guzzlehttp/guzzle", "name": "guzzlehttp/guzzle",
"version": "7.9.1", "version": "7.9.2",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/guzzle/guzzle.git", "url": "https://github.com/guzzle/guzzle.git",
"reference": "a629e5b69db96eb4939c1b34114130077dd4c6fc" "reference": "d281ed313b989f213357e3be1a179f02196ac99b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/a629e5b69db96eb4939c1b34114130077dd4c6fc", "url": "https://api.github.com/repos/guzzle/guzzle/zipball/d281ed313b989f213357e3be1a179f02196ac99b",
"reference": "a629e5b69db96eb4939c1b34114130077dd4c6fc", "reference": "d281ed313b989f213357e3be1a179f02196ac99b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -2910,7 +2910,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/guzzle/guzzle/issues", "issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.9.1" "source": "https://github.com/guzzle/guzzle/tree/7.9.2"
}, },
"funding": [ "funding": [
{ {
@ -2926,7 +2926,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-07-19T16:19:57+00:00" "time": "2024-07-24T11:22:20+00:00"
}, },
{ {
"name": "guzzlehttp/promises", "name": "guzzlehttp/promises",
@ -4614,16 +4614,16 @@
}, },
{ {
"name": "laravel/framework", "name": "laravel/framework",
"version": "v11.16.0", "version": "v11.18.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/framework.git", "url": "https://github.com/laravel/framework.git",
"reference": "bd4808aaf103ccb5cb4b00bcee46140c070c0ec4" "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/framework/zipball/bd4808aaf103ccb5cb4b00bcee46140c070c0ec4", "url": "https://api.github.com/repos/laravel/framework/zipball/b19ba518c56852567e99fbae9321bc436c2cc5a8",
"reference": "bd4808aaf103ccb5cb4b00bcee46140c070c0ec4", "reference": "b19ba518c56852567e99fbae9321bc436c2cc5a8",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4816,20 +4816,20 @@
"issues": "https://github.com/laravel/framework/issues", "issues": "https://github.com/laravel/framework/issues",
"source": "https://github.com/laravel/framework" "source": "https://github.com/laravel/framework"
}, },
"time": "2024-07-16T14:33:07+00:00" "time": "2024-07-26T10:39:29+00:00"
}, },
{ {
"name": "laravel/pint", "name": "laravel/pint",
"version": "v1.16.2", "version": "v1.17.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/pint.git", "url": "https://github.com/laravel/pint.git",
"reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca" "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/pint/zipball/51f1ba679a6afe0315621ad143d788bd7ded0eca", "url": "https://api.github.com/repos/laravel/pint/zipball/4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"reference": "51f1ba679a6afe0315621ad143d788bd7ded0eca", "reference": "4dba80c1de4b81dc4c4fb10ea6f4781495eb29f5",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -4882,7 +4882,7 @@
"issues": "https://github.com/laravel/pint/issues", "issues": "https://github.com/laravel/pint/issues",
"source": "https://github.com/laravel/pint" "source": "https://github.com/laravel/pint"
}, },
"time": "2024-07-09T15:58:08+00:00" "time": "2024-07-23T16:40:20+00:00"
}, },
{ {
"name": "laravel/prompts", "name": "laravel/prompts",
@ -5004,16 +5004,16 @@
}, },
{ {
"name": "laravel/slack-notification-channel", "name": "laravel/slack-notification-channel",
"version": "v3.2.0", "version": "v3.3.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/laravel/slack-notification-channel.git", "url": "https://github.com/laravel/slack-notification-channel.git",
"reference": "fc8d1873e3db63a480bc57aebb4bf5ec05332d91" "reference": "8cd988fad1a08ed88dfd852f140477376c60217f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/fc8d1873e3db63a480bc57aebb4bf5ec05332d91", "url": "https://api.github.com/repos/laravel/slack-notification-channel/zipball/8cd988fad1a08ed88dfd852f140477376c60217f",
"reference": "fc8d1873e3db63a480bc57aebb4bf5ec05332d91", "reference": "8cd988fad1a08ed88dfd852f140477376c60217f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5063,9 +5063,9 @@
], ],
"support": { "support": {
"issues": "https://github.com/laravel/slack-notification-channel/issues", "issues": "https://github.com/laravel/slack-notification-channel/issues",
"source": "https://github.com/laravel/slack-notification-channel/tree/v3.2.0" "source": "https://github.com/laravel/slack-notification-channel/tree/v3.3.0"
}, },
"time": "2024-01-15T20:07:45+00:00" "time": "2024-07-10T19:39:44+00:00"
}, },
{ {
"name": "laravel/socialite", "name": "laravel/socialite",
@ -5407,16 +5407,16 @@
}, },
{ {
"name": "league/commonmark", "name": "league/commonmark",
"version": "2.4.2", "version": "2.5.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/thephpleague/commonmark.git", "url": "https://github.com/thephpleague/commonmark.git",
"reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/ac815920de0eff6de947eac0a6a94e5ed0fb147c",
"reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", "reference": "ac815920de0eff6de947eac0a6a94e5ed0fb147c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -5429,8 +5429,8 @@
}, },
"require-dev": { "require-dev": {
"cebe/markdown": "^1.0", "cebe/markdown": "^1.0",
"commonmark/cmark": "0.30.3", "commonmark/cmark": "0.31.0",
"commonmark/commonmark.js": "0.30.0", "commonmark/commonmark.js": "0.31.0",
"composer/package-versions-deprecated": "^1.8", "composer/package-versions-deprecated": "^1.8",
"embed/embed": "^4.4", "embed/embed": "^4.4",
"erusev/parsedown": "^1.0", "erusev/parsedown": "^1.0",
@ -5452,7 +5452,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "2.5-dev" "dev-main": "2.6-dev"
} }
}, },
"autoload": { "autoload": {
@ -5509,7 +5509,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-02-02T11:59:32+00:00" "time": "2024-07-24T12:52:09+00:00"
}, },
{ {
"name": "league/config", "name": "league/config",
@ -11342,16 +11342,16 @@
}, },
{ {
"name": "symfony/console", "name": "symfony/console",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/console.git", "url": "https://github.com/symfony/console.git",
"reference": "0aa29ca177f432ab68533432db0de059f39c92ae" "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/console/zipball/0aa29ca177f432ab68533432db0de059f39c92ae", "url": "https://api.github.com/repos/symfony/console/zipball/cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
"reference": "0aa29ca177f432ab68533432db0de059f39c92ae", "reference": "cb1dcb30ebc7005c29864ee78adb47b5fb7c3cd9",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11415,7 +11415,7 @@
"terminal" "terminal"
], ],
"support": { "support": {
"source": "https://github.com/symfony/console/tree/v7.1.2" "source": "https://github.com/symfony/console/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -11431,7 +11431,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T10:03:55+00:00" "time": "2024-07-26T12:41:01+00:00"
}, },
{ {
"name": "symfony/css-selector", "name": "symfony/css-selector",
@ -11567,16 +11567,16 @@
}, },
{ {
"name": "symfony/error-handler", "name": "symfony/error-handler",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/error-handler.git", "url": "https://github.com/symfony/error-handler.git",
"reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32" "reference": "432bb369952795c61ca1def65e078c4a80dad13c"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/error-handler/zipball/2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", "url": "https://api.github.com/repos/symfony/error-handler/zipball/432bb369952795c61ca1def65e078c4a80dad13c",
"reference": "2412d3dddb5c9ea51a39cfbff1c565fc9844ca32", "reference": "432bb369952795c61ca1def65e078c4a80dad13c",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11622,7 +11622,7 @@
"description": "Provides tools to manage errors and ease debugging PHP code", "description": "Provides tools to manage errors and ease debugging PHP code",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/error-handler/tree/v7.1.2" "source": "https://github.com/symfony/error-handler/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -11638,7 +11638,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-25T19:55:06+00:00" "time": "2024-07-26T13:02:51+00:00"
}, },
{ {
"name": "symfony/event-dispatcher", "name": "symfony/event-dispatcher",
@ -11864,16 +11864,16 @@
}, },
{ {
"name": "symfony/finder", "name": "symfony/finder",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/finder.git", "url": "https://github.com/symfony/finder.git",
"reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6" "reference": "717c6329886f32dc65e27461f80f2a465412fdca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/finder/zipball/fbb0ba67688b780efbc886c1a0a0948dcf7205d6", "url": "https://api.github.com/repos/symfony/finder/zipball/717c6329886f32dc65e27461f80f2a465412fdca",
"reference": "fbb0ba67688b780efbc886c1a0a0948dcf7205d6", "reference": "717c6329886f32dc65e27461f80f2a465412fdca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -11908,7 +11908,7 @@
"description": "Finds files and directories via an intuitive fluent interface", "description": "Finds files and directories via an intuitive fluent interface",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/finder/tree/v7.1.1" "source": "https://github.com/symfony/finder/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -11924,20 +11924,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-24T07:08:44+00:00"
}, },
{ {
"name": "symfony/http-client", "name": "symfony/http-client",
"version": "v6.4.9", "version": "v6.4.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-client.git", "url": "https://github.com/symfony/http-client.git",
"reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045" "reference": "b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-client/zipball/6e9db0025db565bcf8f1d46ed734b549e51e6045", "url": "https://api.github.com/repos/symfony/http-client/zipball/b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded",
"reference": "6e9db0025db565bcf8f1d46ed734b549e51e6045", "reference": "b5e498f763e0bf5eed8dcd946e50a3b3f71d4ded",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12001,7 +12001,7 @@
"http" "http"
], ],
"support": { "support": {
"source": "https://github.com/symfony/http-client/tree/v6.4.9" "source": "https://github.com/symfony/http-client/tree/v6.4.10"
}, },
"funding": [ "funding": [
{ {
@ -12017,7 +12017,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T07:59:05+00:00" "time": "2024-07-15T09:26:24+00:00"
}, },
{ {
"name": "symfony/http-client-contracts", "name": "symfony/http-client-contracts",
@ -12099,16 +12099,16 @@
}, },
{ {
"name": "symfony/http-foundation", "name": "symfony/http-foundation",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-foundation.git", "url": "https://github.com/symfony/http-foundation.git",
"reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa" "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-foundation/zipball/74d171d5b6a1d9e4bfee09a41937c17a7536acfa", "url": "https://api.github.com/repos/symfony/http-foundation/zipball/f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
"reference": "74d171d5b6a1d9e4bfee09a41937c17a7536acfa", "reference": "f602d5c17d1fa02f8019ace2687d9d136b7f4a1a",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12156,7 +12156,7 @@
"description": "Defines an object-oriented layer for the HTTP specification", "description": "Defines an object-oriented layer for the HTTP specification",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-foundation/tree/v7.1.1" "source": "https://github.com/symfony/http-foundation/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -12172,20 +12172,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-26T12:41:01+00:00"
}, },
{ {
"name": "symfony/http-kernel", "name": "symfony/http-kernel",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/http-kernel.git", "url": "https://github.com/symfony/http-kernel.git",
"reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6" "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/http-kernel/zipball/ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", "url": "https://api.github.com/repos/symfony/http-kernel/zipball/db9702f3a04cc471ec8c70e881825db26ac5f186",
"reference": "ae3fa717db4d41a55d14c2bd92399e37cf5bc0f6", "reference": "db9702f3a04cc471ec8c70e881825db26ac5f186",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12270,7 +12270,7 @@
"description": "Provides a structured process for converting a Request into a Response", "description": "Provides a structured process for converting a Request into a Response",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/http-kernel/tree/v7.1.2" "source": "https://github.com/symfony/http-kernel/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -12286,7 +12286,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T13:13:31+00:00" "time": "2024-07-26T14:58:15+00:00"
}, },
{ {
"name": "symfony/intl", "name": "symfony/intl",
@ -12456,16 +12456,16 @@
}, },
{ {
"name": "symfony/mailgun-mailer", "name": "symfony/mailgun-mailer",
"version": "v6.4.9", "version": "v6.4.10",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/mailgun-mailer.git", "url": "https://github.com/symfony/mailgun-mailer.git",
"reference": "c4917eb14f31fb5c21442375c6baf7f51bd924e8" "reference": "3eb7c7b644179a766f5d816620b7b6d4a4e7ec43"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/c4917eb14f31fb5c21442375c6baf7f51bd924e8", "url": "https://api.github.com/repos/symfony/mailgun-mailer/zipball/3eb7c7b644179a766f5d816620b7b6d4a4e7ec43",
"reference": "c4917eb14f31fb5c21442375c6baf7f51bd924e8", "reference": "3eb7c7b644179a766f5d816620b7b6d4a4e7ec43",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -12505,7 +12505,7 @@
"description": "Symfony Mailgun Mailer Bridge", "description": "Symfony Mailgun Mailer Bridge",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/mailgun-mailer/tree/v6.4.9" "source": "https://github.com/symfony/mailgun-mailer/tree/v6.4.10"
}, },
"funding": [ "funding": [
{ {
@ -12521,7 +12521,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T07:59:05+00:00" "time": "2024-07-04T11:16:22+00:00"
}, },
{ {
"name": "symfony/mime", "name": "symfony/mime",
@ -13540,16 +13540,16 @@
}, },
{ {
"name": "symfony/process", "name": "symfony/process",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/process.git", "url": "https://github.com/symfony/process.git",
"reference": "febf90124323a093c7ee06fdb30e765ca3c20028" "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/process/zipball/febf90124323a093c7ee06fdb30e765ca3c20028", "url": "https://api.github.com/repos/symfony/process/zipball/7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"reference": "febf90124323a093c7ee06fdb30e765ca3c20028", "reference": "7f2f542c668ad6c313dc4a5e9c3321f733197eca",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13581,7 +13581,7 @@
"description": "Executes commands in sub-processes", "description": "Executes commands in sub-processes",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/process/tree/v7.1.1" "source": "https://github.com/symfony/process/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -13597,7 +13597,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-26T12:44:47+00:00"
}, },
{ {
"name": "symfony/property-access", "name": "symfony/property-access",
@ -13677,16 +13677,16 @@
}, },
{ {
"name": "symfony/property-info", "name": "symfony/property-info",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/property-info.git", "url": "https://github.com/symfony/property-info.git",
"reference": "d7b91e4aa07e822a9b935fc29a7254c12d502f16" "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/property-info/zipball/d7b91e4aa07e822a9b935fc29a7254c12d502f16", "url": "https://api.github.com/repos/symfony/property-info/zipball/88a279df2db5b7919cac6f35d6a5d1d7147e6a9b",
"reference": "d7b91e4aa07e822a9b935fc29a7254c12d502f16", "reference": "88a279df2db5b7919cac6f35d6a5d1d7147e6a9b",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13741,7 +13741,7 @@
"validator" "validator"
], ],
"support": { "support": {
"source": "https://github.com/symfony/property-info/tree/v7.1.2" "source": "https://github.com/symfony/property-info/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -13757,20 +13757,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-26T07:21:35+00:00" "time": "2024-07-26T07:36:36+00:00"
}, },
{ {
"name": "symfony/psr-http-message-bridge", "name": "symfony/psr-http-message-bridge",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/psr-http-message-bridge.git", "url": "https://github.com/symfony/psr-http-message-bridge.git",
"reference": "9a5dbb606da711f5d40a7596ad577856f9402140" "reference": "1365d10f5476f74a27cf9c2d1eee70c069019db0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/9a5dbb606da711f5d40a7596ad577856f9402140", "url": "https://api.github.com/repos/symfony/psr-http-message-bridge/zipball/1365d10f5476f74a27cf9c2d1eee70c069019db0",
"reference": "9a5dbb606da711f5d40a7596ad577856f9402140", "reference": "1365d10f5476f74a27cf9c2d1eee70c069019db0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13824,7 +13824,7 @@
"psr-7" "psr-7"
], ],
"support": { "support": {
"source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.1" "source": "https://github.com/symfony/psr-http-message-bridge/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -13840,20 +13840,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-17T06:10:24+00:00"
}, },
{ {
"name": "symfony/routing", "name": "symfony/routing",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/routing.git", "url": "https://github.com/symfony/routing.git",
"reference": "60c31bab5c45af7f13091b87deb708830f3c96c0" "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/routing/zipball/60c31bab5c45af7f13091b87deb708830f3c96c0", "url": "https://api.github.com/repos/symfony/routing/zipball/8a908a3f22d5a1b5d297578c2ceb41b02fa916d0",
"reference": "60c31bab5c45af7f13091b87deb708830f3c96c0", "reference": "8a908a3f22d5a1b5d297578c2ceb41b02fa916d0",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -13905,7 +13905,7 @@
"url" "url"
], ],
"support": { "support": {
"source": "https://github.com/symfony/routing/tree/v7.1.1" "source": "https://github.com/symfony/routing/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -13921,20 +13921,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-17T06:10:24+00:00"
}, },
{ {
"name": "symfony/serializer", "name": "symfony/serializer",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/serializer.git", "url": "https://github.com/symfony/serializer.git",
"reference": "d2077674aaaff02a95f290de512aa358947e6bbe" "reference": "0d5ddac365fbfffc30ca9bc944ad3eb9b3763c09"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/serializer/zipball/d2077674aaaff02a95f290de512aa358947e6bbe", "url": "https://api.github.com/repos/symfony/serializer/zipball/0d5ddac365fbfffc30ca9bc944ad3eb9b3763c09",
"reference": "d2077674aaaff02a95f290de512aa358947e6bbe", "reference": "0d5ddac365fbfffc30ca9bc944ad3eb9b3763c09",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14002,7 +14002,7 @@
"description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/serializer/tree/v7.1.2" "source": "https://github.com/symfony/serializer/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -14018,7 +14018,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T07:42:43+00:00" "time": "2024-07-17T06:10:24+00:00"
}, },
{ {
"name": "symfony/service-contracts", "name": "symfony/service-contracts",
@ -14105,16 +14105,16 @@
}, },
{ {
"name": "symfony/string", "name": "symfony/string",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/string.git", "url": "https://github.com/symfony/string.git",
"reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8" "reference": "ea272a882be7f20cad58d5d78c215001617b7f07"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/string/zipball/14221089ac66cf82e3cf3d1c1da65de305587ff8", "url": "https://api.github.com/repos/symfony/string/zipball/ea272a882be7f20cad58d5d78c215001617b7f07",
"reference": "14221089ac66cf82e3cf3d1c1da65de305587ff8", "reference": "ea272a882be7f20cad58d5d78c215001617b7f07",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14172,7 +14172,7 @@
"utf8" "utf8"
], ],
"support": { "support": {
"source": "https://github.com/symfony/string/tree/v7.1.2" "source": "https://github.com/symfony/string/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -14188,20 +14188,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T09:27:18+00:00" "time": "2024-07-22T10:25:37+00:00"
}, },
{ {
"name": "symfony/translation", "name": "symfony/translation",
"version": "v7.1.1", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/translation.git", "url": "https://github.com/symfony/translation.git",
"reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3" "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/translation/zipball/cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3", "url": "https://api.github.com/repos/symfony/translation/zipball/8d5e50c813ba2859a6dfc99a0765c550507934a1",
"reference": "cf5ae136e124fc7681b34ce9fac9d5b9ae8ceee3", "reference": "8d5e50c813ba2859a6dfc99a0765c550507934a1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14266,7 +14266,7 @@
"description": "Provides tools to internationalize your application", "description": "Provides tools to internationalize your application",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/translation/tree/v7.1.1" "source": "https://github.com/symfony/translation/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -14282,7 +14282,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-31T14:57:53+00:00" "time": "2024-07-26T12:41:01+00:00"
}, },
{ {
"name": "symfony/translation-contracts", "name": "symfony/translation-contracts",
@ -14520,16 +14520,16 @@
}, },
{ {
"name": "symfony/validator", "name": "symfony/validator",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/validator.git", "url": "https://github.com/symfony/validator.git",
"reference": "bed12b7d5bd4dac452db5fa6203331c876b489e7" "reference": "ba711a6cfc008544dad059abb3c1d997f1472237"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/validator/zipball/bed12b7d5bd4dac452db5fa6203331c876b489e7", "url": "https://api.github.com/repos/symfony/validator/zipball/ba711a6cfc008544dad059abb3c1d997f1472237",
"reference": "bed12b7d5bd4dac452db5fa6203331c876b489e7", "reference": "ba711a6cfc008544dad059abb3c1d997f1472237",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14597,7 +14597,7 @@
"description": "Provides tools to validate values", "description": "Provides tools to validate values",
"homepage": "https://symfony.com", "homepage": "https://symfony.com",
"support": { "support": {
"source": "https://github.com/symfony/validator/tree/v7.1.2" "source": "https://github.com/symfony/validator/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -14613,20 +14613,20 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-25T19:55:06+00:00" "time": "2024-07-26T12:41:01+00:00"
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v7.1.2", "version": "v7.1.3",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d" "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/5857c57c6b4b86524c08cf4f4bc95327270a816d", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/86af4617cca75a6e28598f49ae0690f3b9d4591f",
"reference": "5857c57c6b4b86524c08cf4f4bc95327270a816d", "reference": "86af4617cca75a6e28598f49ae0690f3b9d4591f",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -14680,7 +14680,7 @@
"dump" "dump"
], ],
"support": { "support": {
"source": "https://github.com/symfony/var-dumper/tree/v7.1.2" "source": "https://github.com/symfony/var-dumper/tree/v7.1.3"
}, },
"funding": [ "funding": [
{ {
@ -14696,7 +14696,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-06-28T08:00:31+00:00" "time": "2024-07-26T12:41:01+00:00"
}, },
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
@ -15856,30 +15856,38 @@
}, },
{ {
"name": "composer/pcre", "name": "composer/pcre",
"version": "3.1.4", "version": "3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/composer/pcre.git", "url": "https://github.com/composer/pcre.git",
"reference": "04229f163664973f68f38f6f73d917799168ef24" "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/composer/pcre/zipball/04229f163664973f68f38f6f73d917799168ef24", "url": "https://api.github.com/repos/composer/pcre/zipball/ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"reference": "04229f163664973f68f38f6f73d917799168ef24", "reference": "ea4ab6f9580a4fd221e0418f2c357cdd39102a90",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": "^7.4 || ^8.0" "php": "^7.4 || ^8.0"
}, },
"conflict": {
"phpstan/phpstan": "<1.11.8"
},
"require-dev": { "require-dev": {
"phpstan/phpstan": "^1.3", "phpstan/phpstan": "^1.11.8",
"phpstan/phpstan-strict-rules": "^1.1", "phpstan/phpstan-strict-rules": "^1.1",
"symfony/phpunit-bridge": "^5" "phpunit/phpunit": "^8 || ^9"
}, },
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-main": "3.x-dev" "dev-main": "3.x-dev"
},
"phpstan": {
"includes": [
"extension.neon"
]
} }
}, },
"autoload": { "autoload": {
@ -15907,7 +15915,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/composer/pcre/issues", "issues": "https://github.com/composer/pcre/issues",
"source": "https://github.com/composer/pcre/tree/3.1.4" "source": "https://github.com/composer/pcre/tree/3.2.0"
}, },
"funding": [ "funding": [
{ {
@ -15923,7 +15931,7 @@
"type": "tidelift" "type": "tidelift"
} }
], ],
"time": "2024-05-27T13:40:54+00:00" "time": "2024-07-25T09:36:02+00:00"
}, },
{ {
"name": "composer/semver", "name": "composer/semver",
@ -16253,16 +16261,16 @@
}, },
{ {
"name": "friendsofphp/php-cs-fixer", "name": "friendsofphp/php-cs-fixer",
"version": "v3.59.3", "version": "v3.60.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git",
"reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29" "reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/30ba9ecc2b0e5205e578fe29973c15653d9bfd29", "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
"reference": "30ba9ecc2b0e5205e578fe29973c15653d9bfd29", "reference": "e595e4e070d17c5d42ed8c4206f630fcc5f401a4",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -16344,7 +16352,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues",
"source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.59.3" "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.60.0"
}, },
"funding": [ "funding": [
{ {
@ -16352,7 +16360,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-06-16T14:17:03+00:00" "time": "2024-07-25T09:26:51+00:00"
}, },
{ {
"name": "hamcrest/hamcrest-php", "name": "hamcrest/hamcrest-php",
@ -17022,16 +17030,16 @@
}, },
{ {
"name": "phpstan/phpstan", "name": "phpstan/phpstan",
"version": "1.11.7", "version": "1.11.8",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/phpstan/phpstan.git", "url": "https://github.com/phpstan/phpstan.git",
"reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d" "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/phpstan/phpstan/zipball/52d2bbfdcae7f895915629e4694e9497d0f8e28d", "url": "https://api.github.com/repos/phpstan/phpstan/zipball/6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"reference": "52d2bbfdcae7f895915629e4694e9497d0f8e28d", "reference": "6adbd118e6c0515dd2f36b06cde1d6da40f1b8ec",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -17076,7 +17084,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-07-06T11:17:41+00:00" "time": "2024-07-24T07:01:22+00:00"
}, },
{ {
"name": "phpunit/php-code-coverage", "name": "phpunit/php-code-coverage",
@ -17874,31 +17882,31 @@
}, },
{ {
"name": "react/socket", "name": "react/socket",
"version": "v1.15.0", "version": "v1.16.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/reactphp/socket.git", "url": "https://github.com/reactphp/socket.git",
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038" "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/reactphp/socket/zipball/216d3aec0b87f04a40ca04f481e6af01bdd1d038", "url": "https://api.github.com/repos/reactphp/socket/zipball/23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
"reference": "216d3aec0b87f04a40ca04f481e6af01bdd1d038", "reference": "23e4ff33ea3e160d2d1f59a0e6050e4b0fb0eac1",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"evenement/evenement": "^3.0 || ^2.0 || ^1.0", "evenement/evenement": "^3.0 || ^2.0 || ^1.0",
"php": ">=5.3.0", "php": ">=5.3.0",
"react/dns": "^1.11", "react/dns": "^1.13",
"react/event-loop": "^1.2", "react/event-loop": "^1.2",
"react/promise": "^3 || ^2.6 || ^1.2.1", "react/promise": "^3.2 || ^2.6 || ^1.2.1",
"react/stream": "^1.2" "react/stream": "^1.4"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36", "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36",
"react/async": "^4 || ^3 || ^2", "react/async": "^4.3 || ^3.3 || ^2",
"react/promise-stream": "^1.4", "react/promise-stream": "^1.4",
"react/promise-timer": "^1.10" "react/promise-timer": "^1.11"
}, },
"type": "library", "type": "library",
"autoload": { "autoload": {
@ -17942,7 +17950,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/reactphp/socket/issues", "issues": "https://github.com/reactphp/socket/issues",
"source": "https://github.com/reactphp/socket/tree/v1.15.0" "source": "https://github.com/reactphp/socket/tree/v1.16.0"
}, },
"funding": [ "funding": [
{ {
@ -17950,7 +17958,7 @@
"type": "open_collective" "type": "open_collective"
} }
], ],
"time": "2023-12-15T11:02:10+00:00" "time": "2024-07-26T10:38:09+00:00"
}, },
{ {
"name": "react/stream", "name": "react/stream",
@ -19011,16 +19019,16 @@
}, },
{ {
"name": "spatie/error-solutions", "name": "spatie/error-solutions",
"version": "1.1.0", "version": "1.1.1",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/spatie/error-solutions.git", "url": "https://github.com/spatie/error-solutions.git",
"reference": "a014da18f2675ea15af0ba97f7e9aee59e13964f" "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/spatie/error-solutions/zipball/a014da18f2675ea15af0ba97f7e9aee59e13964f", "url": "https://api.github.com/repos/spatie/error-solutions/zipball/ae7393122eda72eed7cc4f176d1e96ea444f2d67",
"reference": "a014da18f2675ea15af0ba97f7e9aee59e13964f", "reference": "ae7393122eda72eed7cc4f176d1e96ea444f2d67",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
@ -19073,7 +19081,7 @@
], ],
"support": { "support": {
"issues": "https://github.com/spatie/error-solutions/issues", "issues": "https://github.com/spatie/error-solutions/issues",
"source": "https://github.com/spatie/error-solutions/tree/1.1.0" "source": "https://github.com/spatie/error-solutions/tree/1.1.1"
}, },
"funding": [ "funding": [
{ {
@ -19081,7 +19089,7 @@
"type": "github" "type": "github"
} }
], ],
"time": "2024-07-22T08:18:22+00:00" "time": "2024-07-25T11:06:04+00:00"
}, },
{ {
"name": "spatie/flare-client-php", "name": "spatie/flare-client-php",

View File

@ -17,8 +17,8 @@ return [
'require_https' => env('REQUIRE_HTTPS', true), 'require_https' => env('REQUIRE_HTTPS', true),
'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_url' => rtrim(env('APP_URL', ''), '/'),
'app_domain' => env('APP_DOMAIN', 'invoicing.co'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'),
'app_version' => env('APP_VERSION', '5.10.13'), 'app_version' => env('APP_VERSION', '5.10.16'),
'app_tag' => env('APP_TAG', '5.10.13'), 'app_tag' => env('APP_TAG', '5.10.16'),
'minimum_client_version' => '5.0.16', 'minimum_client_version' => '5.0.16',
'terms_version' => '1.0.1', 'terms_version' => '1.0.1',
'api_secret' => env('API_SECRET', false), 'api_secret' => env('API_SECRET', false),

View File

@ -5124,7 +5124,7 @@ $lang = array(
'all_contacts' => 'All Contacts', 'all_contacts' => 'All Contacts',
'insert_below' => 'Insert Below', 'insert_below' => 'Insert Below',
'nordigen_handler_subtitle' => 'Bank account authentication. Selecting your institution to complete the request with your account credentials.', '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_contents_unknown' => 'An unknown error has occurred! Reason:',
'nordigen_handler_error_heading_token_invalid' => 'Invalid Token', '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.', 'nordigen_handler_error_contents_token_invalid' => 'The provided token was invalid. Contact support for help, if this issue persists.',

File diff suppressed because one or more lines are too long

109
public/build/assets/app-e0713224.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -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();

View File

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

View File

@ -9,7 +9,7 @@
] ]
}, },
"resources/js/app.js": { "resources/js/app.js": {
"file": "assets/app-234e3402.js", "file": "assets/app-e0713224.js",
"imports": [ "imports": [
"_index-08e160a7.js", "_index-08e160a7.js",
"__commonjsHelpers-725317a4.js" "__commonjsHelpers-725317a4.js"
@ -23,7 +23,7 @@
"src": "resources/js/clients/invoices/action-selectors.js" "src": "resources/js/clients/invoices/action-selectors.js"
}, },
"resources/js/clients/invoices/payment.js": { "resources/js/clients/invoices/payment.js": {
"file": "assets/payment-1bdbd169.js", "file": "assets/payment-292ee4d0.js",
"isEntry": true, "isEntry": true,
"src": "resources/js/clients/invoices/payment.js" "src": "resources/js/clients/invoices/payment.js"
}, },
@ -240,7 +240,7 @@
"src": "resources/js/setup/setup.js" "src": "resources/js/setup/setup.js"
}, },
"resources/sass/app.scss": { "resources/sass/app.scss": {
"file": "assets/app-f3b33400.css", "file": "assets/app-039bd735.css",
"isEntry": true, "isEntry": true,
"src": "resources/sass/app.scss" "src": "resources/sass/app.scss"
} }

View File

@ -9,39 +9,47 @@
*/ */
class Payment { class Payment {
constructor(displayTerms, displaySignature, displayRff) { constructor(displayTerms, displaySignature) {
this.shouldDisplayTerms = displayTerms; this.shouldDisplayTerms = displayTerms;
this.shouldDisplaySignature = displaySignature; this.shouldDisplaySignature = displaySignature;
this.shouldDisplayRff = displayRff;
this.submitting = false; this.submitting = false;
this.steps = new Map() this.steps = new Map()
if (this.shouldDisplayRff) { this.steps.set("rff", {
this.steps.set("rff", { element: document.getElementById('displayRequiredFieldsModal'),
element: document.getElementById('displayRequiredFieldsModal'), nextButton: document.getElementById('rff-next-step'),
nextButton: document.getElementById('rff-next-step'), callback: () => {
callback: () => { const fields = {
const fields = { firstName: document.querySelector('input[name="rff_first_name"]'),
firstName: document.querySelector('input[name="rff_first_name"]'), lastName: document.querySelector('input[name="rff_last_name"]'),
lastName: document.querySelector('input[name="rff_last_name"]'), email: document.querySelector('input[name="rff_email"]'),
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.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) { if (this.shouldDisplaySignature) {
this.steps.set("signature", { this.steps.set("signature", {
@ -71,7 +79,17 @@ class Payment {
element.dataset.companyGatewayId; element.dataset.companyGatewayId;
document.getElementById("payment_method_id").value = document.getElementById("payment_method_id").value =
element.dataset.gatewayTypeId; 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) { if (this.steps.size === 0) {
return this.submitForm(); return this.submitForm();
} }
@ -124,6 +142,5 @@ const signature = document.querySelector(
).content; ).content;
const terms = document.querySelector('meta[name="show-invoice-terms"]').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();

View File

@ -141,12 +141,15 @@
<input type="hidden" name="contact_first_name" value="{{ $contact->first_name }}"> <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_last_name" value="{{ $contact->last_name }}">
<input type="hidden" name="contact_email" value="{{ $contact->email }}"> <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> </form>
@if($steps['started_payment'] == false) @if($steps['started_payment'] == false)
@foreach($this->methods as $method) @foreach($this->methods as $method)
<button <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"> class="px-3 py-2 border rounded mr-4 hover:border-blue-600">
{{ $method['label'] }} {{ $method['label'] }}
</button> </button>
@ -189,27 +192,41 @@
<form wire:submit="handleRff"> <form wire:submit="handleRff">
@csrf @csrf
@if(strlen($contact->first_name) === 0) @if(strlen($contact->first_name ?? '') === 0)
<div class="col-auto mt-3"> <div class="col-auto mt-3">
<label for="first_name" class="input-label">{{ ctrans('texts.first_name') }}</label> <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" /> <input id="first_name" class="input w-full" wire:model="contact_first_name" />
</div> </div>
@endif @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"> <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> <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" /> <input id="last_name" class="input w-full" wire:model="contact_last_name" />
</div> </div>
@endif @endif
@if(strlen($contact->email) === 0) @if(strlen($contact->email ?? '') === 0)
<div class="col-auto mt-3 @if($contact->email) !== 0) hidden @endif"> <div class="col-auto mt-3 @if($contact->email) !== 0) hidden @endif">
<label for="email" class="input-label">{{ ctrans('texts.email') }}</label> <label for="email" class="input-label">{{ ctrans('texts.email') }}</label>
<input id="email" class="input w-full" wire:model="contact_email" /> <input id="email" class="input w-full" wire:model="contact_email" />
</div> </div>
@endif @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 <button
type="submit" type="submit"
class="button button-block bg-primary text-white mt-4"> class="button button-block bg-primary text-white mt-4">

View File

@ -21,6 +21,11 @@
<input type="hidden" name="action" value="payment"> <input type="hidden" name="action" value="payment">
<input type="hidden" name="company_gateway_id" value="{{ $company_gateway_id }}"/> <input type="hidden" name="company_gateway_id" value="{{ $company_gateway_id }}"/>
<input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/> <input type="hidden" name="payment_method_id" value="{{ $payment_method_id }}"/>
<input type="hidden" name="contact_first_name" value="{{ $contact ? $contact->first_name : '' }}">
<input type="hidden" name="contact_last_name" value="{{ $contact ? $contact->last_name : '' }}">
<input type="hidden" name="contact_email" value="{{ $contact ? $contact->email : '' }}">
<input type="hidden" name="client_city" value="{{ $contact ? $contact->client->city : '' }}">
<input type="hidden" name="client_postal_code" value="{{ $contact ? $contact->client->postal_code : '' }}">
</form> </form>
</div> </div>
@ -313,12 +318,67 @@
{{ ctrans('texts.trial_call_to_action') }} {{ ctrans('texts.trial_call_to_action') }}
</button> </button>
</form> </form>
@elseif(count($methods) > 0 && $check_rff)
@if($errors->any())
<div class="w-full mx-auto text-center bg-red-100 border border-red-400 text-red-700 px-4 py-1 rounded">
@foreach($errors->all() as $error)
<p class="w-full">{{ $error }}</p>
@endforeach
</div>
@endif
<form wire:submit="handleRff">
@csrf
@if(strlen($contact->first_name ?? '') === 0)
<div class="col-auto mt-3 flex items-center space-x-0 @if($contact->first_name) !== 0) hidden @endif">
<label for="first_name" class="w-1/4 text-sm font-medium text-white whitespace-nowrap text-left">{{ ctrans('texts.first_name') }}</label>
<input id="first_name" class="w-3/4 rounded-md border-gray-300 pl-2 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" wire:model="contact_first_name" />
</div>
@endif
@if(strlen($contact->last_name ?? '') === 0)
<div class="col-auto mt-3 flex items-center space-x-0 @if($contact->last_name) !== 0) hidden @endif">
<label for="last_name" class="w-1/4 text-sm font-medium text-white whitespace-nowrap text-left">{{ ctrans('texts.last_name') }}</label>
<input id="last_name" class="w-3/4 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" wire:model="contact_last_name" />
</div>
@endif
@if(strlen($contact->email ?? '') === 0)
<div class="col-auto mt-3 flex items-center space-x-0 @if($contact->email) !== 0) hidden @endif">
<label for="email" class="w-1/4 text-sm font-medium text-white whitespace-nowrap text-left">{{ ctrans('texts.email') }}</label>
<input id="email" class="w-3/4 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" wire:model="contact_email" />
</div>
@endif
@if(strlen($client_postal_code ?? '') === 0)
<div class="col-auto mt-3 flex items-center space-x-0 @if($client_postal_code) !== 0) hidden @endif">
<label for="postal_code" class="w-1/4 text-sm font-medium text-white whitespace-nowrap text-left">{{ ctrans('texts.postal_code') }}</label>
<input id="postal_code" class="w-3/4 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" wire:model="client_postal_code" />
</div>
@endif
@if(strlen($client_city ?? '') === 0)
<div class="col-auto mt-3 flex items-center space-x-0 @if($client_city) !== 0) hidden @endif">
<label for="city" class="w-1/4 text-sm font-medium text-white whitespace-nowrap text-left">{{ ctrans('texts.city') }}</label>
<input id="city" class="w-3/4 rounded-md border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 sm:text-sm text-gray-700" wire:model="client_city" />
</div>
@endif
<button
type="submit"
class="relative -ml-px inline-flex items-center space-x-2 rounded border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500 mt-4">
{{ ctrans('texts.next') }}
</button>
</form>
@elseif(count($methods) > 0) @elseif(count($methods) > 0)
<div class="mt-4" x-show.important="!toggle" x-transition> <div class="mt-4" x-show.important="!toggle" x-transition>
@foreach($methods as $method) @foreach($methods as $method)
<button <button
x-on:click="buttonDisabled = true" x-bind:disabled="buttonDisabled" x-on:click="buttonDisabled = true" x-bind:disabled="buttonDisabled"
wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}')" wire:click="handleMethodSelectingEvent('{{ $method['company_gateway_id'] }}', '{{ $method['gateway_type_id'] }}', '{{ $method['is_paypal'] }}')"
class="relative -ml-px inline-flex items-center space-x-2 rounded border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"> class="relative -ml-px inline-flex items-center space-x-2 rounded border border-gray-300 bg-gray-50 px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500">
{{ $method['label'] }} {{ $method['label'] }}
</button> </button>

View File

@ -23,6 +23,7 @@
<a href="#" @click="open = false" dusk="pay-with-custom" <a href="#" @click="open = false" dusk="pay-with-custom"
data-company-gateway-id="{{ $method['company_gateway_id'] }}" data-company-gateway-id="{{ $method['company_gateway_id'] }}"
data-gateway-type-id="{{ $method['gateway_type_id'] }}" data-gateway-type-id="{{ $method['gateway_type_id'] }}"
data-is-paypal="{{ $method['is_paypal'] }}"
class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
dusk="payment-method"> dusk="payment-method">
{{ \App\Models\CompanyGateway::find($method['company_gateway_id'])->firstOrFail()->getConfigField('name') }} {{ \App\Models\CompanyGateway::find($method['company_gateway_id'])->firstOrFail()->getConfigField('name') }}
@ -31,6 +32,7 @@
<a href="#" @click="open = false" dusk="pay-with-{{ $index }}" <a href="#" @click="open = false" dusk="pay-with-{{ $index }}"
data-company-gateway-id="{{ $method['company_gateway_id'] }}" data-company-gateway-id="{{ $method['company_gateway_id'] }}"
data-gateway-type-id="{{ $method['gateway_type_id'] }}" data-gateway-type-id="{{ $method['gateway_type_id'] }}"
data-is-paypal="{{ $method['is_paypal'] }}"
class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900" class="block px-4 py-2 text-sm leading-5 text-gray-700 dropdown-gateway-button hover:bg-gray-100 hover:text-gray-900 focus:outline-none focus:bg-gray-100 focus:text-gray-900"
dusk="payment-method"> dusk="payment-method">
{{ $method['label'] }} {{ $method['label'] }}

View File

@ -83,7 +83,7 @@
@if(!$state['payment_initialised']) @if(!$state['payment_initialised'])
@foreach($this->methods as $method) @foreach($this->methods as $method)
<button <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 bg-white rounded mr-4 hover:border-blue-600"> class="px-3 py-2 border bg-white rounded mr-4 hover:border-blue-600">
{{ $method['label'] }} {{ $method['label'] }}
</button> </button>

View File

@ -2,7 +2,7 @@
style="display: none" style="display: none"
id="displayRequiredFieldsModal" id="displayRequiredFieldsModal"
class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center" class="fixed bottom-0 inset-x-0 px-4 pb-4 sm:inset-0 sm:flex sm:items-center sm:justify-center"
x-data="{ open: true }" x-data="formValidation()"
> >
<div <div
x-show="open" x-show="open"
@ -45,56 +45,120 @@
/> />
</svg> </svg>
</div> </div>
<div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left"> <div class="mt-3 text-center sm:mt-0 sm:ml-4 sm:text-left">
<h3 class="text-lg leading-6 font-medium text-gray-900"> <h3 class="text-lg leading-6 font-medium text-gray-900">
{{ ctrans('texts.details') }} {{ ctrans('texts.details') }}
</h3> </h3>
<div class="mt-2"> <div class="mt-2">
@if(strlen(auth()->guard('contact')->user()->first_name) === 0) @if(strlen(auth()->guard('contact')->user()->first_name) === 0)
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="first_name" class="input-label">{{ ctrans('texts.first_name') }}</label> <label for="rff_first_name" class="input-label">{{ ctrans('texts.first_name') }}</label>
<input id="first_name" class="input w-full" name="rff_first_name" value="{{ auth()->guard('contact')->user()->first_name }}" /> <input
id="rff_first_name"
class="input w-full"
name="rff_first_name"
value="{{ auth()->guard('contact')->user()->first_name }}"
x-model="rff_first_name"
@blur="validateFirstName()"
:class="{ 'border-red-500': errors.rff_first_name }"
/>
<span x-show="errors.rff_first_name" class="validation validation-fail block w-full" role="alert" x-text="errors.rff_first_name"></span>
</div> </div>
@endif @endif
@if(strlen(auth()->guard('contact')->user()->last_name) === 0) @if(strlen(auth()->guard('contact')->user()->last_name) === 0)
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="last_name" class="input-label">{{ ctrans('texts.last_name') }}</label> <label for="rff_last_name" class="input-label">{{ ctrans('texts.last_name') }}</label>
<input id="last_name" class="input w-full" name="rff_last_name" value="{{ auth()->guard('contact')->user()->last_name }}"/> <input
id="rff_last_name"
class="input w-full"
name="rff_last_name"
x-model="rff_last_name"
@blur="validateLastName()"
:class="{ 'border-red-500': errors.rff_last_name }"
/>
<span x-show="errors.rff_last_name" class="validation validation-fail block w-full" role="alert" x-text="errors.rff_last_name"></span>
</div> </div>
@endif @endif
@if(strlen(auth()->guard('contact')->user()->email) === 0) @if(strlen(auth()->guard('contact')->user()->email) === 0)
<div class="col-span-6 sm:col-span-3"> <div class="col-span-6 sm:col-span-3">
<label for="email" class="input-label">{{ ctrans('texts.email') }}</label> <label for="email" class="input-label">{{ ctrans('texts.email') }}</label>
<input id="email" class="input w-full" name="rff_email" value="{{ auth()->guard('contact')->user()->email }}"/> <input
id="rff_email"
class="input w-full"
name="rff_email"
x-model="rff_email"
@blur="validateEmail()"
:class="{ 'border-red-500': errors.rff_email }"
/>
<span x-show="errors.rff_email" class="validation validation-fail block w-full" role="alert" x-text="errors.rff_email"></span>
</div> </div>
@endif @endif
@if(strlen(auth()->guard('contact')->user()->client->city) === 0)
<div class="col-span-6 sm:col-span-3" id="rff_city">
<label for="city" class="input-label">{{ ctrans('texts.city') }}</label>
<input
id="rff_city"
class="input w-full"
name="rff_city"
x-model="rff_city"
@blur="validateCity()"
:class="{ 'border-red-500': errors.rff_city }"
/>
<span x-show="errors.rff_city" class="validation validation-fail block w-full" role="alert" x-text="errors.rff_city"></span>
</div>
@endif
@if(strlen(auth()->guard('contact')->user()->client->postal_code) === 0)
<div class="col-span-6 sm:col-span-3" id="rff_postal_code">
<label for="postal_code" class="input-label">{{ ctrans('texts.postal_code') }}</label>
<input
id="rff_postal_code"
class="input w-full"
name="rff_postal_code"
x-model="rff_postal_code"
@blur="validatePostalCode()"
:class="{ 'border-red-500': errors.rff_postal_code }"
/>
<span x-show="errors.rff_postal_code" class="validation validation-fail block w-full" role="alert" x-text="errors.rff_postal_code"></span>
</div>
@endif
</div> </div>
</div> </div>
</div> </div>
<div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse"> <div class="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse" >
<div <div
class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto" class="flex w-full rounded-md shadow-sm sm:ml-3 sm:w-auto">
x-data
>
<button <button
type="button" type="button"
id="rff-next-step" @@click="validateForm"
class="button button-primary bg-primary" class="button button-primary bg-primary"
> >
{{ ctrans('texts.next_step') }} {{ ctrans('texts.next_step') }}
</button> </button>
<button
type="button"
id="rff-next-step"
class="hidden">
</button>
</div> </div>
<div <div
class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto" class="mt-3 flex w-full rounded-md shadow-sm sm:mt-0 sm:w-auto"
x-data
> >
<button <button
@click="document.getElementById('displayRequiredFieldsModal').style.display = 'none';"
type="button" type="button"
class="button button-secondary" class="button button-secondary"
id="close-button" id="close-button"
@click="document.getElementById('displayRequiredFieldsModal').style.display = 'none';"
> >
{{ ctrans('texts.close') }} {{ ctrans('texts.close') }}
</button> </button>
@ -102,3 +166,64 @@
</div> </div>
</div> </div>
</div> </div>
<script>
function formValidation() {
return {
open: true,
rff_last_name: '{{ auth()->guard('contact')->user()->last_name }}',
rff_first_name: '{{ auth()->guard('contact')->user()->first_name }}',
rff_email: '{{ auth()->guard('contact')->user()->email }}',
rff_city: '{{ auth()->guard('contact')->user()->client->city }}',
rff_postal_code: '{{ auth()->guard('contact')->user()->client->postal_code }}',
errors: {
rff_first_name: '',
rff_last_name: '',
rff_city: '',
rff_postal_code: '',
rff_email: ''
},
validateFirstName() {
this.errors.rff_first_name = this.rff_first_name.trim() === '' ? '{{ ctrans('texts.first_name') }}' + ' ' + '{{ ctrans('texts.required') }}' : '';
},
validateLastName() {
this.errors.rff_last_name = this.rff_last_name.trim() === '' ? '{{ ctrans('texts.last_name') }}' + ' ' + '{{ ctrans('texts.required') }}' : '';
},
validateEmail() {
const emailPattern = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
this.errors.rff_email = !emailPattern.test(this.rff_email.trim()) ? '{{ ctrans('texts.provide_email') }}' : '';
},
validatePostalCode() {
this.errors.rff_postal_code = this.rff_postal_code.trim() === '' ? '{{ ctrans('texts.postal_code') }}' + ' ' + '{{ ctrans('texts.required') }}' : '';
},
validateCity() {
this.errors.rff_city = this.rff_city.trim() === '' ? '{{ ctrans('texts.city') }}' + ' ' + '{{ ctrans('texts.required') }}' : '';
},
validateForm() {
this.validateFirstName();
this.validateLastName();
this.validateEmail();
this.validateCity();
this.validatePostalCode();
if (!this.errors.rff_first_name && !this.errors.rff_last_name && !this.errors.email && !this.errors.rff_postal_code && !this.errors.rff_city) {
const next_rff = document.getElementById('rff-next-step');
next_rff.click();
}
},
}
}
</script>

View File

@ -4,7 +4,6 @@
@push('head') @push('head')
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}"> <meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
<meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}"> <meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}">
<meta name="show-required-fields-form" content="{{ auth()->guard('contact')->user()->showRff() }}" />
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script> <script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
@endpush @endpush
@ -22,6 +21,9 @@
<input type="hidden" name="contact_last_name" value="{{ auth()->guard('contact')->user()->last_name }}"> <input type="hidden" name="contact_last_name" value="{{ auth()->guard('contact')->user()->last_name }}">
<input type="hidden" name="contact_email" value="{{ auth()->guard('contact')->user()->email }}"> <input type="hidden" name="contact_email" value="{{ auth()->guard('contact')->user()->email }}">
<input type="hidden" name="client_city" value="{{ auth()->guard('contact')->user()->client->city }}">
<input type="hidden" name="client_postal_code" value="{{ auth()->guard('contact')->user()->client->postal_code }}">
<div class="container mx-auto"> <div class="container mx-auto">
<div class="grid grid-cols-6 gap-4"> <div class="grid grid-cols-6 gap-4">
<div class="col-span-6 md:col-start-2 md:col-span-4"> <div class="col-span-6 md:col-start-2 md:col-span-4">

View File

@ -4,7 +4,6 @@
@push('head') @push('head')
<meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}"> <meta name="show-invoice-terms" content="{{ $settings->show_accept_invoice_terms ? true : false }}">
<meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}"> <meta name="require-invoice-signature" content="{{ $client->user->account->hasFeature(\App\Models\Account::FEATURE_INVOICE_SETTINGS) && $settings->require_invoice_signature }}">
<meta name="show-required-fields-form" content="{{ auth()->guard('contact')->user()->showRff() }}" />
@include('portal.ninja2020.components.no-cache') @include('portal.ninja2020.components.no-cache')
<script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script> <script src="{{ asset('vendor/signature_pad@2.3.2/signature_pad.min.js') }}"></script>
@ -47,6 +46,8 @@
<input type="hidden" name="contact_first_name" value="{{ auth()->guard('contact')->user()->first_name }}"> <input type="hidden" name="contact_first_name" value="{{ auth()->guard('contact')->user()->first_name }}">
<input type="hidden" name="contact_last_name" value="{{ auth()->guard('contact')->user()->last_name }}"> <input type="hidden" name="contact_last_name" value="{{ auth()->guard('contact')->user()->last_name }}">
<input type="hidden" name="contact_email" value="{{ auth()->guard('contact')->user()->email }}"> <input type="hidden" name="contact_email" value="{{ auth()->guard('contact')->user()->email }}">
<input type="hidden" name="client_city" value="{{ auth()->guard('contact')->user()->client->city }}">
<input type="hidden" name="client_postal_code" value="{{ auth()->guard('contact')->user()->client->postal_code }}">
<div class="bg-white shadow sm:rounded-lg mb-4" translate> <div class="bg-white shadow sm:rounded-lg mb-4" translate>
<div class="px-4 py-5 sm:p-6"> <div class="px-4 py-5 sm:p-6">

View File

@ -164,6 +164,7 @@ Route::group(['middleware' => ['throttle:api', 'api_db', 'token_auth', 'locale']
Route::post('charts/totals_v2', [ChartController::class, 'totalsV2'])->name('chart.totals_v2'); Route::post('charts/totals_v2', [ChartController::class, 'totalsV2'])->name('chart.totals_v2');
Route::post('charts/chart_summary_v2', [ChartController::class, 'chart_summaryV2'])->name('chart.chart_summary_v2'); Route::post('charts/chart_summary_v2', [ChartController::class, 'chart_summaryV2'])->name('chart.chart_summary_v2');
Route::post('charts/calculated_fields', [ChartController::class, 'calculatedFields'])->name('chart.calculated_fields');
Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index'); Route::post('claim_license', [LicenseController::class, 'index'])->name('license.index');

View File

@ -196,7 +196,7 @@ class WaveTest extends TestCase
'import_type' => 'waveaccounting', 'import_type' => 'waveaccounting',
]; ];
Cache::put($hash.'-expense', base64_encode($csv), 360); Cache::put($hash.'-invoice', base64_encode($csv), 360);
$csv_importer = new Wave($data, $this->company); $csv_importer = new Wave($data, $this->company);