Skip to content

Commit

Permalink
Add daily check for possible duplicate payments
Browse files Browse the repository at this point in the history
  • Loading branch information
rjackson committed Apr 7, 2024
1 parent 6c78ad2 commit 07758ad
Show file tree
Hide file tree
Showing 12 changed files with 207 additions and 108 deletions.
69 changes: 69 additions & 0 deletions app/Console/Commands/Payments/CheckForPossibleDuplicates.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

namespace BB\Console\Commands\Payments;

use BB\Entities\Payment;
use BB\Repo\PaymentRepository;
use Illuminate\Console\Command;

class CheckForPossibleDuplicates extends Command
{
/**
* The name and signature of the console command.
*
* @var string
*/
protected $signature = 'payments:check-for-possible-duplicates';

/**
* The console command description.
*
* @var string
*/
protected $description = 'Users can accidentally queue up duplicate payments, this command will check for possible duplicates and alert the board to review them.';

/** @var PaymentRepository */
protected $paymentRepository;

/**
* Create a new command instance.
*
* @return void
*/
public function __construct(PaymentRepository $paymentRepository)
{
parent::__construct();

$this->paymentRepository = $paymentRepository;
}

/**
* Execute the console command.
*
* @return mixed
*/
public function handle()
{
$possibleDuplicates = $this->paymentRepository->getPossibleDuplicates();
if ($possibleDuplicates->isEmpty()) {
return;
}

$this->output->title('Possible Duplicate Payments');
$this->output->text('The following users have multiple payments pending up with the same reason and amount.');
$this->output->table(
['User', 'Reason', 'Amount', 'Count'],
$possibleDuplicates->map(function ($payment) {
return [
$payment->user->name,
$payment->reason,
$payment->amount,
$payment->count
];
})->toArray()
);

$this->output->text('Please review these payments and ensure they are not duplicates.');
$this->output->text(route('payments.possible-duplicates'));
}
}
50 changes: 29 additions & 21 deletions app/Console/Kernel.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php namespace BB\Console;
<?php

namespace BB\Console;

use Illuminate\Console\Scheduling\Schedule;
use Illuminate\Foundation\Console\Kernel as ConsoleKernel;
Expand All @@ -21,9 +23,10 @@ class Kernel extends ConsoleKernel
Commands\BillMembers::class,
Commands\CheckDeviceOnlineStatuses::class,
Commands\TestScheduledTask::class,
Commands\Payments\CheckForPossibleDuplicates::class,
];


