From 6e6019feaf9663f6817cc4da19f00fe7e1bfe1e4 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 09:20:54 +1100 Subject: [PATCH 01/10] Change Pre Payments to be always available as recurring --- app/DataMapper/CompanySettings.php | 4 +- app/Export/CSV/InvoiceItemExport.php | 4 ++ .../ClientPortal/PrePaymentController.php | 4 +- app/Http/ViewComposers/PortalComposer.php | 4 +- .../ninja2020/pre_payments/index.blade.php | 65 ++++++++----------- 5 files changed, 37 insertions(+), 44 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 6feca01c172..07c33104286 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -471,12 +471,12 @@ class CompanySettings extends BaseSettings public $client_initiated_payments_minimum = 0; - public $client_initiated_payments_recurring = false; + // public $client_initiated_payments_recurring = false; public $sync_invoice_quote_columns = true; public static $casts = [ - 'client_initiated_payments_recurring'=> 'bool', + // 'client_initiated_payments_recurring'=> 'bool', 'client_initiated_payments' => 'bool', 'client_initiated_payments_minimum' => 'float', 'sync_invoice_quote_columns' => 'bool', diff --git a/app/Export/CSV/InvoiceItemExport.php b/app/Export/CSV/InvoiceItemExport.php index 5a58f78ab35..10841508ab6 100644 --- a/app/Export/CSV/InvoiceItemExport.php +++ b/app/Export/CSV/InvoiceItemExport.php @@ -34,6 +34,8 @@ class InvoiceItemExport extends BaseExport 'amount' => 'amount', 'balance' => 'balance', 'client' => 'client_id', + 'client_number' => 'client.number', + 'client_id_number' => 'client.id_number', 'custom_surcharge1' => 'custom_surcharge1', 'custom_surcharge2' => 'custom_surcharge2', 'custom_surcharge3' => 'custom_surcharge3', @@ -198,6 +200,8 @@ private function decorateAdvancedFields(Invoice $invoice, array $entity) :array // if(in_array('client_id', $this->input['report_keys'])) $entity['client'] = $invoice->client->present()->name(); + $entity['client_id_number'] = $invoice->client->id_number; + $entity['client_number'] = $invoice->client->number; // if(in_array('status_id', $this->input['report_keys'])) $entity['status'] = $invoice->stringStatus($invoice->status_id); diff --git a/app/Http/Controllers/ClientPortal/PrePaymentController.php b/app/Http/Controllers/ClientPortal/PrePaymentController.php index 1fb91c04a5a..06608a5df5f 100644 --- a/app/Http/Controllers/ClientPortal/PrePaymentController.php +++ b/app/Http/Controllers/ClientPortal/PrePaymentController.php @@ -40,8 +40,8 @@ public function index() { $data = [ 'title' => ctrans('texts.amount'). " " .auth()->guard('contact')->user()->client->currency()->code." (".auth()->guard('contact')->user()->client->currency()->symbol . ")", - 'allows_recurring' => auth()->guard('contact')->user()->client->getSetting('client_initiated_payments_recurring'), - 'allows_recurring' => true, + // 'allows_recurring' => auth()->guard('contact')->user()->client->getSetting('client_initiated_payments_recurring'), + // 'allows_recurring' => true, 'minimum_amount' => auth()->guard('contact')->user()->client->getSetting('client_initiated_payments_minimum'), ]; diff --git a/app/Http/ViewComposers/PortalComposer.php b/app/Http/ViewComposers/PortalComposer.php index 4ab48491770..2f618b5277f 100644 --- a/app/Http/ViewComposers/PortalComposer.php +++ b/app/Http/ViewComposers/PortalComposer.php @@ -138,9 +138,9 @@ private function sidebarMenu() :array $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar']; } - // if (property_exists($this->settings, 'client_initiated_payments') && $this->settings->client_initiated_payments) { + if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) { $data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign']; - // } + } return $data; } diff --git a/resources/views/portal/ninja2020/pre_payments/index.blade.php b/resources/views/portal/ninja2020/pre_payments/index.blade.php index 0e3498850c3..d03316c1321 100644 --- a/resources/views/portal/ninja2020/pre_payments/index.blade.php +++ b/resources/views/portal/ninja2020/pre_payments/index.blade.php @@ -44,7 +44,7 @@ class="input mt-0 mr-4 relative" min="{{ $minimum_amount }}"/> @if($minimum_amount > 0) -

{{ ctrans('texts.minimum_required_payment', ['amount' => $minimum_amount])}}

+

{{ ctrans('texts.minimum_required_payment', ['amount' => $minimum_amount])}}

