Files
ladill-qr-plus/app/Services/Billing/BillingClient.php
T
isaaccladandCursor cd6571f199
Deploy Ladill QR Plus / deploy (push) Failing after 1s
Extract Ladill QR Plus as standalone app at qr.ladill.com.
Utility QR types only (URL, WiFi, link list, business, app download) with
SSO, Billing API integration, public /q resolver, and qr-plus:import for
platform migration. vCard and commerce types stay on the platform.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-06 21:31:24 +00:00

88 lines
2.9 KiB
PHP

<?php
namespace App\Services\Billing;
use Illuminate\Support\Facades\Http;
/**
* Client for the platform Billing HTTP API — the one UserWallet lives on the
* platform; Bird only consumes it. Amounts are integer minor units; the user is
* identified by public_id. Authenticates with the per-consumer config('billing.service') key.
* Contract validated in the monolith (smtp-extraction-runbook §4-A).
*/
class BillingClient
{
private function base(): string
{
return rtrim((string) config('billing.api_url'), '/');
}
private function token(): string
{
return (string) (config('billing.api_key') ?? '');
}
private function get(string $path, array $query): array
{
$res = Http::withToken($this->token())->acceptJson()->timeout(10)->get($this->base().$path, $query);
$res->throw();
return (array) $res->json();
}
public function balanceMinor(string $publicId): int
{
return (int) ($this->get('/balance', ['user' => $publicId])['balance_minor'] ?? 0);
}
public function canAfford(string $publicId, int $amountMinor): bool
{
return (bool) ($this->get('/can-afford', ['user' => $publicId, 'amount_minor' => $amountMinor])['affordable'] ?? false);
}
public function serviceLedger(string $publicId, string $service): array
{
return $this->get('/service-ledger', ['user' => $publicId, 'service' => $service]);
}
/**
* Debit the wallet. Returns true on success, false on insufficient balance
* (HTTP 402). Idempotent by $reference.
*/
public function debit(string $publicId, int $amountMinor, string $service, string $source, string $reference, ?int $serviceId = null, ?string $description = null): bool
{
$res = Http::withToken($this->token())->acceptJson()->timeout(10)->post($this->base().'/debit', array_filter([
'user' => $publicId,
'amount_minor' => $amountMinor,
'service' => $service,
'source' => $source,
'reference' => $reference,
'service_id' => $serviceId,
'description' => $description,
], static fn ($v) => $v !== null));
if ($res->status() === 402) {
return false;
}
$res->throw();
return true;
}
public function credit(string $publicId, int $amountMinor, string $service, string $source, string $reference, ?int $serviceId = null, ?string $description = null): array
{
$res = Http::withToken($this->token())->acceptJson()->timeout(10)->post($this->base().'/credit', array_filter([
'user' => $publicId,
'amount_minor' => $amountMinor,
'service' => $service,
'source' => $source,
'reference' => $reference,
'service_id' => $serviceId,
'description' => $description,
], static fn ($v) => $v !== null));
$res->throw();
return (array) $res->json();
}
}