34 Commits

Author SHA1 Message Date
bc9c202c3b update composer.json 2025-09-21 17:15:45 +00:00
cecf8560ff Merge pull request #1 from anikeen-com/laravel-10
Update composer.json
2025-09-21 19:13:39 +02:00
25248e7822 refactor code 2025-09-21 17:12:19 +00:00
c641ec725d add refund to revoke endpoint 2025-09-21 12:13:56 +00:00
8eb0c25582 fix api path 2025-09-19 17:21:23 +00:00
30ac4ae4f9 update provider 2025-09-19 16:37:22 +00:00
e1a6af11a3 introduce new scopes 2025-09-19 15:36:28 +00:00
bb5df7f115 fix mode
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-18 19:28:08 +00:00
297404b05d add AllowDynamicProperties attribute
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-18 19:22:11 +00:00
5ab57dcdfe add staging key
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-18 19:17:50 +00:00
0f14fa1b4c remove endpoint
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-06 13:32:06 +00:00
437e78770c fix typo
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-06 13:23:00 +00:00
0dbb27fc94 update transaction description
Signed-off-by: Maurice Preuß <hello@envoyr.com>
2025-09-06 13:22:23 +00:00
René Preuß
8232de4003 Update OauthTrait.php 2025-07-30 23:08:13 +02:00
René Preuß
ac3e28f67f Update composer.json 2025-07-30 21:52:09 +02:00
63e3f0a4a2 add refresh token method
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-05-02 07:29:43 +02:00
1d2119a32b update resources
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-05-02 06:27:53 +02:00
1725ec68de update subscriptions
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-05-01 18:16:51 +02:00
dcda4b990e update user provider
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 12:00:34 +02:00
937fde603b update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:03:27 +02:00
80b1f003b2 update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:02:53 +02:00
aff901de4e update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:02:23 +02:00
92eadcca08 update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 10:00:27 +02:00
8b874f540c update docs
Signed-off-by: Maurice Preuß (envoyr) <hello@envoyr.com>
2025-04-30 09:08:29 +02:00
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
62 changed files with 1647 additions and 894 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);
}); });
} }
``` ```
@@ -102,17 +109,18 @@ public function boot(): void
### Implementing Auth ### Implementing Auth
This method should typically be called in the `boot` method of your `AuthServiceProvider` class: This method should typically be called in the `boot` method of your `AppServiceProvider` class:
```php ```php
use Anikeen\Id\AnikeenId; use Anikeen\Id\AnikeenId;
use Anikeen\Id\Providers\AnikeenIdSsoUserProvider; use Anikeen\Id\Providers\AnikeenIdUserProvider;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
public function boot(): void public function boot(): void
{ {
Auth::provider('sso-users', function ($app, array $config) { Auth::provider('anikeen', function ($app, array $config) {
return new AnikeenIdSsoUserProvider( return new AnikeenIdUserProvider(
$app->make(AnikeenId::class), $app->make(AnikeenId::class),
$app->make(Request::class), $app->make(Request::class),
$config['model'], $config['model'],
@@ -132,8 +140,8 @@ reference the guard in the `guards` configuration of your `auth.php` configurati
], ],
'api' => [ 'api' => [
'driver' => 'anikeen-id', 'driver' => 'anikeen',
'provider' => 'sso-users', 'provider' => 'anikeen',
], ],
], ],
``` ```
@@ -147,8 +155,8 @@ reference the provider in the `providers` configuration of your `auth.php` confi
'model' => App\Models\User::class, 'model' => App\Models\User::class,
], ],
'sso-users' => [ 'anikeen' => [
'driver' => 'sso-users', 'driver' => 'anikeen',
'model' => App\Models\User::class, 'model' => App\Models\User::class,
'fields' => ['first_name', 'last_name', 'email'], 'fields' => ['first_name', 'last_name', 'email'],
], ],
@@ -287,9 +295,7 @@ public function createOrderPreview(array $attributes = []): Result
### ManagesSshKeys ### ManagesSshKeys
```php ```php
public function sshKeysByUserId(string $sskKeyId): Result public function sshKeysByUserId(string $sskKeyId): SshKeys
public function createSshKey(string $publicKey, ?string $name = null): Result
public function deleteSshKey(int $sshKeyId): Result
``` ```
### ManagesUsers ### ManagesUsers
@@ -298,6 +304,8 @@ public function deleteSshKey(int $sshKeyId): Result
public function getAuthedUser(): Result public function getAuthedUser(): Result
public function createUser(array $attributes): Result public function createUser(array $attributes): Result
public function isEmailExisting(string $email): Result public function isEmailExisting(string $email): Result
public function setParent(?mixed $parent): self
public function getParent()
``` ```
@@ -306,11 +314,8 @@ public function isEmailExisting(string $email): Result
### ManagesAddresses ### ManagesAddresses
```php ```php
public function addresses(): Result public function addresses(): Addresses
public function createAddress(array $attributes = []): Result public function hasDefaultBillingAddress(): bool
public function address(string $addressId): Result
public function updateAddress(string $addressId, array $attributes = []): Result
public function deleteAddress(string $addressId): Result
``` ```
### ManagesBalance ### ManagesBalance
@@ -318,55 +323,48 @@ public function deleteAddress(string $addressId): Result
```php ```php
public function balance(): float public function balance(): float
public function charges(): float public function charges(): float
public function charge(float $amount, string $paymentMethodId, array $options = []): Result public function charge(float $amount, string $paymentMethodId, array $options = []): Transaction
```
### ManagesCountries
```php
public function countries(): Countries
``` ```
### ManagesInvoices ### ManagesInvoices
```php ```php
public function invoices(): Result public function invoices(array $parameters = []): Invoices
public function invoice(string $invoiceId): Result
public function getInvoiceTemporaryUrl(string $invoiceId): string
``` ```
### ManagesOrders ### ManagesOrders
```php ```php
public function orders(): Result public function orders(array $parameters = []): Orders
public function createOrder(array $attributes = []): Result
public function order(string $orderId): Result
public function updateOrder(string $orderId, array $attributes = []): Result
public function checkoutOrder(string $orderId): Result
public function revokeOrder(string $orderId): Result
public function deleteOrder(string $orderId): Result
public function orderItems(string $orderId): Result
public function createOrderItem(string $orderId, array $attributes = []): Result
public function orderItem(string $orderId, string $orderItemId): Result
public function updateOrderItem(string $orderId, string $orderItemId, array $attributes = []): Result
public function deleteOrderItem(string $orderId, string $orderItemId): Result
``` ```
### ManagesPaymentMethods ### ManagesPaymentMethods
```php ```php
public function hasPaymentMethod(): bool public function paymentMethods(): PaymentMethods
public function paymentMethods(): Result public function hasPaymentMethod(): ?PaymentMethod
public function defaultPaymentMethod(): ?PaymentMethod
public function hasDefaultPaymentMethod(): bool public function hasDefaultPaymentMethod(): bool
public function defaultPaymentMethod(): Result public function billingPortalUrl(?string $returnUrl = null, array $options = []): string
public function billingPortalUrl(string $returnUrl, array $options): string
public function createSetupIntent(array $options = []): Result public function createSetupIntent(array $options = []): Result
``` ```
### ManagesProfile
```php
public function profilePortalUrl(?string $returnUrl = null, array $options = []): string
```
### ManagesSubscriptions ### ManagesSubscriptions
```php ```php
public function subscriptions(): Result public function subscriptions(): Subscriptions
public function subscription(string $subscriptionId): Result
public function createSubscription(array $attributes): Result
public function checkoutSubscription(string $subscriptionId): Result
public function revokeSubscription(string $subscriptionId): Result
public function resumeSubscription(string $subscriptionId): Result
public function deleteSubscription(string $subscriptionId): Result
``` ```
### ManagesTaxation ### ManagesTaxation
@@ -378,9 +376,7 @@ public function vat(): float
### ManagesTransactions ### ManagesTransactions
```php ```php
public function transactions(): Result public function transactions(): Transactions
public function createTransaction(array $attributes = []): Result
public function transaction(string $transactionId): Result
``` ```

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);
}); });
} }
``` ```
@@ -102,17 +109,18 @@ public function boot(): void
### Implementing Auth ### Implementing Auth
This method should typically be called in the `boot` method of your `AuthServiceProvider` class: This method should typically be called in the `boot` method of your `AppServiceProvider` class:
```php ```php
use Anikeen\Id\AnikeenId; use Anikeen\Id\AnikeenId;
use Anikeen\Id\Providers\AnikeenIdSsoUserProvider; use Anikeen\Id\Providers\AnikeenIdUserProvider;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
public function boot(): void public function boot(): void
{ {
Auth::provider('sso-users', function ($app, array $config) { Auth::provider('anikeen', function ($app, array $config) {
return new AnikeenIdSsoUserProvider( return new AnikeenIdUserProvider(
$app->make(AnikeenId::class), $app->make(AnikeenId::class),
$app->make(Request::class), $app->make(Request::class),
$config['model'], $config['model'],
@@ -132,8 +140,8 @@ reference the guard in the `guards` configuration of your `auth.php` configurati
], ],
'api' => [ 'api' => [
'driver' => 'anikeen-id', 'driver' => 'anikeen',
'provider' => 'sso-users', 'provider' => 'anikeen',
], ],
], ],
``` ```
@@ -147,8 +155,8 @@ reference the provider in the `providers` configuration of your `auth.php` confi
'model' => App\Models\User::class, 'model' => App\Models\User::class,
], ],
'sso-users' => [ 'anikeen' => [
'driver' => 'sso-users', 'driver' => 'anikeen',
'model' => App\Models\User::class, 'model' => App\Models\User::class,
'fields' => ['first_name', 'last_name', 'email'], 'fields' => ['first_name', 'last_name', 'email'],
], ],

View File

@@ -13,16 +13,17 @@
} }
], ],
"require": { "require": {
"php": "^8.0", "php": "^8.1",
"ext-json": "*", "ext-json": "*",
"illuminate/support": "^11.0|^12.0", "illuminate/support": "^10.0|^11.0|^12.0",
"illuminate/console": "^11.0|^12.0", "illuminate/console": "^10.0|^11.0|^12.0",
"guzzlehttp/guzzle": "^6.3|^7.0", "guzzlehttp/guzzle": "^6.3|^7.0",
"socialiteproviders/manager": "^3.4|^4.0.1", "socialiteproviders/manager": "^3.4|^4.0.1",
"firebase/php-jwt": "^6.0" "firebase/php-jwt": "^6.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8.0|^9.0" "phpunit/phpunit": "^8.0|^9.0",
"laravel/framework": "^10.0|^11.0|^12.0"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {

14
oauth-public.staging.key Normal file
View File

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAi4Ta8r01zKaGSnGi1EiD
uMFWRXBlK4y/ZIfWBpElmS2ygv4mGeP3hT4Flm696Z2UMy56KC+c7CC/PQCiutLk
5NUphyX/t+0QS5Dqpw6FB33fLTNNY7GqSmGIUE4os8XYZRSyDQRgtOgq3R3vJkoV
7zoavTJmSCQlG5Qf0T//iMmzQ+b+6VZm1CJSz5nGx94u1DuXNyP5Epkk0wuHrtwy
kADR2lmydNodJzqpSD+8yQqnAhOZNtNF4qwQ3g13fRvHycBp3G2nlCfOn2g5PmYD
KYBKqvTq4PQH4E+K3pbbMz6zf/T6Dw7zTfksqHR4hqMgN6byRRxmwuBczIumcu9b
y7xbgoIGIVZXgJliALPFi+zTPTN7c8MedFs/xCBHCmzWYTCZfHgr8RPRewD19tCG
NSny5R0vlArpuZCTTgedPESDeGU4eNEddg4yXFzKlpE2nNuvzZ1Ohruc5ETOSU19
RTCBUBkjeL6ESZRd/yKGjbVx4dEYxZdIz4yBl+hZ2ZOIyG7L3zPrccAWrPpG56xr
E5IDBXxLFhaJ5LlyEAGQehB0ShEuCdkr88Xz7ba9PHpGqY83l4//ULrqPIZPAa4Z
E3AWHT1ZtXNPeA4SzZ9Y9Oij4M3chyHxqM0lL3kYP+dstZehTujStfElDIx2Ni10
73tILu4edYS0FxsL19m8gbsCAwEAAQ==
-----END PUBLIC KEY-----

View File

@@ -23,7 +23,7 @@
<php> <php>
<env name="ANIKEEN_ID_KEY" value="38"/> <env name="ANIKEEN_ID_KEY" value="38"/>
<env name="ANIKEEN_ID_SECRET" value="2goNRF8x37HPVZVaa28ySZGVXJuksvxnxM7TcGzM"/> <env name="ANIKEEN_ID_SECRET" value="2goNRF8x37HPVZVaa28ySZGVXJuksvxnxM7TcGzM"/>
<env name="ANIKEEN_ID_BASE_URL" value="https://id.anikeen.com/api/"/> <env name="ANIKEEN_ID_BASE_URL" value="https://id.anikeen.com"/>
<env name="CLIENT_ACCESS_TOKEN" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxNSIsImp0aSI6IjAxN2QxZDg0Y2MxNjAyZTYxOGZkNjYwZGViZWVjMDY4MTk2YmYzMDk1OGMzY2RiYzBjZmJkZWFjZjFhOTUxODQzZDU1YTk3OGY2YWIxY2YzIiwiaWF0IjoxNjY0NjIzMzA4LjQyODI4MSwibmJmIjoxNjY0NjIzMzA4LjQyODI4NCwiZXhwIjoxNjgwMzQ4MTA4LjQxNjc4MSwic3ViIjoiMzkiLCJzY29wZXMiOlsiYXBpIiwicmVhZF91c2VyIl0sImNsaWVudCI6eyJ0cnVzdGVkIjpmYWxzZX19.vxnzCaU4PpOrNVHa5AnGSS6gX_RCvxIERAnHFhjTrUzRafV9mr2Cvwd-BDVYoUr10wHvxa_TJSYfnAdDuhE-MEyDv13O3dL2XGTtJNa_Rg6L6CQ0JvC3wL-lWPvGPFax9pu-_lqbA3jm5B08hc3-7tq3f2nXcxjhtkqT6TTJv1-RCAppb2HCXiUDAqANzbhyInDjOH2WvFj1OGC_AI03J3W2KRWyeFLtUne8XKPFyr9XGcPuTrqogcuuXLeUt2kcf2bXbuIV1OlgIECrDiP1Ee0F2AzPs27ZVJ2z0R0JbT6AubKhGl5_Qi27cwjOH7hT2dmjcF1mLjzpN1uChLIdSnGSoStH8VzYHnHE2I8G-owW_aadG2UmGdnRY143q6g_28f3WIZNSucBSXkwFeS_t4fylsvpxhpjYJusf5qiEU_X3YbeawYMUCFUkSD2XTIypAqMJLNZQAeJ52eyL-9fln-Bv7n9v7K9G6ieR6Tm0tsJ1PRnaQi7rA1NTFwHoQmIOW9tfMycLzT7bgSoz3ra6Ez2J7ZNuWBZNKS0O-0YfSrAWyWK5U8YRfQuSVzP2VrIU63K6RGU2c284PfQGy11kgKUNQPykirb8p7MDQ8PwrxKaylBnD6hhDgjqEh2bfsr_43DfJA0R58L1HK3BmQnxgap0C77wK1e0yNlABpN28Q"/> <env name="CLIENT_ACCESS_TOKEN" value="eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJhdWQiOiIxNSIsImp0aSI6IjAxN2QxZDg0Y2MxNjAyZTYxOGZkNjYwZGViZWVjMDY4MTk2YmYzMDk1OGMzY2RiYzBjZmJkZWFjZjFhOTUxODQzZDU1YTk3OGY2YWIxY2YzIiwiaWF0IjoxNjY0NjIzMzA4LjQyODI4MSwibmJmIjoxNjY0NjIzMzA4LjQyODI4NCwiZXhwIjoxNjgwMzQ4MTA4LjQxNjc4MSwic3ViIjoiMzkiLCJzY29wZXMiOlsiYXBpIiwicmVhZF91c2VyIl0sImNsaWVudCI6eyJ0cnVzdGVkIjpmYWxzZX19.vxnzCaU4PpOrNVHa5AnGSS6gX_RCvxIERAnHFhjTrUzRafV9mr2Cvwd-BDVYoUr10wHvxa_TJSYfnAdDuhE-MEyDv13O3dL2XGTtJNa_Rg6L6CQ0JvC3wL-lWPvGPFax9pu-_lqbA3jm5B08hc3-7tq3f2nXcxjhtkqT6TTJv1-RCAppb2HCXiUDAqANzbhyInDjOH2WvFj1OGC_AI03J3W2KRWyeFLtUne8XKPFyr9XGcPuTrqogcuuXLeUt2kcf2bXbuIV1OlgIECrDiP1Ee0F2AzPs27ZVJ2z0R0JbT6AubKhGl5_Qi27cwjOH7hT2dmjcF1mLjzpN1uChLIdSnGSoStH8VzYHnHE2I8G-owW_aadG2UmGdnRY143q6g_28f3WIZNSucBSXkwFeS_t4fylsvpxhpjYJusf5qiEU_X3YbeawYMUCFUkSD2XTIypAqMJLNZQAeJ52eyL-9fln-Bv7n9v7K9G6ieR6Tm0tsJ1PRnaQi7rA1NTFwHoQmIOW9tfMycLzT7bgSoz3ra6Ez2J7ZNuWBZNKS0O-0YfSrAWyWK5U8YRfQuSVzP2VrIU63K6RGU2c284PfQGy11kgKUNQPykirb8p7MDQ8PwrxKaylBnD6hhDgjqEh2bfsr_43DfJA0R58L1HK3BmQnxgap0C77wK1e0yNlABpN28Q"/>
</php> </php>
</phpunit> </phpunit>

View File

@@ -46,11 +46,6 @@ class AnikeenId
*/ */
public static bool $unserializesCookies = false; public static bool $unserializesCookies = false;
/**
* The base URL for Anikeen ID API.
*/
private static string $baseUrl = 'https://id.anikeen.com/api/';
/** /**
* The key for the access token. * The key for the access token.
*/ */
@@ -91,6 +86,16 @@ class AnikeenId
*/ */
protected ?string $redirectUri = null; protected ?string $redirectUri = null;
/**
* The base URL for Anikeen ID.
*/
protected string $baseUrl = 'https://id.anikeen.com';
/**
* The staging base URL for Anikeen ID.
*/
protected string $stagingBaseUrl = 'https://staging.id.anikeen.com';
/** /**
* Constructor. * Constructor.
*/ */
@@ -105,22 +110,25 @@ class AnikeenId
if ($redirectUri = config('services.anikeen.redirect')) { if ($redirectUri = config('services.anikeen.redirect')) {
$this->setRedirectUri($redirectUri); $this->setRedirectUri($redirectUri);
} }
if (self::getMode() === 'staging' && !config('services.anikeen.base_url')) {
self::setBaseUrl($this->stagingBaseUrl);
}
if ($baseUrl = config('services.anikeen.base_url')) { if ($baseUrl = config('services.anikeen.base_url')) {
self::setBaseUrl($baseUrl); self::setBaseUrl($baseUrl);
} }
$this->client = new Client([ $this->client = new Client([
'base_uri' => self::$baseUrl, 'base_uri' => $this->baseUrl . '/api/',
]); ]);
} }
/** protected function setBaseUrl(string $baseUrl): void
* @param string $baseUrl
*
* @internal only for internal and debug purposes.
*/
public static function setBaseUrl(string $baseUrl): void
{ {
self::$baseUrl = $baseUrl; $this->baseUrl = $baseUrl;
}
public function getBaseUrl(): string
{
return rtrim($this->baseUrl, '/');
} }
public static function useAccessTokenField(string $accessTokenField): void public static function useAccessTokenField(string $accessTokenField): void
@@ -133,6 +141,11 @@ class AnikeenId
return self::$accessTokenField; return self::$accessTokenField;
} }
public static function getMode(): string
{
return config('services.anikeen.mode') ?: 'production';
}
public static function useRefreshTokenField(string $refreshTokenField): void public static function useRefreshTokenField(string $refreshTokenField): void
{ {
self::$refreshTokenField = $refreshTokenField; self::$refreshTokenField = $refreshTokenField;
@@ -149,7 +162,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 +327,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, bool $useClientSecret = false): Result
{ {
if ($paginator !== null) { if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor(); $parameters[$paginator->action] = $paginator->cursor();
@@ -322,7 +335,7 @@ class AnikeenId
try { try {
$response = $this->client->request($method, $path, [ $response = $this->client->request($method, $path, [
'headers' => $this->buildHeaders((bool)$payload), 'headers' => $this->buildHeaders((bool)$payload, $useClientSecret),
'query' => Query::build($parameters), 'query' => Query::build($parameters),
'json' => $payload ?: null, 'json' => $payload ?: null,
]); ]);
@@ -340,14 +353,14 @@ class AnikeenId
* *
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
private function buildHeaders(bool $json = false): array private function buildHeaders(bool $json = false, bool $useClientSecret = false): array
{ {
$headers = [ $headers = [
'Client-ID' => $this->getClientId(), 'Client-ID' => $this->getClientId(),
'Accept' => 'application/json', 'Accept' => 'application/json',
]; ];
if ($this->token) { if ($bearerToken = $useClientSecret ? $this->getClientSecret() : $this->getToken()) {
$headers['Authorization'] = 'Bearer ' . $this->token; $headers['Authorization'] = 'Bearer ' . $bearerToken;
} }
if ($json) { if ($json) {
$headers['Content-Type'] = 'application/json'; $headers['Content-Type'] = 'application/json';
@@ -360,7 +373,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 +382,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 +391,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 +400,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

@@ -2,59 +2,51 @@
namespace Anikeen\Id; namespace Anikeen\Id;
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;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Helpers\Paginator;
use GuzzleHttp\Exception\GuzzleException;
use stdClass;
trait Billable 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;
use Request;
protected stdClass|null $userData = null;
/** /**
* Get the currently authenticated user. * Get the currently authenticated user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
protected function getUserData(): stdClass public function getUserData(): object
{ {
if (!$this->userData) { if (!isset($this->userDataCache)) {
$this->userData = $this->request('GET', 'v1/user')->data; $this->userDataCache = $this->anikeenId()->request('GET', 'v1/user')->data;
} }
return $this->userData; return $this->userDataCache;
} }
/** /**
* Make a request to the Anikeen API. * Get the AnikeenId class.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
protected function request(string $method, string $path, null|array $payload = null, array $parameters = [], Paginator $paginator = null): Result public function anikeenId(): AnikeenId
{ {
$anikeenId = new AnikeenId(); return app(AnikeenId::class)->withToken($this->{AnikeenId::getAccessTokenField()});
$anikeenId->withToken($this->{AnikeenId::getAccessTokenField()});
return $anikeenId->request($method, $path, $payload, $parameters, $paginator);
} }
} }

View File

@@ -0,0 +1,23 @@
<?php
namespace Anikeen\Id\Concerns;
use Anikeen\Id\Contracts\Billable;
use Illuminate\Database\Eloquent\Model;
trait HasBillable
{
public Billable|Model $billable;
public function setBillable(Billable|Model $billable): self
{
$this->billable = $billable;
return $this;
}
public function getBillable(): Billable
{
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

@@ -2,131 +2,37 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Addresses;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesAddresses trait ManagesAddresses
{ {
use Request; use HasBillable;
/** /**
* Get addresses from the current user. * Get addresses from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function addresses(): Result public function addresses(): Addresses
{ {
return $this->request('GET', 'v1/addresses'); if (!isset($this->addressesCache)) {
$this->addressesCache = Addresses::builder(fn() => $this->anikeenId()
->request('GET', 'v1/addresses'))
->setBillable($this);
}
return $this->addressesCache;
} }
/** /**
* Creates a new address for the current user. * Check if the current user has a default billing address.
* *
* @param array{ * @throws Throwable
* company_name: null|string, * @see \Anikeen\Id\Resources\Addresses::hasDefaultBillingAddress()
* 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 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

@@ -2,20 +2,17 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Transaction;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesBalance trait ManagesBalance
{ {
use Request; use HasBillable;
/** /**
* Get balance from the current user. * Get balance from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function balance(): float public function balance(): float
{ {
@@ -25,8 +22,7 @@ trait ManagesBalance
/** /**
* Get charges from the current user. * Get charges from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function charges(): float public function charges(): float
{ {
@@ -39,15 +35,16 @@ trait ManagesBalance
* @param float $amount Amount to charge in euros. * @param float $amount Amount to charge in euros.
* @param string $paymentMethodId Payment method ID. * @param string $paymentMethodId Payment method ID.
* @param array $options Additional options for the charge. * @param array $options Additional options for the charge.
* @throws RequestRequiresClientIdException * @throws Throwable
* @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(fn() => $this->anikeenId()
'amount' => $amount, ->request('POST', 'billing/charge', [
'payment_method_id' => $paymentMethodId, 'amount' => $amount,
'options' => $options, 'payment_method_id' => $paymentMethodId,
]); 'options' => $options,
])))
->setBillable($this);
} }
} }

View File

@@ -0,0 +1,27 @@
<?php
namespace Anikeen\Id\Concerns;
use Anikeen\Id\Resources\Countries;
use Throwable;
trait ManagesCountries
{
use HasBillable;
/**
* Get available countries for the current user.
*
* @throws Throwable
*/
public function countries(): Countries
{
if (!isset($this->countriesCache)) {
$this->countriesCache = Countries::builder(fn() => $this->anikeenId()
->request('GET', 'v1/countries'))
->setBillable($this);
}
return $this->countriesCache;
}
}

View File

@@ -2,47 +2,26 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Invoices;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesInvoices trait ManagesInvoices
{ {
use Request; use HasBillable;
/** /**
* Get invoices from the current user. * Get invoices from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function invoices(): Result public function invoices(array $parameters = []): Invoices
{ {
return $this->request('GET', 'v1/invoices'); if (!isset($this->invoicesCache)) {
} $this->invoicesCache = Invoices::builder(fn() => $this->anikeenId()
->request('GET', 'v1/invoices', [], $parameters))
->setBillable($this);
}
/** return $this->invoicesCache;
* 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

@@ -2,269 +2,26 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Orders;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesOrders trait ManagesOrders
{ {
use Request; use HasBillable;
/** /**
* Get orders from the current user. * Get orders from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function orders(): Result public function orders(array $parameters = []): Orders
{ {
return $this->request('GET', 'v1/orders'); if (!isset($this->ordersCache)) {
} $this->ordersCache = Orders::builder(fn() => $this->anikeenId()
->request('GET', 'v1/orders', [], $parameters))
->setBillable($this);
}
/** return $this->ordersCache;
* 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

@@ -2,86 +2,93 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\PaymentMethod;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Anikeen\Id\Resources\PaymentMethods;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use Throwable;
trait ManagesPaymentMethods trait ManagesPaymentMethods
{ {
use Request; use HasBillable;
/**
* 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 Throwable
* @throws GuzzleException
*/ */
public function paymentMethods(): Result public function paymentMethods(): PaymentMethods
{ {
return $this->request('GET', 'v1/payment-methods'); if (!isset($this->paymentMethodsCache)) {
$this->paymentMethodsCache = PaymentMethods::builder(fn() => $this->anikeenId()
->request('GET', 'v1/payment-methods'))
->setBillable($this);
}
return $this->paymentMethodsCache;
}
/**
* Check if current user has at least one payment method.
*
* @throws Throwable
* @see \Anikeen\Id\Resources\PaymentMethods::hasPaymentMethod()
*/
public function hasPaymentMethod(): ?PaymentMethod
{
return $this->paymentMethods()->hasPaymentMethod();
} }
/** /**
* Get default payment method from the current user. * Get default payment method from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException * @see \Anikeen\Id\Resources\PaymentMethods::defaultPaymentMethod()
*/
public function defaultPaymentMethod(): ?PaymentMethod
{
return $this->paymentMethods()->defaultPaymentMethod();
}
/**
* Check if the current user has a default payment method.
*
* @throws Throwable
* @see \Anikeen\Id\Resources\PaymentMethods::hasDefaultPaymentMethod()
*/ */
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 Throwable
* @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->anikeenId()
'return_url' => $returnUrl, ->request('POST', 'v1/billing/portal', [
'options' => $options, 'return_url' => $returnUrl,
])->data->url; 'options' => $options,
])
->data
->url;
} }
/** /**
* Create a new setup intent. * Create a new setup intent.
* *
* @param array $options Additional options for the setup intent. * @param array $options Additional options for the setup intent.
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function createSetupIntent(array $options = []): Result public function createSetupIntent(array $options = []): Result
{ {
return $this->request('POST', 'v1/payment-methods', [ return $this->anikeenId()
'options' => $options, ->request('POST', 'v1/payment-methods', [
]); 'options' => $options,
]);
} }
} }

View File

@@ -3,9 +3,8 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Post; use Anikeen\Id\ApiOperations\Post;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use Throwable;
trait ManagesPricing trait ManagesPricing
{ {
@@ -29,8 +28,7 @@ trait ManagesPricing
* } $attributes The order data: * } $attributes The order data:
* - country_iso: ISO 3166-1 alpha-2 country code * - country_iso: ISO 3166-1 alpha-2 country code
* - items: Array of order items (each with type, name, price, unit, units, and quantity) * - items: Array of order items (each with type, name, price, unit, units, and quantity)
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function createOrderPreview(array $attributes = []): Result public function createOrderPreview(array $attributes = []): Result
{ {

View File

@@ -0,0 +1,26 @@
<?php
namespace Anikeen\Id\Concerns;
use Throwable;
trait ManagesProfile
{
use HasBillable;
/**
* 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 Throwable
*/
public function profilePortalUrl(?string $returnUrl = null, array $options = []): string
{
return $this->anikeenId()->request('POST', 'v1/user/profile', [
'return_url' => $returnUrl,
'options' => $options,
])->data->url;
}
}

View File

@@ -3,52 +3,26 @@
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\Resources\SshKeys;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
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.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @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)); if (!isset($this->sshKeysCache)) {
} $this->sshKeysCache = SshKeys::builder(fn() => $this->get(sprintf('v1/users/%s/ssh-keys/json', $sskKeyId)))
->setParent($this);
}
/** return $this->sshKeysCache;
* 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

@@ -2,114 +2,26 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Subscriptions;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesSubscriptions trait ManagesSubscriptions
{ {
use Request; use HasBillable;
/** /**
* Get subscriptions from the current user. * Get subscriptions from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function subscriptions(): Result public function subscriptions(): Subscriptions
{ {
return $this->request('GET', 'v1/subscriptions'); if (!isset($this->subscriptionsCache)) {
} $this->subscriptionsCache = Subscriptions::builder(fn() => $this->anikeenId()
->request('GET', 'v1/subscriptions'))
->setBillable($this);
}
/** return $this->subscriptionsCache;
* 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

@@ -2,22 +2,19 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Throwable;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesTaxation trait ManagesTaxation
{ {
use Request; use HasBillable;
/** /**
* Get VAT for the current user. * Get VAT for the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function vat(): float public function vatRate(): float
{ {
return $this->getUserData()->vat; return $this->getUserData()->vat_rate;
} }
} }

View File

@@ -2,48 +2,26 @@
namespace Anikeen\Id\Concerns; namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Request; use Anikeen\Id\Resources\Transactions;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException; use Throwable;
use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException;
trait ManagesTransactions trait ManagesTransactions
{ {
use Request; use HasBillable;
/** /**
* Get transactions from the current user. * Get transactions from the current user.
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function transactions(): Result public function transactions(): Transactions
{ {
return $this->request('GET', 'v1/transactions'); if (!isset($this->transactionsCache)) {
} $this->transactionsCache = Transactions::builder(fn() => $this->anikeenId()
->request('GET', 'v1/transactions'))
->setBillable($this);
}
/** return $this->transactionsCache;
* 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

@@ -5,19 +5,18 @@ namespace Anikeen\Id\Concerns;
use Anikeen\Id\ApiOperations\Get; use Anikeen\Id\ApiOperations\Get;
use Anikeen\Id\ApiOperations\Post; use Anikeen\Id\ApiOperations\Post;
use Anikeen\Id\Exceptions\RequestRequiresClientIdException;
use Anikeen\Id\Result; use Anikeen\Id\Result;
use GuzzleHttp\Exception\GuzzleException; use Throwable;
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
* *
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function getAuthedUser(): Result public function getAuthedUser(): Result
{ {
@@ -39,20 +38,32 @@ trait ManagesUsers
* - username: The username (optional) * - username: The username (optional)
* - email: The email (required) * - email: The email (required)
* - password: The password (optional, can be reset by the user if not provided) * - password: The password (optional, can be reset by the user if not provided)
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function createUser(array $attributes): Result public function createUser(array $attributes): Result
{ {
return $this->post('v1/users', $attributes); return $this->post('v1/users', $attributes);
} }
/**
* Refreshes the access token using the refresh token.
*/
public function refreshToken(string $storedRefreshToken, string $scope = ''): Result
{
return $this->post('../oauth/token', [
'grant_type' => 'refresh_token',
'refresh_token' => $storedRefreshToken,
'client_id' => $this->clientId,
'client_secret' => $this->clientSecret,
'scope' => $scope,
]);
}
/** /**
* Checks if the given email exists. * Checks if the given email exists.
* *
* @param string $email The email to check. * @param string $email The email to check.
* @throws RequestRequiresClientIdException * @throws Throwable
* @throws GuzzleException
*/ */
public function isEmailExisting(string $email): Result public function isEmailExisting(string $email): Result
{ {

View File

@@ -0,0 +1,12 @@
<?php
namespace Anikeen\Id\Contracts;
use Anikeen\Id\AnikeenId;
interface Billable
{
public function getUserData(): object;
public function anikeenId(): AnikeenId;
}

View File

@@ -6,11 +6,35 @@ class Scope
{ {
const USER = 'user'; const USER = 'user';
const USER_READ = 'user:read'; const USER_READ = 'user:read';
const ORDERS = 'orders';
const ORDERS_READ = 'orders:read'; const ADDRESSES = 'addresses';
const PRODUCTS = 'products'; const ADDRESSES_READ = 'addresses:read';
const PRODUCTS_READ = 'products:read';
const BILLING = 'billing'; const BILLING = 'billing';
const BILLING_READ = 'billing:read'; const BILLING_READ = 'billing:read';
const BILLING_CLIENT = 'billing:client';
const INVOICES = 'invoices';
const INVOICES_READ = 'invoices:read';
const INVOICES_CLIENT = 'invoices:client';
const ORDERS = 'orders';
const ORDERS_READ = 'orders:read';
const ORDERS_CLIENT = 'orders:client';
const PAYMENT_METHODS = 'payment-methods';
const PAYMENT_METHODS_READ = 'payment-methods:read';
const SUBSCRIPTIONS = 'subscriptions';
const SUBSCRIPTIONS_READ = 'subscriptions:read';
const SUBSCRIPTIONS_CLIENT = 'subscriptions:client';
const TRANSACTIONS = 'transactions';
const TRANSACTIONS_READ = 'transactions:read';
const TRANSACTIONS_CLIENT = 'transactions:client';
const SSH_KEYS = 'ssh-keys';
const SSH_KEYS_READ = 'ssh-keys:read';
const ADMIN = 'admin'; const ADMIN = 'admin';
} }

View File

@@ -0,0 +1,11 @@
<?php
namespace Anikeen\Id\Exceptions;
use Anikeen\Id\Result;
use Exception;
class CollectionException extends Exception
{
}

View File

@@ -0,0 +1,11 @@
<?php
namespace Anikeen\Id\Exceptions;
use Anikeen\Id\Result;
use Exception;
class ResourceException extends Exception
{
//
}

View File

@@ -2,6 +2,7 @@
namespace Anikeen\Id\Helpers; namespace Anikeen\Id\Helpers;
use Anikeen\Id\AnikeenId;
use Firebase\JWT\JWT; use Firebase\JWT\JWT;
use Firebase\JWT\Key; use Firebase\JWT\Key;
use Illuminate\Auth\AuthenticationException; use Illuminate\Auth\AuthenticationException;
@@ -30,6 +31,8 @@ class JwtParser
private function getOauthPublicKey(): bool|string private function getOauthPublicKey(): bool|string
{ {
return file_get_contents(dirname(__DIR__, 3) . '/oauth-public.key'); return AnikeenId::getMode() === 'staging'
? file_get_contents(dirname(__DIR__, 3) . '/oauth-public.staging.key')
: file_get_contents(dirname(__DIR__, 3) . '/oauth-public.key');
} }
} }

View File

@@ -9,7 +9,7 @@ use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use stdClass; use stdClass;
abstract class CheckCredentials abstract class CheckCredentials extends UseParameters
{ {
/** /**
* Handle an incoming request. * Handle an incoming request.

View File

@@ -8,7 +8,7 @@ use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
class CheckForAnyScope class CheckForAnyScope extends UseParameters
{ {
/** /**
* Handle the incoming request. * Handle the incoming request.

View File

@@ -8,7 +8,7 @@ use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Http\Response; use Illuminate\Http\Response;
class CheckScopes class CheckScopes extends UseParameters
{ {
/** /**
* Handle the incoming request. * Handle the incoming request.

View File

@@ -0,0 +1,20 @@
<?php
namespace Anikeen\Id\Http\Middleware;
abstract class UseParameters
{
/**
* Specify the parameters for the middleware.
*
* @param string[]|string $param
*/
public static function using(array|string $param, string ...$params): string
{
if (is_array($param)) {
return static::class . ':' . implode(',', $param);
}
return static::class . ':' . implode(',', [$param, ...$params]);
}
}

View File

@@ -23,13 +23,11 @@ trait OauthTrait
], ],
]); ]);
$result = new Result($response, null); $result = new Result($response, null, $this);
} catch (RequestException $exception) { } catch (RequestException $exception) {
$result = new Result($exception->getResponse(), $exception); $result = new Result($exception->getResponse(), $exception, $this);
} }
$result->anikeenId = $this;
return $result; return $result;
} }
} }

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

