Working on invoice payments with gateway fees

This commit is contained in:
David Bomba 2020-08-31 12:00:43 +10:00
parent ac08a146a1
commit eb867522df
8 changed files with 127 additions and 143 deletions

View File

@ -136,7 +136,21 @@ class PaymentController extends Controller
$payment_method_id = request()->input('payment_method_id'); $payment_method_id = request()->input('payment_method_id');
$invoice_totals = array_sum(array_column($payable_invoices,'amount')); $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 = new PaymentHash;
$payment_hash->hash = Str::random(128); $payment_hash->hash = Str::random(128);

View File

@ -267,7 +267,7 @@ class CompanyGateway extends BaseModel
return $label; return $label;
} }
public function calcGatewayFee($amount) public function calcGatewayFee($amount, $include_taxes = false)
{ {
$fees_and_limits = $this->getFeesAndLimits(); $fees_and_limits = $this->getFeesAndLimits();
@ -286,29 +286,32 @@ class CompanyGateway extends BaseModel
$fee += $amount * $fees_and_limits->fee_percent / 100; $fee += $amount * $fees_and_limits->fee_percent / 100;
info("fee after adding fee percent = {$fee}"); 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)) if($fees_and_limits->fee_cap > 0 && ($fee > $fees_and_limits->fee_cap))
$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; return $fee;
} }

View File

@ -167,7 +167,7 @@ class BaseDriver extends AbstractPaymentDriver
* @param PaymentResponseRequest $request The incoming payment request * @param PaymentResponseRequest $request The incoming payment request
* @return void Success/Failure * @return void Success/Failure
*/ */
public function appendGatewayFeeToInvoice(PaymentResponseRequest $request) :void public function confirmGatewayFee(PaymentResponseRequest $request) :void
{ {
/*Payment meta data*/ /*Payment meta data*/
$payment_hash = $request->getPaymentHash(); $payment_hash = $request->getPaymentHash();
@ -175,22 +175,24 @@ class BaseDriver extends AbstractPaymentDriver
/*Payment invoices*/ /*Payment invoices*/
$payment_invoices = $payment_hash->invoices(); $payment_invoices = $payment_hash->invoices();
/*Fee charged at gateway*/ // /*Fee charged at gateway*/
$fee_total = $payment_hash->fee_total; $fee_total = $payment_hash->fee_total;
/*Sum of invoice amounts*/ // Sum of invoice amounts
$invoice_totals = array_sum(array_column($payment_invoices,'amount')); // $invoice_totals = array_sum(array_column($payment_invoices,'amount'));
/*Hydrate invoices*/ /*Hydrate invoices*/
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get(); $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
/*Append gateway fee to invoice line item of first invoice*/ $invoices->each(function($invoice) use($fee_total){
if($fee_total != 0){
$invoices->first()->service()->addGatewayFee($this->company_gateway, $invoice_totals)->save();
//We need to update invoice balance / client balance at this point so if(collect($invoice->line_items)->contains('type_id', '3')){
//that payment record sanity is preserved //todo $invoice->service()->toggleFeesPaid()->save();
} $invoice->client->service()->updateBalance($fee_total)->save();
$invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment');
}
});
} }
} }

View File

