mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-05-24 02:14:21 -04:00
Working on partial invoice payments
This commit is contained in:
parent
78ae24df46
commit
a57de08178
@ -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;
|
||||
|
54
app/Listeners/Invoice/UpdateInvoiceInvitations.php
Normal file
54
app/Listeners/Invoice/UpdateInvoiceInvitations.php
Normal 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]);
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
}
|
@ -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');
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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',
|
||||
|
39
app/Utils/Traits/SystemLogTrait.php
Normal file
39
app/Utils/Traits/SystemLogTrait.php
Normal 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);
|
||||
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user