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) {
|
||||
$payment = $account->company->payment;
|
||||
|
||||
$gateway = $this->paymentService->createGateway($payment->account_gateway);
|
||||
$refund = $gateway->refund(array(
|
||||
'transactionReference' => $payment->transaction_reference,
|
||||
'amount' => $payment->amount
|
||||
));
|
||||
$refund->send();
|
||||
$payment->delete();
|
||||
$this->paymentService->refund($payment);
|
||||
Session::flash('message', trans('texts.plan_refunded'));
|
||||
\Log::info("Refunded Plan Payment: {$account->name} - {$user->email}");
|
||||
} else {
|
||||
|
@ -57,6 +57,7 @@ class PaymentController extends BaseController
|
||||
'method',
|
||||
'payment_amount',
|
||||
'payment_date',
|
||||
'status',
|
||||
''
|
||||
]),
|
||||
));
|
||||
@ -630,11 +631,12 @@ class PaymentController extends BaseController
|
||||
public function bulk()
|
||||
{
|
||||
$action = Input::get('action');
|
||||
$amount = Input::get('amount');
|
||||
$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) {
|
||||
$message = Utils::pluralize($action.'d_payment', $count);
|
||||
$message = Utils::pluralize($action=='refund'?'refunded_payment':$action.'d_payment', $count);
|
||||
Session::flash('message', $message);
|
||||
}
|
||||
|
||||
|
@ -252,6 +252,9 @@ class PublicClientController extends BaseController
|
||||
'invoice' => trans('texts.invoice') . ' ' . $model->invoice,
|
||||
'contact' => Utils::getClientDisplayName($model),
|
||||
'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);
|
||||
@ -321,7 +324,7 @@ class PublicClientController extends BaseController
|
||||
'clientFontUrl' => $account->getFontsUrl(),
|
||||
'entityType' => ENTITY_PAYMENT,
|
||||
'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);
|
||||
@ -340,9 +343,37 @@ 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('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('status', function ($model) { return $this->getPaymentStatusLabel($model); })
|
||||
->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()
|
||||
{
|
||||
if (!$invitation = $this->getInvitation()) {
|
||||
|
@ -398,6 +398,7 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
//define('ACTIVITY_TYPE_UPDATE_PAYMENT', 11);
|
||||
define('ACTIVITY_TYPE_ARCHIVE_PAYMENT', 12);
|
||||
define('ACTIVITY_TYPE_DELETE_PAYMENT', 13);
|
||||
define('ACTIVITY_TYPE_REFUNDED_PAYMENT', 39);
|
||||
|
||||
define('ACTIVITY_TYPE_CREATE_CREDIT', 14);
|
||||
//define('ACTIVITY_TYPE_UPDATE_CREDIT', 15);
|
||||
@ -475,6 +476,12 @@ if (!defined('CONTACT_EMAIL')) {
|
||||
define('INVOICE_STATUS_PARTIAL', 5);
|
||||
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('CUSTOM_DESIGN', 11);
|
||||
|
||||
|
@ -22,6 +22,7 @@ use App\Events\QuoteInvitationWasViewed;
|
||||
use App\Events\QuoteInvitationWasApproved;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Events\PaymentWasArchived;
|
||||
use App\Events\PaymentWasRestored;
|
||||
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)
|
||||
{
|
||||
if ($event->payment->is_deleted) {
|
||||
|
@ -3,6 +3,7 @@
|
||||
use Carbon;
|
||||
use App\Models\Credit;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Ninja\Repositories\CreditRepository;
|
||||
|
||||
class CreditListener
|
||||
@ -26,7 +27,24 @@ class CreditListener
|
||||
$credit = Credit::createNew();
|
||||
$credit->client_id = $payment->client_id;
|
||||
$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->save();
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ use App\Events\InvoiceWasUpdated;
|
||||
use App\Events\InvoiceWasCreated;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasDeleted;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use App\Events\PaymentWasRestored;
|
||||
use App\Events\InvoiceInvitationWasViewed;
|
||||
|
||||
@ -58,7 +59,17 @@ class InvoiceListener
|
||||
{
|
||||
$payment = $event->payment;
|
||||
$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->updatePaidStatus();
|
||||
@ -72,7 +83,7 @@ class InvoiceListener
|
||||
|
||||
$payment = $event->payment;
|
||||
$invoice = $payment->invoice;
|
||||
$adjustment = $payment->amount * -1;
|
||||
$adjustment = ($payment->amount - $payment->refunded) * -1;
|
||||
|
||||
$invoice->updateBalances($adjustment);
|
||||
$invoice->updatePaidStatus();
|
||||
|
@ -70,6 +70,8 @@ class Activity extends Eloquent
|
||||
'quote' => $invoice ? link_to($invoice->getRoute(), $invoice->getDisplayName()) : null,
|
||||
'contact' => $contactId ? $client->getDisplayName() : $user->getDisplayName(),
|
||||
'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,
|
||||
];
|
||||
|
||||
|
@ -1,7 +1,9 @@
|
||||
<?php namespace App\Models;
|
||||
|
||||
use Event;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use App\Events\PaymentWasCreated;
|
||||
use App\Events\PaymentWasRefunded;
|
||||
use Laracasts\Presenter\PresentableTrait;
|
||||
|
||||
class Payment extends EntityModel
|
||||
@ -52,6 +54,11 @@ class Payment extends EntityModel
|
||||
return $this->belongsTo('App\Models\PaymentType');
|
||||
}
|
||||
|
||||
public function payment_status()
|
||||
{
|
||||
return $this->belongsTo('App\Models\PaymentStatus');
|
||||
}
|
||||
|
||||
public function getRoute()
|
||||
{
|
||||
return "/payments/{$this->public_id}/edit";
|
||||
@ -69,6 +76,51 @@ class Payment extends EntityModel
|
||||
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()
|
||||
{
|
||||
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.email as email',
|
||||
'payments.transaction_reference as payment',
|
||||
'payments.amount as payment_amount',
|
||||
'credits.amount as credit'
|
||||
);
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ class PaymentRepository extends BaseRepository
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_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('account_gateways', 'account_gateways.id', '=', 'payments.account_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',
|
||||
'payments.amount',
|
||||
'payments.payment_date',
|
||||
'payments.payment_status_id',
|
||||
'payments.payment_type_id',
|
||||
'invoices.public_id as invoice_public_id',
|
||||
'invoices.user_id as invoice_user_id',
|
||||
'invoices.invoice_number',
|
||||
@ -50,8 +53,11 @@ class PaymentRepository extends BaseRepository
|
||||
'payments.deleted_at',
|
||||
'payments.is_deleted',
|
||||
'payments.user_id',
|
||||
'payments.refunded',
|
||||
'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')) {
|
||||
@ -78,6 +84,7 @@ class PaymentRepository extends BaseRepository
|
||||
->join('clients', 'clients.id', '=', 'payments.client_id')
|
||||
->join('invoices', 'invoices.id', '=', 'payments.invoice_id')
|
||||
->join('contacts', 'contacts.client_id', '=', 'clients.id')
|
||||
->join('payment_statuses', 'payment_statuses.id', '=', 'payments.payment_status_id')
|
||||
->leftJoin('invitations', function ($join) {
|
||||
$join->on('invitations.invoice_id', '=', 'invoices.id')
|
||||
->on('invitations.contact_id', '=', 'contacts.id');
|
||||
@ -104,7 +111,10 @@ class PaymentRepository extends BaseRepository
|
||||
'contacts.last_name',
|
||||
'contacts.email',
|
||||
'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) {
|
||||
@ -189,6 +199,4 @@ class PaymentRepository extends BaseRepository
|
||||
|
||||
parent::restore($payment);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -107,6 +107,11 @@ class EventServiceProvider extends ServiceProvider {
|
||||
'App\Listeners\InvoiceListener@deletedPayment',
|
||||
'App\Listeners\CreditListener@deletedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasRefunded' => [
|
||||
'App\Listeners\ActivityListener@refundedPayment',
|
||||
'App\Listeners\InvoiceListener@refundedPayment',
|
||||
'App\Listeners\CreditListener@refundedPayment',
|
||||
],
|
||||
'App\Events\PaymentWasRestored' => [
|
||||
'App\Listeners\ActivityListener@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,
|
||||
'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 ?: '',
|
||||
'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);
|
||||
|
@ -24,6 +24,10 @@ class PaymentService extends BaseService
|
||||
public $lastError;
|
||||
protected $datatableService;
|
||||
|
||||
protected static $refundableGateways = array(
|
||||
GATEWAY_STRIPE
|
||||
);
|
||||
|
||||
public function __construct(PaymentRepository $paymentRepo, AccountRepository $accountRepo, DatatableService $datatableService)
|
||||
{
|
||||
$this->datatableService = $datatableService;
|
||||
@ -375,6 +379,12 @@ class PaymentService extends BaseService
|
||||
function ($model) {
|
||||
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) {
|
||||
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('BanksSeeder');
|
||||
$this->call('InvoiceStatusSeeder');
|
||||
$this->call('PaymentStatusSeeder');
|
||||
$this->call('CurrenciesSeeder');
|
||||
$this->call('DateFormatsSeeder');
|
||||
$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('BanksSeeder');
|
||||
$this->call('InvoiceStatusSeeder');
|
||||
$this->call('PaymentStatusSeeder');
|
||||
$this->call('CurrenciesSeeder');
|
||||
$this->call('DateFormatsSeeder');
|
||||
$this->call('InvoiceDesignsSeeder');
|
||||
|
@ -1177,6 +1177,20 @@ $LANG = array(
|
||||
'live_preview_disabled' => 'Live preview has been disabled to support selected font',
|
||||
'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)',
|
||||
|
||||
);
|
||||
|
||||
return $LANG;
|
||||
|
@ -54,6 +54,40 @@
|
||||
->setOptions('aaSorting', [[isset($sortCol) ? $sortCol : '1', 'desc']])
|
||||
->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() !!}
|
||||
|
||||
<script type="text/javascript">
|
||||
@ -103,6 +137,22 @@
|
||||
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() {
|
||||
var checked = $('#trashed').is(':checked');
|
||||
var url = '{{ URL::to('view_archive/' . $entityType) }}' + (checked ? '/true' : '/false');
|
||||
@ -157,6 +207,10 @@
|
||||
actionListHandler();
|
||||
}
|
||||
|
||||
@if ($entityType == ENTITY_PAYMENT)
|
||||
$('#completeRefundButton').click(handleRefundClicked)
|
||||
@endif
|
||||
|
||||
$('.archive, .invoice').prop('disabled', true);
|
||||
$('.archive:not(.dropdown-toggle)').click(function() {
|
||||
submitForm('archive');
|
||||
|
Loading…
x
Reference in New Issue
Block a user