@ -295,7 +295,7 @@ class BasePaymentDriver
* @param PaymentResponseRequest $request The incoming payment request * @param PaymentResponseRequest $request The incoming payment request
* @return void Success/Failure * @return void Success/Failure
*/ */
public function appendGatewayFeeToInvoice(PaymentResponseRequest $request) :void public function confirmGatewayFee(PaymentResponseRequest $request) :void
{ {
/*Payment meta data*/ /*Payment meta data*/
$payment_hash = $request->getPaymentHash(); $payment_hash = $request->getPaymentHash();
@ -303,22 +303,24 @@ class BasePaymentDriver
/*Payment invoices*/ /*Payment invoices*/
$payment_invoices = $payment_hash->invoices(); $payment_invoices = $payment_hash->invoices();
/*Fee charged at gateway*/ // /*Fee charged at gateway*/
$fee_total = $payment_hash->fee_total; $fee_total = $payment_hash->fee_total;
/*Sum of invoice amounts*/ // Sum of invoice amounts
$invoice_totals = array_sum(array_column($payment_invoices,'amount')); // $invoice_totals = array_sum(array_column($payment_invoices,'amount'));
/*Hydrate invoices*/ /*Hydrate invoices*/
$invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get(); $invoices = Invoice::whereIn('id', $this->transformKeys(array_column($payment_invoices, 'invoice_id')))->get();
/*Append gateway fee to invoice line item of first invoice*/ $invoices->each(function($invoice) use($fee_total){
if($fee_total != 0){
$invoices->first()->service()->addGatewayFee($this->company_gateway, $invoice_totals)->save();
//We need to update invoice balance / client balance at this point so if(collect($invoice->line_items)->contains('type_id', '3')){
//that payment record sanity is preserved //todo $invoice->service()->toggleFeesPaid()->save();
} $invoice->client->service()->updateBalance($fee_total)->save();
$invoice->ledger()->updateInvoiceBalance($fee_total, $notes = 'Gateway fee adjustment');
}
});
} }
} }

View File

@ -123,6 +123,7 @@ class CreditCard
'gateway_type_id' => $request->payment_method_id, 'gateway_type_id' => $request->payment_method_id,
'hashed_ids' => $request->hashed_ids, 'hashed_ids' => $request->hashed_ids,
'server_response' => $server_response, 'server_response' => $server_response,
'payment_hash' => $payment_hash,
]; ];
$invoices = Invoice::whereIn('id', $this->stripe->transformKeys(array_column($payment_hash->invoices(), 'invoice_id'))) $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') { if ($state['payment_status'] == 'succeeded') {
/* Add gateway fees if needed! */ /* Add gateway fees if needed! */
$this->stripe->appendGatewayFeeToInvoice($request); $this->stripe->confirmGatewayFee($request);
return $this->processSuccessfulPayment($state); return $this->processSuccessfulPayment($state);
} }
@ -190,7 +191,7 @@ class CreditCard
$this->stripe->attachInvoices($payment, $state['hashed_ids']); $this->stripe->attachInvoices($payment, $state['hashed_ids']);
$payment->service()->updateInvoicePayment(); $payment->service()->updateInvoicePayment($state['payment_hash']);
event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars())); event(new PaymentWasCreated($payment, $payment->company, Ninja::eventVars()));

View File

