Read the orders that bill the app's subscriptions

$user->appOrders() and app(AnikeenId::class)->appOrders() read
v1/client/orders with the app's own token (scope orders:client):
all(), pending() and find(), paginated transparently, user scoped
when called on a billable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Maurice PreußandClaude Opus 5.5 committed 2026-10-25 18:14:40 +01:00
1 parent 9613d31a8d
commit 820f1b37cc
6 files changed
+208

No files matched your search

+10
View File
@@ -2,6 +2,7 @@
namespace Anikeen\Id\Concerns;
use Anikeen\Id\Resources\AppOrders;
use Anikeen\Id\Resources\AppSubscriptions;
use Anikeen\Id\Resources\Subscriptions;
use Throwable;
@@ -35,4 +36,13 @@ trait ManagesSubscriptions
{
return new AppSubscriptions($this);
}
/**
* The user's orders that bill the app's subscriptions, read with the app's own token
* (trusted apps only, scope orders:client), e.g. ->pending() for unpaid ones.
*/
public function appOrders(): AppOrders
{
return new AppOrders($this);
}
}␍
+9
View File
@@ -2,6 +2,7 @@
namespace Anikeen\Id\Concerns;
use Anikeen\Id\Resources\AppOrders;
use Anikeen\Id\Result;
use Throwable;
@@ -10,6 +11,14 @@ use Throwable;
*/
trait ManagesUserBilling
{
/**
* Orders of all users that bill the app's subscriptions (e.g. ->pending() for unpaid ones).
*/
public function appOrders(): AppOrders
{
return new AppOrders();
}
/**
* Create an order for a user (VAT from the billing address). With `checkout`, it is paid right
* away: with the given `transaction` (a payment captured elsewhere) or by charging the user's
+88
View File
@@ -0,0 +1,88 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\AnikeenId;
use Anikeen\Id\Contracts\Billable;
use Anikeen\Id\Exceptions\ApiException;
use Anikeen\Id\Support\AppRequest;
use Illuminate\Database\Eloquent\Model;
/**
* Orders that bill the app's subscriptions, read with the app's own token (trusted apps,
* scope orders:client) - of one user ($user->appOrders()) or of all users
* (app(AnikeenId::class)->appOrders()). Nothing is requested until a method is called.
*
* Each order is an object with id, user_id, group, status (processing|pending|completed|cancelled),
* is_collective, net_total, vat_total, gross_total, currency_iso, created_at, paid_at,
* payment_attempts, next_payment_attempt_at and last_payment_error ({code, message} or null).
*/
class AppOrders
{
public function __construct(protected Billable|Model|null $billable = null)
{
}
/**
* All matching orders, oldest first.
*
* @param array{status?: string, group?: string} $filters
* @return list<object>
*
* @throws ApiException
*/
public function all(array $filters = []): array
{
$orders = [];
$page = 1;
do {
$result = AppRequest::send('GET', 'v1/client/orders', null, [
...$filters,
...($this->billable ? ['user_id' => $this->userId()] : []),
'per_page' => 100,
'page' => $page,
]);
array_push($orders, ...$result->data);
$lastPage = (int) ($result->meta->last_page ?? $page);
} while ($page++ < $lastPage);
return $orders;
}
/**
* Closed orders that aren't paid (yet), oldest first.
*
* @return list<object>
*
* @throws ApiException
*/
public function pending(array $filters = []): array
{
return $this->all([...$filters, 'status' => 'pending']);
}
/**
* @throws ApiException
*/
public function find(string $id): ?object
{
try {
$order = AppRequest::send('GET', sprintf('v1/client/orders/%s', $id))->data;
} catch (ApiException $e) {
if ($e->isNotFound()) {
return null;
}
throw $e;
}
return !$this->billable || $order->user_id === $this->userId() ? $order : null;
}
private function userId(): string
{
return (new AppSubscriptions($this->billable))->userId();
}
}