mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-08 17:54:30 -04:00
commit
5dfc4cf7e1
@ -115,8 +115,6 @@ class InvoiceController extends Controller
|
||||
'total' => $total,
|
||||
];
|
||||
|
||||
//REFACTOR entry point for online payments starts here
|
||||
|
||||
return $this->render('invoices.payment', $data);
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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 = [
|
||||
|
@ -23,6 +23,8 @@ class CompanyGateway extends BaseModel
|
||||
{
|
||||
use SoftDeletes;
|
||||
|
||||
public const GATEWAY_CREDIT = 10000000;
|
||||
|
||||
protected $casts = [
|
||||
'fees_and_limits' => 'object',
|
||||
'updated_at' => 'timestamp',
|
||||
|
@ -27,6 +27,7 @@ class GatewayType extends StaticModel
|
||||
const SOFORT = 7;
|
||||
const APPLE_PAY = 8;
|
||||
const SEPA = 9;
|
||||
const CREDIT = 10;
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
|
@ -47,6 +47,7 @@ class ClientService
|
||||
|
||||
public function getCreditBalance() :float
|
||||
{
|
||||
|
||||
$credits = $this->client->credits
|
||||
->where('is_deleted', false)
|
||||
->where('balance', '>', 0)
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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}");
|
||||
|
@ -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');
|
||||
});
|
||||
|
||||
|
||||
|
@ -42,7 +42,7 @@
|
||||
<li class="list-group-item d-flex list-group-item-action justify-content-between align-items-center"><strong>{{ ctrans('texts.credit_amount')}}</strong>
|
||||
<h3><span class="badge badge-primary badge-pill"><strong>{{ $credit_totals }}</strong></span></h3>
|
||||
</li>
|
||||
@endifs
|
||||
@endif
|
||||
@if($fee > 0)
|
||||
<li class="list-group-item d-flex list-group-item-action justify-content-between align-items-center"><strong>{{ ctrans('texts.gateway_fee')}}</strong>
|
||||
<h3><span class="badge badge-primary badge-pill"><strong>{{ $fee }}</strong></span></h3>
|
||||
|
@ -0,0 +1,52 @@
|
||||
@extends('portal.ninja2020.layout.app')
|
||||
@section('meta_title', ctrans('texts.credit'))
|
||||
|
||||
@section('body')
|
||||
<form action="{{route('client.payments.credit_response')}}" method="post" id="credit-payment">
|
||||
@csrf
|
||||
<input type="hidden" name="payment_hash" value="{{$payment_hash}}">
|
||||
</form>
|
||||
|
||||
<div class="container mx-auto">
|
||||
<div class="grid grid-cols-6 gap-4">
|
||||
<div class="col-span-6 md:col-start-2 md:col-span-4">
|
||||
<div class="alert alert-failure mb-4" hidden id="errors"></div>
|
||||
<div class="bg-white shadow overflow-hidden sm:rounded-lg">
|
||||
<div class="px-4 py-5 border-b border-gray-200 sm:px-6">
|
||||
<h3 class="text-lg leading-6 font-medium text-gray-900">
|
||||
{{ ctrans('texts.pay_now') }}
|
||||
</h3>
|
||||
<p class="mt-1 max-w-2xl text-sm leading-5 text-gray-500">
|
||||
{{ ctrans('texts.complete_your_payment') }}
|
||||
</p>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.subtotal') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['invoice_totals'], $client) }}
|
||||
</dd>
|
||||
@if($total['credit_totals'] > 0)
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.credit_amount') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['credit_totals'], $client) }}
|
||||
</dd>
|
||||
@endif
|
||||
<dt class="text-sm leading-5 font-medium text-gray-500">
|
||||
{{ ctrans('texts.amount_due') }}
|
||||
</dt>
|
||||
<dd class="mt-1 text-sm leading-5 text-gray-900 sm:mt-0 sm:col-span-2">
|
||||
{{ App\Utils\Number::formatMoney($total['amount_with_fee'], $client) }}
|
||||
</dd>
|
||||
</div>
|
||||
<div class="bg-white px-4 py-5 flex justify-end">
|
||||
<button form="credit-payment" class="button button-primary bg-primary inline-flex items-center">Pay with credit</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user