diff --git a/app/Http/Controllers/ClientPortal/PaymentController.php b/app/Http/Controllers/ClientPortal/PaymentController.php index cab7f4b66a27..e07161bee3c6 100644 --- a/app/Http/Controllers/ClientPortal/PaymentController.php +++ b/app/Http/Controllers/ClientPortal/PaymentController.php @@ -136,7 +136,21 @@ class PaymentController extends Controller $payment_method_id = request()->input('payment_method_id'); $invoice_totals = array_sum(array_column($payable_invoices,'amount')); - $fee_totals = round($gateway->calcGatewayFee($invoice_totals), $invoices->first()->client->currency()->precision); + + $first_invoice = $invoices->first(); + $fee_totals = round($gateway->calcGatewayFee($invoice_totals, true), $first_invoice->client->currency()->precision); + + if(!$first_invoice->uses_inclusive_taxes) { + + $fee_tax = 0; + $fee_tax += round(($first_invoice->tax_rate1/100)*$fee_totals, $first_invoice->client->currency()->precision); + $fee_tax += round(($first_invoice->tax_rate2/100)*$fee_totals, $first_invoice->client->currency()->precision); + $fee_tax += round(($first_invoice->tax_rate3/100)*$fee_totals, $first_invoice->client->currency()->precision); + + $fee_totals += $fee_tax; + } + + $first_invoice->service()->addGatewayFee($gateway, $invoice_totals)->save(); $payment_hash = new PaymentHash; $payment_hash->hash = Str::random(128); diff --git a/app/Models/CompanyGateway.php b/app/Models/CompanyGateway.php index 7501c48aa7c3..2f521501aedb 100644 --- a/app/Models/CompanyGateway.php +++ b/app/Models/CompanyGateway.php @@ -267,7 +267,7 @@ class CompanyGateway extends BaseModel return $label; } - public function calcGatewayFee($amount) + public function calcGatewayFee($amount, $include_taxes = false) { $fees_and_limits = $this->getFeesAndLimits(); @@ -286,29 +286,32 @@ class CompanyGateway extends BaseModel $fee += $amount * $fees_and_limits->fee_percent / 100; info("fee after adding fee percent = {$fee}"); } - - $pre_tax_fee = $fee; - - //we shouldn't calculate the taxes - they'll be done when we re-process the invoice - - // if ($fees_and_limits->fee_tax_rate1) { - // $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100; - // info("fee after adding fee tax 1 = {$fee}"); - // } - - // if ($fees_and_limits->fee_tax_rate2) { - // $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100; - // info("fee after adding fee tax 2 = {$fee}"); - // } - - // if ($fees_and_limits->fee_tax_rate3) { - // $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100; - // info("fee after adding fee tax 3 = {$fee}"); - // } + /* Cap fee if we have to here. */ if($fees_and_limits->fee_cap > 0 && ($fee > $fees_and_limits->fee_cap)) $fee = $fees_and_limits->fee_cap; + $pre_tax_fee = $fee; + + /**/ + if($include_taxes) + { + if ($fees_and_limits->fee_tax_rate1) { + $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate1 / 100; + info("fee after adding fee tax 1 = {$fee}"); + } + + if ($fees_and_limits->fee_tax_rate2) { + $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate2 / 100; + info("fee after adding fee tax 2 = {$fee}"); + } + + if ($fees_and_limits->fee_tax_rate3) { + $fee += $pre_tax_fee * $fees_and_limits->fee_tax_rate3 / 100; + info("fee after adding fee tax 3 = {$fee}"); + } + } + return $fee; } diff --git a/app/PaymentDrivers/BaseDriver.php b/app/PaymentDrivers/BaseDriver.php index a0d3ce0d86df..f95b97e9c449 100644 --- a/app/PaymentDrivers/BaseDriver.php +++ b/app/PaymentDrivers/BaseDriver.php @@ -167,7 +167,7 @@ class BaseDriver extends AbstractPaymentDriver * @param PaymentResponseRequest $request The incoming payment request * @return void Success/Failure */ - public function appendGatewayFeeToInvoice(PaymentResponseRequest $request) :void + public function confirmGatewayFee(PaymentResponseRequest $request) :void { /*Payment meta data*/ $payment_hash = $request->getPaymentHash(); @@ -175,22 +175,24 @@ class BaseDriver extends AbstractPaymentDriver /*Payment invoices*/ $payment_invoices = $payment_hash->invoices(); - /*Fee charged at gateway*/ + // /*Fee charged at gateway*/ $fee_total = $payment_hash->fee_total; - /*Sum of invoice amounts*/ - $invoice_totals = array_sum(array_column($payment_invoices,'amount')); + // Sum of invoice amounts + // $invoice_totals = array_sum(array_column($payment_invoices,'amount')); /*Hydrate invoices*/ $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get(); - /*Append gateway fee to invoice line item of first invoice*/ - if($fee_total != 0){ - $invoices->first()->service()->addGatewayFee($this->company_gateway, $invoice_totals)->save(); + $invoices->each(function($invoice) use($fee_total){ - //We need to update invoice balance / client balance at this point so - //that payment record sanity is preserved //todo - } + if(collect($invoice->line_items)->contains('type_id', '3')){ + $invoice->service()->toggleFeesPaid()->save(); + $invoice->client->service()->updateBalance($fee_total)->save(); + $invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment'); + } + + }); } } diff --git a/app/PaymentDrivers/BasePaymentDriver.php b/app/PaymentDrivers/BasePaymentDriver.php index c9b6a0c6051d..81fd86fe97b6 100644 --- a/app/PaymentDrivers/BasePaymentDriver.php +++ b/app/PaymentDrivers/BasePaymentDriver.php @@ -295,7 +295,7 @@ class BasePaymentDriver * @param PaymentResponseRequest $request The incoming payment request * @return void Success/Failure */ - public function appendGatewayFeeToInvoice(PaymentResponseRequest $request) :void + public function confirmGatewayFee(PaymentResponseRequest $request) :void { /*Payment meta data*/ $payment_hash = $request->getPaymentHash(); @@ -303,22 +303,24 @@ class BasePaymentDriver /*Payment invoices*/ $payment_invoices = $payment_hash->invoices(); - /*Fee charged at gateway*/ + // /*Fee charged at gateway*/ $fee_total = $payment_hash->fee_total; - /*Sum of invoice amounts*/ - $invoice_totals = array_sum(array_column($payment_invoices,'amount')); + // Sum of invoice amounts + // $invoice_totals = array_sum(array_column($payment_invoices,'amount')); /*Hydrate invoices*/ $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get(); - /*Append gateway fee to invoice line item of first invoice*/ - if($fee_total != 0){ - $invoices->first()->service()->addGatewayFee($this->company_gateway, $invoice_totals)->save(); + $invoices->each(function($invoice) use($fee_total){ - //We need to update invoice balance / client balance at this point so - //that payment record sanity is preserved //todo - } + if(collect($invoice->line_items)->contains('type_id', '3')){ + $invoice->service()->toggleFeesPaid()->save(); + $invoice->client->service()->updateBalance($fee_total)->save(); + $invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment'); + } + + }); } } diff --git a/app/PaymentDrivers/Stripe/CreditCard.php b/app/PaymentDrivers/Stripe/CreditCard.php index 4fb0e34a5e23..b00552e87a30 100644 --- a/app/PaymentDrivers/Stripe/CreditCard.php +++ b/app/PaymentDrivers/Stripe/CreditCard.php @@ -123,6 +123,7 @@ class CreditCard 'gateway_type_id' => $request->payment_method_id, 'hashed_ids' => $request->hashed_ids, 'server_response' => $server_response, + 'payment_hash' => $payment_hash, ]; $invoices = Invoice::whereIn('id', $this->stripe->transformKeys(array_column($payment_hash->invoices(), 'invoice_id'))) @@ -143,7 +144,7 @@ class CreditCard if ($state['payment_status'] == 'succeeded') { /* Add gateway fees if needed! */ - $this->stripe->appendGatewayFeeToInvoice($request); + $this->stripe->confirmGatewayFee($request); return $this->processSuccessfulPayment($state); } @@ -190,7 +191,7 @@ class CreditCard $this->stripe->attachInvoices($payment, $state['hashed_ids']); - $payment->service()->updateInvoicePayment(); + $payment->service()->updateInvoicePayment($state['payment_hash']); event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); diff --git a/app/Services/Invoice/AddGatewayFee.php b/app/Services/Invoice/AddGatewayFee.php index 024c1d1f0f06..1566d6c47951 100644 --- a/app/Services/Invoice/AddGatewayFee.php +++ b/app/Services/Invoice/AddGatewayFee.php @@ -60,7 +60,7 @@ class AddGatewayFee extends AbstractService $invoice_items = $this->invoice->line_items; $invoice_items = collect($invoice_items)->filter(function ($item){ - return $item->type_id != 3; + return $item->type_id != '3'; }); $this->invoice->line_items = $invoice_items; @@ -71,7 +71,7 @@ class AddGatewayFee extends AbstractService private function processGatewayFee($gateway_fee) { $invoice_item = new InvoiceItem; - $invoice_item->type_id = '4'; + $invoice_item->type_id = '3'; $invoice_item->product_key = ctrans('texts.surcharge'); $invoice_item->notes = ctrans('texts.online_payment_surcharge'); $invoice_item->quantity = 1; @@ -92,10 +92,9 @@ class AddGatewayFee extends AbstractService /**Refresh Invoice values*/ $this->invoice = $this->invoice->calc()->getInvoice(); - /*Update client balance*/ - $this->invoice->client->service()->updateBalance($gateway_fee)->save(); - - $this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Gateway fee adjustment'); + /*Update client balance*/ // don't increment until we have process the payment! + //$this->invoice->client->service()->updateBalance($gateway_fee)->save(); + //$this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Gateway fee adjustment'); return $this->invoice; @@ -104,7 +103,7 @@ class AddGatewayFee extends AbstractService private function processGatewayDiscount($gateway_fee) { $invoice_item = new InvoiceItem; - $invoice_item->type_id = 3; + $invoice_item->type_id = '3'; $invoice_item->product_key = ctrans('texts.discount'); $invoice_item->notes = ctrans('texts.online_payment_discount'); $invoice_item->quantity = 1; @@ -124,9 +123,9 @@ class AddGatewayFee extends AbstractService $this->invoice = $this->invoice->calc()->getInvoice(); - $this->invoice->client->service()->updateBalance($gateway_fee)->save(); + // $this->invoice->client->service()->updateBalance($gateway_fee)->save(); - $this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Discount fee adjustment'); + // $this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Discount fee adjustment'); return $this->invoice; } diff --git a/app/Services/Payment/PaymentService.php b/app/Services/Payment/PaymentService.php index 47f2a25fd1d7..3ac7de0e4e37 100644 --- a/app/Services/Payment/PaymentService.php +++ b/app/Services/Payment/PaymentService.php @@ -84,9 +84,9 @@ class PaymentService return (new DeletePayment($this->payment))->run(); } - public function updateInvoicePayment() :?Payment + public function updateInvoicePayment($payment_hash = null) :?Payment { - return ((new UpdateInvoicePayment($this->payment)))->run(); + return ((new UpdateInvoicePayment($this->payment, $payment_hash)))->run(); } public function applyNumber() diff --git a/app/Services/Payment/UpdateInvoicePayment.php b/app/Services/Payment/UpdateInvoicePayment.php index 8257ab3187e9..8fd45d241cff 100644 --- a/app/Services/Payment/UpdateInvoicePayment.php +++ b/app/Services/Payment/UpdateInvoicePayment.php @@ -17,119 +17,82 @@ use App\Jobs\Payment\EmailPayment; use App\Jobs\Util\SystemLogger; use App\Models\Invoice; use App\Models\SystemLog; +use App\Utils\Traits\MakesHash; class UpdateInvoicePayment { + use MakesHash; + /** * @deprecated This is bad logic, assumes too much. */ public $payment; - public function __construct($payment) + public $payment_hash; + + public function __construct($payment, $payment_hash) { $this->payment = $payment; + $this->payment_hash = $payment_hash; } public function run() { - $invoices = $this->payment->invoices()->get(); + // $invoices = $this->payment->invoices()->get(); + // $invoices_total = $invoices->sum('balance'); - $invoices_total = $invoices->sum('balance'); + $paid_invoices = $this->payment_hash->invoices(); + $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($paid_invoices, 'invoice_id')))->get(); - /* Simplest scenario - All invoices are paid in full*/ - if (strval($invoices_total) === strval($this->payment->amount)) { - $invoices->each(function ($invoice) { - $this->payment - ->ledger() - ->updatePaymentBalance($invoice->balance*-1); - - $this->payment->client - ->service() - ->updateBalance($invoice->balance*-1) - ->updatePaidToDate($invoice->balance) - ->save(); - - $invoice->pivot->amount = $invoice->balance; + collect($paid_invoices)->each(function ($paid_invoice) use($invoices) { + + $invoice = $invoices->first(function ($inv) use($paid_invoice) { + return $paid_invoice['invoice_id'] == $inv->hashed_id; + }); + + $this->payment + ->ledger() + ->updatePaymentBalance($paid_invoice->amount*-1); + + $this->payment + ->client + ->service() + ->updateBalance($paid_invoice->amount*-1) + ->updatePaidToDate($paid_invoice->amount) + ->save(); + + $invoice->pivot->amount = $paid_invoice->amount; $invoice->pivot->save(); - $invoice->service() + $invoice->service() //caution what if we amount paid was less than partial - we wipe it! ->clearPartial() - ->updateBalance($invoice->balance*-1) + ->updateBalance($paid_invoice->amount*-1) ->save(); - }); - } - /*Combination of partials and full invoices are being paid*/ - else { - $total = 0; - /* Calculate the grand total of the invoices*/ - foreach ($invoices as $invoice) { - if ($invoice->hasPartial()) { - $total += $invoice->partial; - } else { - $total += $invoice->balance; - } - } + }); - /*Test if there is a batch of partial invoices that have been paid */ - if ($this->payment->amount == $total) { - $invoices->each(function ($invoice) { - if ($invoice->hasPartial()) { - $this->payment - ->ledger() - ->updatePaymentBalance($invoice->partial*-1); + // } else { + // SystemLogger::dispatch( + // [ + // 'payment' => $this->payment, + // 'invoices' => $invoices, + // 'invoices_total' => $invoices_total, + // 'payment_amount' => $this->payment->amount, + // 'partial_check_amount' => $total, + // ], + // SystemLog::CATEGORY_GATEWAY_RESPONSE, + // SystemLog::EVENT_PAYMENT_RECONCILIATION_FAILURE, + // SystemLog::TYPE_LEDGER, + // $this->payment->client + // ); - $this->payment->client->service() - ->updateBalance($invoice->partial*-1) - ->updatePaidToDate($invoice->partial) - ->save(); + // throw new \Exception("payment amount {$this->payment->amount} does not match invoice totals {$invoices_total} reversing payment"); - $invoice->pivot->amount = $invoice->partial; - $invoice->pivot->save(); - - $invoice->service()->updateBalance($invoice->partial*-1) - ->clearPartial() - ->setDueDate() - ->setStatus(Invoice::STATUS_PARTIAL) - ->save(); - } else { - $this->payment - ->ledger() - ->updatePaymentBalance($invoice->balance*-1); - - $this->payment->client->service() - ->updateBalance($invoice->balance*-1) - ->updatePaidToDate($invoice->balance) - ->save(); - - $invoice->pivot->amount = $invoice->balance; - $invoice->pivot->save(); - - $invoice->service()->clearPartial()->updateBalance($invoice->balance*-1)->save(); - } - }); - } else { - SystemLogger::dispatch( - [ - 'payment' => $this->payment, - 'invoices' => $invoices, - 'invoices_total' => $invoices_total, - 'payment_amount' => $this->payment->amount, - 'partial_check_amount' => $total, - ], - SystemLog::CATEGORY_GATEWAY_RESPONSE, - SystemLog::EVENT_PAYMENT_RECONCILIATION_FAILURE, - SystemLog::TYPE_LEDGER, - $this->payment->client - ); - - throw new \Exception("payment amount {$this->payment->amount} does not match invoice totals {$invoices_total} reversing payment"); - - $this->payment->invoice()->delete(); - $this->payment->is_deleted=true; - $this->payment->save(); - $this->payment->delete(); - } + // $this->payment->invoice()->delete(); + // $this->payment->is_deleted=true; + // $this->payment->save(); + // $this->payment->delete(); + // } } return $this->payment;