From 20bfc999d0e4bf676ea65a31c352564d49082b02 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 7 Oct 2021 14:33:43 +1100 Subject: [PATCH 1/5] Fixes for Company Import --- app/Jobs/Company/CompanyImport.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/Jobs/Company/CompanyImport.php b/app/Jobs/Company/CompanyImport.php index 2898617112..234040ea9f 100644 --- a/app/Jobs/Company/CompanyImport.php +++ b/app/Jobs/Company/CompanyImport.php @@ -1331,6 +1331,12 @@ class CompanyImport implements ShouldQueue $new_obj->save(['timestamps' => false]); $new_obj->number = $this->getNextQuoteNumber($client = Client::find($obj_array['client_id']), $new_obj); } + elseif($class == 'App\Models\ClientContact'){ + $new_obj = new ClientContact(); + $new_obj->company_id = $this->company->id; + $new_obj->fill($obj_array); + $new_obj->save(['timestamps' => false]); + } else{ $new_obj = $class::firstOrNew( [$match_key => $obj->{$match_key}, 'company_id' => $this->company->id], From 40a90c4ea84e80309ee95d5e518ed46cd83ce46f Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 7 Oct 2021 17:34:23 +1100 Subject: [PATCH 2/5] Fixes for invoice observer --- app/Observers/InvoiceObserver.php | 12 ++++++++++-- app/Repositories/BaseRepository.php | 9 ++++----- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/app/Observers/InvoiceObserver.php b/app/Observers/InvoiceObserver.php index c7da824f24..6fdb6ee829 100644 --- a/app/Observers/InvoiceObserver.php +++ b/app/Observers/InvoiceObserver.php @@ -34,9 +34,11 @@ class InvoiceObserver ->where('event_id', Webhook::EVENT_CREATE_INVOICE) ->exists(); - $invoice->load('client'); if ($subscriptions) { + + $invoice->load('client'); + WebhookHandler::dispatch(Webhook::EVENT_CREATE_INVOICE, $invoice, $invoice->company); } } @@ -53,11 +55,14 @@ class InvoiceObserver ->where('event_id', Webhook::EVENT_UPDATE_INVOICE) ->exists(); - $invoice->load('client'); if ($subscriptions) { + + $invoice->load('client'); + WebhookHandler::dispatch(Webhook::EVENT_UPDATE_INVOICE, $invoice, $invoice->company); + } } @@ -75,6 +80,9 @@ class InvoiceObserver ->exists(); if ($subscriptions) { + + $invoice->load('client'); + WebhookHandler::dispatch(Webhook::EVENT_DELETE_INVOICE, $invoice, $invoice->company); } } diff --git a/app/Repositories/BaseRepository.php b/app/Repositories/BaseRepository.php index b828ff1ba1..9f0eece3a6 100644 --- a/app/Repositories/BaseRepository.php +++ b/app/Repositories/BaseRepository.php @@ -174,10 +174,6 @@ class BaseRepository if(array_key_exists('client_id', $data)) $model->client_id = $data['client_id']; - //pickup changes here to recalculate reminders - //if($model instanceof Invoice && ($model->isDirty('date') || $model->isDirty('due_date'))) - // $model->service()->setReminder()->save(); - $client = Client::where('id', $model->client_id)->withTrashed()->first(); $state = []; @@ -210,7 +206,10 @@ class BaseRepository $model->custom_surcharge_tax3 = $client->company->custom_surcharge_taxes3; $model->custom_surcharge_tax4 = $client->company->custom_surcharge_taxes4; - $model->saveQuietly(); + if(!$model->id) + $model->save(); + else + $model->saveQuietly(); /* Model now persisted, now lets do some child tasks */ From ca2bb74d4aa986268ef83c2f72036aa1056dd511 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 7 Oct 2021 17:52:29 +1100 Subject: [PATCH 3/5] Add missing class --- app/Services/ClientPortal/InstantPayment.php | 291 +++++++++++++++++++ 1 file changed, 291 insertions(+) create mode 100644 app/Services/ClientPortal/InstantPayment.php diff --git a/app/Services/ClientPortal/InstantPayment.php b/app/Services/ClientPortal/InstantPayment.php new file mode 100644 index 0000000000..6d17873c86 --- /dev/null +++ b/app/Services/ClientPortal/InstantPayment.php @@ -0,0 +1,291 @@ +request = $request; + } + + public function run() + { + + $is_credit_payment = false; + + $tokens = []; + + if ($this->request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) { + $is_credit_payment = true; + } + + $gateway = CompanyGateway::find($this->request->input('company_gateway_id')); + + /** + * find invoices + * + * ['invoice_id' => xxx, 'amount' => 22.00] + */ + + $payable_invoices = collect($this->request->payable_invoices); + $invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->withTrashed()->get(); + + $invoices->each(function($invoice){ + $invoice->service()->removeUnpaidGatewayFees()->save(); + }); + + /* pop non payable invoice from the $payable_invoices array */ + + $payable_invoices = $payable_invoices->filter(function ($payable_invoice) use ($invoices) { + return $invoices->where('hashed_id', $payable_invoice['invoice_id'])->first()->isPayable(); + }); + + /*return early if no invoices*/ + + if ($payable_invoices->count() == 0) { + return redirect() + ->route('client.invoices.index') + ->with(['message' => 'No payable invoices selected.']); + } + + $client = $invoices->first()->client; + $settings = $client->getMergedSettings(); + + /* This loop checks for under / over payments and returns the user if a check fails */ + + foreach ($payable_invoices as $payable_invoice) { + + /*Match the payable invoice to the Model Invoice*/ + + $invoice = $invoices->first(function ($inv) use ($payable_invoice) { + return $payable_invoice['invoice_id'] == $inv->hashed_id; + }); + + /* + * Check if company supports over & under payments. + * Determine the payable amount and the max payable. ie either partial or invoice balance + */ + + $payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision)); + $invoice_balance = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision); + + /*If we don't allow under/over payments force the payable amount - prevents inspect element adjustments in JS*/ + + if ($settings->client_portal_allow_under_payment == false && $settings->client_portal_allow_over_payment == false) { + $payable_invoice['amount'] = Number::roundValue(($invoice->partial > 0 ? $invoice->partial : $invoice->balance), $client->currency()->precision); + } + + if (!$settings->client_portal_allow_under_payment && $payable_amount < $invoice_balance) { + return redirect() + ->route('client.invoices.index') + ->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance])); + } + + if ($settings->client_portal_allow_under_payment) { + if ($invoice_balance < $settings->client_portal_under_payment_minimum && $payable_amount < $invoice_balance) { + return redirect() + ->route('client.invoices.index') + ->with('message', ctrans('texts.minimum_required_payment', ['amount' => $invoice_balance])); + } + + if ($invoice_balance < $settings->client_portal_under_payment_minimum) { + // Skip the under payment rule. + } + + if ($invoice_balance >= $settings->client_portal_under_payment_minimum && $payable_amount < $settings->client_portal_under_payment_minimum) { + return redirect() + ->route('client.invoices.index') + ->with('message', ctrans('texts.minimum_required_payment', ['amount' => $settings->client_portal_under_payment_minimum])); + } + } + + /* If we don't allow over payments and the amount exceeds the balance */ + + if (!$settings->client_portal_allow_over_payment && $payable_amount > $invoice_balance) { + return redirect() + ->route('client.invoices.index') + ->with('message', ctrans('texts.over_payments_disabled')); + } + + } + + /*Iterate through invoices and add gateway fees and other payment metadata*/ + + //$payable_invoices = $payable_invoices->map(function ($payable_invoice) use ($invoices, $settings) { + $payable_invoice_collection = collect(); + + foreach ($payable_invoices as $payable_invoice) { + // nlog($payable_invoice); + + $payable_invoice['amount'] = Number::parseFloat($payable_invoice['amount']); + + $invoice = $invoices->first(function ($inv) use ($payable_invoice) { + return $payable_invoice['invoice_id'] == $inv->hashed_id; + }); + + $payable_amount = Number::roundValue(Number::parseFloat($payable_invoice['amount'], $client->currency()->precision)); + $invoice_balance = Number::roundValue($invoice->balance, $client->currency()->precision); + + $payable_invoice['due_date'] = $this->formatDate($invoice->due_date, $invoice->client->date_format()); + $payable_invoice['invoice_number'] = $invoice->number; + + if (isset($invoice->po_number)) { + $additional_info = $invoice->po_number; + } elseif (isset($invoice->public_notes)) { + $additional_info = $invoice->public_notes; + } else { + $additional_info = $invoice->date; + } + + $payable_invoice['additional_info'] = $additional_info; + + $payable_invoice_collection->push($payable_invoice); + } + + + if ($this->request->has('signature') && !is_null($this->request->signature) && !empty($this->request->signature)) { + $invoices->each(function ($invoice){ + InjectSignature::dispatch($invoice, $this->request->signature); + }); + } + + $payable_invoices = $payable_invoice_collection; + + $payment_method_id = $this->request->input('payment_method_id'); + $invoice_totals = $payable_invoices->sum('amount'); + $first_invoice = $invoices->first(); + $credit_totals = $first_invoice->client->getSetting('use_credits_payment') == 'always' ? $first_invoice->client->service()->getCreditBalance() : 0; + $starting_invoice_amount = $first_invoice->balance; + + if ($gateway) { + $first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save(); + } + + /** + * Gateway fee is calculated + * by adding it as a line item, and then subtract + * the starting and finishing amounts of the invoice. + */ + $fee_totals = $first_invoice->balance - $starting_invoice_amount; + + if ($gateway) { + $tokens = $client->gateway_tokens() + ->whereCompanyGatewayId($gateway->id) + ->whereGatewayTypeId($payment_method_id) + ->get(); + } + + if(!$is_credit_payment){ + $credit_totals = 0; + } + + $hash_data = ['invoices' => $payable_invoices->toArray(), 'credits' => $credit_totals, 'amount_with_fee' => max(0, (($invoice_totals + $fee_totals) - $credit_totals))]; + + if ($this->request->query('hash')) { + $hash_data['billing_context'] = Cache::get($this->request->query('hash')); + } + + $payment_hash = new PaymentHash; + $payment_hash->hash = Str::random(32); + $payment_hash->data = $hash_data; + $payment_hash->fee_total = $fee_totals; + $payment_hash->fee_invoice_id = $first_invoice->id; + + $payment_hash->save(); + + if($is_credit_payment){ + $amount_with_fee = max(0, (($invoice_totals + $fee_totals) - $credit_totals)); + } + else{ + $credit_totals = 0; + $amount_with_fee = max(0, $invoice_totals + $fee_totals); + } + + $totals = [ + 'credit_totals' => $credit_totals, + 'invoice_totals' => $invoice_totals, + 'fee_total' => $fee_totals, + 'amount_with_fee' => $amount_with_fee, + ]; + + $data = [ + 'payment_hash' => $payment_hash->hash, + 'total' => $totals, + 'invoices' => $payable_invoices, + 'tokens' => $tokens, + 'payment_method_id' => $payment_method_id, + 'amount_with_fee' => $invoice_totals + $fee_totals, + ]; + + if ($is_credit_payment || $totals <= 0) { + return $this->processCreditPayment($this->request, $data); + } + + try { + + return $gateway + ->driver($client) + ->setPaymentMethod($payment_method_id) + ->setPaymentHash($payment_hash) + ->checkRequirements() + ->processPaymentView($data); + } catch (\Exception $e) { + SystemLogger::dispatch( + $e->getMessage(), + SystemLog::CATEGORY_GATEWAY_RESPONSE, + SystemLog::EVENT_GATEWAY_ERROR, + SystemLog::TYPE_FAILURE, + $client, + $client->company + ); + + throw new PaymentFailed($e->getMessage()); + } + + } + + public function processCreditPayment(Request $request, array $data) + { + return render('gateways.credit.index', $data); + } + +} \ No newline at end of file From d45316fd5ffeed6a7092ac644279606b2ec0fdbe Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 7 Oct 2021 18:01:37 +1100 Subject: [PATCH 4/5] Minor fixes for requirements --- app/PaymentDrivers/BaseDriver.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index d778527499..6084b5164b 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -561,15 +561,15 @@ class BaseDriver extends AbstractPaymentDriver } } - if ($this->company_gateway->require_contact_name) { - if ($this->checkRequiredResource($this->first_name)) { - $this->required_fields[] = 'contact_first_name'; - } + // if ($this->company_gateway->require_contact_name) { + // if ($this->checkRequiredResource($this->first_name)) { + // $this->required_fields[] = 'contact_first_name'; + // } - if ($this->checkRequiredResource($this->last_name)) { - $this->required_fields[] = 'contact_last_name'; - } - } + // if ($this->checkRequiredResource($this->last_name)) { + // $this->required_fields[] = 'contact_last_name'; + // } + // } if ($this->company_gateway->require_postal_code) { // In case "require_postal_code" is true, we don't need billing address. From 1160f68e9d248a218b72fb68c03aab466975ccab Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 7 Oct 2021 18:22:56 +1100 Subject: [PATCH 5/5] v5.3.22 --- VERSION.txt | 2 +- config/ninja.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/VERSION.txt b/VERSION.txt index e26d379c80..100b04464b 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -5.3.21 \ No newline at end of file +5.3.22 \ No newline at end of file diff --git a/config/ninja.php b/config/ninja.php index 6a5846dd11..e697e07143 100644 --- a/config/ninja.php +++ b/config/ninja.php @@ -14,8 +14,8 @@ return [ 'require_https' => env('REQUIRE_HTTPS', true), 'app_url' => rtrim(env('APP_URL', ''), '/'), 'app_domain' => env('APP_DOMAIN', 'invoicing.co'), - 'app_version' => '5.3.21', - 'app_tag' => '5.3.21', + 'app_version' => '5.3.22', + 'app_tag' => '5.3.22', 'minimum_client_version' => '5.0.16', 'terms_version' => '1.0.1', 'api_secret' => env('API_SECRET', ''),