/**
* Define the application's command schedule.
*
Expand All @@ -37,48 +40,53 @@ protected function schedule(Schedule $schedule)
$schedule
->command(Commands\CheckMembershipStatus::class)
->dailyAt('06:00')
->then( function () use ($telegram) {
->then(function () use ($telegram) {
$message = "✔️ Checked Memberships";
\Log::info($message);
\Log::info($message);
$telegram->notify(
TelegramHelper::JOB,
TelegramHelper::JOB,
$message
);
});

$schedule
->command(Commands\CreateTodaysSubCharges::class)
->dailyAt('01:00')
->then( function () use ($telegram) {
->then(function () use ($telegram) {
$message = "✔️ Created today's subscription charges";
\Log::info($message);
\Log::info($message);
$telegram->notify(
TelegramHelper::JOB,
TelegramHelper::JOB,
$message
);
} );
});

$schedule
->command(Commands\BillMembers::class)
->dailyAt('01:30')
->then( function ($result) use ($telegram) {
->then(function ($result) use ($telegram) {
$message = "✅ Billed members: " . $result['gc_users'] . " GC users, " . $result['gc_users_blled'] . " bills created.";
\Log::info($message);
\Log::info($message);
$telegram->notify(
TelegramHelper::JOB,
$message
);
});

$schedule
->command(Commands\TestScheduledTask::class)
->hourly()
->then(function ($result) use ($telegram) {
$message = "✔️ Test Scheduled Task successfully ran (notification from 'then' hook)";
$telegram->notify(
TelegramHelper::JOB,
TelegramHelper::JOB,
$message
);
});

$schedule
->command(Commands\TestScheduledTask::class)
->hourly()
->then(function($result) use ($telegram) {
$message = "✔️ Test Scheduled Task successfully ran (notification from 'then' hook)";
$telegram->notify(
TelegramHelper::JOB,
$message
);
});
->command(Commands\Payments\CheckForPossibleDuplicates::class)
->dailyAt('16:30')
->emailOutputTo('[email protected]', true);
}
}
7 changes: 6 additions & 1 deletion app/Entities/Payment.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,14 @@

class Payment extends Model
{

use PresentableTrait;

const STATUS_PENDING = 'pending';
const STATUS_PENDING_SUBMISSION = 'pending_submission';
const STATUS_CANCELLED = 'cancelled';
const STATUS_PAID = 'paid';
const STATUS_WITHDRAWN = 'withdrawn';

/**
* The database table used by the model.
*
Expand Down
53 changes: 17 additions & 36 deletions app/Http/Controllers/PaymentController.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php namespace BB\Http\Controllers;
<?php

namespace BB\Http\Controllers;

use BB\Entities\Induction;
use BB\Entities\Payment;
Expand Down Expand Up @@ -50,7 +52,6 @@ function __construct(
$this->subscriptionChargeRepository = $subscriptionChargeRepository;

$this->middleware('role:member', array('only' => ['create', 'destroy']));

}


Expand Down Expand Up @@ -134,11 +135,11 @@ public function store($userId)
{
$user = User::findWithPermission($userId);

if ( ! \Auth::user()->hasRole('admin') && ! \Auth::user()->hasRole('finance')) {
if (!\Auth::user()->hasRole('admin') && !\Auth::user()->hasRole('finance')) {
throw new \BB\Exceptions\AuthenticationException;
}

\Log::debug('Manual payment endpoint getting hit. account/{id}/payment. paymentController@store '.json_encode(\Input::all()));
\Log::debug('Manual payment endpoint getting hit. account/{id}/payment. paymentController@store ' . json_encode(\Input::all()));

$reason = \Input::get('reason');

Expand All @@ -154,7 +155,6 @@ public function store($userId)
$user->payments()->save($payment);

$user->extendMembership(\Input::get('source'), \Carbon\Carbon::now()->addMonth());

} elseif ($reason == 'induction') {
if (\Input::get('source') == 'manual') {
$ref = \Input::get('induction_key');
Expand Down Expand Up @@ -191,7 +191,6 @@ public function store($userId)

$user->key_deposit_payment_id = $payment->id;
$user->save();

} elseif ($reason == 'storage-box') {
$payment = new Payment([
'reason' => $reason,
Expand All @@ -207,7 +206,7 @@ public function store($userId)
$user->save();
} elseif ($reason == 'balance') {
$amount = \Input::get('amount') * 1; //convert the users amount into a number
if ( ! is_numeric($amount)) {
if (!is_numeric($amount)) {
$exceptionErrors = new \Illuminate\Support\MessageBag(['amount' => 'Invalid amount']);
throw new \BB\Exceptions\FormValidationException('Not a valid amount', $exceptionErrors);
}
Expand Down Expand Up @@ -237,31 +236,6 @@ public function store($userId)
return \Redirect::route('account.show', [$user->id]);
}


/**
* Display the specified resource.
*
* @param int $id
* @return Response
*/
public function show($id)
{
//
}


/**
* Show the form for editing the specified resource.
*
* @param int $id
* @return Response
*/
public function edit($id)
{
//
}


