From 0d17b299a0126140f74e3f15e34fc0099f9cf5ec Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 15 Oct 2020 11:37:16 +1100 Subject: [PATCH 1/5] Refactor payments for credits --- .../ClientPortal/InvoiceController.php | 2 -- .../ClientPortal/PaymentController.php | 21 +++++++++++++------ app/Models/Client.php | 8 +++++++ app/Models/Company.php | 16 -------------- app/Models/CompanyGateway.php | 2 ++ app/Models/GatewayType.php | 1 + app/Services/Client/ClientService.php | 1 + app/Services/Invoice/AutoBillInvoice.php | 4 +++- 8 files changed, 30 insertions(+), 25 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/InvoiceController.php b/app/Http/Controllers/ClientPortal/InvoiceController.php index 85289b87c533..1f39b528f3eb 100644 --- a/app/Http/Controllers/ClientPortal/InvoiceController.php +++ b/app/Http/Controllers/ClientPortal/InvoiceController.php @@ -115,8 +115,6 @@ class InvoiceController extends Controller 'total' => $total, ]; - //REFACTOR entry point for online payments starts here - return $this->render('invoices.payment', $data); } diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index 85220ddc2aff..cfc602556b67 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -68,10 +68,15 @@ class PaymentController extends Controller * * @return \Illuminate\Http\RedirectResponse|mixed */ - public function process() + public function process(Request $request) { + if($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) + return $this->processCreditPayment($request); + $gateway = CompanyGateway::findOrFail(request()->input('company_gateway_id')); + //refactor from here! + /** * find invoices * @@ -171,17 +176,16 @@ class PaymentController extends Controller $first_invoice = $invoices->first(); - $credit_totals = $first_invoice->client->service()->getCreditBalance(); + $credit_totals = $first_invoice->company->use_credits_payment == 'off' ? 0 : $first_invoice->client->service()->getCreditBalance(); $starting_invoice_amount = $first_invoice->amount; $first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save(); /** - * - * The best way to determine the exact gateway fee is to not calculate it in isolation (due to rounding) - * but to simply add it as a line item, and then subtract the starting and finishing amounts of - * the invoice. + * 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->amount - $starting_invoice_amount; @@ -282,4 +286,9 @@ class PaymentController extends Controller return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); } + + public function processCreditPayment(Request $request) + { + + } } diff --git a/app/Models/Client.php b/app/Models/Client.php index e02fd1b5c898..b2a0d8dfd093 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -524,6 +524,14 @@ class Client extends BaseModel implements HasLocalePreference } } + if($this->company->use_credit_payment = 'optin' && $this->service()->getCreditBalance() > 0) { + $payment_urls[] = [ + 'label' => ctrans('texts.apply_credit'), + 'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT, + 'gateway_type_id' => GatewayType::CREDIT, + ]; + } + return $payment_urls; } diff --git a/app/Models/Company.php b/app/Models/Company.php index 9f149f806a01..4690d8c2fc6e 100644 --- a/app/Models/Company.php +++ b/app/Models/Company.php @@ -66,22 +66,6 @@ class Company extends BaseModel const ENTITY_RECURRING_TASK = 'task'; const ENTITY_RECURRING_QUOTE = 'recurring_quote'; - // const int kModuleRecurringInvoices = 1; - // const int kModuleCredits = 2; - // const int kModuleQuotes = 4; - // const int kModuleTasks = 8; - // const int kModuleExpenses = 16; - // const int kModuleProjects = 32; - // const int kModuleVendors = 64; - // const int kModuleTickets = 128; - // const int kModuleProposals = 256; - // const int kModuleRecurringExpenses = 512; - // const int kModuleRecurringTasks = 1024; - // const int kModuleRecurringQuotes = 2048; - // kModuleInvoices = 4096; - // kModulePayments = 8192; - // 16383 - protected $presenter = \App\Models\Presenters\CompanyPresenter::class; protected $fillable = [ diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index fbf065fd19d8..f37f594112af 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -23,6 +23,8 @@ class CompanyGateway extends BaseModel { use SoftDeletes; + public const GATEWAY_CREDIT = 10000000; + protected $casts = [ 'fees_and_limits' => 'object', 'updated_at' => 'timestamp', diff --git a/app/Models/GatewayType.php b/app/Models/GatewayType.php index 5ed2e80c2fc5..04b72832a2a6 100644 --- a/app/Models/GatewayType.php +++ b/app/Models/GatewayType.php @@ -27,6 +27,7 @@ class GatewayType extends StaticModel const SOFORT = 7; const APPLE_PAY = 8; const SEPA = 9; + const CREDIT = 10; public function gateway() { diff --git a/app/Services/Client/ClientService.php b/app/Services/Client/ClientService.php index 593091d3c915..9632db976cf1 100644 --- a/app/Services/Client/ClientService.php +++ b/app/Services/Client/ClientService.php @@ -47,6 +47,7 @@ class ClientService public function getCreditBalance() :float { + $credits = $this->client->credits ->where('is_deleted', false) ->where('balance', '>', 0) diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index d3afe99dd3d1..9c53612e7f7c 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -55,7 +55,9 @@ class AutoBillInvoice extends AbstractService return $this->invoice->service()->markPaid()->save(); //if the credits cover the payments, we stop here, build the payment with credits and exit early - $this->applyCreditPayment(); + + if($this->invoice->company->use_credits_payment == 'always' || $this->invoice->company->use_credits_payment == 'optin') + $this->applyCreditPayment(); info("partial = {$this->invoice->partial}"); info("balance = {$this->invoice->balance}"); From c44e8330a503b6c98239315f59a2ac1c0459e4e1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 15 Oct 2020 13:07:48 +1100 Subject: [PATCH 2/5] Fixes for tests --- .../migrations/2020_10_14_201320_project_ids_to_entities.php | 2 +- tests/Unit/AutoBillInvoiceTest.php | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/database/migrations/2020_10_14_201320_project_ids_to_entities.php b/database/migrations/2020_10_14_201320_project_ids_to_entities.php index 9d3af7e58eb5..5b61a0a911fe 100644 --- a/database/migrations/2020_10_14_201320_project_ids_to_entities.php +++ b/database/migrations/2020_10_14_201320_project_ids_to_entities.php @@ -21,7 +21,7 @@ class ProjectIdsToEntities extends Migration $table->longText('fields')->change(); }); - Schema::table('gateways', function (Blueprint $table) { + Schema::table('companies', function (Blueprint $table) { $table->boolean('mark_expenses_invoiceable')->default(0); $table->boolean('mark_expenses_paid')->default(0); $table->enum('use_credits_payment', ['always', 'off', 'optin'])->nullable(); diff --git a/tests/Unit/AutoBillInvoiceTest.php b/tests/Unit/AutoBillInvoiceTest.php index 5b7e4c89e05c..26b276b25a09 100644 --- a/tests/Unit/AutoBillInvoiceTest.php +++ b/tests/Unit/AutoBillInvoiceTest.php @@ -33,7 +33,9 @@ class AutoBillInvoiceTest extends TestCase public function testAutoBillFunctionality() { - + $this->company->use_credits_payment = 'always'; + $this->company->save(); + $this->assertEquals($this->client->balance, 10); $this->assertEquals($this->client->paid_to_date, 0); $this->assertEquals($this->client->credit_balance, 10); From 901f7c4117d6371484ed279aeda3c4e6d1166996 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 15 Oct 2020 14:35:35 +1100 Subject: [PATCH 3/5] Working on credit payments --- .../ClientPortal/PaymentController.php | 37 +++++++++++-------- .../portal/default/gateways/pay_now.blade.php | 2 +- tests/Unit/AutoBillInvoiceTest.php | 22 ++++++++++- 3 files changed, 44 insertions(+), 17 deletions(-) diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index cfc602556b67..ff36acb2ee4e 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -70,10 +70,13 @@ class PaymentController extends Controller */ public function process(Request $request) { - if($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) - return $this->processCreditPayment($request); + $is_credit_payment = false; + $token = false; - $gateway = CompanyGateway::findOrFail(request()->input('company_gateway_id')); + if($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) + $is_credit_payment = true; + + $gateway = CompanyGateway::find($request->input('company_gateway_id')); //refactor from here! @@ -81,10 +84,9 @@ class PaymentController extends Controller * find invoices * * ['invoice_id' => xxx, 'amount' => 22.00] - * */ - $payable_invoices = collect(request()->payable_invoices); + $payable_invoices = collect($request->payable_invoices); $invoices = Invoice::whereIn('id', $this->transformKeys($payable_invoices->pluck('invoice_id')->toArray()))->get(); /* pop non payable invoice from the $payable_invoices array */ @@ -164,23 +166,20 @@ class PaymentController extends Controller }); - if ((bool) request()->signature) { + if ((bool) $request->signature) { $invoices->each(function ($invoice) { - InjectSignature::dispatch($invoice, request()->signature); + InjectSignature::dispatch($invoice, $request->signature); }); } - $payment_method_id = request()->input('payment_method_id'); - + $payment_method_id = $request->input('payment_method_id'); $invoice_totals = $payable_invoices->sum('amount'); - $first_invoice = $invoices->first(); - $credit_totals = $first_invoice->company->use_credits_payment == 'off' ? 0 : $first_invoice->client->service()->getCreditBalance(); - $starting_invoice_amount = $first_invoice->amount; - $first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save(); + if($gateway) + $first_invoice->service()->addGatewayFee($gateway, $payment_method_id, $invoice_totals)->save(); /** * Gateway fee is calculated @@ -189,6 +188,9 @@ class PaymentController extends Controller */ $fee_totals = $first_invoice->amount - $starting_invoice_amount; + if($gateway) + $token = auth()->user()->client->gateway_token($gateway->id, $payment_method_id); + $payment_hash = new PaymentHash; $payment_hash->hash = Str::random(128); $payment_hash->data = $payable_invoices->toArray(); @@ -207,11 +209,14 @@ class PaymentController extends Controller 'payment_hash' => $payment_hash->hash, 'total' => $totals, 'invoices' => $payable_invoices, - 'token' => auth()->user()->client->gateway_token($gateway->id, $payment_method_id), + 'token' => $token, 'payment_method_id' => $payment_method_id, 'amount_with_fee' => $invoice_totals + $fee_totals, ]; + if($is_credit_payment) + return $this->processCreditPayment($request, $data); + return $gateway ->driver(auth()->user()->client) ->setPaymentMethod($payment_method_id) @@ -287,8 +292,10 @@ class PaymentController extends Controller } - public function processCreditPayment(Request $request) + public function processCreditPayment(Request $request, array $data) { + return render('gateways.credit.index', $data); + } } diff --git a/resources/views/portal/default/gateways/pay_now.blade.php b/resources/views/portal/default/gateways/pay_now.blade.php index 6e97da3731b4..8a42afe6a558 100644 --- a/resources/views/portal/default/gateways/pay_now.blade.php +++ b/resources/views/portal/default/gateways/pay_now.blade.php @@ -42,7 +42,7 @@
  • {{ ctrans('texts.credit_amount')}}

    {{ $credit_totals }}

  • - @endifs + @endif @if($fee > 0)
  • {{ ctrans('texts.gateway_fee')}}

    {{ $fee }}

    diff --git a/tests/Unit/AutoBillInvoiceTest.php b/tests/Unit/AutoBillInvoiceTest.php index 26b276b25a09..3503df3ddb13 100644 --- a/tests/Unit/AutoBillInvoiceTest.php +++ b/tests/Unit/AutoBillInvoiceTest.php @@ -35,7 +35,7 @@ class AutoBillInvoiceTest extends TestCase { $this->company->use_credits_payment = 'always'; $this->company->save(); - + $this->assertEquals($this->client->balance, 10); $this->assertEquals($this->client->paid_to_date, 0); $this->assertEquals($this->client->credit_balance, 10); @@ -51,4 +51,24 @@ class AutoBillInvoiceTest extends TestCase } + + public function testAutoBillSetOffFunctionality() + { + $this->company->use_credits_payment = 'off'; + $this->company->save(); + + $this->assertEquals($this->client->balance, 10); + $this->assertEquals($this->client->paid_to_date, 0); + $this->assertEquals($this->client->credit_balance, 10); + + $this->invoice->service()->markSent()->autoBill()->save(); + + $this->assertNotNull($this->invoice->payments()); + $this->assertEquals(0, $this->invoice->payments()->sum('payments.amount')); + + $this->assertEquals($this->client->balance, 10); + $this->assertEquals($this->client->paid_to_date, 0); + $this->assertEquals($this->client->credit_balance, 10); + + } } From 27f678bd8c1f89edc20ff1c7728ef3a34df90da1 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 15 Oct 2020 15:07:42 +1100 Subject: [PATCH 4/5] working on credit payments --- app/Models/Client.php | 2 +- app/Services/Credit/ApplyPayment.php | 6 +++--- app/Services/Invoice/AutoBillInvoice.php | 2 +- .../2020_10_14_201320_project_ids_to_entities.php | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/Models/Client.php b/app/Models/Client.php index b2a0d8dfd093..a97e306d51c7 100644 --- a/app/Models/Client.php +++ b/app/Models/Client.php @@ -524,7 +524,7 @@ class Client extends BaseModel implements HasLocalePreference } } - if($this->company->use_credit_payment = 'optin' && $this->service()->getCreditBalance() > 0) { + if($this->company->use_credits_payment == 'option' && $this->service()->getCreditBalance() > 0) { $payment_urls[] = [ 'label' => ctrans('texts.apply_credit'), 'company_gateway_id' => CompanyGateway::GATEWAY_CREDIT, diff --git a/app/Services/Credit/ApplyPayment.php b/app/Services/Credit/ApplyPayment.php index e9a5665e5f39..59e6f0f770aa 100644 --- a/app/Services/Credit/ApplyPayment.php +++ b/app/Services/Credit/ApplyPayment.php @@ -47,6 +47,7 @@ class ApplyPayment //$available_credit_balance = $this->credit->balance; $applicable_amount = min($this->amount, $this->credit->balance); $invoice_balance = $this->invoice->balance; + $credit_balance = $this->credit->balance; /* Check invoice partial for amount to be cleared first */ if($this->invoice->partial > 0){ @@ -56,7 +57,7 @@ class ApplyPayment $this->invoice->partial -= $partial_payment; $invoice_balance -= $partial_payment; $this->amount -= $partial_payment; - // $this->credit->balance -= $partial_payment; + $credit_balance -= $partial_payment; $applicable_amount -= $partial_payment; $this->amount_applied += $partial_payment; @@ -65,11 +66,10 @@ class ApplyPayment /* If there is remaining amount use it on the balance */ if($this->amount > 0 && $applicable_amount > 0 && $invoice_balance > 0){ - $balance_payment = min($invoice_balance, $this->amount); + $balance_payment = min($invoice_balance, min($this->amount, $credit_balance)); $invoice_balance -= $balance_payment; $this->amount -= $balance_payment; - // $this->credit->balance -= $balance_payment; $this->amount_applied += $balance_payment; } diff --git a/app/Services/Invoice/AutoBillInvoice.php b/app/Services/Invoice/AutoBillInvoice.php index 9c53612e7f7c..860f49073e7a 100644 --- a/app/Services/Invoice/AutoBillInvoice.php +++ b/app/Services/Invoice/AutoBillInvoice.php @@ -56,7 +56,7 @@ class AutoBillInvoice extends AbstractService //if the credits cover the payments, we stop here, build the payment with credits and exit early - if($this->invoice->company->use_credits_payment == 'always' || $this->invoice->company->use_credits_payment == 'optin') + if($this->invoice->company->use_credits_payment == 'always' || $this->invoice->company->use_credits_payment == 'option') $this->applyCreditPayment(); info("partial = {$this->invoice->partial}"); diff --git a/database/migrations/2020_10_14_201320_project_ids_to_entities.php b/database/migrations/2020_10_14_201320_project_ids_to_entities.php index 5b61a0a911fe..f203b9de1bea 100644 --- a/database/migrations/2020_10_14_201320_project_ids_to_entities.php +++ b/database/migrations/2020_10_14_201320_project_ids_to_entities.php @@ -24,7 +24,7 @@ class ProjectIdsToEntities extends Migration Schema::table('companies', function (Blueprint $table) { $table->boolean('mark_expenses_invoiceable')->default(0); $table->boolean('mark_expenses_paid')->default(0); - $table->enum('use_credits_payment', ['always', 'off', 'optin'])->nullable(); + $table->enum('use_credits_payment', ['always', 'off', 'option'])->default('off'); }); From 52558d5b41bd177be5c45df675e42de860457d50 Mon Sep 17 00:00:00 2001 From: David Bomba Date: Thu, 15 Oct 2020 15:08:01 +1100 Subject: [PATCH 5/5] Credit view --- .../ninja2020/gateways/credit/index.blade.php | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100644 resources/views/portal/ninja2020/gateways/credit/index.blade.php diff --git a/resources/views/portal/ninja2020/gateways/credit/index.blade.php b/resources/views/portal/ninja2020/gateways/credit/index.blade.php new file mode 100644 index 000000000000..d0176f9d136f --- /dev/null +++ b/resources/views/portal/ninja2020/gateways/credit/index.blade.php @@ -0,0 +1,52 @@ +@extends('portal.ninja2020.layout.app') +@section('meta_title', ctrans('texts.credit')) + +@section('body') +
    + @csrf + +
    + +
    +
    +
    + +
    +
    +

    + {{ ctrans('texts.pay_now') }} +

    +

    + {{ ctrans('texts.complete_your_payment') }} +

    +
    +
    +
    + {{ ctrans('texts.subtotal') }} +
    +
    + {{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }} +
    + @if($total['credit_totals'] > 0) +
    + {{ ctrans('texts.credit_amount') }} +
    +
    + {{ App\Utils\Number::formatMoney($total['credit_totals'], $client) }} +
    + @endif +
    + {{ ctrans('texts.amount_due') }} +
    +
    + {{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }} +
    +
    +
    + +
    +
    +
    +
    +
    +@endsection \ No newline at end of file