mirror of
https://github.com/anikeen-com/id.git
synced 2026-11-03 22:29:33 +00:00
Add checkout sessions and webhook event type constants
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a0470111fa
commit
3acd36ca29
12 files changed
+708
-7
No files matched your search
@@ -0,0 +1,97 @@
|
||||
<?php
|
||||
|
||||
namespace Anikeen\Id\Resources;
|
||||
|
||||
use Anikeen\Id\Concerns\IsCheckoutSession;
|
||||
use Anikeen\Id\Exceptions\ApiException;
|
||||
use Anikeen\Id\Support\AppRequest;
|
||||
use JsonSerializable;
|
||||
|
||||
/**
|
||||
* A checkout session created with the app's own token, see AppCheckoutSessions.
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $user_id
|
||||
* @property ?string $oauth_client_id
|
||||
* @property string $status open, completed, cancelled or expired
|
||||
* @property ?string $return_url
|
||||
* @property ?object $metadata
|
||||
* @property ?string $confirmation_url
|
||||
* @property list<string> $subscription_ids
|
||||
* @property list<object> $subscriptions
|
||||
* @property ?string $expires_at
|
||||
* @property ?string $completed_at
|
||||
* @property ?string $cancelled_at
|
||||
* @property ?string $created_at
|
||||
*/
|
||||
class AppCheckoutSession implements JsonSerializable
|
||||
{
|
||||
use IsCheckoutSession;
|
||||
|
||||
public const STATUS_OPEN = 'open';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
public function __construct(protected object $data)
|
||||
{
|
||||
}
|
||||
|
||||
public function __get(string $name): mixed
|
||||
{
|
||||
return $this->data->{$name} ?? null;
|
||||
}
|
||||
|
||||
public function __isset(string $name): bool
|
||||
{
|
||||
return isset($this->data->{$name});
|
||||
}
|
||||
|
||||
/**
|
||||
* The session's subscriptions (drafts until the user confirms).
|
||||
*
|
||||
* @return list<AppSubscription>
|
||||
*/
|
||||
public function subscriptions(): array
|
||||
{
|
||||
return array_map(fn (object $subscription) => new AppSubscription($subscription), array_values((array) ($this->data->subscriptions ?? [])));
|
||||
}
|
||||
|
||||
/**
|
||||
* Withdraw an open session (e.g. the cart changed): it and its drafts are cancelled.
|
||||
*
|
||||
* @throws ApiException 409 "checkout_session_completed", "checkout_session_cancelled", "checkout_session_expired"
|
||||
*/
|
||||
public function cancel(): self
|
||||
{
|
||||
return $this->send('POST', '/cancel');
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ApiException
|
||||
*/
|
||||
public function refresh(): self
|
||||
{
|
||||
return $this->send('GET', '');
|
||||
}
|
||||
|
||||
public function toArray(): array
|
||||
{
|
||||
return (array) $this->data;
|
||||
}
|
||||
|
||||
public function jsonSerialize(): array
|
||||
{
|
||||
return $this->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws ApiException
|
||||
*/
|
||||
private function send(string $method, string $action): self
|
||||
{
|
||||
$this->data = AppRequest::send($method, sprintf('v1/client/checkout-sessions/%s%s', $this->id, $action))->data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
namespace Anikeen\Id\Resources;
|
||||
|
||||
use Anikeen\Id\Contracts\Billable;
|
||||
use Anikeen\Id\Exceptions\ApiException;
|
||||
use Anikeen\Id\Support\AppRequest;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* Checkout sessions created with the app's own token (trusted apps, scope `subscriptions:client`):
|
||||
* several subscriptions of one user that the user confirms together on Anikeen ID, instead of the
|
||||
* app checking them out itself. For one user ($user->appCheckoutSessions()) or, to look sessions
|
||||
* up e.g. from a webhook, for all users (app(AnikeenId::class)->appCheckoutSessions()).
|
||||
* Nothing is requested until a method is called.
|
||||
*/
|
||||
class AppCheckoutSessions
|
||||
{
|
||||
public function __construct(protected Billable|Model|null $billable = null)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a checkout session with new subscriptions (their attributes, like
|
||||
* AppSubscriptions::create()) and/or existing drafts of the app (`['id' => …]`).
|
||||
*
|
||||
* @param array{
|
||||
* subscriptions: list<array{id: string}|array{
|
||||
* group?: null|string,
|
||||
* name: string,
|
||||
* description?: null|string,
|
||||
* unit: string,
|
||||
* price: float|string,
|
||||
* vat_rate?: float,
|
||||
* payload?: null|array,
|
||||
* ends_at?: null|string
|
||||
* }>,
|
||||
* return_url?: null|string,
|
||||
* metadata?: null|array,
|
||||
* idempotency_key?: null|string,
|
||||
* user_id?: string
|
||||
* } $attributes return_url must share scheme, host and port with one of the app's redirect
|
||||
* URIs; the user comes back to it with `status` and `checkout_session`. A
|
||||
* repeated idempotency_key returns the first session instead of a new one.
|
||||
*
|
||||
* @throws ApiException 422 validation (e.g. return_url), 409 "idempotency_key_conflict"
|
||||
*/
|
||||
public function create(array $attributes): AppCheckoutSession
|
||||
{
|
||||
if ($this->billable) {
|
||||
$attributes['user_id'] = (new AppSubscriptions($this->billable))->userId();
|
||||
} elseif (empty($attributes['user_id'])) {
|
||||
throw new InvalidArgumentException('A checkout session needs a user: use $user->appCheckoutSessions() or pass user_id.');
|
||||
}
|
||||
|
||||
return new AppCheckoutSession(AppRequest::send('POST', 'v1/client/checkout-sessions', $attributes)->data);
|
||||
}
|
||||
|
||||
/**
|
||||
* A session of the app; null when it doesn't exist (or belongs to another user).
|
||||
*
|
||||
* @throws ApiException
|
||||
*/
|
||||
public function find(string $id): ?AppCheckoutSession
|
||||
{
|
||||
try {
|
||||
$session = new AppCheckoutSession(AppRequest::send('GET', sprintf('v1/client/checkout-sessions/%s', $id))->data);
|
||||
} catch (ApiException $e) {
|
||||
if ($e->isNotFound()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
|
||||
return !$this->billable || $session->user_id === (new AppSubscriptions($this->billable))->userId() ? $session : null;
|
||||
}
|
||||
}
|
||||
@@ -29,6 +29,7 @@ use JsonSerializable;
|
||||
* @property int $failed_renewal_retries
|
||||
* @property ?array $payload
|
||||
* @property ?string $idempotency_key
|
||||
* @property ?string $checkout_session_id the checkout session the user confirms it in, if any
|
||||
*/
|
||||
class AppSubscription implements JsonSerializable
|
||||
{
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace Anikeen\Id\Resources;
|
||||
|
||||
use Anikeen\Id\Concerns\HasBillable;
|
||||
use Anikeen\Id\Concerns\IsCheckoutSession;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* A checkout session created with the user's access token, see CheckoutSessions.
|
||||
*
|
||||
* @property string $id
|
||||
* @property string $user_id
|
||||
* @property ?string $oauth_client_id
|
||||
* @property string $status open, completed, cancelled or expired
|
||||
* @property ?string $return_url
|
||||
* @property ?object $metadata
|
||||
* @property ?string $confirmation_url
|
||||
* @property list<string> $subscription_ids
|
||||
* @property list<object> $subscriptions
|
||||
* @property ?string $expires_at
|
||||
* @property ?string $completed_at
|
||||
* @property ?string $cancelled_at
|
||||
* @property ?string $created_at
|
||||
*/
|
||||
class CheckoutSession extends BaseResource
|
||||
{
|
||||
use HasBillable;
|
||||
use IsCheckoutSession;
|
||||
|
||||
public const STATUS_OPEN = 'open';
|
||||
public const STATUS_COMPLETED = 'completed';
|
||||
public const STATUS_CANCELLED = 'cancelled';
|
||||
public const STATUS_EXPIRED = 'expired';
|
||||
|
||||
/**
|
||||
* Withdraw an open session: it and its drafts are cancelled.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function cancel(): self
|
||||
{
|
||||
return (new self(fn () => $this->billable->anikeenId()
|
||||
->request('POST', sprintf('v1/checkout-sessions/%s/cancel', $this->id))))
|
||||
->setBillable($this->billable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function refresh(): self
|
||||
{
|
||||
return (new self(fn () => $this->billable->anikeenId()
|
||||
->request('GET', sprintf('v1/checkout-sessions/%s', $this->id))))
|
||||
->setBillable($this->billable);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace Anikeen\Id\Resources;
|
||||
|
||||
use Anikeen\Id\Concerns\HasBillable;
|
||||
use Anikeen\Id\Contracts\Billable;
|
||||
use Anikeen\Id\Exceptions\ResourceException;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Checkout sessions of the signed-in user, with the user's access token (any app): several
|
||||
* subscriptions the user confirms together on Anikeen ID. Nothing is requested until a method is
|
||||
* called.
|
||||
*/
|
||||
class CheckoutSessions
|
||||
{
|
||||
use HasBillable;
|
||||
|
||||
public function __construct(Billable|Model $billable)
|
||||
{
|
||||
$this->billable = $billable;
|
||||
}
|
||||
|
||||
/**
|
||||
* Open a checkout session with new subscriptions and/or existing drafts of the app
|
||||
* (`['id' => …]`); see AppCheckoutSessions::create() for the attributes (no user_id here,
|
||||
* vat_rate only for trusted apps).
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function create(array $attributes): CheckoutSession
|
||||
{
|
||||
return (new CheckoutSession(fn () => $this->billable->anikeenId()
|
||||
->request('POST', 'v1/checkout-sessions', $attributes)))
|
||||
->setBillable($this->billable);
|
||||
}
|
||||
|
||||
/**
|
||||
* A session the app created for the user; null when it doesn't exist.
|
||||
*
|
||||
* @throws Throwable
|
||||
*/
|
||||
public function find(string $id): ?CheckoutSession
|
||||
{
|
||||
try {
|
||||
return (new CheckoutSession(fn () => $this->billable->anikeenId()
|
||||
->request('GET', sprintf('v1/checkout-sessions/%s', $id))))
|
||||
->setBillable($this->billable);
|
||||
} catch (ResourceException $e) {
|
||||
if ($e->getCode() === 404) {
|
||||
return null;
|
||||
}
|
||||
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user