/**
* Update a payment
* Change where the money goes by altering the original record or creating a secondary payment
Expand All @@ -277,7 +251,7 @@ public function update(Request $request, $paymentId)
{
$payment = $this->paymentRepository->getById($paymentId);

switch($request->get('change')) {
switch ($request->get('change')) {
case 'assign-unknown-to-user':
$newUserId = $request->get('user_id');
try {
Expand Down Expand Up @@ -360,7 +334,6 @@ public function migrateDD()
return \Redirect::back();
}
} catch (\Exception $e) {

}

$user->payment_method = '';
Expand All @@ -369,8 +342,8 @@ public function migrateDD()

$payment_details = array(
"description" => "Hackspace Manchester",
'success_redirect_url' => str_replace('http://','https://',route('account.subscription.store', $user->id)),
"session_token" => 'user-token-'.$user->id,
'success_redirect_url' => str_replace('http://', 'https://', route('account.subscription.store', $user->id)),
"session_token" => 'user-token-' . $user->id,
'prefilled_customer' => [
'given_name' => $user->given_name,
'family_name' => $user->family_name,
Expand All @@ -386,4 +359,12 @@ public function migrateDD()
return \Redirect::to($this->goCardless->newPreAuthUrl($user, $payment_details));
}

public function possibleDuplicates()
{
$possibleDuplicates = $this->paymentRepository->getPossibleDuplicates();

return view('payments.possible-duplicates', [
'possibleDuplicates' => $possibleDuplicates
]);
}
}
11 changes: 6 additions & 5 deletions app/Presenters/PaymentPresenter.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php namespace BB\Presenters;

use BB\Entities\Payment;
use Laracasts\Presenter\Presenter;

class PaymentPresenter extends Presenter
Expand Down Expand Up @@ -38,14 +39,14 @@ public function reason()
public function status()
{
switch ($this->entity->status) {
case 'pending':
case Payment::STATUS_PENDING:
return 'Pending confirmation';
case 'pending_submission':
case Payment::STATUS_PENDING_SUBMISSION:
return 'Pending submission to members bank';
case 'cancelled':
case PaymenT::STATUS_CANCELLED:
return 'Cancelled';
case 'paid':
case 'withdrawn':
case Payment::STATUS_PAID:
case Payment::STATUS_WITHDRAWN:
return 'Paid';
default:
return $this->entity->status;
Expand Down
24 changes: 18 additions & 6 deletions app/Repo/PaymentRepository.php
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
<?php namespace BB\Repo;
<?php

namespace BB\Repo;

use BB\Entities\Payment;
use BB\Events\MemberBalanceChanged;
Expand Down Expand Up @@ -102,7 +104,7 @@ public function recordPayment($reason, $userId, $source, $sourceId, $amount, $st
}
//If we have an existing similer record dont create another, except for when there is no source id
$existingRecord = $this->model->where('source', $source)->where('source_id', $sourceId)->where('user_id', $userId)->first();
if ( ! $existingRecord || empty($sourceId)) {
if (!$existingRecord || empty($sourceId)) {
$record = new $this->model;
$record->user_id = $userId;
$record->reason = $reason;
Expand Down Expand Up @@ -292,7 +294,7 @@ public function getPaymentsByReference($reference)
*/
public function getEquipmentFeePayments($referencePrefix)
{
return $this->model->where('reason', 'equipment-fee')->get()->filter(function($payment) use($referencePrefix) {
return $this->model->where('reason', 'equipment-fee')->get()->filter(function ($payment) use ($referencePrefix) {
return strpos($payment->reference, ':' . $referencePrefix) !== false;
});
}
Expand Down Expand Up @@ -370,7 +372,7 @@ public function reasonFilter($reasonFilter)

private function hasReasonFilter()
{
return ! is_null($this->reason);
return !is_null($this->reason);
}

/**
Expand All @@ -385,7 +387,7 @@ public function sourceFilter($sourceFilter)

private function hasSourceFilter()
{
return ! is_null($this->source);
return !is_null($this->source);
}

/**
Expand Down Expand Up @@ -428,4 +430,14 @@ public function recordBalanceTransfer($sourceUserId, $targetUserId, $amount)
event(new MemberBalanceChanged($sourceUserId));
event(new MemberBalanceChanged($targetUserId));
}
}

public function getPossibleDuplicates()
{
return $this->model
->select('user_id', 'reason', 'amount', \DB::raw('count(*) as count'))
->where('status', Payment::STATUS_PENDING)
->groupBy('user_id', 'reason', 'amount')
->havingRaw('count(*) > 1')
->get();
}
}
Loading

0 comments on commit 07758ad

Please sign in to comment.