@ -60,7 +60,7 @@ class AddGatewayFee extends AbstractService
$invoice_items = $this->invoice->line_items; $invoice_items = $this->invoice->line_items;
$invoice_items = collect($invoice_items)->filter(function ($item){ $invoice_items = collect($invoice_items)->filter(function ($item){
return $item->type_id != 3; return $item->type_id != '3';
}); });
$this->invoice->line_items = $invoice_items; $this->invoice->line_items = $invoice_items;
@ -71,7 +71,7 @@ class AddGatewayFee extends AbstractService
private function processGatewayFee($gateway_fee) private function processGatewayFee($gateway_fee)
{ {
$invoice_item = new InvoiceItem; $invoice_item = new InvoiceItem;
$invoice_item->type_id = '4'; $invoice_item->type_id = '3';
$invoice_item->product_key = ctrans('texts.surcharge'); $invoice_item->product_key = ctrans('texts.surcharge');
$invoice_item->notes = ctrans('texts.online_payment_surcharge'); $invoice_item->notes = ctrans('texts.online_payment_surcharge');
$invoice_item->quantity = 1; $invoice_item->quantity = 1;
@ -92,10 +92,9 @@ class AddGatewayFee extends AbstractService
/**Refresh Invoice values*/ /**Refresh Invoice values*/
$this->invoice = $this->invoice->calc()->getInvoice(); $this->invoice = $this->invoice->calc()->getInvoice();
/*Update client balance*/ /*Update client balance*/ // don't increment until we have process the payment!
$this->invoice->client->service()->updateBalance($gateway_fee)->save(); //$this->invoice->client->service()->updateBalance($gateway_fee)->save();
//$this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Gateway fee adjustment');
$this->invoice->ledger()->updateInvoiceBalance($gateway_fee, $notes = 'Gateway fee adjustment');
return $this->invoice; return $this->invoice;
@ -104,7 +103,7 @@ class AddGatewayFee extends AbstractService
private function processGatewayDiscount($gateway_fee) private function processGatewayDiscount($gateway_fee)
{ {
$invoice_item = new InvoiceItem; $invoice_item = new InvoiceItem;
$invoice_item->type_id = 3; $invoice_item->type_id = '3';
$invoice_item->product_key = ctrans('texts.discount'); $invoice_item->product_key = ctrans('texts.discount');
$invoice_item->notes = ctrans('texts.online_payment_discount'); $invoice_item->notes = ctrans('texts.online_payment_discount');
$invoice_item->quantity = 1; $invoice_item->quantity = 1;
@ -124,9 +123,9 @@ class AddGatewayFee extends AbstractService
$this->invoice = $this->invoice->calc()->getInvoice(); $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; return $this->invoice;
} }

View File

@ -84,9 +84,9 @@ class PaymentService
return (new DeletePayment($this->payment))->run(); 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() public function applyNumber()

View File

@ -17,119 +17,82 @@ use App\Jobs\Payment\EmailPayment;
use App\Jobs\Util\SystemLogger; use App\Jobs\Util\SystemLogger;
use App\Models\Invoice; use App\Models\Invoice;
use App\Models\SystemLog; use App\Models\SystemLog;
use App\Utils\Traits\MakesHash;
class UpdateInvoicePayment class UpdateInvoicePayment
{ {
use MakesHash;
/** /**
* @deprecated This is bad logic, assumes too much. * @deprecated This is bad logic, assumes too much.
*/ */
public $payment; public $payment;
public function __construct($payment) public $payment_hash;
public function __construct($payment, $payment_hash)
{ {
$this->payment = $payment; $this->payment = $payment;
$this->payment_hash = $payment_hash;
} }
public function run() 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*/ collect($paid_invoices)->each(function ($paid_invoice) use($invoices) {
if (strval($invoices_total) === strval($this->payment->amount)) {
$invoices->each(function ($invoice) { $invoice = $invoices->first(function ($inv) use($paid_invoice) {
$this->payment return $paid_invoice['invoice_id'] == $inv->hashed_id;
->ledger() });
->updatePaymentBalance($invoice->balance*-1);
$this->payment
$this->payment->client ->ledger()
->service() ->updatePaymentBalance($paid_invoice->amount*-1);
->updateBalance($invoice->balance*-1)
->updatePaidToDate($invoice->balance) $this->payment
->save(); ->client
->service()
$invoice->pivot->amount = $invoice->balance; ->updateBalance($paid_invoice->amount*-1)
->updatePaidToDate($paid_invoice->amount)
->save();
$invoice->pivot->amount = $paid_invoice->amount;
$invoice->pivot->save(); $invoice->pivot->save();
$invoice->service() $invoice->service() //caution what if we amount paid was less than partial - we wipe it!
->clearPartial() ->clearPartial()
->updateBalance($invoice->balance*-1) ->updateBalance($paid_invoice->amount*-1)
->save(); ->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 */ // } else {
if ($this->payment->amount == $total) { // SystemLogger::dispatch(
$invoices->each(function ($invoice) { // [
if ($invoice->hasPartial()) { // 'payment' => $this->payment,
$this->payment // 'invoices' => $invoices,
->ledger() // 'invoices_total' => $invoices_total,
->updatePaymentBalance($invoice->partial*-1); // '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() // throw new \Exception("payment amount {$this->payment->amount} does not match invoice totals {$invoices_total} reversing payment");
->updateBalance($invoice->partial*-1)
->updatePaidToDate($invoice->partial)
->save();
$invoice->pivot->amount = $invoice->partial; // $this->payment->invoice()->delete();
$invoice->pivot->save(); // $this->payment->is_deleted=true;
// $this->payment->save();
$invoice->service()->updateBalance($invoice->partial*-1) // $this->payment->delete();
->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();
}
} }
return $this->payment; return $this->payment;