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..ff36acb2ee4e 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -68,18 +68,25 @@ class PaymentController extends Controller * * @return \Illuminate\Http\RedirectResponse|mixed */ - public function process() + public function process(Request $request) { - $gateway = CompanyGateway::findOrFail(request()->input('company_gateway_id')); + $is_credit_payment = false; + $token = false; + + if($request->input('company_gateway_id') == CompanyGateway::GATEWAY_CREDIT) + $is_credit_payment = true; + + $gateway = CompanyGateway::find($request->input('company_gateway_id')); + + //refactor from here! /** * 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 */ @@ -159,32 +166,31 @@ 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->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(); + if($gateway) + $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; + 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(); @@ -203,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) @@ -282,4 +291,11 @@ class PaymentController extends Controller return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]); } + + public function processCreditPayment(Request $request, array $data) + { + + return render('gateways.credit.index', $data); + + } } diff --git a/app/Models/Client.php b/app/Models/Client.php index e02fd1b5c898..a97e306d51c7 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_credits_payment == 'option' && $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/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 d3afe99dd3d1..860f49073e7a 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 == 'option') + $this->applyCreditPayment(); info("partial = {$this->invoice->partial}"); info("balance = {$this->invoice->balance}"); 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..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 @@ -21,10 +21,10 @@ 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(); + $table->enum('use_credits_payment', ['always', 'off', 'option'])->default('off'); }); 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/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 diff --git a/tests/Unit/AutoBillInvoiceTest.php b/tests/Unit/AutoBillInvoiceTest.php index 5b7e4c89e05c..3503df3ddb13 100644 --- a/tests/Unit/AutoBillInvoiceTest.php +++ b/tests/Unit/AutoBillInvoiceTest.php @@ -33,6 +33,8 @@ 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); @@ -49,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); + + } }