@@ -1,113 +0,0 @@
<?php
namespace Anikeen\Id\Providers;
use Anikeen\Id\AnikeenId;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class AnikeenIdSsoUserProvider implements UserProvider
{
private ?string $accessTokenField = null;
public function __construct(
private AnikeenId $anikeenId,
private Request $request,
private string $model,
private array $fields
)
{
$this->accessTokenField = AnikeenId::getAccessTokenField();
}
public function retrieveById(mixed $identifier): ?Authenticatable
{
$model = $this->createModel();
$token = $this->request->bearerToken();
$user = $this->newModelQuery($model)
->where($model->getAuthIdentifierName(), $identifier)
->first();
// Return user when found
if ($user) {
// Update access token when updated
if ($this->accessTokenField) {
$user[$this->accessTokenField] = $token;
if ($user->isDirty()) {
$user->save();
}
}
return $user;
}
// Create new user
$this->anikeenId->setToken($token);
$result = $this->anikeenId->getAuthedUser();
if (!$result->success()) {
return null;
}
$attributes = Arr::only((array)$result->data(), $this->fields);
$attributes[$model->getAuthIdentifierName()] = $result->data->id;
if ($this->accessTokenField) {
$attributes[$this->accessTokenField] = $token;
}
return $this->newModelQuery($model)->create($attributes);
}
/**
* Create a new instance of the model.
*/
public function createModel(): Model
{
$class = '\\' . ltrim($this->model, '\\');
return new $class;
}
/**
* Get a new query builder for the model instance.
*/
protected function newModelQuery(?Model $model = null): Builder
{
return is_null($model)
? $this->createModel()->newQuery()
: $model->newQuery();
}
public function retrieveByToken($identifier, $token)
{
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
// void
}
public function retrieveByCredentials(array $credentials)
{
return null;
}
public function validateCredentials(Authenticatable $user, array $credentials): bool
{
return false;
}
public function rehashPasswordIfRequired(Authenticatable $user, #[\SensitiveParameter] array $credentials, bool $force = false)
{
// TODO: Implement rehashPasswordIfRequired() method.
}
}

View File

@@ -0,0 +1,124 @@
<?php
namespace Anikeen\Id\Providers;
use Anikeen\Id\AnikeenId;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
class AnikeenIdUserProvider implements UserProvider
{
private ?string $accessTokenField;
public function __construct(
private AnikeenId $anikeenId,
private Request $request,
private string $model,
private array $fields = []
) {
$this->accessTokenField = AnikeenId::getAccessTokenField();
}
/**
* {@inheritDoc}
*/
public function retrieveByToken($identifier, $token): ?Authenticatable
{
// Token from request (if not already pass from $token):
$token = $token ?: $this->request->bearerToken();
if (! $token) {
return null;
}
// Set token in SSO client and request user info
$this->anikeenId->setToken($token);
$result = $this->anikeenId->getAuthedUser();
if (! $result->success()) {
return null;
}
// Only the desired fields
$data = Arr::only((array)$result->data(), $this->fields);
// Primary key (e.g. $user->id)
$pk = $this->createModel()->getAuthIdentifierName();
$data[$pk] = $result->data->id;
// Fill in access token field, if available
if ($this->accessTokenField) {
$data[$this->accessTokenField] = $token;
}
// Local eloquent model: either find or create a new one
/** @var Model $modelInstance */
$modelInstance = $this->newModelQuery()
->firstOrNew([$pk => $data[$pk]]);
$modelInstance->fill($data);
$modelInstance->save();
return $modelInstance;
}
/**
* {@inheritDoc}
*/
public function updateRememberToken(Authenticatable $user, $token): void
{
// no-op
}
/**
* {@inheritDoc}
*/
public function retrieveByCredentials(array $credentials): ?Authenticatable
{
return null;
}
/**
* {@inheritDoc}
*/
public function validateCredentials(Authenticatable $user, array $credentials): bool
{
return true;
}
/**
* {@inheritDoc}
*/
public function retrieveById($identifier): ?Authenticatable
{
return $this->newModelQuery()
->where($this->createModel()->getAuthIdentifierName(), $identifier)
->first();
}
/**
* {@inheritDoc}
*/
public function rehashPasswordIfRequired(Authenticatable $user, #[\SensitiveParameter] array $credentials, bool $force = false): void
{
// no-op
}
/**
* @return Model
*/
protected function createModel(): Model
{
$class = '\\' . ltrim($this->model, '\\');
return new $class;
}
/**
* @return Builder
*/
protected function newModelQuery(): Builder
{
return $this->createModel()->newQuery();
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
/**
* @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 Throwable
*/
public function update(array $attributes = []): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/addresses/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Delete given address from the current user.
*
* @throws Throwable
*/
public function delete(): bool
{
return $this->billable->anikeenId()
->request('DELETE', sprintf('v1/addresses/%s', $this->id))->success();
}
}

View File

@@ -0,0 +1,85 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
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 Throwable
*/
public function create(array $attributes = []): Address
{
return (new Address(fn() => $this->billable->anikeenId()
->request('POST', 'v1/addresses', $attributes)))
->setBillable($this->billable);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?Address
{
return (new Address(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/addresses/%s', $id))))
->setBillable($this->billable);
}
/**
* Get default address from the current user.
*
* @throws Throwable
*/
public function defaultBillingAddress(): Address
{
return (new Address(fn() => $this->billable->anikeenId()
->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 Throwable
*/
public function hasDefaultBillingAddress(): bool
{
return $this->billable->getUserData()->billing_address_id !== null;
}
}

View File

@@ -0,0 +1,90 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\AnikeenId;
use Anikeen\Id\Exceptions\CollectionException;
use Anikeen\Id\Helpers\Paginator;
use Anikeen\Id\Result;
use Closure;
use GuzzleHttp\Psr7\Response;
use JsonSerializable;
use Throwable;
/**
* @property bool $success
* @property mixed $data
* @property int $total
* @property int $status
* @property null|array $links
* @property null|array $meta
* @property null|Paginator $paginator
* @property AnikeenId $anikeenId
* @property Response $response
* @property null|Throwable $exception
*/
#[\AllowDynamicProperties]
abstract class BaseCollection implements JsonSerializable
{
private Closure $callable;
public ?Result $result = null;
/**
* @throws CollectionException
*/
protected function __construct(callable $callable)
{
$this->result = $callable();
if (!$this->result->success()) {
throw new CollectionException(sprintf('%s for collection [%s]', rtrim($this->result->data->message, '.'), get_called_class()), $this->result->response->getStatusCode());
}
}
/**
* @throws CollectionException
*/
public static function builder(callable $callable): static
{
return new static($callable, false);
}
/**
* 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;
public function __get(string $name)
{
return $this->result->{$name} ?? null;
}
public function __isset(string $name): bool
{
return isset($this->result->{$name});
}
}

View File

@@ -0,0 +1,57 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Exceptions\ResourceException;
use Anikeen\Id\Result;
use JsonSerializable;
#[\AllowDynamicProperties]
abstract class BaseResource implements JsonSerializable
{
public Result $result;
/**
* @throws ResourceException
*/
public function __construct(callable $callable)
{
$this->result = $callable();
if (!$this->result->success()) {
throw new ResourceException(sprintf('%s for resource [%s]', rtrim($this->result->data->message, '.'), get_called_class()), $this->result->response->getStatusCode());
}
foreach ($this->result->data as $key => $value) {
if (!property_exists($this, $key)) {
$this->{$key} = $value;
}
}
}
/**
* Returns the collection of resources as a JSON string.
*/
public function jsonSerialize(): array
{
return $this->toArray();
}
/**
* Returns the collection of resources as an array.
*/
public function toArray(): array
{
return (array)$this->result->data;
}
public function __get(string $name)
{
return null;
}
public function __isset(string $name): bool
{
return false;
}
}

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,27 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
/**
* @property string $id
*/
class Invoice extends BaseResource
{
use HasBillable;
/**
* Get temporary download url from given invoice.
*
* @throws Throwable
*/
public function getInvoiceTemporaryUrl(): string
{
return $this->billable->anikeenId()
->request('PUT', sprintf('v1/invoices/%s', $this->id))
->data
->temporary_url;
}
}

View File

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

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

@@ -0,0 +1,113 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
/**
* @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 Throwable
*/
public function update(array $attributes = []): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/orders/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Checkout given order from the current user.
*
* @throws Throwable
*/
public function checkout(): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/orders/%s/checkout', $this->id))))
->setBillable($this->billable);
}
/**
* Revoke given order from the current user.
*
* @throws Throwable
*/
public function revoke(): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/orders/%s/revoke', $this->id))))
->setBillable($this->billable);
}
/**
* Delete given order from the current user.
*
* @throws Throwable
*/
public function delete(): bool
{
return $this->billable->anikeenId()
->request('DELETE', sprintf('v1/orders/%s', $this->id))->success();
}
/**
* Get order items from given order.
*
* @throws Throwable
*/
public function orderItems(array $parameters = []): OrderItems
{
return OrderItems::builder(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/orders/%s/items', $this->id), [], $parameters))
->setBillable($this->billable)
->setParent($this);
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Concerns\HasParent;
use Throwable;
/**
* @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 Throwable
*/
public function update(array $attributes = []): self
{
return (new self(fn() => $this->billable->anikeenId()
->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 Throwable
*/
public function delete(): bool
{
return $this->billable->anikeenId()
->request('DELETE', sprintf('v1/orders/%s/items/%s', $this->parent->id, $this->id))->success();
}
}

View File

@@ -0,0 +1,51 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Concerns\HasParent;
use Throwable;
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 Throwable
*/
public function create(string $orderId, array $attributes = []): OrderItem
{
return (new OrderItem(fn() => $this->billable->anikeenId()
->request('POST', sprintf('v1/orders/%s', $orderId), $attributes)))
->setBillable($this->billable)
->setParent($this->parent);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?OrderItem
{
return (new OrderItem(fn() => $this->parent->anikeenId()
->request('GET', sprintf('v1/orders/%s/items/%s', $this->parent->id, $id))))
->setBillable($this->billable)
->setParent($this->parent);
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
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 Throwable
*/
public function create(array $attributes = []): Order
{
return (new Order(fn() => $this->billable->anikeenId()
->request('POST', 'v1/orders', $attributes)))
->setBillable($this->billable);
}
/**
* Get given order from the current user.
*
* @throws Throwable
*/
public function find(string $id): ?Order
{
return (new Order(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/orders/%s', $id))))
->setBillable($this->billable);
}
}

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,59 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Throwable;
class PaymentMethods extends BaseCollection
{
use HasBillable;
/**
* Check if current user has at least one payment method.
*
* @throws Throwable
*/
public function hasPaymentMethod(): bool
{
return $this->result->count() > 0;
}
/**
* Check if the current user has a default payment method.
*
* @throws Throwable
*/
public function hasDefaultPaymentMethod(): bool
{
return $this->defaultPaymentMethod()?->id !== null;
}
/**
* Get default payment method from the current user.
*
* @throws Throwable
*/
public function defaultPaymentMethod(): PaymentMethod
{
if (!isset($this->defaultPaymentMethodCache)) {
$this->defaultPaymentMethodCache = (new PaymentMethod(fn() => $this->billable->anikeenId()
->request('GET', 'v1/payment-methods/default')))
->setBillable($this->billable);
}
return $this->defaultPaymentMethodCache;
}
/**
* {@inheritDoc}
*
* @throws Throwable
*/
public function find(string $id): ?PaymentMethod
{
return (new PaymentMethod(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/payment-methods/%s', $id))))
->setBillable($this->billable);
}
}

View File

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

View File

@@ -0,0 +1,34 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasParent;
use Throwable;
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 Throwable
*/
public function create(string $publicKey, ?string $name = null): SshKey
{
return (new SshKey(fn() => $this->parent->post('v1/ssh-keys', [
'public_key' => $publicKey,
'name' => $name,
])))->setParent($this->parent);
}
/**
* {@inheritDoc}
*/
public function find(string $id): ?SshKey
{
return (new SshKey(fn() => $this->parent->get(sprintf('v1/ssh-keys/%s', $id))));
}
}

View File

@@ -0,0 +1,110 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Contracts\AppTokenRepository;
use Throwable;
/**
* @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{
* group: string,
* name: string,
* description: null|string,
* unit: string,
* price: float,
* vat_rate: float,
* payload: null|array,
* ends_at: null|string,
* webhook_url: null|string,
* webhook_secret: null|string
* } $attributes The subscription data:
* - group: The group (optional)
* - name: The name (required when set)
* - description: The description (optional)
* - unit: The unit (required when set, e.g. "hour", "day", "week", "month", "year")
* - price: The price per unit (required when set)
* - vat_rate: The VAT rate (required when set)
* - payload: The payload (optional)
* - ends_at: The end date (optional)
* - webhook_url: The webhook URL (optional)
* - webhook_secret: The webhook secret (optional)
* @throws Throwable
*/
public function update(array $attributes): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/subscriptions/%s', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Force given subscription to check out (trusted apps only).
*
* @throws Throwable
*/
public function checkout(): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/subscriptions/%s/checkout', $this->id))))
->setBillable($this->billable);
}
/**
* Revoke a given running subscription from the current user.
*
* @throws Throwable
*/
public function revoke(bool $refund = false): self
{
$attributes = [
'refund' => $refund,
];
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/subscriptions/%s/revoke', $this->id), $attributes)))
->setBillable($this->billable);
}
/**
* Pause a given running subscription from the current user.
*
* @throws Throwable
*/
public function pause(): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/subscriptions/%s/pause', $this->id))))
->setBillable($this->billable);
}
/**
* Resume a given running subscription from the current user.
*
* @throws Throwable
*/
public function resume(): self
{
return (new self(fn() => $this->billable->anikeenId()
->request('PUT', sprintf('v1/subscriptions/%s/resume', $this->id))))
->setBillable($this->billable);
}
}

View File

@@ -0,0 +1,59 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
use Anikeen\Id\Contracts\AppTokenRepository;
use Anikeen\Id\Exceptions\ResourceException;
use Throwable;
class Subscriptions extends BaseCollection
{
use HasBillable;
/**
* Create a new subscription for the current user.
*
* @param array{
* group: null|string,
* name: string,
* description: null|string,
* unit: string,
* price: float,
* vat_rate: float,
* payload: null|array,
* ends_at: null|string,
* webhook_url: null|string,
* webhook_secret: null|string
* } $attributes The subscription data:
* - group: The group (optional)
* - 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 (required when set)
* - payload: The payload (optional)
* - ends_at: The end date (optional)
* - webhook_url: The webhook URL (optional)
* - webhook_secret: The webhook secret (optional)
* @throws Throwable
*/
public function create(array $attributes): Subscription
{
return (new Subscription(fn() => $this->billable->anikeenId()
->request('POST', 'v1/subscriptions', $attributes)))
->setBillable($this->billable);
}
/**
* {@inheritDoc}
*
* @throws ResourceException
*/
public function find(string $id): ?Subscription
{
return (new Subscription(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/subscriptions/%s', $id))))
->setBillable($this->billable);
}
}

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,20 @@
<?php
namespace Anikeen\Id\Resources;
use Anikeen\Id\Concerns\HasBillable;
class Transactions extends BaseCollection
{
use HasBillable;
/**
* {@inheritDoc}
*/
public function find(string $id): ?Transaction
{
return (new Transaction(fn() => $this->billable->anikeenId()
->request('GET', sprintf('v1/transactions/%s', $id))))
->setBillable($this->billable);
}
}

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

@@ -2,8 +2,10 @@
namespace Anikeen\Id\Socialite; namespace Anikeen\Id\Socialite;
use Anikeen\Id\AnikeenId;
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 +16,7 @@ class Provider extends AbstractProvider implements ProviderInterface
/** /**
* Unique Provider Identifier. * Unique Provider Identifier.
*/ */
const IDENTIFIER = 'ANIKEEN_ID'; const IDENTIFIER = 'ANIKEEN';
/** /**
* {@inheritdoc} * {@inheritdoc}
@@ -26,13 +28,21 @@ class Provider extends AbstractProvider implements ProviderInterface
*/ */
protected $scopeSeparator = ' '; protected $scopeSeparator = ' ';
/**
* Get the base URL for the API.
*/
protected function getBaseUrl(): string
{
return app(AnikeenId::class)->getBaseUrl();
}
/** /**
* {@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 +51,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 +62,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,
@@ -85,4 +95,12 @@ class Provider extends AbstractProvider implements ProviderInterface
'grant_type' => 'authorization_code', 'grant_type' => 'authorization_code',
]); ]);
} }
/**
* Returns the user logout url for the provider.
*/
public function getLogoutUrl(string $redirect = null): string
{
return app(AnikeenId::class)->getBaseUrl() . '/logout?redirect=' . urlencode($redirect ?: '/');
}
} }