Mini API: avatar, wallet payout/withdraw, stale-payment auto-cancel.
Deploy Ladill Mini / deploy (push) Successful in 48s
Deploy Ladill Mini / deploy (push) Successful in 48s
Adds account avatar upload (multipart proxy to identity), wallet payout-account + withdrawal endpoints, and a hourly mini:cancel-stale-payments command that cancels payments pending beyond 6h (new MiniPayment::STATUS_CANCELED). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c39e38cbe0
commit
af1f197059
@@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\MiniPayment;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Cancels Mini payments left pending longer than MiniPayment::STALE_PENDING_HOURS.
|
||||
* Scheduled hourly (routes/console.php).
|
||||
*/
|
||||
class CancelStalePayments extends Command
|
||||
{
|
||||
protected $signature = 'mini:cancel-stale-payments';
|
||||
|
||||
protected $description = 'Cancel Ladill Mini payments stuck pending beyond the stale window.';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$cutoff = now()->subHours(MiniPayment::STALE_PENDING_HOURS);
|
||||
|
||||
$count = MiniPayment::query()
|
||||
->where('status', MiniPayment::STATUS_PENDING)
|
||||
->where('created_at', '<', $cutoff)
|
||||
->update([
|
||||
'status' => MiniPayment::STATUS_CANCELED,
|
||||
'updated_at' => now(),
|
||||
]);
|
||||
|
||||
$this->info("Canceled {$count} stale pending payment(s).");
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ use App\Http\Controllers\Controller;
|
||||
use App\Models\QrSetting;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
|
||||
class AccountController extends Controller
|
||||
{
|
||||
@@ -89,6 +90,34 @@ class AccountController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function uploadAvatar(Request $request): JsonResponse
|
||||
{
|
||||
$account = ladill_account();
|
||||
|
||||
$request->validate([
|
||||
'avatar' => ['required', 'image', 'mimes:jpg,jpeg,png,webp', 'max:4096'],
|
||||
]);
|
||||
|
||||
$file = $request->file('avatar');
|
||||
|
||||
$response = Http::baseUrl(rtrim((string) config('services.ladill_identity.url'), '/'))
|
||||
->withToken((string) config('services.ladill_identity.key'))
|
||||
->acceptJson()
|
||||
->attach('avatar', (string) file_get_contents($file->getRealPath()), $file->getClientOriginalName())
|
||||
->post('/api/identity/avatar', ['user' => $account->public_id]);
|
||||
|
||||
$this->rethrowValidation($response, 'avatar');
|
||||
|
||||
if ($response->successful()) {
|
||||
$picture = (string) $response->json('data.user.picture', '');
|
||||
if ($picture !== '') {
|
||||
$account->update(['avatar_url' => $picture]);
|
||||
}
|
||||
}
|
||||
|
||||
return response()->json(['data' => ['avatar_url' => $account->fresh()->avatarUrl()]]);
|
||||
}
|
||||
|
||||
public function changePassword(Request $request): JsonResponse
|
||||
{
|
||||
$account = ladill_account();
|
||||
|
||||
@@ -62,4 +62,54 @@ class WalletController extends Controller
|
||||
|
||||
return response()->json(['data' => ['checkout_url' => $checkoutUrl]]);
|
||||
}
|
||||
|
||||
public function payoutAccount(): JsonResponse
|
||||
{
|
||||
$response = $this->identitySend('GET', '/api/identity/payout-account?user='.urlencode((string) ladill_account()->public_id), []);
|
||||
|
||||
return response()->json(['data' => ['payout_account' => $response->json('data.payout_account')]]);
|
||||
}
|
||||
|
||||
public function updatePayoutAccount(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'account_type' => ['required', 'in:mobile_money,bank_account'],
|
||||
'account_name' => ['required', 'string', 'max:200'],
|
||||
'account_number' => ['required', 'string', 'max:30'],
|
||||
'bank_code' => ['required', 'string', 'max:30'],
|
||||
'bank_name' => ['required', 'string', 'max:200'],
|
||||
'currency' => ['sometimes', 'string', 'size:3'],
|
||||
]);
|
||||
$data['currency'] = $data['currency'] ?? 'GHS';
|
||||
|
||||
$response = $this->identitySend('PUT', '/api/identity/payout-account', array_merge(
|
||||
['user' => ladill_account()->public_id],
|
||||
$data,
|
||||
));
|
||||
$this->rethrowValidation($response, 'account_number');
|
||||
|
||||
return response()->json(['data' => ['payout_account' => $response->json('data.payout_account')]]);
|
||||
}
|
||||
|
||||
public function withdrawals(): JsonResponse
|
||||
{
|
||||
$response = $this->identitySend('GET', '/api/identity/wallet/withdrawals?user='.urlencode((string) ladill_account()->public_id), []);
|
||||
|
||||
return response()->json(['data' => $response->json('data', [])]);
|
||||
}
|
||||
|
||||
public function withdraw(Request $request): JsonResponse
|
||||
{
|
||||
$data = $request->validate([
|
||||
'amount' => ['required', 'numeric', 'min:1', 'max:50000'],
|
||||
]);
|
||||
|
||||
$response = $this->identitySend('POST', '/api/identity/wallet/withdraw', [
|
||||
'user' => ladill_account()->public_id,
|
||||
'amount' => (float) $data['amount'],
|
||||
]);
|
||||
$this->rethrowValidation($response, 'amount');
|
||||
|
||||
return response()->json(['data' => $response->json('data', [])]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,10 @@ class MiniPayment extends Model
|
||||
public const STATUS_PENDING = 'pending';
|
||||
public const STATUS_PAID = 'paid';
|
||||
public const STATUS_FAILED = 'failed';
|
||||
public const STATUS_CANCELED = 'canceled';
|
||||
|
||||
/** Pending payments are auto-canceled after this many hours. */
|
||||
public const STALE_PENDING_HOURS = 6;
|
||||
|
||||
/** Platform fee on trader payments (Ladill Mini tier). */
|
||||
public const PLATFORM_FEE_RATE = 0.05;
|
||||
|
||||
Reference in New Issue
Block a user