Add Ladill POS v1 — register, Pay checkout, and commerce links.
Deploy Ladill Mini / deploy (push) Successful in 23s
Deploy Ladill Mini / deploy (push) Successful in 23s
Staff-facing counter register at pos.ladill.com with catalog cart, cash and MoMo/card checkout via Ladill Pay, CRM timeline/import, invoice prefill, and Merchant catalog import. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,148 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Mini;
|
||||
|
||||
use App\Models\QrSetting;
|
||||
use App\Models\User;
|
||||
use App\Services\Billing\BillingClient;
|
||||
use App\Services\Notifications\MiniNotificationService;
|
||||
use Illuminate\Http\Client\Response as HttpResponse;
|
||||
use Illuminate\Support\Facades\Http;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Throwable;
|
||||
|
||||
class AutoWithdrawService
|
||||
{
|
||||
public function __construct(
|
||||
private BillingClient $billing,
|
||||
private MiniNotificationService $notifications,
|
||||
) {}
|
||||
|
||||
public function attemptForUser(User $user): bool
|
||||
{
|
||||
$settings = $user->getOrCreateQrSetting();
|
||||
$thresholdMinor = $settings->auto_withdraw_amount_minor;
|
||||
|
||||
if ($thresholdMinor === null || $thresholdMinor < 100) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$balanceMinor = $this->billing->balanceMinor($user->public_id);
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('Auto-withdraw balance check failed', [
|
||||
'user' => $user->public_id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($balanceMinor < $thresholdMinor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (! $this->hasPayoutAccount($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->hasPendingWithdrawal($user)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$amountMajor = round($balanceMinor / 100, 2);
|
||||
if ($amountMajor < 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
$response = $this->identitySend('POST', '/api/identity/wallet/withdraw', [
|
||||
'user' => $user->public_id,
|
||||
'amount' => $amountMajor,
|
||||
]);
|
||||
|
||||
if (! $response->successful()) {
|
||||
Log::warning('Auto-withdraw failed', [
|
||||
'user' => $user->public_id,
|
||||
'status' => $response->status(),
|
||||
'body' => $response->body(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->notifications->withdrawalSubmitted($user, $amountMajor);
|
||||
|
||||
return true;
|
||||
} catch (Throwable $e) {
|
||||
Log::warning('Auto-withdraw exception', [
|
||||
'user' => $user->public_id,
|
||||
'error' => $e->getMessage(),
|
||||
]);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public function processAll(): int
|
||||
{
|
||||
$count = 0;
|
||||
|
||||
QrSetting::query()
|
||||
->whereNotNull('auto_withdraw_amount_minor')
|
||||
->where('auto_withdraw_amount_minor', '>=', 100)
|
||||
->with('user')
|
||||
->chunkById(50, function ($settings) use (&$count) {
|
||||
foreach ($settings as $setting) {
|
||||
$user = $setting->user;
|
||||
if ($user && $this->attemptForUser($user)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
private function hasPayoutAccount(User $user): bool
|
||||
{
|
||||
$response = $this->identitySend(
|
||||
'GET',
|
||||
'/api/identity/payout-account?user='.urlencode((string) $user->public_id),
|
||||
[],
|
||||
);
|
||||
|
||||
if (! $response->successful()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return filled($response->json('data.payout_account.account_number'));
|
||||
}
|
||||
|
||||
private function hasPendingWithdrawal(User $user): bool
|
||||
{
|
||||
$response = $this->identitySend(
|
||||
'GET',
|
||||
'/api/identity/wallet/withdrawals?user='.urlencode((string) $user->public_id),
|
||||
[],
|
||||
);
|
||||
|
||||
if (! $response->successful()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return collect($response->json('data', []))
|
||||
->contains(fn ($withdrawal) => in_array($withdrawal['status'] ?? '', ['pending', 'processing'], true));
|
||||
}
|
||||
|
||||
private function identitySend(string $method, string $path, array $payload): HttpResponse
|
||||
{
|
||||
return Http::baseUrl(rtrim((string) config('services.ladill_identity.url'), '/'))
|
||||
->withToken((string) config('services.ladill_identity.key'))
|
||||
->connectTimeout(10)
|
||||
->timeout(20)
|
||||
->acceptJson()
|
||||
->asJson()
|
||||
->send($method, $path, ['json' => $payload]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user