Working on partial invoice payments

This commit is contained in:
David Bomba 2019-10-02 08:44:13 +10:00
parent 78ae24df46
commit a57de08178
10 changed files with 189 additions and 73 deletions

View File

@ -46,8 +46,7 @@ class UpdateCompanyLedgerWithInvoice
*/
public function handle()
{
\Log::error('in update company ledger with invoice');
$balance = 0;
$ledger = CompanyLedger::whereClientId($this->invoice->client_id)
@ -60,7 +59,6 @@ class UpdateCompanyLedgerWithInvoice
$adjustment = $balance + $this->adjustment;
\Log::error("adjusting balance {$balance} to {$adjustment}");
$company_ledger = CompanyLedgerFactory::create($this->invoice->company_id, $this->invoice->user_id);
$company_ledger->client_id = $this->invoice->client_id;

View File

@ -0,0 +1,54 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Listeners\Invoice;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class UpdateInvoiceInvitations implements ShouldQueue
{
/**
* Create the event listener.
*
* @return void
*/
public function __construct()
{
}
/**
* Handle the event.
*
* @param object $event
* @return void
*/
public function handle($event)
{
$payment = $event->payment;
$invoices = $payment->invoices;
/**
* Move this into an event
*/
$invoices->each(function ($invoice) use($payment) {
$invoice->status_id = Invoice::STATUS_PAID;
$invoice->save();
$invoice->invitations()->update(['transaction_reference' => $payment->transaction_reference]);
});
}
}

View File

@ -14,11 +14,14 @@ namespace App\Listeners\Invoice;
use App\Jobs\Company\UpdateCompanyLedgerWithInvoice;
use App\Jobs\Company\UpdateCompanyLedgerWithPayment;
use App\Models\SystemLog;
use App\Utils\Traits\SystemLogTrait;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
class UpdateInvoicePayment implements ShouldQueue
{
use SystemLogTrait;
/**
* Create the event listener.
*
@ -44,17 +47,17 @@ class UpdateInvoicePayment implements ShouldQueue
/* Simplest scenario*/
if($invoices_total == $payment->amount)
{
\Log::error("invoice totals match payment amount");
$invoices->each(function ($invoice) use($payment){
//$invoice->updateBalance($invoice->balance*-1);
//UpdateCompanyLedgerWithInvoice::dispatchNow($invoice, ($invoice->balance*-1));
UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($invoice->balance*-1));
$invoice->clearPartial();
$invoice->updateBalance($invoice->balance*-1);
});
}
else {
\Log::error("invoice totals don't match, search for partials");
$total = 0;
foreach($invoice as $invoice)
@ -71,12 +74,29 @@ class UpdateInvoicePayment implements ShouldQueue
/* test if there is a batch of partial invoices that have been paid */
if($payment->amount == $total)
{
//process invoices and update balance depending on
//whether the invoice balance or partial amount was
//paid
$invoices->each(function ($invoice) use($payment){
if($invoice->isPartial()) {
UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($invoice->partial*-1));
$invoice->updateBalance($invoice->partial*-1);
$invoice->clearPartial();
$invoice->setDueDate();
//todo do we need to mark it as a partial?
}
else
{
UpdateCompanyLedgerWithPayment::dispatchNow($payment, ($invoice->balance*-1));
$invoice->clearPartial();
$invoice->updateBalance($invoice->balance*-1);
}
});
}
else {
\Log::error("no matches, fail");
$data = [
'payment' => $payment,
'invoices' => $invoices,
@ -85,16 +105,8 @@ class UpdateInvoicePayment implements ShouldQueue
'partial_check_amount' => $total,
];
$sl = [
'client_id' => $payment->client_id,
'user_id' => $payment->user_id,
'company_id' => $payment->company_id,
'log' => $data,
'category_id' => SystemLog::PAYMENT_RESPONSE,
'event_id' => SystemLog::PAYMENT_RECONCILIATION_FAILURE,
];
SystemLog::create($sl);
$this->sysLog($data, SystemLog::GATEWAY_RESPONSE, SystemLog::PAYMENT_RECONCILIATION_FAILURE);
throw new Exception('payment amount does not match invoice totals');
}

View File

@ -15,6 +15,7 @@ use App\Events\Invoice\InvoiceWasUpdated;
use App\Helpers\Invoice\InvoiceCalc;
use App\Models\Currency;
use App\Models\Filterable;
use App\Models\PaymentTerm;
use App\Utils\Number;
use App\Utils\Traits\MakesDates;
use App\Utils\Traits\MakesInvoiceValues;
@ -302,6 +303,17 @@ class Invoice extends BaseModel
//return $this->status_id >= self::STATUS_PARTIAL;
}
/**
* Clear partial fields
* @return void
*/
public function clearPartial() : void
{
$this->partial = null;
$this->partial_due_date = null;
$this->save();
}
/**
* @param float $balance_adjustment
*/
@ -322,4 +334,11 @@ class Invoice extends BaseModel
$this->save();
\Log::error('finished updatingoice balance');
}
public function setDueDate()
{
$this->due_date = Carbon::now()->addDays(PaymentTerm::find($this->company->settings->payment_terms_id)->num_days);
$this->save();
}
}