@endif @if($errors->has('amount')) @@ -53,45 +53,34 @@ class="input mt-0 mr-4 relative" @endcomponent - @if($allows_recurring) -
- @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.enable_recurring')]) - + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.number_of_payments')]) + + + + @endcomponent -
- @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.number_of_payments')]) - - - - - @endcomponent - @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.frequency')]) - - @endcomponent -
- -
- - @endif + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.frequency')]) + + @endcomponent
From 0e8030303337c6151d9e75cffb79237aea145182 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 11:36:46 +1100 Subject: [PATCH 02/10] Adjustments for gocardless currency amount --- app/Http/Controllers/EmailController.php | 2 +- app/Http/Requests/Email/SendEmailRequest.php | 2 +- app/PaymentDrivers/GoCardless/ACH.php | 3 ++- app/PaymentDrivers/GoCardless/DirectDebit.php | 3 ++- app/PaymentDrivers/GoCardless/SEPA.php | 2 +- 5 files changed, 7 insertions(+), 5 deletions(-) diff --git a/app/Http/Controllers/EmailController.php b/app/Http/Controllers/EmailController.php index 3899dbf7662..6eb974f3316 100644 --- a/app/Http/Controllers/EmailController.php +++ b/app/Http/Controllers/EmailController.php @@ -136,7 +136,7 @@ public function send(SendEmailRequest $request) $mo->email_template_body = $request->input('template'); $mo->email_template_subject = str_replace("template", "subject", $request->input('template')); - if ($request->has('cc_email')) { + if ($request->has('cc_email') && $request->cc_email) { $mo->cc[] = new Address($request->cc_email); } diff --git a/app/Http/Requests/Email/SendEmailRequest.php b/app/Http/Requests/Email/SendEmailRequest.php index c6d16d54c60..66a80e3f61e 100644 --- a/app/Http/Requests/Email/SendEmailRequest.php +++ b/app/Http/Requests/Email/SendEmailRequest.php @@ -43,7 +43,7 @@ public function rules() 'template' => 'bail|required', 'entity' => 'bail|required', 'entity_id' => 'bail|required', - 'cc_email' => 'bail|sometimes|email', + 'cc_email' => 'bail|sometimes|email|nullable', ]; } diff --git a/app/PaymentDrivers/GoCardless/ACH.php b/app/PaymentDrivers/GoCardless/ACH.php index f84b319e919..10e0adc3463 100644 --- a/app/PaymentDrivers/GoCardless/ACH.php +++ b/app/PaymentDrivers/GoCardless/ACH.php @@ -175,7 +175,8 @@ public function paymentResponse(PaymentResponseRequest $request) try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ - 'amount' => $request->amount, + // 'amount' => $request->amount, + 'amount' => (int)rtrim(round($request->amount),0), 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardless/DirectDebit.php b/app/PaymentDrivers/GoCardless/DirectDebit.php index 4de93dc8dfc..3705db72e4b 100644 --- a/app/PaymentDrivers/GoCardless/DirectDebit.php +++ b/app/PaymentDrivers/GoCardless/DirectDebit.php @@ -179,7 +179,8 @@ public function paymentResponse(PaymentResponseRequest $request) try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ - 'amount' => $request->amount, + // 'amount' => $request->amount, + 'amount' => (int)rtrim(round($request->amount),0), 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardless/SEPA.php b/app/PaymentDrivers/GoCardless/SEPA.php index c4950314d49..eac4fd81fa8 100644 --- a/app/PaymentDrivers/GoCardless/SEPA.php +++ b/app/PaymentDrivers/GoCardless/SEPA.php @@ -175,7 +175,7 @@ public function paymentResponse(PaymentResponseRequest $request) try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ - 'amount' => $request->amount, + 'amount' => (int)rtrim(round($request->amount),0), 'currency' => $request->currency, 'description' => $description, 'metadata' => [ From 67a7f823e24a936ce98e4a0daca2f12370a45773 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 12:36:28 +1100 Subject: [PATCH 03/10] Subscription table view client portal --- app/DataMapper/CompanySettings.php | 2 - .../ClientPortal/PrePaymentController.php | 11 ++- .../PrePayments/StorePrePaymentRequest.php | 12 +++- app/Http/ViewComposers/PortalComposer.php | 8 +-- .../ninja2020/pre_payments/index.blade.php | 71 +++++++++++-------- 5 files changed, 64 insertions(+), 40 deletions(-) diff --git a/app/DataMapper/CompanySettings.php b/app/DataMapper/CompanySettings.php index 07c33104286..1e64f0ec1c1 100644 --- a/app/DataMapper/CompanySettings.php +++ b/app/DataMapper/CompanySettings.php @@ -505,7 +505,6 @@ class CompanySettings extends BaseSettings 'purchase_order_design_id' => 'string', 'purchase_order_footer' => 'string', 'purchase_order_number_pattern' => 'string', - 'purchase_order_number_counter' => 'int', 'page_numbering_alignment' => 'string', 'page_numbering' => 'bool', 'auto_archive_invoice_cancelled' => 'bool', @@ -537,7 +536,6 @@ class CompanySettings extends BaseSettings 'reminder_send_time' => 'int', 'email_sending_method' => 'string', 'gmail_sending_user_id' => 'string', - 'currency_id' => 'string', 'counter_number_applied' => 'string', 'quote_number_applied' => 'string', 'email_subject_custom1' => 'string', diff --git a/app/Http/Controllers/ClientPortal/PrePaymentController.php b/app/Http/Controllers/ClientPortal/PrePaymentController.php index 06608a5df5f..107cbac6d95 100644 --- a/app/Http/Controllers/ClientPortal/PrePaymentController.php +++ b/app/Http/Controllers/ClientPortal/PrePaymentController.php @@ -38,11 +38,16 @@ class PrePaymentController extends Controller */ public function index() { + $client = auth()->guard('contact')->user()->client; + $minimum = $client->getSetting('client_initiated_payments_minimum'); + $minimum_amount = $minimum == 0 ? "" : Number::formatMoney($minimum, $client); + $data = [ - 'title' => ctrans('texts.amount'). " " .auth()->guard('contact')->user()->client->currency()->code." (".auth()->guard('contact')->user()->client->currency()->symbol . ")", + 'title' => ctrans('texts.amount'). " " .$client->currency()->code." (".auth()->guard('contact')->user()->client->currency()->symbol . ")", // 'allows_recurring' => auth()->guard('contact')->user()->client->getSetting('client_initiated_payments_recurring'), - // 'allows_recurring' => true, - 'minimum_amount' => auth()->guard('contact')->user()->client->getSetting('client_initiated_payments_minimum'), + 'allows_recurring' => true, + 'minimum' => $minimum, + 'minimum_amount' => $minimum_amount, ]; return $this->render('pre_payments.index', $data); diff --git a/app/Http/Requests/ClientPortal/PrePayments/StorePrePaymentRequest.php b/app/Http/Requests/ClientPortal/PrePayments/StorePrePaymentRequest.php index f596ef2e629..baa5e2a903f 100644 --- a/app/Http/Requests/ClientPortal/PrePayments/StorePrePaymentRequest.php +++ b/app/Http/Requests/ClientPortal/PrePayments/StorePrePaymentRequest.php @@ -26,7 +26,17 @@ public function rules() { return [ 'notes' => 'required|bail|', - 'amount' => 'required|bail|', + 'amount' => 'required|bail|gte:minimum_amount', + 'minimum_amount' => '', ]; } + + public function prepareForValidation() + { + $input = $this->all(); + + + $this->replace($input); + + } } diff --git a/app/Http/ViewComposers/PortalComposer.php b/app/Http/ViewComposers/PortalComposer.php index 2f618b5277f..9c5321f5282 100644 --- a/app/Http/ViewComposers/PortalComposer.php +++ b/app/Http/ViewComposers/PortalComposer.php @@ -132,11 +132,11 @@ private function sidebarMenu() :array $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity']; - if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) { - $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card']; - } else { + // if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) { + // $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card']; + // } else { $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar']; - } + // } if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) { $data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign']; diff --git a/resources/views/portal/ninja2020/pre_payments/index.blade.php b/resources/views/portal/ninja2020/pre_payments/index.blade.php index d03316c1321..29300554b66 100644 --- a/resources/views/portal/ninja2020/pre_payments/index.blade.php +++ b/resources/views/portal/ninja2020/pre_payments/index.blade.php @@ -34,17 +34,17 @@ @endif @endcomponent + @component('portal.ninja2020.components.general.card-element', ['title' => $title]) + placeholder=""/> - @if($minimum_amount > 0) -

