10 Commits

Author SHA1 Message Date
3bcebd9d45 update subscription, add alias
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 09:02:24 +02:00
71663bffd8 small fixes
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 06:43:07 +02:00
5b2b3c72cc add serializable
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 04:23:10 +02:00
235918f0c0 fix some issues
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 04:12:54 +02:00
4b23f6ddbb refactored code
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 03:45:10 +02:00
85702fcb2c update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:50:12 +02:00
65d4b12006 update identifier
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:47:21 +02:00
5d621b7cdc update config
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:46:44 +02:00
4d6fe7c325 update provider
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:39:49 +02:00
d447a88430 add mode
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-29 21:33:02 +02:00
47 changed files with 1263 additions and 587 deletions

View File

@@ -31,12 +31,19 @@ ANIKEEN_ID_SECRET=
ANIKEEN_ID_CALLBACK_URL=http://localhost/auth/callback ANIKEEN_ID_CALLBACK_URL=http://localhost/auth/callback
``` ```
To switch from `production` to `staging` use following variable:
```
ANIKEEN_ID_MODE=staging
```
You will need to add an entry to the services configuration file so that after config files are cached for usage in production environment (Laravel command `artisan config:cache`) all config is still available. You will need to add an entry to the services configuration file so that after config files are cached for usage in production environment (Laravel command `artisan config:cache`) all config is still available.
Add to `config/services.php` file: Add to `config/services.php` file:
```php ```php
'anikeen' => [ 'anikeen' => [
'mode' => env('ANIKEEN_ID_MODE'),
'client_id' => env('ANIKEEN_ID_KEY'), 'client_id' => env('ANIKEEN_ID_KEY'),
'client_secret' => env('ANIKEEN_ID_SECRET'), 'client_secret' => env('ANIKEEN_ID_SECRET'),
'redirect' => env('ANIKEEN_ID_CALLBACK_URL'), 'redirect' => env('ANIKEEN_ID_CALLBACK_URL'),
@@ -52,7 +59,7 @@ In Laravel 11, the default EventServiceProvider provider was removed. Instead, a
public function boot(): void public function boot(): void
{ {
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('anikeen-id', \Anikeen\Id\Socialite\Provider::class); $event->extendSocialite('anikeen', \Anikeen\Id\Socialite\Provider::class);
}); });
} }
``` ```
@@ -132,7 +139,7 @@ reference the guard in the `guards` configuration of your `auth.php` configurati
], ],
'api' => [ 'api' => [
'driver' => 'anikeen-id', 'driver' => 'anikeen',
'provider' => 'sso-users', 'provider' => 'sso-users',
], ],
], ],

View File

@@ -31,12 +31,19 @@ ANIKEEN_ID_SECRET=
ANIKEEN_ID_CALLBACK_URL=http://localhost/auth/callback ANIKEEN_ID_CALLBACK_URL=http://localhost/auth/callback
``` ```
To switch from `production` to `staging` use following variable:
```
ANIKEEN_ID_MODE=staging
```
You will need to add an entry to the services configuration file so that after config files are cached for usage in production environment (Laravel command `artisan config:cache`) all config is still available. You will need to add an entry to the services configuration file so that after config files are cached for usage in production environment (Laravel command `artisan config:cache`) all config is still available.
Add to `config/services.php` file: Add to `config/services.php` file:
```php ```php
'anikeen' => [ 'anikeen' => [
'mode' => env('ANIKEEN_ID_MODE'),
'client_id' => env('ANIKEEN_ID_KEY'), 'client_id' => env('ANIKEEN_ID_KEY'),
'client_secret' => env('ANIKEEN_ID_SECRET'), 'client_secret' => env('ANIKEEN_ID_SECRET'),
'redirect' => env('ANIKEEN_ID_CALLBACK_URL'), 'redirect' => env('ANIKEEN_ID_CALLBACK_URL'),
@@ -52,7 +59,7 @@ In Laravel 11, the default EventServiceProvider provider was removed. Instead, a
public function boot(): void public function boot(): void
{ {
Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) { Event::listen(function (\SocialiteProviders\Manager\SocialiteWasCalled $event) {
$event->extendSocialite('anikeen-id', \Anikeen\Id\Socialite\Provider::class); $event->extendSocialite('anikeen', \Anikeen\Id\Socialite\Provider::class);
}); });
} }
``` ```
@@ -132,7 +139,7 @@ reference the guard in the `guards` configuration of your `auth.php` configurati
], ],
'api' => [ 'api' => [
'driver' => 'anikeen-id', 'driver' => 'anikeen',
'provider' => 'sso-users', 'provider' => 'sso-users',
], ],
], ],

View File