View File

@ -16,8 +16,7 @@ use Illuminate\Database\Eloquent\Model;
class SystemLog extends Model
{
/* Category IDs */
const PAYMENT_RESPONSE = 1;
const GATEWAY_RESPONSE = 2;
const GATEWAY_RESPONSE = 1;
/* Event IDs*/
const PAYMENT_RECONCILIATION_FAILURE = 10;

View File

@ -64,9 +64,13 @@ class BasePaymentDriver
public function __construct(CompanyGateway $company_gateway, Client $client, $invitation = false)
{
$this->company_gateway = $company_gateway;
$this->invitation = $invitation;
$this->client = $client;
}
/**
@ -175,27 +179,16 @@ class BasePaymentDriver
acceptNotification() - convert an incoming request from an off-site gateway to a generic notification object for further processing
*/
protected function paymentDetails($input)
protected function paymentDetails($input) : array
{
// $gatewayTypeAlias = $this->gatewayType == GatewayType::TOKEN ? $this->gatewayType : GatewayType::getAliasFromId($this->gatewayType);
$data = [
'currency' => $this->client->getCurrencyCode(),
'transactionType' => 'Purchase',
'clientIp' => request()->getClientIp(),
];
/*
if ($paymentMethod) {
if ($this->customerReferenceParam) {
$data[$this->customerReferenceParam] = $paymentMethod->account_gateway_token->token;
}
$data[$this->sourceReferenceParam] = $paymentMethod->source_reference;
} elseif ($this->input) {
$data['card'] = new CreditCard($this->paymentDetailsFromInput($this->input));
} else {
$data['card'] = new CreditCard($this->paymentDetailsFromClient());
}
*/
return $data;
}
@ -208,17 +201,7 @@ class BasePaymentDriver
->setItems($items)
->send();
if ($response->isRedirect()) {
// redirect to offsite payment gateway
$response->redirect();
} elseif ($response->isSuccessful()) {
// payment was successful: update database
print_r($response);
} else {
// payment failed: display message to customer
echo $response->getMessage();
}
return $response;
/*
$this->purchaseResponse = (array)$response->getData();*/
}
@ -232,7 +215,7 @@ class BasePaymentDriver
->send();
}
public function createPayment($data)
public function createPayment($data) : Payment
{
$payment = PaymentFactory::create($this->client->company->id, $this->client->user->id);
@ -246,7 +229,7 @@ class BasePaymentDriver
}
public function attachInvoices(Payment $payment, $hashed_ids)
public function attachInvoices(Payment $payment, $hashed_ids) : Payment
{
$invoices = Invoice::whereIn('id', $this->transformKeys(explode(",",$hashed_ids)))
->whereClientId($this->client->id)

View File

@ -14,6 +14,7 @@ namespace App\PaymentDrivers;
use App\Events\Payment\PaymentWasCreated;
use App\Models\ClientGatewayToken;
use App\Models\GatewayType;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
@ -90,7 +91,28 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
*/
public function processPaymentView(array $data)
{
$this->purchase($this->paymentDetails($data), $this->paymentItems($data));
$response = $this->purchase($this->paymentDetails($data), $this->paymentItems($data));
if ($response->isRedirect()) {
// redirect to offsite payment gateway
$response->redirect();
} elseif ($response->isSuccessful()) {
// payment was successful: update database
/* for this driver this method wont be hit*/
} else {
// payment failed: display message to customer
$log = [
'server_response' => $response->getData(),
'data' => $data
];
$this->sysLog($log);
throw new Exception("Error Processing Payment", 1);
}
}
public function processPaymentResponse($request)
@ -103,6 +125,14 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
if ($response->isCancelled()) {
return redirect()->route('client.invoices.index')->with('warning',ctrans('texts.status_voided'));
} elseif (! $response->isSuccessful()) {
$data = [
'request' => $request->all(),
'server_response' => $response->getData()
];
$this->sysLog($data);
throw new Exception($response->getMessage());
}
@ -211,7 +241,7 @@ class PayPalExpressPaymentDriver extends BasePaymentDriver
return $items;
}
public function createPayment($data)
public function createPayment($data) : Payment
{
$payment = parent::createPayment($data);

View File

@ -18,7 +18,6 @@ use App\Models\GatewayType;
use App\Models\Invoice;
use App\Models\Payment;
use App\Models\PaymentType;
use App\Models\SystemLog;
use App\Utils\Traits\MakesHash;
use Illuminate\Http\Request;
use Illuminate\Support\Carbon;
@ -231,6 +230,7 @@ class StripePaymentDriver extends BasePaymentDriver
$data['intent'] = $this->createPaymentIntent($payment_intent_data);
$data['gateway'] = $this;
return view($this->viewForType($data['payment_method_id']), $data);
@ -362,17 +362,6 @@ class StripePaymentDriver extends BasePaymentDriver
event(new PaymentWasCreated($payment));
/**
* Move this into an event
*/
$invoices->each(function ($invoice) use($payment) {
$invoice->status_id = Invoice::STATUS_PAID;
$invoice->save();
$invoice->invitations()->update(['transaction_reference' => $payment->transaction_reference]);
});
return redirect()->route('client.payments.show', ['payment' => $this->encodePrimaryKey($payment->id)]);
@ -386,16 +375,7 @@ class StripePaymentDriver extends BasePaymentDriver
'invoices' => $invoices,
];
$sl = [
'company_id' => $this->client->company->id,
'client_id' => $this->client->id,
'user_id' => $this->client->user_id,
'log' => $log,
'category_id' => SystemLog::GATEWAY_RESPONSE,
'event_id' => SystemLog::GATEWAY_FAILURE,
];
SystemLog::create($sl);
$this->sysLog($log);
throw new Exception("Failed to process payment", 1);