{{ ctrans('texts.minimum_required_payment', ['amount' => $minimum_amount])}}

+ @if($minimum > 0) +

{{ ctrans('texts.minimum_required_payment', ['amount' => $minimum_amount])}}

@endif @if($errors->has('amount')) @@ -53,34 +53,45 @@ class="input mt-0 mr-4 relative" @endcomponent - @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.number_of_payments')]) - - - - + @if($allows_recurring) +
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.enable_recurring')]) + @endcomponent - @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.frequency')]) - - @endcomponent +
+ @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.number_of_payments')]) + + + + + @endcomponent + @component('portal.ninja2020.components.general.card-element', ['title' => ctrans('texts.frequency')]) + + @endcomponent +
+ +
+ + @endif
From c906bcbf93ec5db0ff5c55a837e9107ae10cf9e5 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 12:38:04 +1100 Subject: [PATCH 04/10] Subscription table view client portal --- .../ClientPortal/SubscriptionController.php | 26 +++++++++---------- app/Http/ViewComposers/PortalComposer.php | 8 +++--- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionController.php b/app/Http/Controllers/ClientPortal/SubscriptionController.php index 73a32f6b47d..5aa47229b54 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionController.php @@ -21,20 +21,20 @@ class SubscriptionController extends Controller { public function index() { - if (Ninja::isHosted()) { - $count = RecurringInvoice::query() - ->where('client_id', auth()->guard('contact')->user()->client->id) - ->where('company_id', auth()->guard('contact')->user()->client->company_id) - ->where('status_id', RecurringInvoice::STATUS_ACTIVE) - ->where('is_deleted', 0) - ->whereNotNull('subscription_id') - ->withTrashed() - ->count(); + // if (Ninja::isHosted()) { + // $count = RecurringInvoice::query() + // ->where('client_id', auth()->guard('contact')->user()->client->id) + // ->where('company_id', auth()->guard('contact')->user()->client->company_id) + // ->where('status_id', RecurringInvoice::STATUS_ACTIVE) + // ->where('is_deleted', 0) + // ->whereNotNull('subscription_id') + // ->withTrashed() + // ->count(); - if ($count == 0) { - return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); - } - } + // if ($count == 0) { + // return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); + // } + // } return render('subscriptions.index'); } diff --git a/app/Http/ViewComposers/PortalComposer.php b/app/Http/ViewComposers/PortalComposer.php index 9c5321f5282..2f618b5277f 100644 --- a/app/Http/ViewComposers/PortalComposer.php +++ b/app/Http/ViewComposers/PortalComposer.php @@ -132,11 +132,11 @@ private function sidebarMenu() :array $data[] = ['title' => ctrans('texts.statement'), 'url' => 'client.statement', 'icon' => 'activity']; - // if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) { - // $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card']; - // } else { + if (Ninja::isHosted() && auth()->guard('contact')->user()->company->id == config('ninja.ninja_default_company_id')) { + $data[] = ['title' => ctrans('texts.plan'), 'url' => 'client.plan', 'icon' => 'credit-card']; + } else { $data[] = ['title' => ctrans('texts.subscriptions'), 'url' => 'client.subscriptions.index', 'icon' => 'calendar']; - // } + } if (auth()->guard('contact')->user()->client->getSetting('client_initiated_payments')) { $data[] = ['title' => ctrans('texts.pre_payment'), 'url' => 'client.pre_payments.index', 'icon' => 'dollar-sign']; From 0cd984b29b766559fe7792ebe351f20348dd1f7e Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 12:41:44 +1100 Subject: [PATCH 05/10] Subscription table view client portal --- .../ClientPortal/SubscriptionController.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionController.php b/app/Http/Controllers/ClientPortal/SubscriptionController.php index 5aa47229b54..73a32f6b47d 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionController.php @@ -21,20 +21,20 @@ class SubscriptionController extends Controller { public function index() { - // if (Ninja::isHosted()) { - // $count = RecurringInvoice::query() - // ->where('client_id', auth()->guard('contact')->user()->client->id) - // ->where('company_id', auth()->guard('contact')->user()->client->company_id) - // ->where('status_id', RecurringInvoice::STATUS_ACTIVE) - // ->where('is_deleted', 0) - // ->whereNotNull('subscription_id') - // ->withTrashed() - // ->count(); + if (Ninja::isHosted()) { + $count = RecurringInvoice::query() + ->where('client_id', auth()->guard('contact')->user()->client->id) + ->where('company_id', auth()->guard('contact')->user()->client->company_id) + ->where('status_id', RecurringInvoice::STATUS_ACTIVE) + ->where('is_deleted', 0) + ->whereNotNull('subscription_id') + ->withTrashed() + ->count(); - // if ($count == 0) { - // return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); - // } - // } + if ($count == 0) { + return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); + } + } return render('subscriptions.index'); } From d7ff984818474088e3f1f1c87b8fc7df1bb423cf Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 12:42:38 +1100 Subject: [PATCH 06/10] Subscription table view client portal --- .../ClientPortal/SubscriptionController.php | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionController.php b/app/Http/Controllers/ClientPortal/SubscriptionController.php index 73a32f6b47d..4b7bfd56986 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionController.php @@ -21,20 +21,20 @@ class SubscriptionController extends Controller { public function index() { - if (Ninja::isHosted()) { - $count = RecurringInvoice::query() - ->where('client_id', auth()->guard('contact')->user()->client->id) - ->where('company_id', auth()->guard('contact')->user()->client->company_id) - ->where('status_id', RecurringInvoice::STATUS_ACTIVE) - ->where('is_deleted', 0) - ->whereNotNull('subscription_id') - ->withTrashed() - ->count(); + // if (Ninja::isHosted()) { + // $count = RecurringInvoice::query() + // ->where('client_id', auth()->guard('contact')->user()->client->id) + // ->where('company_id', auth()->guard('contact')->user()->client->company_id) + // ->where('status_id', RecurringInvoice::STATUS_ACTIVE) + // ->where('is_deleted', 0) + // ->whereNotNull('subscription_id') + // ->withTrashed() + // ->count(); - if ($count == 0) { - return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); - } - } + // if ($count == 0) { + // return redirect()->route('client.ninja_contact_login', ['contact_key' => auth()->guard('contact')->user()->contact_key, 'company_key' => auth()->guard('contact')->user()->company->company_key]); + // } + // } return render('subscriptions.index'); } From 6e9ebd57797138d229994b52f6979b5f3c216c1b Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 15:04:04 +1100 Subject: [PATCH 07/10] Refactor for GoCardless Billing Flow --- .../ClientPortal/SubscriptionController.php | 7 +- app/PaymentDrivers/GoCardless/ACH.php | 4 +- app/PaymentDrivers/GoCardless/DirectDebit.php | 158 ++++++++++++++---- app/PaymentDrivers/GoCardless/SEPA.php | 4 +- .../GoCardlessPaymentDriver.php | 2 +- 5 files changed, 138 insertions(+), 37 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/SubscriptionController.php b/app/Http/Controllers/ClientPortal/SubscriptionController.php index 4b7bfd56986..17d910fe0ea 100644 --- a/app/Http/Controllers/ClientPortal/SubscriptionController.php +++ b/app/Http/Controllers/ClientPortal/SubscriptionController.php @@ -19,6 +19,12 @@ class SubscriptionController extends Controller { + /** + * This function is used to display the subscription page. + * + * @return \Illuminate\View\View|\Illuminate\Contracts\View\Factory + */ + public function index() { // if (Ninja::isHosted()) { @@ -44,7 +50,6 @@ public function index() * * @param ShowRecurringInvoiceRequest $request * @param RecurringInvoice $recurring_invoice - * @return Factory|View */ public function show(ShowRecurringInvoiceRequest $request, RecurringInvoice $recurring_invoice) { diff --git a/app/PaymentDrivers/GoCardless/ACH.php b/app/PaymentDrivers/GoCardless/ACH.php index 10e0adc3463..ac987300252 100644 --- a/app/PaymentDrivers/GoCardless/ACH.php +++ b/app/PaymentDrivers/GoCardless/ACH.php @@ -172,11 +172,13 @@ public function paymentResponse(PaymentResponseRequest $request) $description = "Amount {$request->amount} from client {$this->go_cardless->client->present()->name()}"; } + $amount = $this->go_cardless->convertToGoCardlessAmount($this->go_cardless->payment_hash?->amount_with_fee(), $this->go_cardless->client->currency()->precision); + try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ // 'amount' => $request->amount, - 'amount' => (int)rtrim(round($request->amount),0), + 'amount' => $amount, 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardless/DirectDebit.php b/app/PaymentDrivers/GoCardless/DirectDebit.php index 3705db72e4b..a6b55c113cd 100644 --- a/app/PaymentDrivers/GoCardless/DirectDebit.php +++ b/app/PaymentDrivers/GoCardless/DirectDebit.php @@ -49,37 +49,97 @@ public function __construct(GoCardlessPaymentDriver $go_cardless) * @return Redirector|RedirectResponse|void */ public function authorizeView(array $data) + { + return $this->billingRequestFlows($data); + // $session_token = \Illuminate\Support\Str::uuid()->toString(); + + // try { + // $redirect = $this->go_cardless->gateway->redirectFlows()->create([ + // 'params' => [ + // 'session_token' => $session_token, + // 'success_redirect_url' => route('client.payment_methods.confirm', [ + // 'method' => GatewayType::DIRECT_DEBIT, + // 'session_token' => $session_token, + // ]), + // 'prefilled_customer' => [ + // 'given_name' => auth()->guard('contact')->user()->first_name ?: '', + // 'family_name' => auth()->guard('contact')->user()->last_name ?: '', + // 'email' => auth()->guard('contact')->user()->email ?: '', + // 'address_line1' => auth()->guard('contact')->user()->client->address1 ?: '', + // 'city' => auth()->guard('contact')->user()->client->city ?: '', + // 'postal_code' => auth()->guard('contact')->user()->client->postal_code ?: '', + // 'country_code' => auth()->guard('contact')->user()->client->country->iso_3166_2, + // ], + // ], + // ]); + + // return redirect( + // $redirect->redirect_url + // ); + // } catch (\Exception $exception) { + // return $this->processUnsuccessfulAuthorization($exception); + // } + } + + /** + * Response + * { + * "billing_request_flows": { + * "authorisation_url": "https://pay.gocardless.com/flow/static/billing_request?id=", + * "lock_customer_details": false, + * "lock_bank_account": false, + * "auto_fulfil": true, + * "created_at": "2021-03-30T16:23:10.679Z", + * "expires_at": "2021-04-06T16:23:10.679Z", + * "redirect_uri": "https://my-company.com/completed", + * "links": { + * "billing_request": "BRQ123" + * } + * } + * } + * + * + */ + public function billingRequestFlows(array $data) { $session_token = \Illuminate\Support\Str::uuid()->toString(); + $exit_uri = route('client.payment_methods.index'); + + $response = $this->go_cardless->gateway->billingRequests()->create([ + "params" => [ + "mandate_request" => [ + "currency" => auth()->guard('contact')->user()->client->currency()->code + ] + ] + ]); try { - $redirect = $this->go_cardless->gateway->redirectFlows()->create([ - 'params' => [ - 'session_token' => $session_token, - 'success_redirect_url' => route('client.payment_methods.confirm', [ - 'method' => GatewayType::DIRECT_DEBIT, - 'session_token' => $session_token, - ]), - 'prefilled_customer' => [ - 'given_name' => auth()->guard('contact')->user()->first_name ?: '', - 'family_name' => auth()->guard('contact')->user()->last_name ?: '', - 'email' => auth()->guard('contact')->user()->email ?: '', - 'address_line1' => auth()->guard('contact')->user()->client->address1 ?: '', - 'city' => auth()->guard('contact')->user()->client->city ?: '', - 'postal_code' => auth()->guard('contact')->user()->client->postal_code ?: '', - 'country_code' => auth()->guard('contact')->user()->client->country->iso_3166_2, + $brf = $this->go_cardless->gateway->billingRequestFlows()->create([ + "params" => [ + "redirect_uri" => route('client.payment_methods.confirm', [ + 'method' => GatewayType::DIRECT_DEBIT, + 'session_token' => $session_token, + 'billing_request' => $response->id, + ]), + "exit_uri" => $exit_uri, + "links" => [ + "billing_request" => $response->id ], - ], + "show_redirect_buttons" => true, + "show_success_redirect_button" => true, + ] ]); - return redirect( - $redirect->redirect_url - ); + return redirect($brf->authorisation_url); + } catch (\Exception $exception) { + nlog($exception->getMessage()); return $this->processUnsuccessfulAuthorization($exception); } + } + /** * Handle unsuccessful authorization. * @@ -109,31 +169,61 @@ public function processUnsuccessfulAuthorization(\Exception $exception): void */ public function authorizeResponse(Request $request) { - try { - $redirect_flow = $this->go_cardless->gateway->redirectFlows()->complete( - $request->redirect_flow_id, - ['params' => [ - 'session_token' => $request->session_token, - ]], - ); + + try{ + + $billing_request = $this->go_cardless->gateway->billingRequests()->get($request->billing_request); $payment_meta = new \stdClass; - $payment_meta->brand = ctrans('texts.payment_type_direct_debit'); + $payment_meta->brand = $billing_request->resources->customer_bank_account->bank_name; $payment_meta->type = GatewayType::DIRECT_DEBIT; - $payment_meta->state = 'authorized'; + $payment_meta->state = 'pending'; + $payment_meta->last4 = $billing_request->resources->customer_bank_account->account_number_ending; $data = [ 'payment_meta' => $payment_meta, - 'token' => $redirect_flow->links->mandate, - 'payment_method_id' => $this->resolveScheme($redirect_flow->scheme), + 'token' => $billing_request->mandate_request->links->mandate, + 'payment_method_id' => $this->resolveScheme($billing_request->mandate_request->scheme), ]; - $payment_method = $this->go_cardless->storeGatewayToken($data, ['gateway_customer_reference' => $redirect_flow->links->customer]); + $payment_method = $this->go_cardless->storeGatewayToken($data, ['gateway_customer_reference' => $billing_request->resources->customer->id]); + + $mandate = $this->go_cardless->gateway->mandates()->get($billing_request->mandate_request->links->mandate); + + nlog($mandate); return redirect()->route('client.payment_methods.show', $payment_method->hashed_id); - } catch (\Exception $exception) { + + } + catch (\Exception $exception) { return $this->processUnsuccessfulAuthorization($exception); } + + // try { + // $redirect_flow = $this->go_cardless->gateway->redirectFlows()->complete( + // $request->redirect_flow_id, + // ['params' => [ + // 'session_token' => $request->session_token, + // ]], + // ); + + // $payment_meta = new \stdClass; + // $payment_meta->brand = ctrans('texts.payment_type_direct_debit'); + // $payment_meta->type = GatewayType::DIRECT_DEBIT; + // $payment_meta->state = 'authorized'; + + // $data = [ + // 'payment_meta' => $payment_meta, + // 'token' => $redirect_flow->links->mandate, + // 'payment_method_id' => $this->resolveScheme($redirect_flow->scheme), + // ]; + + // $payment_method = $this->go_cardless->storeGatewayToken($data, ['gateway_customer_reference' => $redirect_flow->links->customer]); + + // return redirect()->route('client.payment_methods.show', $payment_method->hashed_id); + // } catch (\Exception $exception) { + // return $this->processUnsuccessfulAuthorization($exception); + // } } private function resolveScheme(string $scheme): int @@ -176,11 +266,13 @@ public function paymentResponse(PaymentResponseRequest $request) $description = "Amount {$request->amount} from client {$this->go_cardless->client->present()->name()}"; } + $amount = $this->go_cardless->convertToGoCardlessAmount($this->go_cardless->payment_hash?->amount_with_fee(), $this->go_cardless->client->currency()->precision); + try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ // 'amount' => $request->amount, - 'amount' => (int)rtrim(round($request->amount),0), + 'amount' => $amount, 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardless/SEPA.php b/app/PaymentDrivers/GoCardless/SEPA.php index eac4fd81fa8..fbcc43e7ca4 100644 --- a/app/PaymentDrivers/GoCardless/SEPA.php +++ b/app/PaymentDrivers/GoCardless/SEPA.php @@ -172,10 +172,12 @@ public function paymentResponse(PaymentResponseRequest $request) $description = "Amount {$request->amount} from client {$this->go_cardless->client->present()->name()}"; } + $amount = $this->go_cardless->convertToGoCardlessAmount($this->go_cardless->payment_hash?->amount_with_fee(), $this->go_cardless->client->currency()->precision); + try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ - 'amount' => (int)rtrim(round($request->amount),0), + 'amount' => $amount), 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 869924adebf..7e6410582ed 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -131,7 +131,7 @@ public function refund(Payment $payment, $amount, $return_client_response = fals } public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) - { + {nlog("here"); $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total; $converted_amount = $this->convertToGoCardlessAmount($amount, $this->client->currency()->precision); From 5d66f5df2ce25771eaf3c2af834fd6a8566a68fa Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 17:45:33 +1100 Subject: [PATCH 08/10] Refactor for Gocardless --- app/Jobs/Mail/PaymentFailedMailer.php | 4 +- .../Subscription/CleanStaleInvoiceOrder.php | 2 + app/Mail/Admin/ClientPaymentFailureObject.php | 4 ++ app/Mail/Admin/PaymentFailureObject.php | 4 ++ app/PaymentDrivers/GoCardless/DirectDebit.php | 3 +- app/PaymentDrivers/GoCardless/SEPA.php | 2 +- .../GoCardlessPaymentDriver.php | 45 ++++++++++++++----- .../views/email/client/generic.blade.php | 28 ++++++++---- 8 files changed, 68 insertions(+), 24 deletions(-) diff --git a/app/Jobs/Mail/PaymentFailedMailer.php b/app/Jobs/Mail/PaymentFailedMailer.php index c5fa3c14dbc..4408637ba2b 100644 --- a/app/Jobs/Mail/PaymentFailedMailer.php +++ b/app/Jobs/Mail/PaymentFailedMailer.php @@ -65,8 +65,8 @@ public function __construct(?PaymentHash $payment_hash, Company $company, Client */ public function handle() { - if (!is_string($this->error)) { - $this->error = "Payment failed, no reason given."; + if (!is_string($this->error) || strlen($this->error) <=1) { + $this->error = ""; } //Set DB diff --git a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php index 6d042ccd820..f0912aefbcf 100644 --- a/app/Jobs/Subscription/CleanStaleInvoiceOrder.php +++ b/app/Jobs/Subscription/CleanStaleInvoiceOrder.php @@ -37,6 +37,8 @@ public function __construct() */ public function handle(InvoiceRepository $repo) : void { + nlog("Cleaning Stale Invoices:"); + if (! config('ninja.db.multi_db_enabled')) { Invoice::query() ->withTrashed() diff --git a/app/Mail/Admin/ClientPaymentFailureObject.php b/app/Mail/Admin/ClientPaymentFailureObject.php index ee62778f6bd..d8516e02f6d 100644 --- a/app/Mail/Admin/ClientPaymentFailureObject.php +++ b/app/Mail/Admin/ClientPaymentFailureObject.php @@ -124,6 +124,10 @@ private function getData() 'company' => $this->company, ]; + if (strlen($this->error > 1)) { + $data['content'] .= "\n\n".$this->error; + } + return $data; } } diff --git a/app/Mail/Admin/PaymentFailureObject.php b/app/Mail/Admin/PaymentFailureObject.php index 8b484be26ee..3d0297cd212 100644 --- a/app/Mail/Admin/PaymentFailureObject.php +++ b/app/Mail/Admin/PaymentFailureObject.php @@ -120,6 +120,10 @@ private function getData() 'additional_info' => $this->error, ]; + if (strlen($this->error > 1)) { + $data['content'] .= "\n\n".$this->error; + } + return $data; } diff --git a/app/PaymentDrivers/GoCardless/DirectDebit.php b/app/PaymentDrivers/GoCardless/DirectDebit.php index a6b55c113cd..81bb8c105b7 100644 --- a/app/PaymentDrivers/GoCardless/DirectDebit.php +++ b/app/PaymentDrivers/GoCardless/DirectDebit.php @@ -176,7 +176,7 @@ public function authorizeResponse(Request $request) $payment_meta = new \stdClass; $payment_meta->brand = $billing_request->resources->customer_bank_account->bank_name; - $payment_meta->type = GatewayType::DIRECT_DEBIT; + $payment_meta->type = $this->resolveScheme($billing_request->mandate_request->scheme); $payment_meta->state = 'pending'; $payment_meta->last4 = $billing_request->resources->customer_bank_account->account_number_ending; @@ -230,6 +230,7 @@ private function resolveScheme(string $scheme): int { match ($scheme) { 'sepa_core' => $type = GatewayType::SEPA, + 'ach' => $type = GatewayType::BANK_TRANSFER, default => $type = GatewayType::DIRECT_DEBIT, }; diff --git a/app/PaymentDrivers/GoCardless/SEPA.php b/app/PaymentDrivers/GoCardless/SEPA.php index fbcc43e7ca4..1a96bcc9fcd 100644 --- a/app/PaymentDrivers/GoCardless/SEPA.php +++ b/app/PaymentDrivers/GoCardless/SEPA.php @@ -177,7 +177,7 @@ public function paymentResponse(PaymentResponseRequest $request) try { $payment = $this->go_cardless->gateway->payments()->create([ 'params' => [ - 'amount' => $amount), + 'amount' => $amount, 'currency' => $request->currency, 'description' => $description, 'metadata' => [ diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index 7e6410582ed..d1c499f4f11 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -11,22 +11,23 @@ namespace App\PaymentDrivers; -use App\Factory\ClientContactFactory; -use App\Factory\ClientFactory; -use App\Http\Requests\Payments\PaymentWebhookRequest; -use App\Jobs\Util\SystemLogger; use App\Models\Client; -use App\Models\ClientGatewayToken; use App\Models\Country; -use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; +use App\Models\SystemLog; +use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Models\SystemLog; -use App\Utils\Traits\GeneratesCounter; +use App\Factory\ClientFactory; +use App\Jobs\Util\SystemLogger; use App\Utils\Traits\MakesHash; +use App\Models\ClientGatewayToken; +use App\Factory\ClientContactFactory; +use App\Jobs\Mail\PaymentFailedMailer; +use App\Utils\Traits\GeneratesCounter; use Illuminate\Database\QueryException; +use App\Http\Requests\Payments\PaymentWebhookRequest; class GoCardlessPaymentDriver extends BaseDriver { @@ -46,7 +47,7 @@ class GoCardlessPaymentDriver extends BaseDriver private bool $completed = true; public static $methods = [ - GatewayType::BANK_TRANSFER => \App\PaymentDrivers\GoCardless\ACH::class, + GatewayType::BANK_TRANSFER => \App\PaymentDrivers\GoCardless\DirectDebit::class, GatewayType::DIRECT_DEBIT => \App\PaymentDrivers\GoCardless\DirectDebit::class, GatewayType::SEPA => \App\PaymentDrivers\GoCardless\SEPA::class, GatewayType::INSTANT_BANK_PAY => \App\PaymentDrivers\GoCardless\InstantBankPay::class, @@ -79,7 +80,7 @@ public function gatewayTypes(): array $this->client && isset($this->client->country) // && in_array($this->client->country->iso_3166_3, ['GBR']) - && in_array($this->client->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD','USD']) + && in_array($this->client->currency()->code, ['EUR', 'GBP','DKK','SEK','AUD','NZD']) ) { $types[] = GatewayType::DIRECT_DEBIT; } @@ -131,7 +132,8 @@ public function refund(Payment $payment, $amount, $return_client_response = fals } public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) - {nlog("here"); + { + $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total; $converted_amount = $this->convertToGoCardlessAmount($amount, $this->client->currency()->precision); @@ -278,9 +280,28 @@ public function processWebhookRequest(PaymentWebhookRequest $request) ->first(); if ($payment) { + + if ($payment->status_id == Payment::STATUS_PENDING) { + $payment->service()->deletePayment(); + } + $payment->status_id = Payment::STATUS_FAILED; $payment->save(); - nlog('GoCardless completed'); + + $payment_hash = PaymentHash::where('payment_id', $payment->id)->first(); + $error = ''; + + if (isset($event['details']['description'])) { + $error = $event['details']['description']; + } + + PaymentFailedMailer::dispatch( + $payment_hash, + $payment->client->company, + $payment->client, + $error + ); + } } diff --git a/resources/views/email/client/generic.blade.php b/resources/views/email/client/generic.blade.php index b54c9cbea74..187630d27fd 100644 --- a/resources/views/email/client/generic.blade.php +++ b/resources/views/email/client/generic.blade.php @@ -21,17 +21,29 @@ @endisset @isset($url) - - - - +
- - {{ ctrans($button) }} +
+ + + + + -
+ + {{ ctrans($button) }} -
+
+ +
@endisset From 17ceeb8a299e786df22414ea9b70931311a2cad7 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 19:33:27 +1100 Subject: [PATCH 09/10] Updates for direct debit payment screen --- .../ninja2020/gateways/gocardless/direct_debit/pay.blade.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/resources/views/portal/ninja2020/gateways/gocardless/direct_debit/pay.blade.php b/resources/views/portal/ninja2020/gateways/gocardless/direct_debit/pay.blade.php index da00715475f..ac8071c3563 100644 --- a/resources/views/portal/ninja2020/gateways/gocardless/direct_debit/pay.blade.php +++ b/resources/views/portal/ninja2020/gateways/gocardless/direct_debit/pay.blade.php @@ -22,7 +22,7 @@ @endforeach From e1a0a1a375d4195dbae531ad3423039e5068e0a1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Wed, 22 Mar 2023 19:37:47 +1100 Subject: [PATCH 10/10] Significant upgrade for GoCardless supporting their Billing Request Flows --- app/PaymentDrivers/GoCardless/ACH.php | 1 + .../GoCardlessPaymentDriver.php | 22 ++++++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/app/PaymentDrivers/GoCardless/ACH.php b/app/PaymentDrivers/GoCardless/ACH.php index ac987300252..66e24ba2dc3 100644 --- a/app/PaymentDrivers/GoCardless/ACH.php +++ b/app/PaymentDrivers/GoCardless/ACH.php @@ -30,6 +30,7 @@ use Illuminate\Routing\Redirector; use Illuminate\View\View; +//@deprecated class ACH implements MethodInterface { use MakesHash; diff --git a/app/PaymentDrivers/GoCardlessPaymentDriver.php b/app/PaymentDrivers/GoCardlessPaymentDriver.php index d1c499f4f11..433ab7230f3 100644 --- a/app/PaymentDrivers/GoCardlessPaymentDriver.php +++ b/app/PaymentDrivers/GoCardlessPaymentDriver.php @@ -11,23 +11,23 @@ namespace App\PaymentDrivers; +use App\Factory\ClientContactFactory; +use App\Factory\ClientFactory; +use App\Http\Requests\Payments\PaymentWebhookRequest; +use App\Jobs\Mail\PaymentFailedMailer; +use App\Jobs\Util\SystemLogger; use App\Models\Client; +use App\Models\ClientGatewayToken; use App\Models\Country; +use App\Models\GatewayType; use App\Models\Invoice; use App\Models\Payment; -use App\Models\SystemLog; -use App\Models\GatewayType; use App\Models\PaymentHash; use App\Models\PaymentType; -use App\Factory\ClientFactory; -use App\Jobs\Util\SystemLogger; -use App\Utils\Traits\MakesHash; -use App\Models\ClientGatewayToken; -use App\Factory\ClientContactFactory; -use App\Jobs\Mail\PaymentFailedMailer; +use App\Models\SystemLog; use App\Utils\Traits\GeneratesCounter; +use App\Utils\Traits\MakesHash; use Illuminate\Database\QueryException; -use App\Http\Requests\Payments\PaymentWebhookRequest; class GoCardlessPaymentDriver extends BaseDriver { @@ -133,7 +133,6 @@ public function refund(Payment $payment, $amount, $return_client_response = fals public function tokenBilling(ClientGatewayToken $cgt, PaymentHash $payment_hash) { - $amount = array_sum(array_column($payment_hash->invoices(), 'amount')) + $payment_hash->fee_total; $converted_amount = $this->convertToGoCardlessAmount($amount, $this->client->currency()->precision); @@ -244,7 +243,6 @@ public function processWebhookRequest(PaymentWebhookRequest $request) $this->init(); nlog('GoCardless Event'); - nlog($request->all()); if (! $request->has('events')) { nlog('No GoCardless events to process in response?'); @@ -280,7 +278,6 @@ public function processWebhookRequest(PaymentWebhookRequest $request) ->first(); if ($payment) { - if ($payment->status_id == Payment::STATUS_PENDING) { $payment->service()->deletePayment(); } @@ -301,7 +298,6 @@ public function processWebhookRequest(PaymentWebhookRequest $request) $payment->client, $error ); - } }