@@ -13,7 +13,7 @@
} }
], ],
"require": { "require": {
"php": "^8.0", "php": "^8.1",
"ext-json": "*", "ext-json": "*",
"illuminate/support": "^11.0|^12.0", "illuminate/support": "^11.0|^12.0",
"illuminate/console": "^11.0|^12.0", "illuminate/console": "^11.0|^12.0",

View File

@@ -51,6 +51,11 @@ class AnikeenId
*/ */
private static string $baseUrl = 'https://id.anikeen.com/api/'; private static string $baseUrl = 'https://id.anikeen.com/api/';
/**
* The staging base URL for Anikeen ID API.
*/
private static string $stagingBaseUrl = 'https://staging.id.anikeen.com/api/';
/** /**
* The key for the access token. * The key for the access token.
*/ */
@@ -105,6 +110,9 @@ class AnikeenId
if ($redirectUri = config('services.anikeen.redirect')) { if ($redirectUri = config('services.anikeen.redirect')) {
$this->setRedirectUri($redirectUri); $this->setRedirectUri($redirectUri);
} }
if (config('services.anikeen.mode') === 'staging') {
self::setBaseUrl(self::$stagingBaseUrl);
}
if ($baseUrl = config('services.anikeen.base_url')) { if ($baseUrl = config('services.anikeen.base_url')) {
self::setBaseUrl($baseUrl); self::setBaseUrl($baseUrl);
} }
@@ -149,7 +157,7 @@ class AnikeenId
* @param string|null $cookie * @param string|null $cookie
* @return string|static * @return string|static
*/ */
public static function cookie(string $cookie = null): string|static public static function cookie(?string $cookie = null): string|static
{ {
if (is_null($cookie)) { if (is_null($cookie)) {
return static::$cookie; return static::$cookie;
@@ -314,7 +322,7 @@ class AnikeenId
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function request(string $method, string $path, null|array $payload = null, array $parameters = [], Paginator $paginator = null): Result public function request(string $method, string $path, null|array $payload = null, array $parameters = [], ?Paginator $paginator = null): Result
{ {
if ($paginator !== null) { if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor(); $parameters[$paginator->action] = $paginator->cursor();
@@ -360,7 +368,7 @@ class AnikeenId
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function get(string $path, array $parameters = [], Paginator $paginator = null): Result public function get(string $path, array $parameters = [], ?Paginator $paginator = null): Result
{ {
return $this->request('GET', $path, null, $parameters, $paginator); return $this->request('GET', $path, null, $parameters, $paginator);
} }
@@ -369,7 +377,7 @@ class AnikeenId
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function post(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result public function post(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result
{ {
return $this->request('POST', $path, $payload, $parameters, $paginator); return $this->request('POST', $path, $payload, $parameters, $paginator);
} }
@@ -378,7 +386,7 @@ class AnikeenId
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function put(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result public function put(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result
{ {
return $this->request('PUT', $path, $payload, $parameters, $paginator); return $this->request('PUT', $path, $payload, $parameters, $paginator);
} }
@@ -387,7 +395,7 @@ class AnikeenId
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function delete(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result public function delete(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result
{ {
return $this->request('DELETE', $path, $payload, $parameters, $paginator); return $this->request('DELETE', $path, $payload, $parameters, $paginator);
} }

View File

@@ -15,5 +15,5 @@ trait Delete
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
abstract public function delete(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result; abstract public function delete(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result;
} }

View File

@@ -15,5 +15,5 @@ trait Get
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
abstract public function get(string $path, array $parameters = [], Paginator $paginator = null): Result; abstract public function get(string $path, array $parameters = [], ?Paginator $paginator = null): Result;
} }

View File

@@ -15,5 +15,5 @@ trait Post
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
abstract public function post(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result; abstract public function post(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result;
} }

View File

@@ -15,5 +15,5 @@ trait Put
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
abstract public function put(string $path, array $payload = [], array $parameters = [], Paginator $paginator = null): Result; abstract public function put(string $path, array $payload = [], array $parameters = [], ?Paginator $paginator = null): Result;
} }

View File

@@ -15,5 +15,5 @@ trait Request
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
abstract public function request(string $method, string $path, null|array $payload = null, array $parameters = [], Paginator $paginator = null): Result; abstract public function request(string $method, string $path, null|array $payload = null, array $parameters = [], ?Paginator $paginator = null): Result;
} }

View File

@@ -5,9 +5,11 @@ namespace Anikeen\Id;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Concerns\ManagesAddresses; use Anikeen\Id\Concerns\ManagesAddresses;
use Anikeen\Id\Concerns\ManagesBalance; use Anikeen\Id\Concerns\ManagesBalance;
use Anikeen\Id\Concerns\ManagesCountries;
use Anikeen\Id\Concerns\ManagesInvoices; use Anikeen\Id\Concerns\ManagesInvoices;
use Anikeen\Id\Concerns\ManagesOrders; use Anikeen\Id\Concerns\ManagesOrders;
use Anikeen\Id\Concerns\ManagesPaymentMethods; use Anikeen\Id\Concerns\ManagesPaymentMethods;
use Anikeen\Id\Concerns\ManagesProfile;
use Anikeen\Id\Concerns\ManagesSubscriptions; use Anikeen\Id\Concerns\ManagesSubscriptions;
use Anikeen\Id\Concerns\ManagesTaxation; use Anikeen\Id\Concerns\ManagesTaxation;
use Anikeen\Id\Concerns\ManagesTransactions; use Anikeen\Id\Concerns\ManagesTransactions;
@@ -20,9 +22,11 @@ trait Billable
{ {
use ManagesAddresses; use ManagesAddresses;
use ManagesBalance; use ManagesBalance;
use ManagesCountries;
use ManagesInvoices; use ManagesInvoices;
use ManagesOrders; use ManagesOrders;
use ManagesPaymentMethods; use ManagesPaymentMethods;
use ManagesProfile;
use ManagesSubscriptions; use ManagesSubscriptions;
use ManagesTaxation; use ManagesTaxation;
use ManagesTransactions; use ManagesTransactions;
@@ -36,7 +40,7 @@ trait Billable
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
protected function getUserData(): stdClass public function getUserData(): stdClass
{ {
if (!$this->userData) { if (!$this->userData) {
$this->userData = $this->request('GET', 'v1/user')->data; $this->userData = $this->request('GET', 'v1/user')->data;
@@ -50,7 +54,7 @@ trait Billable
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
protected function request(string $method, string $path, null|array $payload = null, array $parameters = [], Paginator $paginator = null): Result public function request(string $method, string $path, null|array $payload = null, array $parameters = [], ?Paginator $paginator = null): Result
{ {
$anikeenId = new AnikeenId(); $anikeenId = new AnikeenId();
$anikeenId->withToken($this->{AnikeenId::getAccessTokenField()}); $anikeenId->withToken($this->{AnikeenId::getAccessTokenField()});

View File

@@ -0,0 +1,20 @@
<?php
namespace Anikeen\Id\Concerns;
trait HasBillable
{
public mixed $billable;
public function setBillable(mixed $billable): self
{
$this->billable = $billable;
return $this;
}
public function getBillable(): mixed
{
return $this->billable;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Anikeen\Id\Concerns;
trait HasParent
{
protected mixed $parent;
public function setParent(mixed $parent): self
{
$this->parent = $parent;
return $this;
}
public function getParent()
{
return $this->parent;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Anikeen\Id\Concerns;
use stdClass;
trait MagicProperties
{
protected function setMagicProperties(stdClass|array $data): void
{
foreach ((object)$data as $key => $value) {
if (!property_exists($this, $key)) {
$this->{$key} = $value;
}
}
}
/**
* Magic getter: return null for undefined properties
*
* @param string $name
* @return mixed|null
*/
public function __get(string $name)
{
return null;
}
/**
* Magic isset: return false for undefined properties
*
* @param string $name
* @return bool
*/
public function __isset(string $name): bool
{
return false;
}
}

View File

@@ -4,7 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Resources\Addresses;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
trait ManagesAddresses trait ManagesAddresses
@@ -17,116 +17,21 @@ trait ManagesAddresses
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function addresses(): Result public function addresses(): Addresses
{ {
return $this->request('GET', 'v1/addresses'); return (new Addresses($this->request('GET', 'v1/addresses')))
->setBillable($this);
} }
/** /**
* Creates a new address for the current user. * Check if the current user has a default billing address.
* *
* @param array{ * @see \Anikeen\Id\Resources\Addresses::hasDefaultBillingAddress()
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: string,
* address_2: null|string,
* house_number: null|string,
* postal_code: string,
* city: string,
* state: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string,
* primary: bool,
* primary_billing_address: bool
* } $attributes The address data:
*   - company_name: Company name (optional)
*   - first_name: First name
*   - last_name: Last name
*   - address: Address line 1 (e.g. street address, P.O. Box, etc.)
*   - address_2: Address line 2 (optional, e.g. apartment number, c/o, etc.)
*   - house_number: House number (optional)
*   - postal_code: Postal code
*   - city: City
*   - state: State (optional, e.g. province, region, etc.)
*   - country_iso: Country ISO code (e.g. US, CA, etc.)
*   - phone_number: Phone number (optional)
*   - email: Email address (optional, e.g. for delivery notifications)
*   - primary: Whether this address should be the primary address (optional)
*   - primary_billing_address: Whether this address should be the primary billing address (optional)
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function createAddress(array $attributes = []): Result public function hasDefaultBillingAddress(): bool
{ {
return $this->request('POST', 'v1/addresses', $attributes); return $this->addresses()->hasDefaultBillingAddress();
}
/**
* Get given address from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function address(string $addressId): Result
{
return $this->request('GET', sprintf('v1/addresses/%s', $addressId));
}
/**
* Update given address from the current user.
*
* VAT is calculated based on the billing address and shown in the address response.
*
* @param string $addressId The address ID.
* @param array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address_2: null|string,
* address: string,
* house_number: null|string,
* postal_code: string,
* city: string,
* state: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string,
* primary: bool,
* primary_billing_address: bool
* } $attributes The address data:
* - company_name: Company name (optional)
* - first_name: First name (required when set)
* - last_name: Last name (required when set)
* - address: Address line 1 (e.g. street address, P.O. Box, etc.)
* - address_2: Address line 2 (optional, e.g. apartment number, c/o, etc.)
* - house_number: House number (optional)
* - postal_code: Postal code (required when set)
* - city: City (required when set)
* - state: State (optional, e.g. province, region, etc.)
* - country_iso: Country ISO code (required when set, e.g. US, CA, etc.)
* - phone_number: Phone number (optional)
* - email: Email address (optional, e.g. for delivery notifications)
* - primary: Whether this address should be the primary address (optional)
* - primary_billing_address: Whether this address should be the primary billing address (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function updateAddress(string $addressId, array $attributes = []): Result
{
return $this->request('PUT', sprintf('v1/addresses/%s', $addressId), $attributes);
}
/**
* Delete given address from the current user.
*
* @param string $addressId The address ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function deleteAddress(string $addressId): Result
{
return $this->request('DELETE', sprintf('v1/addresses/%s', $addressId));
} }
} }

View File

@@ -4,6 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Resources\Transaction;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
@@ -42,12 +43,12 @@ trait ManagesBalance
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function charge(float $amount, string $paymentMethodId, array $options = []): Result public function charge(float $amount, string $paymentMethodId, array $options = []): Transaction
{ {
return $this->request('POST', 'billing/charge', [ return new Transaction($this->request('POST', 'billing/charge', [
'amount' => $amount, 'amount' => $amount,
'payment_method_id' => $paymentMethodId, 'payment_method_id' => $paymentMethodId,
'options' => $options, 'options' => $options,
]); ]));
} }
} }

View File

@@ -0,0 +1,25 @@
<?php
namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Resources\Countries;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesCountries
{
use Request;
/**
* Get available countries for the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function countries(): Countries
{
return (new Countries($this->request('GET', 'v1/countries')))
->setBillable($this);
}
}

View File

@@ -4,7 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Resources\Invoices;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
trait ManagesInvoices trait ManagesInvoices
@@ -17,32 +17,9 @@ trait ManagesInvoices
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function invoices(): Result public function invoices(array $parameters = []): Invoices
{ {
return $this->request('GET', 'v1/invoices'); return (new Invoices($this->request('GET', 'v1/invoices', [], $parameters)))
} ->setBillable($this);
/**
* Get given invoice from the current user.
*
* @param string $invoiceId The invoice ID
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function invoice(string $invoiceId): Result
{
return $this->request('GET', sprintf('v1/invoices/%s', $invoiceId));
}
/**
* Get temporary download url from given invoice.
*
* @param string $invoiceId The invoice ID
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function getInvoiceTemporaryUrl(string $invoiceId): string
{
return $this->request('PUT', sprintf('v1/invoices/%s', $invoiceId))->data->temporary_url;
} }
} }

View File

@@ -4,6 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Resources\Orders;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
@@ -17,254 +18,9 @@ trait ManagesOrders
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function orders(): Result public function orders(array $parameters = []): Orders
{ {
return $this->request('GET', 'v1/orders'); return (new Orders($this->request('GET', 'v1/orders', [], $parameters)))
} ->setBillable($this);
/**
* Creates a new order for the current user.
*
* VAT is calculated based on the billing address and shown in the order response.
*
* The billing and shipping addresses are each persisted as standalone Address entities
* in the database, but are also embedded (deep-copied) into the Order object itself
* rather than merely referenced. This guarantees that the order retains its own snapshot
* of both addresses for future reference.
*
* @param array{
* billing_address: array{
* company_name: null|string,
* first_name: null|string,
* last_name: null|string,
* address: null|string,
* address_2: null|string,
* house_number: null|string,
* city: null|string,
* state: null|string,
* postal_code: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* shipping_address: null|array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: null|string,
* address_2: string,
* house_number: null|string,
* city: string,
* state: string,
* postal_code: string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - billing_address: Billing address (ISO 3166-1 alpha-2 country code)
* - shipping_address: Shipping address (first name, last name, ISO 3166-1 alpha-2 country code)
* - items: Array of order items (each with type, name, price, unit, units, and quantity)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function createOrder(array $attributes = []): Result
{
return $this->request('POST', 'v1/orders', $attributes);
}
/**
* Get given order from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function order(string $orderId): Result
{
return $this->request('GET', sprintf('v1/orders/%s', $orderId));
}
/**
* Update given order from the current user.
*
* VAT is calculated based on the billing address and shown in the order response.
*
* The billing and shipping addresses are each persisted as standalone Address entities
* in the database, but are also embedded (deep-copied) into the Order object itself
* rather than merely referenced. This guarantees that the order retains its own snapshot
* of both addresses for future reference.
*
* @param string $orderId The order ID.
* @param array{
* billing_address: array{
* company_name: null|string,
* first_name: null|string,
* last_name: null|string,
* address: null|string,
* address_2: null|string,
* house_number: null|string,
* city: null|string,
* state: null|string,
* postal_code: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* shipping_address: null|array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: null|string,
* address_2: string,
* house_number: null|string,
* city: string,
* state: string,
* postal_code: string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* }
* } $attributes The order data:
* - billing_address: Billing address (ISO 3166-1 alpha-2 country code)
* - shipping_address: Shipping address (first name, last name, ISO 3166-1 alpha-2 country code)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function updateOrder(string $orderId, array $attributes = []): Result
{
return $this->request('PUT', sprintf('v1/orders/%s', $orderId), $attributes);
}
/**
* Checkout given order from the current user.
*
* @param string $orderId The order ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function checkoutOrder(string $orderId): Result
{
return $this->request('PUT', sprintf('v1/orders/%s/checkout', $orderId));
}
/**
* Revoke given order from the current user.
*
* @param string $orderId The order ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function revokeOrder(string $orderId): Result
{
return $this->request('PUT', sprintf('v1/orders/%s/revoke', $orderId));
}
/**
* Delete given order from the current user.
*
* @param string $orderId The order ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function deleteOrder(string $orderId): Result
{
return $this->request('DELETE', sprintf('v1/orders/%s', $orderId));
}
/**
* Get order items from given order.
*
* @param string $orderId The order ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function orderItems(string $orderId): Result
{
return $this->request('GET', sprintf('v1/orders/%s/items', $orderId));
}
/**
* Create a new order item for given order.
*
* VAT is calculated based on the billing address and shown in the order item response.
*
* @param string $orderId The order ID.
* @param array{
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - items: Array of order items, each with type, name, description, price, unit, and quantity
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function createOrderItem(string $orderId, array $attributes = []): Result
{
return $this->request('POST', sprintf('v1/orders/%s', $orderId), $attributes);
}
/**
* Get given order item from given order.
*
* @param string $orderId The order ID.
* @param string $orderItemId The order item ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function orderItem(string $orderId, string $orderItemId): Result
{
return $this->request('GET', sprintf('v1/orders/%s/items/%s', $orderId, $orderItemId));
}
/**
* Update given order item from given order.
*
* VAT is calculated based on the billing address and shown in the order item response.
*
* @param string $orderId The order ID.
* @param string $orderItemId The order item ID.
* @param array{
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - items: Array of order items, each with type, name, description, price, unit, and quantity
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function updateOrderItem(string $orderId, string $orderItemId, array $attributes = []): Result
{
return $this->request('PUT', sprintf('v1/orders/%s/items/%s', $orderId, $orderItemId), $attributes);
}
/**
* Delete given order item from given order.
*
* @param string $orderId The order ID.
* @param string $orderItemId The order item ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function deleteOrderItem(string $orderId, string $orderItemId): Result
{
return $this->request('DELETE', sprintf('v1/orders/%s/items/%s', $orderId, $orderItemId));
} }
} }

View File

@@ -4,6 +4,8 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Resources\PaymentMethod;
use Anikeen\Id\Resources\PaymentMethods;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
@@ -11,59 +13,63 @@ trait ManagesPaymentMethods
{ {
use Request; use Request;
/**
* Check if current user has at least one payment method.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function hasPaymentMethod(): bool
{
return $this->paymentMethods()->count() > 0;
}
/** /**
* Get payment methods from the current user. * Get payment methods from the current user.
* *
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function paymentMethods(): Result public function paymentMethods(): PaymentMethods
{ {
return $this->request('GET', 'v1/payment-methods'); return (new PaymentMethods($this->request('GET', 'v1/payment-methods')))
->setBillable($this);
}
/**
* Check if current user has at least one payment method.
*
* @see \Anikeen\Id\Resources\PaymentMethods::hasPaymentMethod()
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function hasPaymentMethod(): ?PaymentMethod
{
return $this->paymentMethods()->hasPaymentMethod();
} }
/** /**
* Get default payment method from the current user. * Get default payment method from the current user.
* *
* @see \Anikeen\Id\Resources\PaymentMethods::defaultPaymentMethod()
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function defaultPaymentMethod(): ?PaymentMethod
{
return $this->paymentMethods()->defaultPaymentMethod();
}
/**
* Check if the current user has a default payment method.
*
* @see \Anikeen\Id\Resources\PaymentMethods::hasDefaultPaymentMethod()
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function hasDefaultPaymentMethod(): bool public function hasDefaultPaymentMethod(): bool
{ {
return (bool)$this->defaultPaymentMethod()->data; return $this->paymentMethods()->hasDefaultPaymentMethod();
}
/**
* Get default payment method from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function defaultPaymentMethod(): Result
{
return $this->request('GET', 'v1/payment-methods/default');
} }
/** /**
* Get billing portal URL for the current user. * Get billing portal URL for the current user.
* *
* @param string $returnUrl The URL to redirect to after the user has finished in the billing portal. * @param string|null $returnUrl The URL to redirect to after the user has finished in the billing portal.
* @param array $options Additional options for the billing portal. * @param array $options Additional options for the billing portal.
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function billingPortalUrl(string $returnUrl, array $options): string public function billingPortalUrl(?string $returnUrl = null, array $options = []): string
{ {
return $this->request('POST', 'v1/billing/portal', [ return $this->request('POST', 'v1/billing/portal', [
'return_url' => $returnUrl, 'return_url' => $returnUrl,

View File

@@ -0,0 +1,30 @@
<?php
namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesProfile
{
use Request;
/**
* Get the profile url for the current user.
*
* @param string|null $returnUrl The URL to redirect to after the user has completed their profile.
* @param array $options Additional options for the profile URL.
* @return string
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function profilePortalUrl(?string $returnUrl = null, array $options = []): string
{
return $this->request('POST', 'v1/user/profile', [
'return_url' => $returnUrl,
'options' => $options,
])->data->url;
}
}

View File

@@ -3,16 +3,14 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Delete;
use Anikeen\Id\ApiOperations\Get; use Anikeen\Id\ApiOperations\Get;
use Anikeen\Id\ApiOperations\Post;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Resources\SshKeys;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
trait ManagesSshKeys trait ManagesSshKeys
{ {
use Get, Post, Delete; use Get;
/** /**
* Get currently authed user with Bearer Token. * Get currently authed user with Bearer Token.
@@ -20,35 +18,9 @@ trait ManagesSshKeys
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function sshKeysByUserId(string $sskKeyId): Result public function sshKeysByUserId(string $sskKeyId): SshKeys
{ {
return $this->get(sprintf('v1/users/%s/ssh-keys/json', $sskKeyId)); return (new SshKeys($this->get(sprintf('v1/users/%s/ssh-keys/json', $sskKeyId))))
} ->setParent($this);
/**
* Creates ssh key for the currently authed user.
*
* @param string $publicKey The public key to be added
* @param string|null $name The name of the key (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function createSshKey(string $publicKey, string $name = null): Result
{
return $this->post('v1/ssh-keys', [
'public_key' => $publicKey,
'name' => $name,
]);
}
/**
* Deletes a given ssh key for the currently authed user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function deleteSshKey(int $sshKeyId): Result
{
return $this->delete(sprintf('v1/ssh-keys/%s', $sshKeyId));
} }
} }

View File

@@ -4,7 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Resources\Subscriptions;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
trait ManagesSubscriptions trait ManagesSubscriptions
@@ -17,99 +17,9 @@ trait ManagesSubscriptions
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function subscriptions(): Result public function subscriptions(): Subscriptions
{ {
return $this->request('GET', 'v1/subscriptions'); return (new Subscriptions($this->request('GET', 'v1/subscriptions')))
} ->setBillable($this);
/**
* Get given subscription from the current user.
*
* @param string $subscriptionId The subscription ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function subscription(string $subscriptionId): Result
{
return $this->request('GET', sprintf('v1/subscriptions/%s', $subscriptionId));
}
/**
* Create a new subscription for the current user.
*
* @param array{
* name: null,
* description: string,
* unit: string,
* price: float,
* vat: null|float,
* payload: null|array,
* ends_at: null|string,
* webhook_url: null|string,
* webhook_secret: null|string
* } $attributes The subscription data:
* - name: The name
* - description: The description
* - unit: The unit (e.g. "hour", "day", "week", "month", "year")
* - price: The price per unit
* - vat: The VAT (optional)
* - payload: The payload (optional)
* - ends_at: The end date (optional)
* - webhook_url: The webhook URL (optional)
* - webhook_secret: The webhook secret (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function createSubscription(array $attributes): Result
{
return $this->request('POST', 'v1/subscriptions', $attributes);
}
/**
* Force given subscription to check out (trusted apps only).
*
* @param string $subscriptionId The subscription ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function checkoutSubscription(string $subscriptionId): Result
{
return $this->request('PUT', sprintf('v1/subscriptions/%s/checkout', $subscriptionId));
}
/**
* Revoke a given running subscription from the current user.
*
* @param string $subscriptionId The subscription ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function revokeSubscription(string $subscriptionId): Result
{
return $this->request('PUT', sprintf('v1/subscriptions/%s/revoke', $subscriptionId));
}
/**
* Resume a given running subscription from the current user.
*
* @param string $subscriptionId The subscription ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function resumeSubscription(string $subscriptionId): Result
{
return $this->request('PUT', sprintf('v1/subscriptions/%s/resume', $subscriptionId));
}
/**
* Delete a given subscription from the current user.
*
* @param string $subscriptionId The subscription ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function deleteSubscription(string $subscriptionId): Result
{
return $this->request('DELETE', sprintf('v1/subscriptions/%s', $subscriptionId));
} }
} }

View File

@@ -4,6 +4,7 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\ApiOperations\Request;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Resources\Transactions;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
@@ -17,33 +18,9 @@ trait ManagesTransactions
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
* @throws GuzzleException * @throws GuzzleException
*/ */
public function transactions(): Result public function transactions(): Transactions
{ {
return $this->request('GET', 'v1/transactions'); return (new Transactions($this->request('GET', 'v1/transactions')))
} ->setBillable($this);;
/**
* Create a new transaction for the current user.
*
* @param array $attributes The attributes for the transaction.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
* @todo Add type hinting for the attributes array.
*/
public function createTransaction(array $attributes = []): Result
{
return $this->request('POST', 'v1/transactions', $attributes);
}
/**
* Get given transaction from current current user.
*
* @param string $transactionId The transaction ID.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function transaction(string $transactionId): Result
{
return $this->request('GET', sprintf('v1/transactions/%s', $transactionId));
} }
} }

View File

@@ -12,6 +12,7 @@ use GuzzleHttp\Exception\GuzzleException;
trait ManagesUsers trait ManagesUsers
{ {
use Get, Post; use Get, Post;
use HasParent;
/** /**
* Get currently authed user with Bearer Token * Get currently authed user with Bearer Token

View File

@@ -43,7 +43,7 @@ class AnikeenIdServiceProvider extends ServiceProvider
protected function registerGuard(): void protected function registerGuard(): void
{ {
Auth::resolved(function ($auth) { Auth::resolved(function ($auth) {
$auth->extend('anikeen-id', function ($app, $name, array $config) { $auth->extend('anikeen', function ($app, $name, array $config) {
return tap($this->makeGuard($config), function ($guard) { return tap($this->makeGuard($config), function ($guard) {
$this->app->refresh('request', $guard, 'setRequest'); $this->app->refresh('request', $guard, 'setRequest');
}); });

View File

@@ -0,0 +1,85 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
* @property null|string $company_name
* @property null|string $first_name
* @property null|string $last_name
* @property null|string $address_2
* @property null|string $address
* @property null|string $house_number
* @property null|string $postal_code
* @property null|string $city
* @property null|string $state
* @property string $country_iso
* @property null|string $phone_number
* @property null|string $email
* @property bool $primary
* @property bool $primary_billing_address
*/
class Address extends BaseResource
{
use HasBillable;
/**
* Update given address from the current user.
*
* VAT is calculated based on the billing address and shown in the address response.
*
* @param array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address_2: null|string,
* address: string,
* house_number: null|string,
* postal_code: string,
* city: string,
* state: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string,
* primary: bool,
* primary_billing_address: bool
* } $attributes The address data:
* - company_name: Company name (optional)
* - first_name: First name (required when set)
* - last_name: Last name (required when set)
* - address: Address line 1 (e.g. street address, P.O. Box, etc.)
* - address_2: Address line 2 (optional, e.g. apartment number, c/o, etc.)
* - house_number: House number (optional)
* - postal_code: Postal code (required when set)
* - city: City (required when set)
* - state: State (optional, e.g. province, region, etc.)
* - country_iso: Country ISO code (required when set, e.g. US, CA, etc.)
* - phone_number: Phone number (optional)
* - email: Email address (optional, e.g. for delivery notifications)
* - primary: Whether this address should be the primary address (optional)
* - primary_billing_address: Whether this address should be the primary billing address (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function update(array $attributes = []): self
{
return (new self($this->billable->request('PUT', sprintf('v1/addresses/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Delete given address from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function delete(): bool
{
return $this->billable->request('DELETE', sprintf('v1/addresses/%s', $this->id))->success();
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class Addresses extends BaseCollection
{
use HasBillable;
/**
* Creates a new address for the current user.
*
* @param array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: string,
* address_2: null|string,
* house_number: null|string,
* postal_code: string,
* city: string,
* state: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string,
* primary: bool,
* primary_billing_address: bool
* } $attributes The address data:
*   - company_name: Company name (optional)
*   - first_name: First name
*   - last_name: Last name
*   - address: Address line 1 (e.g. street address, P.O. Box, etc.)
*   - address_2: Address line 2 (optional, e.g. apartment number, c/o, etc.)
*   - house_number: House number (optional)
*   - postal_code: Postal code
*   - city: City
*   - state: State (optional, e.g. province, region, etc.)
*   - country_iso: Country ISO code (e.g. US, CA, etc.)
*   - phone_number: Phone number (optional)
*   - email: Email address (optional, e.g. for delivery notifications)
*   - primary: Whether this address should be the primary address (optional)
*   - primary_billing_address: Whether this address should be the primary billing address (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function create(array $attributes = []): Address
{
return (new Address($this->billable->request('POST', 'v1/addresses', $attributes)))
->setBillable($this->billable);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?Address
{
$result = $this->billable->request('GET', sprintf('v1/addresses/%s', $id));
return $result->success() ?
(new Address($result))
->setBillable($this->billable)
: null;
}
/**
* Get default address from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function defaultBillingAddress(): Address
{
return (new Address($this->billable->request('GET', sprintf('v1/addresses/%s', $this->billable->getUserData()->billing_address_id))))
->setBillable($this->billable);
}
/**
* Check if the current user has a default billing address.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function hasDefaultBillingAddress(): bool
{
return $this->billable->getUserData()->billing_address_id !== null;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\MagicProperties;
use Anikeen\Id\Result;
use JsonSerializable;
abstract class BaseCollection implements JsonSerializable
{
use MagicProperties;
public function __construct(protected Result $result)
{
//
}
/**
* Returns the collection of resources as an array.
*/
public function toArray(): array
{
return (array)$this->result->data;
}
/**
* Returns the collection of resources as a JSON string.
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Returns the collection of resources.
*/
public function paginate(): Result
{
return $this->result;
}
/**
* Returns the Resource based on the ID.
*/
abstract public function find(string $id): ?BaseResource;
}

View File

@@ -0,0 +1,33 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\MagicProperties;
use Anikeen\Id\Result;
use JsonSerializable;
abstract class BaseResource implements JsonSerializable
{
use MagicProperties;
public function __construct(protected Result $result)
{
$this->setMagicProperties($this->result->data);
}
/**
* Returns the collection of resources as an array.
*/
public function toArray(): array
{
return (array)$this->result->data;
}
/**
* Returns the collection of resources as a JSON string.
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
class Countries extends BaseCollection
{
use HasBillable;
/**
* {@inheritDoc}
*/
public function find(string $id): ?BaseResource
{
return null;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
*/
class Invoice extends BaseResource
{
use HasBillable;
/**
* Get temporary download url from given invoice.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function getInvoiceTemporaryUrl(): string
{
return $this->billable->request('PUT', sprintf('v1/invoices/%s', $this->id))->data->temporary_url;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class Invoices extends BaseCollection
{
use HasBillable;
/**
* {@inheritDoc}
*/
public function find(string $id): ?Invoice
{
$result = $this->billable->request('GET', sprintf('v1/invoices/%s', $id));
return $result->success()
? (new Invoice($result))
->setBillable($this->billable)
: null;
}
}

114
src/Id/Resources/Order.php Normal file
View File

@@ -0,0 +1,114 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
*/
class Order extends BaseResource
{
use HasBillable;
/**
* Update given order from the current user.
*
* VAT is calculated based on the billing address and shown in the order response.
*
* The billing and shipping addresses are each persisted as standalone Address entities
* in the database, but are also embedded (deep-copied) into the Order object itself
* rather than merely referenced. This guarantees that the order retains its own snapshot
* of both addresses for future reference.
*
* @param array{
* billing_address: array{
* company_name: null|string,
* first_name: null|string,
* last_name: null|string,
* address: null|string,
* address_2: null|string,
* house_number: null|string,
* city: null|string,
* state: null|string,
* postal_code: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* shipping_address: null|array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: null|string,
* address_2: string,
* house_number: null|string,
* city: string,
* state: string,
* postal_code: string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* }
* } $attributes The order data:
* - billing_address: Billing address (ISO 3166-1 alpha-2 country code)
* - shipping_address: Shipping address (first name, last name, ISO 3166-1 alpha-2 country code)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function update(array $attributes = []): self
{
return (new self($this->billable->request('PUT', sprintf('v1/orders/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Checkout given order from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function checkout(string $orderId): self
{
return (new self($this->billable->request('PUT', sprintf('v1/orders/%s/checkout', $this->id))))
->setBillable($this->billable);
}
/**
* Revoke given order from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function revoke(string $orderId): self
{
return (new self($this->billable->request('PUT', sprintf('v1/orders/%s/revoke', $this->id))))
->setBillable($this->billable);
}
/**
* Delete given order from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function delete(): bool
{
return $this->billable->request('DELETE', sprintf('v1/orders/%s', $this->id))->success();
}
/**
* Get order items from given order.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function orderItems(array $parameters = []): OrderItems
{
return (new OrderItems($this->billable->request('GET', sprintf('v1/orders/%s/items', $this->id), [], $parameters)))
->setBillable($this->billable)
->setParent($this);
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Concerns\HasParent;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
*/
class OrderItem extends BaseResource
{
use HasBillable;
use HasParent;
/**
* Update given order item from given order.
*
* VAT is calculated based on the billing address and shown in the order item response.
*
* @param array{
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - items: Array of order items, each with type, name, description, price, unit, and quantity
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function update(array $attributes = []): self
{
return (new self($this->billable->request('PUT', sprintf('v1/orders/%s/items/%s', $this->parent->id, $this->id), $attributes)))
->setBillable($this->billable)
->setParent($this->parent);
}
/**
* Delete given order item from given order.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function delete(): bool
{
return $this->billable->request('DELETE', sprintf('v1/orders/%s/items/%s', $this->parent->id, $this->id))->success();
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Concerns\HasParent;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class OrderItems extends BaseCollection
{
use HasBillable;
use HasParent;
/**
* Create a new order item for given order.
*
* VAT is calculated based on the billing address and shown in the order item response.
*
* @param string $orderId The order ID.
* @param array{
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - items: Array of order items, each with type, name, description, price, unit, and quantity
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function create(string $orderId, array $attributes = []): OrderItem
{
return (new OrderItem($this->billable->request('POST', sprintf('v1/orders/%s', $orderId), $attributes)))
->setBillable($this->billable)
->setParent($this->parent);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?OrderItem
{
/** @var Result $result */
$result = $this->parent->request('GET', sprintf('v1/orders/%s/items/%s', $this->parent->id, $id));
return $result->success() ?
(new OrderItem($result))
->setBillable($this->billable)
->setParent($this->parent)
: null;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class Orders extends BaseCollection
{
use HasBillable;
/**
* Creates a new order for the current user.
*
* VAT is calculated based on the billing address and shown in the order response.
*
* The billing and shipping addresses are each persisted as standalone Address entities
* in the database, but are also embedded (deep-copied) into the Order object itself
* rather than merely referenced. This guarantees that the order retains its own snapshot
* of both addresses for future reference.
*
* @param array{
* billing_address: array{
* company_name: null|string,
* first_name: null|string,
* last_name: null|string,
* address: null|string,
* address_2: null|string,
* house_number: null|string,
* city: null|string,
* state: null|string,
* postal_code: null|string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* shipping_address: null|array{
* company_name: null|string,
* first_name: string,
* last_name: string,
* address: null|string,
* address_2: string,
* house_number: null|string,
* city: string,
* state: string,
* postal_code: string,
* country_iso: string,
* phone_number: null|string,
* email: null|string
* },
* items: array<array{
* type: string,
* name: string,
* description: string,
* price: float|int,
* unit: string,
* units: int
* }>
* } $attributes The order data:
* - billing_address: Billing address (ISO 3166-1 alpha-2 country code)
* - shipping_address: Shipping address (first name, last name, ISO 3166-1 alpha-2 country code)
* - items: Array of order items (each with type, name, price, unit, units, and quantity)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function create(array $attributes = []): Order
{
return (new Order($this->billable->request('POST', 'v1/orders', $attributes)))
->setBillable($this->billable);
}
/**
* Get given order from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function find(string $id): ?Order
{
/** @var Result $result */
$result = $this->billable->request('GET', sprintf('v1/orders/%s', $id));
return $result->success()
? (new Order($result))
->setBillable($this->billable)
: null;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
/**
* @property string $id
*/
class PaymentMethod extends BaseResource
{
use HasBillable;
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class PaymentMethods extends BaseCollection
{
use HasBillable;
private ?PaymentMethod $cachedDefaultPaymentMethod = null;
/**
* Check if current user has at least one payment method.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function hasPaymentMethod(): bool
{
return $this->result->count() > 0;
}
/**
* Get default payment method from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function defaultPaymentMethod(): PaymentMethod
{
if ($this->cachedDefaultPaymentMethod === null) {
$this->cachedDefaultPaymentMethod = (new PaymentMethod(
$this->billable->request('GET', 'v1/payment-methods/default')
))->setBillable($this->billable);
}
return $this->cachedDefaultPaymentMethod;
}
/**
* Check if the current user has a default payment method.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function hasDefaultPaymentMethod(): bool
{
return $this->defaultPaymentMethod()?->id !== null;
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?PaymentMethod
{
$result = $this->billable->request('GET', sprintf('v1/payment-methods/%s', $id));
return $result->success()
? (new PaymentMethod($result))
->setBillable($this->billable)
: null;
}
}

View File

@@ -0,0 +1,26 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasParent;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
*/
class SshKey extends BaseResource
{
use HasParent;
/**
* Deletes a given ssh key for the currently authed user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function delete(): bool
{
return $this->parent->delete(sprintf('v1/ssh-keys/%s', $this->id))->success();
}
}

View File

@@ -0,0 +1,43 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasParent;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class SshKeys extends BaseCollection
{
use HasParent;
/**
* Creates ssh key for the currently authed user.
*
* @param string $publicKey The public key to be added
* @param string|null $name The name of the key (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function create(string $publicKey, ?string $name = null): SshKey
{
return (new SshKey($this->parent->post('v1/ssh-keys', [
'public_key' => $publicKey,
'name' => $name,
])))->setParent($this->parent);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?SshKey
{
/** @var Result $result */
$result = $this->parent->get(sprintf('v1/ssh-keys/%s', $id));
return $result->success()
? (new SshKey($result))
->setParent($this->parent)
: null;
}
}

View File

@@ -0,0 +1,103 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
/**
* @property string $id
* @property string $name
* @property string $description
* @property string $unit
* @property float $price
* @property float $vat_rate
* @property array $payload
* @property string $ends_at
* @property string $webhook_url
* @property string $webhook_secret
*/
class Subscription extends BaseResource
{
use HasBillable;
/**
* Update a given subscription from the current user.
*
* @param array{
* name: null,
* description: null|string,
* unit: string,
* price: float,
* vat_rate: null|float,
* payload: null|array,
* ends_at: null|string,
* webhook_url: null|string,
* webhook_secret: null|string
* } $attributes The subscription data:
* - name: The name
* - description: The description (optional)
* - unit: The unit (e.g. "hour", "day", "week", "month", "year")
* - price: The price per unit
* - vat_rate: The VAT rate (optional)
* - payload: The payload (optional)
* - ends_at: The end date (optional)
* - webhook_url: The webhook URL (optional)
* - webhook_secret: The webhook secret (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function update(array $attributes): self
{
return (new self($this->billable->request('PUT', sprintf('v1/subscriptions/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Force given subscription to check out (trusted apps only).
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function checkout(): self
{
return (new self($this->billable->request('PUT', sprintf('v1/subscriptions/%s/checkout', $this->id))))
->setBillable($this->billable);
}
/**
* Revoke a given running subscription from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function revoke(): self
{
return (new self($this->billable->request('PUT', sprintf('v1/subscriptions/%s/revoke', $this->id))))
->setBillable($this->billable);
}
/**
* Resume a given running subscription from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function resume(): self
{
return (new self($this->billable->request('PUT', sprintf('v1/subscriptions/%s/resume', $this->id))))
->setBillable($this->billable);
}
/**
* Delete a given subscription from the current user.
*
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function delete(): bool
{
return $this->billable->request('DELETE', sprintf('v1/subscriptions/%s', $this->id))->success();
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
class Subscriptions extends BaseCollection
{
use HasBillable;
/**
* Create a new subscription for the current user.
*
* @param array{
* name: null,
* description: null|string,
* unit: string,
* price: float,
* vat_rate: null|float,
* payload: null|array,
* ends_at: null|string,
* webhook_url: null|string,
* webhook_secret: null|string
* } $attributes The subscription data:
* - name: The name
* - description: The description (optional)
* - unit: The unit (e.g. "hour", "day", "week", "month", "year")
* - price: The price per unit
* - vat_rate: The VAT rate (optional)
* - payload: The payload (optional)
* - ends_at: The end date (optional)
* - webhook_url: The webhook URL (optional)
* - webhook_secret: The webhook secret (optional)
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function create(array $attributes): Subscription
{
return (new Subscription($this->billable->request('POST', 'v1/subscriptions', $attributes)))
->setBillable($this->billable);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?Subscription
{
$result = $this->billable->request('GET', sprintf('v1/subscriptions/%s', $id));
return $result->success()
? (new Subscription($result))
->setBillable($this->billable)
: null;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
class Transaction extends BaseResource
{
use HasBillable;
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
class Transactions extends BaseCollection
{
use HasBillable;
/**
* Create a new transaction for the current user.
*
* @param array $attributes The attributes for the transaction.
* @throws RequestRequiresClientIdException
* @throws GuzzleException
* @todo Add type hinting for the attributes array.
*/
public function create(array $attributes = []): Transaction
{
return new Transaction($this->billable->request('POST', 'v1/transactions', $attributes));
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?Transaction
{
$result = $this->billable->request('GET', sprintf('v1/transactions/%s', $id));
return $result->success()
? (new Transaction($result))
->setBillable($this->billable)
: null;
}
}

View File

@@ -179,7 +179,7 @@ class Result
/** /**
* Rate limit info from headers * Rate limit info from headers
*/ */
public function rateLimit(string $key = null): array|int|string|null public function rateLimit(?string $key = null): array|int|string|null
{ {
if (!$this->response) { if (!$this->response) {
return null; return null;

View File

@@ -8,6 +8,6 @@ class AnikeenIdExtendSocialite
{ {
public function handle(SocialiteWasCalled $socialiteWasCalled): void public function handle(SocialiteWasCalled $socialiteWasCalled): void
{ {
$socialiteWasCalled->extendSocialite('anikeen-id', Provider::class); $socialiteWasCalled->extendSocialite('anikeen', Provider::class);
} }
} }

View File

@@ -4,6 +4,7 @@ namespace Anikeen\Id\Socialite;
use Anikeen\Id\Enums\Scope; use Anikeen\Id\Enums\Scope;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use Illuminate\Http\Request;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Laravel\Socialite\Two\ProviderInterface; use Laravel\Socialite\Two\ProviderInterface;
use SocialiteProviders\Manager\OAuth2\AbstractProvider; use SocialiteProviders\Manager\OAuth2\AbstractProvider;
@@ -14,7 +15,7 @@ class Provider extends AbstractProvider implements ProviderInterface
/** /**
* Unique Provider Identifier. * Unique Provider Identifier.
*/ */
const IDENTIFIER = 'ANIKEEN_ID'; const IDENTIFIER = 'ANIKEEN';
/** /**
* {@inheritdoc} * {@inheritdoc}
@@ -26,13 +27,25 @@ class Provider extends AbstractProvider implements ProviderInterface
*/ */
protected $scopeSeparator = ' '; protected $scopeSeparator = ' ';
/**
* Get the base URL for the API.
*/
protected function getBaseUrl(): string
{
$mode = config('services.anikeen.mode') ?? 'production';
return $mode === 'staging'
? 'https://staging.id.anikeen.com'
: 'https://id.anikeen.com';
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function getAuthUrl($state): string protected function getAuthUrl($state): string
{ {
return $this->buildAuthUrlFromBase( return $this->buildAuthUrlFromBase(
'https://id.anikeen.com/oauth/authorize', $state $this->getBaseUrl() . '/oauth/authorize', $state
); );
} }
@@ -41,7 +54,7 @@ class Provider extends AbstractProvider implements ProviderInterface
*/ */
protected function getTokenUrl(): string protected function getTokenUrl(): string
{ {
return 'https://id.anikeen.com/oauth/token'; return $this->getBaseUrl() . '/oauth/token';
} }
/** /**
@@ -52,7 +65,7 @@ class Provider extends AbstractProvider implements ProviderInterface
protected function getUserByToken($token) protected function getUserByToken($token)
{ {
$response = $this->getHttpClient()->get( $response = $this->getHttpClient()->get(
'https://id.anikeen.com/api/v1/user', [ $this->getBaseUrl() . '/api/v1/user', [
'headers' => [ 'headers' => [
'Accept' => 'application/json', 'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token, 'Authorization' => 'Bearer ' . $token,