View File

@ -26,6 +26,7 @@ use App\Listeners\Invoice\CreateInvoiceActivity;
use App\Listeners\Invoice\CreateInvoiceInvitation;
use App\Listeners\Invoice\CreateInvoicePdf;
use App\Listeners\Invoice\UpdateInvoiceActivity;
use App\Listeners\Invoice\UpdateInvoiceInvitations;
use App\Listeners\Invoice\UpdateInvoicePayment;
use App\Listeners\SendVerificationNotification;
use App\Listeners\User\UpdateUserLastLogin;
@ -56,6 +57,7 @@ class EventServiceProvider extends ServiceProvider
PaymentWasCreated::class => [
PaymentCreatedActivity::class,
UpdateInvoicePayment::class,
UpdateInvoiceInvitations::class,
],
'App\Events\ClientWasArchived' => [
'App\Listeners\ActivityListener@archivedClient',

View File

@ -0,0 +1,39 @@
<?php
/**
* Invoice Ninja (https://invoiceninja.com)
*
* @link https://github.com/invoiceninja/invoiceninja source repository
*
* @copyright Copyright (c) 2019. Invoice Ninja LLC (https://invoiceninja.com)
*
* @license https://opensource.org/licenses/AAL
*/
namespace App\Utils\Traits;
use App\Models\SystemLog;
/**
* Class SystemLogTrait
* @package App\Utils\Traits
*/
trait SystemLogTrait
{
public function sysLog($log, $category_id = SystemLog::GATEWAY_RESPONSE, $event_id = SystemLog::GATEWAY_FAILURE)
{
$sl = [
'client_id' => $this->client->id,
'company_id' => $this->client->company->id,
'user_id' => $this->client->user_id,
'log' => $log,
'category_id' => $category_id,
'event_id' => $event_id,
];
SystemLog::create($sl);
}
}