165 lines
5.1 KiB
PHP
165 lines
5.1 KiB
PHP
<?php
|
|
|
|
namespace App\Services\Moneybird;
|
|
|
|
use Illuminate\Http\Client\PendingRequest;
|
|
use Illuminate\Http\Client\Response;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Thin wrapper around the Moneybird REST API (https://developer.moneybird.com)
|
|
* exposing only the calls subscription billing needs. Every method returns
|
|
* plain decoded-JSON arrays rather than a bespoke SDK's value objects, and
|
|
* throws MoneybirdException on unexpected failures.
|
|
*/
|
|
class MoneybirdClient
|
|
{
|
|
private function http(): PendingRequest
|
|
{
|
|
$administrationId = config('services.moneybird.administration_id');
|
|
|
|
if (blank(config('services.moneybird.api_token')) || blank($administrationId)) {
|
|
throw new MoneybirdException('Moneybird is not configured — set MONEYBIRD_API_TOKEN and MONEYBIRD_ADMINISTRATION_ID (see `php artisan moneybird:setup`).');
|
|
}
|
|
|
|
return Http::withToken(config('services.moneybird.api_token'))
|
|
->baseUrl(rtrim(config('services.moneybird.base_url'), '/').'/'.$administrationId)
|
|
->acceptJson()
|
|
->timeout(15);
|
|
}
|
|
|
|
public function findContactByCustomerId(string $customerId): ?array
|
|
{
|
|
$response = $this->http()->get("contacts/customer_id/{$customerId}.json");
|
|
|
|
if ($response->status() === 404) {
|
|
return null;
|
|
}
|
|
|
|
return $this->decode($response, "GET contacts/customer_id/{$customerId}");
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function createContact(array $attributes): array
|
|
{
|
|
$response = $this->http()->post('contacts.json', ['contact' => $attributes]);
|
|
|
|
return $this->decode($response, 'POST contacts');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function createSalesInvoice(array $attributes): array
|
|
{
|
|
$response = $this->http()->post('sales_invoices.json', ['sales_invoice' => $attributes]);
|
|
|
|
return $this->decode($response, 'POST sales_invoices');
|
|
}
|
|
|
|
public function findSalesInvoice(string $id): array
|
|
{
|
|
$response = $this->http()->get("sales_invoices/{$id}.json");
|
|
|
|
return $this->decode($response, "GET sales_invoices/{$id}");
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function createRecurringSalesInvoice(array $attributes): array
|
|
{
|
|
$response = $this->http()->post('recurring_sales_invoices.json', ['recurring_sales_invoice' => $attributes]);
|
|
|
|
return $this->decode($response, 'POST recurring_sales_invoices');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function updateRecurringSalesInvoice(string $id, array $attributes): array
|
|
{
|
|
$response = $this->http()->patch("recurring_sales_invoices/{$id}.json", ['recurring_sales_invoice' => $attributes]);
|
|
|
|
return $this->decode($response, "PATCH recurring_sales_invoices/{$id}");
|
|
}
|
|
|
|
public function deleteRecurringSalesInvoice(string $id): void
|
|
{
|
|
$response = $this->http()->delete("recurring_sales_invoices/{$id}.json");
|
|
|
|
// Already gone is fine — deleting is meant to be idempotent here.
|
|
if ($response->status() === 404) {
|
|
return;
|
|
}
|
|
|
|
$this->decode($response, "DELETE recurring_sales_invoices/{$id}", allowEmpty: true);
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function listAdministrations(): array
|
|
{
|
|
$response = Http::withToken(config('services.moneybird.api_token'))
|
|
->acceptJson()
|
|
->timeout(15)
|
|
->get(rtrim(config('services.moneybird.base_url'), '/').'/administrations.json');
|
|
|
|
return $this->decode($response, 'GET administrations');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function listTaxRates(): array
|
|
{
|
|
$response = $this->http()->get('tax_rates.json');
|
|
|
|
return $this->decode($response, 'GET tax_rates');
|
|
}
|
|
|
|
/**
|
|
* @return array<int, array<string, mixed>>
|
|
*/
|
|
public function listLedgerAccounts(): array
|
|
{
|
|
$response = $this->http()->get('ledger_accounts.json');
|
|
|
|
return $this->decode($response, 'GET ledger_accounts');
|
|
}
|
|
|
|
/**
|
|
* @param array<string, mixed> $attributes
|
|
*/
|
|
public function createWebhook(array $attributes): array
|
|
{
|
|
$response = $this->http()->post('webhooks.json', $attributes);
|
|
|
|
return $this->decode($response, 'POST webhooks');
|
|
}
|
|
|
|
/**
|
|
* @return array<mixed>
|
|
*/
|
|
private function decode(Response $response, string $context, bool $allowEmpty = false): array
|
|
{
|
|
if ($response->failed()) {
|
|
Log::error("Moneybird API request failed: {$context}", [
|
|
'status' => $response->status(),
|
|
'body' => $response->body(),
|
|
]);
|
|
|
|
throw new MoneybirdException("Moneybird API request failed ({$context}): HTTP {$response->status()}");
|
|
}
|
|
|
|
if ($allowEmpty && blank($response->body())) {
|
|
return [];
|
|
}
|
|
|
|
return $response->json() ?? [];
|
|
}
|
|
}
|