mirror of
https://github.com/invoiceninja/invoiceninja.git
synced 2025-07-09 03:14:30 -04:00
Support refunds from the admin UI
This commit is contained in:
parent
f9c36fd761
commit
b8170f0324
25
app/Events/PaymentWasRefunded.php
Normal file
25
app/Events/PaymentWasRefunded.php
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
<?php namespace App\Events;
|
||||||
|
|
||||||
|
use App\Events\Event;
|
||||||
|
|
||||||
|
use Illuminate\Queue\SerializesModels;
|
||||||
|
|
||||||
|
class PaymentWasRefunded extends Event {
|
||||||
|
|
||||||
|
use SerializesModels;
|
||||||
|
|
||||||
|
public $payment;
|
||||||
|
public $refundAmount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new event instance.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct($payment, $refundAmount)
|
||||||
|
{
|
||||||
|
$this->payment = $payment;
|
||||||
|
$this->refundAmount = $refundAmount;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -182,14 +182,7 @@ class AccountController extends BaseController
|
|||||||
|
|
||||||
if ($account->company->payment) {
|
if ($account->company->payment) {
|
||||||
$payment = $account->company->payment;
|
$payment = $account->company->payment;
|
||||||
|
$this->paymentService->refund($payment);
|
||||||
$gateway = $this->paymentService->createGateway($payment->account_gateway);
|
|
||||||
$refund = $gateway->refund(array(
|
|
||||||
'transactionReference' => $payment->transaction_reference,
|
|
||||||
'amount' => $payment->amount
|
|
||||||
));
|
|
||||||
$refund->send();
|
|
||||||
$payment->delete();
|
|
||||||
Session::flash('message', trans('texts.plan_refunded'));
|
Session::flash('message', trans('texts.plan_refunded'));
|
||||||
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
|
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
|
||||||
} else {
|
} else {
|
||||||
|
@ -57,6 +57,7 @@ class PaymentController extends BaseController
|
|||||||
'method',
|
'method',
|
||||||
'payment_amount',
|
'payment_amount',
|
||||||
'payment_date',
|
'payment_date',
|
||||||
|
'status',
|
||||||
''
|
''
|
||||||
]),
|
]),
|
||||||
));
|
));
|
||||||
@ -630,11 +631,12 @@ class PaymentController extends BaseController
|
|||||||
public function bulk()
|
public function bulk()
|
||||||
{
|
{
|
||||||
$action = Input::get('action');
|
$action = Input::get('action');
|
||||||
|
$amount = Input::get('amount');
|
||||||
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
$ids = Input::get('public_id') ? Input::get('public_id') : Input::get('ids');
|
||||||
$count = $this->paymentService->bulk($ids, $action);
|
$count = $this->paymentService->bulk($ids, $action, array('amount'=>$amount));
|
||||||
|
|
||||||
if ($count > 0) {
|
if ($count > 0) {
|
||||||
$message = Utils::pluralize($action.'d_payment', $count);
|
$message = Utils::pluralize($action=='refund'?'refunded_payment':$action.'d_payment', $count);
|
||||||
Session::flash('message', $message);
|
Session::flash('message', $message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -252,6 +252,9 @@ class PublicClientController extends BaseController
|
|||||||
'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
|
'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
|
||||||
'contact' => Utils::getClientDisplayName($model),
|
'contact' => Utils::getClientDisplayName($model),
|
||||||
'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''),
|
'payment' => trans('texts.payment') . ($model->payment ? ' ' . $model->payment : ''),
|
||||||
|
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
|
||||||
|
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
|
||||||
|
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null,
|
||||||
];
|
];
|
||||||
|
|
||||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||||
@ -321,7 +324,7 @@ class PublicClientController extends BaseController
|
|||||||
'clientFontUrl' => $account->getFontsUrl(),
|
'clientFontUrl' => $account->getFontsUrl(),
|
||||||
'entityType' => ENTITY_PAYMENT,
|
'entityType' => ENTITY_PAYMENT,
|
||||||
'title' => trans('texts.payments'),
|
'title' => trans('texts.payments'),
|
||||||
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date'])
|
'columns' => Utils::trans(['invoice', 'transaction_reference', 'method', 'payment_amount', 'payment_date', 'status'])
|
||||||
];
|
];
|
||||||
|
|
||||||
return response()->view('public_list', $data);
|
return response()->view('public_list', $data);
|
||||||
@ -340,8 +343,36 @@ class PublicClientController extends BaseController
|
|||||||
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
->addColumn('payment_type', function ($model) { return $model->payment_type ? $model->payment_type : ($model->account_gateway_id ? '<i>Online payment</i>' : ''); })
|
||||||
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
->addColumn('amount', function ($model) { return Utils::formatMoney($model->amount, $model->currency_id, $model->country_id); })
|
||||||
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
->addColumn('payment_date', function ($model) { return Utils::dateToString($model->payment_date); })
|
||||||
|
->addColumn('status', function ($model) { return $this->getPaymentStatusLabel($model); })
|
||||||
->make();
|
->make();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getPaymentStatusLabel($model)
|
||||||
|
{
|
||||||
|
$label = trans("texts.status_" . strtolower($model->payment_status_name));
|
||||||
|
$class = 'default';
|
||||||
|
switch ($model->payment_status_id) {
|
||||||
|
case PAYMENT_STATUS_PENDING:
|
||||||
|
$class = 'info';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_COMPLETED:
|
||||||
|
$class = 'success';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_FAILED:
|
||||||
|
$class = 'danger';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_PARTIALLY_REFUNDED:
|
||||||
|
$label = trans('texts.status_partially_refunded_amount', [
|
||||||
|
'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
|
||||||
|
]);
|
||||||
|
$class = 'primary';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_REFUNDED:
|
||||||
|
$class = 'default';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||||
|
}
|
||||||
|
|
||||||
public function quoteIndex()
|
public function quoteIndex()
|
||||||
{
|
{
|
||||||
|
@ -398,6 +398,7 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
|
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
|
||||||
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
|
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
|
||||||
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
|
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
|
||||||
|
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 39);
|
||||||
|
|
||||||
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
|
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
|
||||||
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
|
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
|
||||||
@ -474,6 +475,12 @@ if (!defined('CONTACT_EMAIL')) {
|
|||||||
define('INVOICE_STATUS_APPROVED', 4);
|
define('INVOICE_STATUS_APPROVED', 4);
|
||||||
define('INVOICE_STATUS_PARTIAL', 5);
|
define('INVOICE_STATUS_PARTIAL', 5);
|
||||||
define('INVOICE_STATUS_PAID', 6);
|
define('INVOICE_STATUS_PAID', 6);
|
||||||
|
|
||||||
|
define('PAYMENT_STATUS_PENDING', 1);
|
||||||
|
define('PAYMENT_STATUS_FAILED', 2);
|
||||||
|
define('PAYMENT_STATUS_COMPLETED', 3);
|
||||||
|
define('PAYMENT_STATUS_PARTIALLY_REFUNDED', 4);
|
||||||
|
define('PAYMENT_STATUS_REFUNDED', 5);
|
||||||
|
|
||||||
define('PAYMENT_TYPE_CREDIT', 1);
|
define('PAYMENT_TYPE_CREDIT', 1);
|
||||||
define('CUSTOM_DESIGN', 11);
|
define('CUSTOM_DESIGN', 11);
|
||||||
|
@ -22,6 +22,7 @@ use App\Events\QuoteInvitationWasViewed;
|
|||||||
use App\Events\QuoteInvitationWasApproved;
|
use App\Events\QuoteInvitationWasApproved;
|
||||||
use App\Events\PaymentWasCreated;
|
use App\Events\PaymentWasCreated;
|
||||||
use App\Events\PaymentWasDeleted;
|
use App\Events\PaymentWasDeleted;
|
||||||
|
use App\Events\PaymentWasRefunded;
|
||||||
use App\Events\PaymentWasArchived;
|
use App\Events\PaymentWasArchived;
|
||||||
use App\Events\PaymentWasRestored;
|
use App\Events\PaymentWasRestored;
|
||||||
use App\Events\CreditWasCreated;
|
use App\Events\CreditWasCreated;
|
||||||
@ -309,6 +310,18 @@ class ActivityListener
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function refundedPayment(PaymentWasRefunded $event)
|
||||||
|
{
|
||||||
|
$payment = $event->payment;
|
||||||
|
|
||||||
|
$this->activityRepo->create(
|
||||||
|
$payment,
|
||||||
|
ACTIVITY_TYPE_REFUNDED_PAYMENT,
|
||||||
|
$event->refundAmount,
|
||||||
|
$event->refundAmount * -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function archivedPayment(PaymentWasArchived $event)
|
public function archivedPayment(PaymentWasArchived $event)
|
||||||
{
|
{
|
||||||
if ($event->payment->is_deleted) {
|
if ($event->payment->is_deleted) {
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
use Carbon;
|
use Carbon;
|
||||||
use App\Models\Credit;
|
use App\Models\Credit;
|
||||||
use App\Events\PaymentWasDeleted;
|
use App\Events\PaymentWasDeleted;
|
||||||
|
use App\Events\PaymentWasRefunded;
|
||||||
use App\Ninja\Repositories\CreditRepository;
|
use App\Ninja\Repositories\CreditRepository;
|
||||||
|
|
||||||
class CreditListener
|
class CreditListener
|
||||||
@ -26,7 +27,24 @@ class CreditListener
|
|||||||
$credit = Credit::createNew();
|
$credit = Credit::createNew();
|
||||||
$credit->client_id = $payment->client_id;
|
$credit->client_id = $payment->client_id;
|
||||||
$credit->credit_date = Carbon::now()->toDateTimeString();
|
$credit->credit_date = Carbon::now()->toDateTimeString();
|
||||||
$credit->balance = $credit->amount = $payment->amount;
|
$credit->balance = $credit->amount = $payment->amount - $payment->refunded;
|
||||||
|
$credit->private_notes = $payment->transaction_reference;
|
||||||
|
$credit->save();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refundedPayment(PaymentWasRefunded $event)
|
||||||
|
{
|
||||||
|
$payment = $event->payment;
|
||||||
|
|
||||||
|
// if the payment was from a credit we need to refund the credit
|
||||||
|
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$credit = Credit::createNew();
|
||||||
|
$credit->client_id = $payment->client_id;
|
||||||
|
$credit->credit_date = Carbon::now()->toDateTimeString();
|
||||||
|
$credit->balance = $credit->amount = $event->refundAmount;
|
||||||
$credit->private_notes = $payment->transaction_reference;
|
$credit->private_notes = $payment->transaction_reference;
|
||||||
$credit->save();
|
$credit->save();
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ use App\Events\InvoiceWasUpdated;
|
|||||||
use App\Events\InvoiceWasCreated;
|
use App\Events\InvoiceWasCreated;
|
||||||
use App\Events\PaymentWasCreated;
|
use App\Events\PaymentWasCreated;
|
||||||
use App\Events\PaymentWasDeleted;
|
use App\Events\PaymentWasDeleted;
|
||||||
|
use App\Events\PaymentWasRefunded;
|
||||||
use App\Events\PaymentWasRestored;
|
use App\Events\PaymentWasRestored;
|
||||||
use App\Events\InvoiceInvitationWasViewed;
|
use App\Events\InvoiceInvitationWasViewed;
|
||||||
|
|
||||||
@ -58,7 +59,17 @@ class InvoiceListener
|
|||||||
{
|
{
|
||||||
$payment = $event->payment;
|
$payment = $event->payment;
|
||||||
$invoice = $payment->invoice;
|
$invoice = $payment->invoice;
|
||||||
$adjustment = $payment->amount;
|
$adjustment = $payment->amount - $payment->refunded;
|
||||||
|
|
||||||
|
$invoice->updateBalances($adjustment);
|
||||||
|
$invoice->updatePaidStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refundedPayment(PaymentWasRefunded $event)
|
||||||
|
{
|
||||||
|
$payment = $event->payment;
|
||||||
|
$invoice = $payment->invoice;
|
||||||
|
$adjustment = $event->refundAmount;
|
||||||
|
|
||||||
$invoice->updateBalances($adjustment);
|
$invoice->updateBalances($adjustment);
|
||||||
$invoice->updatePaidStatus();
|
$invoice->updatePaidStatus();
|
||||||
@ -72,7 +83,7 @@ class InvoiceListener
|
|||||||
|
|
||||||
$payment = $event->payment;
|
$payment = $event->payment;
|
||||||
$invoice = $payment->invoice;
|
$invoice = $payment->invoice;
|
||||||
$adjustment = $payment->amount * -1;
|
$adjustment = ($payment->amount - $payment->refunded) * -1;
|
||||||
|
|
||||||
$invoice->updateBalances($adjustment);
|
$invoice->updateBalances($adjustment);
|
||||||
$invoice->updatePaidStatus();
|
$invoice->updatePaidStatus();
|
||||||
|
@ -70,6 +70,8 @@ class Activity extends Eloquent
|
|||||||
'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
||||||
'contact' => $contactId ? $client->getDisplayName() : $user->getDisplayName(),
|
'contact' => $contactId ? $client->getDisplayName() : $user->getDisplayName(),
|
||||||
'payment' => $payment ? $payment->transaction_reference : null,
|
'payment' => $payment ? $payment->transaction_reference : null,
|
||||||
|
'payment_amount' => $payment ? $account->formatMoney($payment->amount, $payment) : null,
|
||||||
|
'adjustment' => $this->adjustment ? $account->formatMoney($this->adjustment, $this) : asdf,
|
||||||
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
|
'credit' => $credit ? $account->formatMoney($credit->amount, $client) : null,
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,7 +1,9 @@
|
|||||||
<?php namespace App\Models;
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Event;
|
||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use App\Events\PaymentWasCreated;
|
use App\Events\PaymentWasCreated;
|
||||||
|
use App\Events\PaymentWasRefunded;
|
||||||
use Laracasts\Presenter\PresentableTrait;
|
use Laracasts\Presenter\PresentableTrait;
|
||||||
|
|
||||||
class Payment extends EntityModel
|
class Payment extends EntityModel
|
||||||
@ -50,6 +52,11 @@ class Payment extends EntityModel
|
|||||||
public function payment_type()
|
public function payment_type()
|
||||||
{
|
{
|
||||||
return $this->belongsTo('App\Models\PaymentType');
|
return $this->belongsTo('App\Models\PaymentType');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function payment_status()
|
||||||
|
{
|
||||||
|
return $this->belongsTo('App\Models\PaymentStatus');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRoute()
|
public function getRoute()
|
||||||
@ -69,6 +76,51 @@ class Payment extends EntityModel
|
|||||||
return trim("payment {$this->transaction_reference}");
|
return trim("payment {$this->transaction_reference}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isPending()
|
||||||
|
{
|
||||||
|
return $this->payment_status_id = PAYMENT_STATUS_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isFailed()
|
||||||
|
{
|
||||||
|
return $this->payment_status_id = PAYMENT_STATUS_FAILED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isCompleted()
|
||||||
|
{
|
||||||
|
return $this->payment_status_id >= PAYMENT_STATUS_COMPLETED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPartiallyRefunded()
|
||||||
|
{
|
||||||
|
return $this->payment_status_id == PAYMENT_STATUS_PARTIALLY_REFUNDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isRefunded()
|
||||||
|
{
|
||||||
|
return $this->payment_status_id == PAYMENT_STATUS_REFUNDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function recordRefund($amount = null)
|
||||||
|
{
|
||||||
|
if (!$this->isRefunded()) {
|
||||||
|
if (!$amount) {
|
||||||
|
$amount = $this->amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$new_refund = min($this->amount, $this->refunded + $amount);
|
||||||
|
$refund_change = $new_refund - $this->refunded;
|
||||||
|
|
||||||
|
if ($refund_change) {
|
||||||
|
$this->refunded = $new_refund;
|
||||||
|
$this->payment_status_id = $this->refunded == $this->amount ? PAYMENT_STATUS_REFUNDED : PAYMENT_STATUS_PARTIALLY_REFUNDED;
|
||||||
|
$this->save();
|
||||||
|
|
||||||
|
Event::fire(new PaymentWasRefunded($this, $refund_change));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public function getEntityType()
|
public function getEntityType()
|
||||||
{
|
{
|
||||||
return ENTITY_PAYMENT;
|
return ENTITY_PAYMENT;
|
||||||
|
8
app/Models/PaymentStatus.php
Normal file
8
app/Models/PaymentStatus.php
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<?php namespace App\Models;
|
||||||
|
|
||||||
|
use Eloquent;
|
||||||
|
|
||||||
|
class PaymentStatus extends Eloquent
|
||||||
|
{
|
||||||
|
public $timestamps = false;
|
||||||
|
}
|
@ -97,6 +97,7 @@ class ActivityRepository
|
|||||||
'contacts.last_name as last_name',
|
'contacts.last_name as last_name',
|
||||||
'contacts.email as email',
|
'contacts.email as email',
|
||||||
'payments.transaction_reference as payment',
|
'payments.transaction_reference as payment',
|
||||||
|
'payments.amount as payment_amount',
|
||||||
'credits.amount as credit'
|
'credits.amount as credit'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,7 @@ class PaymentRepository extends BaseRepository
|
|||||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||||
|
->join('payment_statuses', 'payment_statuses.id', '=', 'payments.payment_status_id')
|
||||||
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
->leftJoin('payment_types', 'payment_types.id', '=', 'payments.payment_type_id')
|
||||||
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
|
->leftJoin('account_gateways', 'account_gateways.id', '=', 'payments.account_gateway_id')
|
||||||
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
|
->leftJoin('gateways', 'gateways.id', '=', 'account_gateways.gateway_id')
|
||||||
@ -39,6 +40,8 @@ class PaymentRepository extends BaseRepository
|
|||||||
'clients.user_id as client_user_id',
|
'clients.user_id as client_user_id',
|
||||||
'payments.amount',
|
'payments.amount',
|
||||||
'payments.payment_date',
|
'payments.payment_date',
|
||||||
|
'payments.payment_status_id',
|
||||||
|
'payments.payment_type_id',
|
||||||
'invoices.public_id as invoice_public_id',
|
'invoices.public_id as invoice_public_id',
|
||||||
'invoices.user_id as invoice_user_id',
|
'invoices.user_id as invoice_user_id',
|
||||||
'invoices.invoice_number',
|
'invoices.invoice_number',
|
||||||
@ -50,8 +53,11 @@ class PaymentRepository extends BaseRepository
|
|||||||
'payments.deleted_at',
|
'payments.deleted_at',
|
||||||
'payments.is_deleted',
|
'payments.is_deleted',
|
||||||
'payments.user_id',
|
'payments.user_id',
|
||||||
|
'payments.refunded',
|
||||||
'invoices.is_deleted as invoice_is_deleted',
|
'invoices.is_deleted as invoice_is_deleted',
|
||||||
'gateways.name as gateway_name'
|
'gateways.name as gateway_name',
|
||||||
|
'gateways.id as gateway_id',
|
||||||
|
'payment_statuses.name as payment_status_name'
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!\Session::get('show_trash:payment')) {
|
if (!\Session::get('show_trash:payment')) {
|
||||||
@ -78,6 +84,7 @@ class PaymentRepository extends BaseRepository
|
|||||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||||
|
->join('payment_statuses', 'payment_statuses.id', '=', 'payments.payment_status_id')
|
||||||
->leftJoin('invitations', function ($join) {
|
->leftJoin('invitations', function ($join) {
|
||||||
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
||||||
->on('invitations.contact_id', '=', 'contacts.id');
|
->on('invitations.contact_id', '=', 'contacts.id');
|
||||||
@ -104,7 +111,10 @@ class PaymentRepository extends BaseRepository
|
|||||||
'contacts.last_name',
|
'contacts.last_name',
|
||||||
'contacts.email',
|
'contacts.email',
|
||||||
'payment_types.name as payment_type',
|
'payment_types.name as payment_type',
|
||||||
'payments.account_gateway_id'
|
'payments.account_gateway_id',
|
||||||
|
'payments.refunded',
|
||||||
|
'payments.payment_status_id',
|
||||||
|
'payment_statuses.name as payment_status_name'
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($filter) {
|
if ($filter) {
|
||||||
@ -189,6 +199,4 @@ class PaymentRepository extends BaseRepository
|
|||||||
|
|
||||||
parent::restore($payment);
|
parent::restore($payment);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -107,6 +107,11 @@ class EventServiceProvider extends ServiceProvider {
|
|||||||
'App\Listeners\InvoiceListener@deletedPayment',
|
'App\Listeners\InvoiceListener@deletedPayment',
|
||||||
'App\Listeners\CreditListener@deletedPayment',
|
'App\Listeners\CreditListener@deletedPayment',
|
||||||
],
|
],
|
||||||
|
'App\Events\PaymentWasRefunded' => [
|
||||||
|
'App\Listeners\ActivityListener@refundedPayment',
|
||||||
|
'App\Listeners\InvoiceListener@refundedPayment',
|
||||||
|
'App\Listeners\CreditListener@refundedPayment',
|
||||||
|
],
|
||||||
'App\Events\PaymentWasRestored' => [
|
'App\Events\PaymentWasRestored' => [
|
||||||
'App\Listeners\ActivityListener@restoredPayment',
|
'App\Listeners\ActivityListener@restoredPayment',
|
||||||
'App\Listeners\InvoiceListener@restoredPayment',
|
'App\Listeners\InvoiceListener@restoredPayment',
|
||||||
|
@ -44,7 +44,9 @@ class ActivityService extends BaseService
|
|||||||
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null,
|
'quote' => $model->invoice ? link_to('/quotes/' . $model->invoice_public_id, $model->invoice)->toHtml() : null,
|
||||||
'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
|
'contact' => $model->contact_id ? link_to('/clients/' . $model->client_public_id, Utils::getClientDisplayName($model))->toHtml() : Utils::getPersonDisplayName($model->user_first_name, $model->user_last_name, $model->user_email),
|
||||||
'payment' => $model->payment ?: '',
|
'payment' => $model->payment ?: '',
|
||||||
'credit' => Utils::formatMoney($model->credit, $model->currency_id, $model->country_id)
|
'credit' => $model->payment_amount ? Utils::formatMoney($model->credit, $model->currency_id, $model->country_id) : '',
|
||||||
|
'payment_amount' => $model->payment_amount ? Utils::formatMoney($model->payment_amount, $model->currency_id, $model->country_id) : null,
|
||||||
|
'adjustment' => $model->adjustment ? Utils::formatMoney($model->adjustment, $model->currency_id, $model->country_id) : null
|
||||||
];
|
];
|
||||||
|
|
||||||
return trans("texts.activity_{$model->activity_type_id}", $data);
|
return trans("texts.activity_{$model->activity_type_id}", $data);
|
||||||
|
@ -23,6 +23,10 @@ class PaymentService extends BaseService
|
|||||||
{
|
{
|
||||||
public $lastError;
|
public $lastError;
|
||||||
protected $datatableService;
|
protected $datatableService;
|
||||||
|
|
||||||
|
protected static $refundableGateways = array(
|
||||||
|
GATEWAY_STRIPE
|
||||||
|
);
|
||||||
|
|
||||||
public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService)
|
public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService)
|
||||||
{
|
{
|
||||||
@ -375,6 +379,12 @@ class PaymentService extends BaseService
|
|||||||
function ($model) {
|
function ($model) {
|
||||||
return Utils::dateToString($model->payment_date);
|
return Utils::dateToString($model->payment_date);
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'payment_status_name',
|
||||||
|
function ($model) use ($entityType) {
|
||||||
|
return self::getStatusLabel($entityType, $model);
|
||||||
|
}
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -390,9 +400,103 @@ class PaymentService extends BaseService
|
|||||||
function ($model) {
|
function ($model) {
|
||||||
return Payment::canEditItem($model);
|
return Payment::canEditItem($model);
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
trans('texts.refund_payment'),
|
||||||
|
function ($model) {
|
||||||
|
$max_refund = number_format($model->amount - $model->refunded, 2);
|
||||||
|
$formatted = Utils::formatMoney($max_refund, $model->currency_id, $model->country_id);
|
||||||
|
$symbol = Utils::getFromCache($model->currency_id, 'currencies')->symbol;
|
||||||
|
return "javascript:showRefundModal({$model->public_id}, '{$max_refund}', '{$formatted}', '{$symbol}')";
|
||||||
|
},
|
||||||
|
function ($model) {
|
||||||
|
return Payment::canEditItem($model) && (
|
||||||
|
($model->transaction_reference && in_array($model->gateway_id , static::$refundableGateways))
|
||||||
|
|| $model->payment_type_id == PAYMENT_TYPE_CREDIT
|
||||||
|
);
|
||||||
|
}
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function bulk($ids, $action, $params = array())
|
||||||
|
{
|
||||||
|
if ($action == 'refund') {
|
||||||
|
if ( ! $ids ) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
$payments = $this->getRepo()->findByPublicIdsWithTrashed($ids);
|
||||||
|
|
||||||
|
foreach ($payments as $payment) {
|
||||||
|
if($payment->canEdit()){
|
||||||
|
if(!empty($params['amount'])) {
|
||||||
|
$this->refund($payment, floatval($params['amount']));
|
||||||
|
} else {
|
||||||
|
$this->refund($payment);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return count($payments);
|
||||||
|
} else {
|
||||||
|
return parent::bulk($ids, $action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStatusLabel($entityType, $model)
|
||||||
|
{
|
||||||
|
$label = trans("texts.status_" . strtolower($model->payment_status_name));
|
||||||
|
$class = 'default';
|
||||||
|
switch ($model->payment_status_id) {
|
||||||
|
case PAYMENT_STATUS_PENDING:
|
||||||
|
$class = 'info';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_COMPLETED:
|
||||||
|
$class = 'success';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_FAILED:
|
||||||
|
$class = 'danger';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_PARTIALLY_REFUNDED:
|
||||||
|
$label = trans('texts.status_partially_refunded_amount', [
|
||||||
|
'amount' => Utils::formatMoney($model->refunded, $model->currency_id, $model->country_id),
|
||||||
|
]);
|
||||||
|
$class = 'primary';
|
||||||
|
break;
|
||||||
|
case PAYMENT_STATUS_REFUNDED:
|
||||||
|
$class = 'default';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return "<h4><div class=\"label label-{$class}\">$label</div></h4>";
|
||||||
|
}
|
||||||
|
|
||||||
|
public function refund($payment, $amount = null) {
|
||||||
|
if (!$amount) {
|
||||||
|
$amount = $payment->amount;
|
||||||
|
}
|
||||||
|
|
||||||
|
$amount = min($amount, $payment->amount - $payment->refunded);
|
||||||
|
|
||||||
|
if (!$amount) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($payment->payment_type_id != PAYMENT_TYPE_CREDIT) {
|
||||||
|
$accountGateway = $this->createGateway($payment->account_gateway);
|
||||||
|
$refund = $accountGateway->refund(array(
|
||||||
|
'transactionReference' => $payment->transaction_reference,
|
||||||
|
'amount' => $amount,
|
||||||
|
));
|
||||||
|
$response = $refund->send();
|
||||||
|
|
||||||
|
if ($response->isSuccessful()) {
|
||||||
|
$payment->recordRefund($amount);
|
||||||
|
} else {
|
||||||
|
$this->error('Unknown', $response->getMessage(), $accountGateway);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
$payment->recordRefund($amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
49
database/migrations/2016_04_23_182223_payments_changes.php
Normal file
49
database/migrations/2016_04_23_182223_payments_changes.php
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
|
||||||
|
class PaymentsChanges extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function up()
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('payment_statuses');
|
||||||
|
|
||||||
|
Schema::create('payment_statuses', function($table)
|
||||||
|
{
|
||||||
|
$table->increments('id');
|
||||||
|
$table->string('name');
|
||||||
|
});
|
||||||
|
|
||||||
|
(new \PaymentStatusSeeder())->run();
|
||||||
|
|
||||||
|
Schema::table('payments', function($table)
|
||||||
|
{
|
||||||
|
$table->decimal('refunded', 13, 2);
|
||||||
|
$table->unsignedInteger('payment_status_id')->default(3);
|
||||||
|
$table->foreign('payment_status_id')->references('id')->on('payment_statuses');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function down()
|
||||||
|
{
|
||||||
|
Schema::table('payments', function($table)
|
||||||
|
{
|
||||||
|
$table->dropColumn('refunded');
|
||||||
|
$table->dropForeign('payments_payment_status_id_foreign');
|
||||||
|
$table->dropColumn('payment_status_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
Schema::dropIfExists('payment_statuses');
|
||||||
|
}
|
||||||
|
}
|
@ -19,6 +19,7 @@ class DatabaseSeeder extends Seeder
|
|||||||
$this->call('FontsSeeder');
|
$this->call('FontsSeeder');
|
||||||
$this->call('BanksSeeder');
|
$this->call('BanksSeeder');
|
||||||
$this->call('InvoiceStatusSeeder');
|
$this->call('InvoiceStatusSeeder');
|
||||||
|
$this->call('PaymentStatusSeeder');
|
||||||
$this->call('CurrenciesSeeder');
|
$this->call('CurrenciesSeeder');
|
||||||
$this->call('DateFormatsSeeder');
|
$this->call('DateFormatsSeeder');
|
||||||
$this->call('InvoiceDesignsSeeder');
|
$this->call('InvoiceDesignsSeeder');
|
||||||
|
36
database/seeds/PaymentStatusSeeder.php
Normal file
36
database/seeds/PaymentStatusSeeder.php
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Models\PaymentStatus;
|
||||||
|
|
||||||
|
class PaymentStatusSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function run()
|
||||||
|
{
|
||||||
|
Eloquent::unguard();
|
||||||
|
|
||||||
|
$this->createPaymentStatuses();
|
||||||
|
|
||||||
|
Eloquent::reguard();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createPaymentStatuses()
|
||||||
|
{
|
||||||
|
$statuses = [
|
||||||
|
['id' => '1', 'name' => 'Pending'],
|
||||||
|
['id' => '2', 'name' => 'Failed'],
|
||||||
|
['id' => '3', 'name' => 'Completed'],
|
||||||
|
['id' => '4', 'name' => 'Partially Refunded'],
|
||||||
|
['id' => '5', 'name' => 'Refunded'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($statuses as $status) {
|
||||||
|
$record = PaymentStatus::find($status['id']);
|
||||||
|
if ($record) {
|
||||||
|
$record->name = $status['name'];
|
||||||
|
$record->save();
|
||||||
|
} else {
|
||||||
|
PaymentStatus::create($status);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@ class UpdateSeeder extends Seeder
|
|||||||
$this->call('FontsSeeder');
|
$this->call('FontsSeeder');
|
||||||
$this->call('BanksSeeder');
|
$this->call('BanksSeeder');
|
||||||
$this->call('InvoiceStatusSeeder');
|
$this->call('InvoiceStatusSeeder');
|
||||||
|
$this->call('PaymentStatusSeeder');
|
||||||
$this->call('CurrenciesSeeder');
|
$this->call('CurrenciesSeeder');
|
||||||
$this->call('DateFormatsSeeder');
|
$this->call('DateFormatsSeeder');
|
||||||
$this->call('InvoiceDesignsSeeder');
|
$this->call('InvoiceDesignsSeeder');
|
||||||
|
@ -1176,6 +1176,20 @@ $LANG = array(
|
|||||||
'page_size' => 'Page Size',
|
'page_size' => 'Page Size',
|
||||||
'live_preview_disabled' => 'Live preview has been disabled to support selected font',
|
'live_preview_disabled' => 'Live preview has been disabled to support selected font',
|
||||||
'invoice_number_padding' => 'Padding',
|
'invoice_number_padding' => 'Padding',
|
||||||
|
|
||||||
|
// Payment updates
|
||||||
|
'refund_payment' => 'Refund Payment',
|
||||||
|
'refund_max' => 'Max:',
|
||||||
|
'refund' => 'Refund',
|
||||||
|
'are_you_sure_refund' => 'Refund selected payments?',
|
||||||
|
'status_pending' => 'Pending',
|
||||||
|
'status_completed' => 'Completed',
|
||||||
|
'status_failed' => 'Failed',
|
||||||
|
'status_partially_refunded' => 'Partially Refunded',
|
||||||
|
'status_partially_refunded_amount' => ':amount Refunded',
|
||||||
|
'status_refunded' => 'Refunded',
|
||||||
|
'refunded_payment' => 'Refunded Payment',
|
||||||
|
'activity_39' => ':user refunded :adjustment of a :payment_amount payment (:payment)',
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -53,7 +53,41 @@
|
|||||||
->setOptions('sPaginationType', 'bootstrap')
|
->setOptions('sPaginationType', 'bootstrap')
|
||||||
->setOptions('aaSorting', [[isset($sortCol) ? $sortCol : '1', 'desc']])
|
->setOptions('aaSorting', [[isset($sortCol) ? $sortCol : '1', 'desc']])
|
||||||
->render('datatable') !!}
|
->render('datatable') !!}
|
||||||
|
|
||||||
|
@if ($entityType == ENTITY_PAYMENT)
|
||||||
|
<div class="modal fade" id="paymentRefundModal" tabindex="-1" role="dialog" aria-labelledby="paymentRefundModalLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog" style="min-width:150px">
|
||||||
|
<div class="modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||||
|
<h4 class="modal-title" id="paymentRefundModalLabel">{{ trans('texts.refund_payment') }}</h4>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="form-horizontal">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="refundAmount" class="col-sm-offset-2 col-sm-2 control-label">{{ trans('texts.amount') }}</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="input-group">
|
||||||
|
<span class="input-group-addon" id="refundCurrencySymbol"></span>
|
||||||
|
<input type="number" class="form-control" id="refundAmount" name="amount" step="0.01" min="0.01" placeholder="{{ trans('texts.amount') }}">
|
||||||
|
</div>
|
||||||
|
<div class="help-block">{{ trans('texts.refund_max') }} <span id="refundMax"></span></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal-footer" style="margin-top: 0px">
|
||||||
|
<button type="button" class="btn btn-default" data-dismiss="modal">{{ trans('texts.cancel') }}</button>
|
||||||
|
<button type="button" class="btn btn-primary" id="completeRefundButton">{{ trans('texts.refund') }}</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
{!! Former::close() !!}
|
{!! Former::close() !!}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
@ -102,6 +136,22 @@
|
|||||||
$('#public_id').val(id);
|
$('#public_id').val(id);
|
||||||
submitForm('invoice');
|
submitForm('invoice');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if ($entityType == ENTITY_PAYMENT)
|
||||||
|
var paymentId = null;
|
||||||
|
function showRefundModal(id, amount, formatted, symbol){
|
||||||
|
paymentId = id;
|
||||||
|
$('#refundCurrencySymbol').text(symbol);
|
||||||
|
$('#refundMax').text(formatted);
|
||||||
|
$('#refundAmount').val(amount).attr('max', amount);
|
||||||
|
$('#paymentRefundModal').modal('show');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleRefundClicked(){
|
||||||
|
$('#public_id').val(paymentId);
|
||||||
|
submitForm('refund');
|
||||||
|
}
|
||||||
|
@endif
|
||||||
|
|
||||||
function setTrashVisible() {
|
function setTrashVisible() {
|
||||||
var checked = $('#trashed').is(':checked');
|
var checked = $('#trashed').is(':checked');
|
||||||
@ -156,6 +206,10 @@
|
|||||||
|
|
||||||
actionListHandler();
|
actionListHandler();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if ($entityType == ENTITY_PAYMENT)
|
||||||
|
$('#completeRefundButton').click(handleRefundClicked)
|
||||||
|
@endif
|
||||||
|
|
||||||
$('.archive, .invoice').prop('disabled', true);
|
$('.archive, .invoice').prop('disabled', true);
|
||||||
$('.archive:not(.dropdown-toggle)').click(function() {
|
$('.archive:not(.dropdown-toggle)').click(function() {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user