85 Commits

Author SHA1 Message Date
3f8c83f351 Update oauth-public.key 2023-09-02 00:20:15 +02:00
2ac7a361fb Update Wallets.php 2023-08-09 13:12:22 +02:00
1b8aef1f6f fix algorithm
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-20 17:16:48 +01:00
f17519743e update jwt encode/decode
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-20 16:22:06 +01:00
aea65e0894 update firebase and illuminate
Signed-off-by: envoyr <hello@envoyr.com>
2023-02-20 14:53:12 +01:00
ce36527fe0 Update Subscriptions.php 2022-10-03 11:15:54 +02:00
c0dee13277 Update UsersTrait.php 2022-10-02 23:25:42 +02:00
4fae63297c Update Provider.php 2022-10-02 22:15:26 +02:00
f8439f81e9 Update Provider.php 2022-10-02 22:12:46 +02:00
95b8559b01 Update Scope.php 2022-10-02 12:51:51 +02:00
441900e1d3 Update Orders.php 2022-10-01 14:42:01 +02:00
René Preuß
f5e9b0e5e6 Refactoring 2022-10-01 14:03:00 +02:00
René Preuß
a2405875ad Refactoring 2022-10-01 14:00:25 +02:00
René Preuß
7ba3cdffb0 Add app token repository 2022-10-01 12:09:36 +02:00
00f30e097d Update README.md 2022-09-30 15:56:57 +02:00
99a9331b6e Update README.stub 2022-09-30 15:56:35 +02:00
5caef21b01 Update HasBitinflowPaymentsWallet.php 2022-09-29 16:22:14 +02:00
8e88d12d88 Update Wallets.php 2022-09-29 16:14:00 +02:00
d44400fcfa Update Orders.php 2022-09-29 16:01:11 +02:00
e4378d67c1 Update Subscriptions.php 2022-09-29 16:00:34 +02:00
a205365bf5 Update HasBitinflowPaymentsWallet.php 2022-09-29 15:56:57 +02:00
c7eadf30ee Update HasBitinflowPaymentsWallet.php 2022-09-29 15:55:37 +02:00
49bbefae64 Update Subscriptions.php 2022-09-29 14:58:49 +02:00
6290f6a419 Update Wallets.php 2022-09-29 14:57:45 +02:00
ac734a04c4 Update Taxation.php 2022-09-29 14:57:28 +02:00
32b7437239 Update HasBitinflowPaymentsWallet.php 2022-09-29 14:57:15 +02:00
120bd61ae5 Create Taxation.php 2022-09-29 14:56:04 +02:00
78f570f404 Create Balance.php 2022-09-29 14:54:37 +02:00
7dad10bd47 Update CheckoutSessions.php 2022-09-29 14:52:11 +02:00
87fc416b44 Create CheckoutSessions.php 2022-09-29 14:51:51 +02:00
d03a95b552 Update Subscriptions.php 2022-09-29 14:49:11 +02:00
1476446765 Create Orders.php 2022-09-29 14:48:44 +02:00
76243f0883 Create Subscriptions.php 2022-09-29 14:46:17 +02:00
91bdad593f Create Wallets.php 2022-09-29 14:41:26 +02:00
4fdbe165bc Update HasBitinflowPaymentsWallet.php 2022-09-28 21:15:09 +02:00
f929f32887 Update HasBitinflowPaymentsWallet.php 2022-09-25 22:18:24 +02:00
eb30953d8e Update HasBitinflowPaymentsWallet.php 2022-09-25 21:53:34 +02:00
c6c815523c Update HasBitinflowPaymentsWallet.php 2022-09-25 21:40:17 +02:00
f86a547ea1 Update HasBitinflowPaymentsWallet.php 2022-09-25 21:24:58 +02:00
ac7ffab887 Update HasBitinflowPaymentsWallet.php 2022-09-25 20:37:43 +02:00
db5685d612 Update AUTH.md 2022-07-08 00:20:31 +02:00
3c46197a61 Update BitinflowAccountsSsoUserProvider.php 2022-07-08 00:19:47 +02:00
a11436fbfe Update BitinflowAccounts.php 2022-07-08 00:18:08 +02:00
c1bd13b449 Update composer.json 2022-07-07 23:58:02 +02:00
fc54c8b542 Update composer.json 2022-07-07 22:59:04 +02:00
6fb7f0b38a Update JwtParser.php 2022-05-23 14:55:40 +02:00
e902a3a5b8 fix dirname depth 2022-05-14 18:26:08 +02:00
377dc53037 change namespace and cleanup code 2022-05-14 18:21:55 +02:00
René Preuß
76edd961b7 Update composer.json 2022-05-14 17:58:17 +02:00
René Preuß
8816ade239 Update README.md 2022-05-12 17:43:32 +02:00
032c771e49 add bitinflow payments subscriptions 2022-05-08 22:53:02 +02:00
b5b4f9cf5e Merge pull request #9 from envoyr/patch-1
Update composer.json
2022-05-08 17:27:28 +02:00
4f3fa6d571 Update composer.json 2022-05-08 17:25:12 +02:00
René Preuß
2df4501eae Merge pull request #8 from envoyr/master
Update BitinflowAccountsSsoUserProvider.php
2022-01-02 19:01:07 +01:00
55c4276d9b Update BitinflowAccountsSsoUserProvider.php 2022-01-02 18:52:35 +01:00
René Preuß
baa45edb8a Merge pull request #7 from envoyr/sso
Add sso user provider
2022-01-02 14:25:29 +01:00
8f962d1e06 Add sso user provider 2022-01-02 14:04:01 +01:00
René Preuß
3760e312cd Update phpunit 2021-03-31 13:21:53 +02:00
René Preuß
76b0569b0f Add php 8 support 2021-03-31 13:20:47 +02:00
René Preuß
e60c5a5cd3 Update can method 2021-03-31 12:15:57 +02:00
René Preuß
7af00cb7ab Allow * tokens 2021-03-31 12:14:08 +02:00
René Preuß
3ba4805e3f Fix namespace 2021-03-31 12:00:52 +02:00
René Preuß
80a6491058 Remove static version 2021-03-30 23:20:17 +02:00
René Preuß
14bf9d5480 Add jwt handling, inspired by passport 2021-03-30 23:19:36 +02:00
1elf-me
32990da8a0 updated oauth-public.key 2021-03-20 17:02:35 +01:00
1elf-me
7805933b10 add CheckClientCredentials middleware 2021-03-20 16:48:50 +01:00
René Preuß
e006cd64b5 Merge pull request #6 from 1elf-me/patch-1
Add guzzle ^7.0.1 support
2021-03-09 13:51:31 +01:00
1elf
7edff12765 Add guzzle ^7.0.1 support 2021-03-09 13:49:15 +01:00
René Preuß
94a6a91e84 Delete composer.lock 2021-01-20 13:25:42 +01:00
René Preuß
da00b4a97c Bump version to laravel v8 2020-09-08 19:39:42 +02:00
René Preuß
26543d806c Merge pull request #3 from ghostzero/dependabot/composer/symfony/http-foundation-4.4.7
Bump symfony/http-foundation from 4.4.1 to 4.4.7
2020-09-08 19:33:29 +02:00
René Preuß
2c82e61790 Laravel 8.x 2020-09-08 19:32:53 +02:00
René Preuß
ca57ff1c65 Merge pull request #4 from 1elf-me/patch-1
Update to Laravel 7.x
2020-07-08 06:24:48 +02:00
1elf-me
9207f38f93 Update to Laravel 7.x 2020-06-21 14:08:53 +02:00
dependabot[bot]
da904f31cb Bump symfony/http-foundation from 4.4.1 to 4.4.7
Bumps [symfony/http-foundation](https://github.com/symfony/http-foundation) from 4.4.1 to 4.4.7.
- [Release notes](https://github.com/symfony/http-foundation/releases)
- [Changelog](https://github.com/symfony/http-foundation/blob/master/CHANGELOG.md)
- [Commits](https://github.com/symfony/http-foundation/compare/v4.4.1...v4.4.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-03-30 21:17:12 +00:00
René Preuß
c182baab17 Add method to check if a email exists 2019-12-28 18:42:59 +01:00
René Preuß
0e07c295e3 Update composer 2019-12-11 11:41:52 +01:00
René Preuß
940617af2e Fix unit test 2019-12-11 11:37:48 +01:00
René Preuß
f2d064caef Fix oauth token generation 2019-12-09 22:46:23 +01:00
René Preuß
29a46a9ef9 Add oauth token method 2019-12-09 21:58:35 +01:00
René Preuß
553167d108 Fix user registration 2019-12-02 15:26:43 +01:00
René Preuß
918bcd4644 Add methods to change base url 2019-12-02 14:18:58 +01:00
René Preuß
2bd19efb3c Add create user method 2019-11-19 17:32:10 +01:00
René Preuß
b5ad7786f2 Fix parameter type and generate documentation 2019-11-19 17:21:13 +01:00
René Preuß
203dc18766 Add test implementation for charges, documents & payment intents 2019-11-19 17:12:27 +01:00
79 changed files with 2778 additions and 5614 deletions

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
vendor vendor
.phpunit.result.cache .phpunit.result.cache
.idea .idea
composer.lock

61
AUTH.md Normal file
View File

@@ -0,0 +1,61 @@
# Implementing Auth
This method should typically be called in the `boot` method of your `AuthServiceProvider` class:
```php
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Providers\BitinflowAccountsSsoUserProvider;
use Illuminate\Http\Request;
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
Auth::provider('sso-users', function ($app, array $config) {
return new BitinflowAccountsSsoUserProvider(
$app->make(BitinflowAccounts::class),
$app->make(Request::class),
$config['model'],
$config['fields'] ?? [],
$config['access_token_field'] ?? null
);
});
}
```
reference the guard in the `guards` configuration of your `auth.php` configuration file:
```php
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'bitinflow-accounts',
'provider' => 'sso-users',
],
],
```
reference the provider in the `providers` configuration of your `auth.php` configuration file:
```php
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\Models\User::class,
],
'sso-users' => [
'driver' => 'sso-users',
'model' => App\Models\User::class,
'fields' => ['first_name', 'last_name', 'email'],
'access_token_field' => 'sso_access_token',
],
],
```

View File

@@ -1,8 +1,9 @@
# bitinflow Accounts # bitinflow Accounts
[![Latest Stable Version](https://img.shields.io/packagist/v/ghostzero/bitinflow-accounts.svg?style=flat-square)](https://packagist.org/packages/ghostzero/bitinflow-accounts) <a href="https://packagist.org/packages/ghostzero/bitinflow-accounts"><img src="https://img.shields.io/packagist/dt/ghostzero/bitinflow-accounts" alt="Total Downloads"></a>
[![Total Downloads](https://img.shields.io/packagist/dt/ghostzero/bitinflow-accounts.svg?style=flat-square)](https://packagist.org/packages/ghostzero/bitinflow-accounts) <a href="https://packagist.org/packages/ghostzero/bitinflow-accounts"><img src="https://img.shields.io/packagist/v/ghostzero/bitinflow-accounts" alt="Latest Stable Version"></a>
[![License](https://img.shields.io/packagist/l/ghostzero/bitinflow-accounts.svg?style=flat-square)](https://packagist.org/packages/ghostzero/bitinflow-accounts) <a href="https://packagist.org/packages/ghostzero/bitinflow-accounts"><img src="https://img.shields.io/packagist/l/ghostzero/bitinflow-accounts" alt="License"></a>
<a href="https://ghostzero.dev/discord"><img src="https://discordapp.com/api/guilds/590942233126240261/embed.png?style=shield" alt="Discord"></a>
PHP bitinflow Accounts API Client for Laravel 5+ PHP bitinflow Accounts API Client for Laravel 5+
@@ -26,7 +27,7 @@ composer require ghostzero/bitinflow-accounts
Add Service Provider to your `app.php` configuration file: Add Service Provider to your `app.php` configuration file:
```php ```php
GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider::class, Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider::class,
``` ```
## Event Listener ## Event Listener
@@ -46,7 +47,7 @@ GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider::class,
protected $listen = [ protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
// add your listeners (aka providers) here // add your listeners (aka providers) here
'GhostZero\\BitinflowAccounts\\Socialite\\BitinflowExtendSocialite@handle', 'Bitinflow\\Accounts\\Socialite\\BitinflowExtendSocialite@handle',
], ],
]; ];
``` ```
@@ -56,7 +57,7 @@ protected $listen = [
Copy configuration to config folder: Copy configuration to config folder:
``` ```
$ php artisan vendor:publish --provider="GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider" $ php artisan vendor:publish --provider="Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider"
``` ```
Add environmental variables to your `.env` Add environmental variables to your `.env`
@@ -84,7 +85,7 @@ You will need to add an entry to the services configuration file so that after c
#### Basic #### Basic
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
@@ -105,7 +106,7 @@ echo $sshKey->name;
#### Setters #### Setters
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
$bitinflowAccounts->setClientSecret('abc456'); $bitinflowAccounts->setClientSecret('abc456');
@@ -119,7 +120,7 @@ $bitinflowAccounts = $bitinflowAccounts->withToken('abcdef123456');
#### OAuth Tokens #### OAuth Tokens
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
$bitinflowAccounts->setToken('abcdef123456'); $bitinflowAccounts->setToken('abcdef123456');
@@ -142,27 +143,24 @@ $result = $bitinflowAccounts->withToken('uvwxyz456789')->getAuthedUser();
#### Facade #### Facade
```php ```php
use GhostZero\BitinflowAccounts\Facades\BitinflowAccounts; use Bitinflow\Accounts\Facades\BitinflowAccounts;
BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedUser(); BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedUser();
``` ```
## Documentation ## Documentation
### Charges ### Oauth
```php ```php
public function createCharge(array $parameters) public function retrievingToken(string $grantType, array $attributes)
public function getCharge(string $id)
public function updateCharge(string $id, array $parameters)
public function captureCharge(string $id, array $parameters = [])
``` ```
### CheckoutSessions ### PaymentIntents
```php ```php
public function getCheckoutSession(string $id) public function getPaymentIntent(string $id)
public function createCheckoutSession(array $parameters) public function createPaymentIntent(array $parameters)
``` ```
### SshKeys ### SshKeys
@@ -177,6 +175,7 @@ public function deleteSshKey(int $id)
```php ```php
public function getAuthedUser() public function getAuthedUser()
public function createUser(array $parameters)
``` ```
[**OAuth Scopes Enums**](https://github.com/ghostzero/bitinflow-accounts/blob/master/src/Enums/Scope.php) [**OAuth Scopes Enums**](https://github.com/ghostzero/bitinflow-accounts/blob/master/src/Enums/Scope.php)
@@ -190,7 +189,7 @@ composer test
``` ```
```shell ```shell
CLIENT_ID=xxxx CLIENT_KEY=yyyy CLIENT_ACCESS_TOKEN=zzzz composer test BASE_URL=xxxx CLIENT_ID=xxxx CLIENT_KEY=yyyy CLIENT_ACCESS_TOKEN=zzzz composer test
``` ```
#### Generate Documentation #### Generate Documentation

View File

@@ -9,9 +9,10 @@ PHP bitinflow Accounts API Client for Laravel 5+
## Table of contents ## Table of contents
1. [Installation](#installation) 1. [Installation](#installation)
2. [Configuration](#configuration) 2. [Event Listener](#event-listener)
3. [Examples](#examples) 3. [Configuration](#configuration)
4. [Documentation](#documentation) 4. [Examples](#examples)
5. [Documentation](#documentation)
6. [Development](#Development) 6. [Development](#Development)
## Installation ## Installation
@@ -25,7 +26,7 @@ composer require ghostzero/bitinflow-accounts
Add Service Provider to your `app.php` configuration file: Add Service Provider to your `app.php` configuration file:
```php ```php
GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider::class, Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider::class,
``` ```
## Event Listener ## Event Listener
@@ -45,7 +46,7 @@ GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider::class,
protected $listen = [ protected $listen = [
\SocialiteProviders\Manager\SocialiteWasCalled::class => [ \SocialiteProviders\Manager\SocialiteWasCalled::class => [
// add your listeners (aka providers) here // add your listeners (aka providers) here
'GhostZero\\BitinflowAccounts\\Socialite\\BitinflowExtendSocialite@handle', 'Bitinflow\\Accounts\\Socialite\\BitinflowExtendSocialite@handle',
], ],
]; ];
``` ```
@@ -55,7 +56,7 @@ protected $listen = [
Copy configuration to config folder: Copy configuration to config folder:
``` ```
$ php artisan vendor:publish --provider="GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider" $ php artisan vendor:publish --provider="Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider"
``` ```
Add environmental variables to your `.env` Add environmental variables to your `.env`
@@ -83,7 +84,7 @@ You will need to add an entry to the services configuration file so that after c
#### Basic #### Basic
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
@@ -104,7 +105,7 @@ echo $sshKey->name;
#### Setters #### Setters
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
$bitinflowAccounts->setClientSecret('abc456'); $bitinflowAccounts->setClientSecret('abc456');
@@ -118,7 +119,7 @@ $bitinflowAccounts = $bitinflowAccounts->withToken('abcdef123456');
#### OAuth Tokens #### OAuth Tokens
```php ```php
$bitinflowAccounts = new GhostZero\BitinflowAccounts\BitinflowAccounts(); $bitinflowAccounts = new Bitinflow\Accounts\BitinflowAccounts();
$bitinflowAccounts->setClientId('abc123'); $bitinflowAccounts->setClientId('abc123');
$bitinflowAccounts->setToken('abcdef123456'); $bitinflowAccounts->setToken('abcdef123456');
@@ -141,7 +142,7 @@ $result = $bitinflowAccounts->withToken('uvwxyz456789')->getAuthedUser();
#### Facade #### Facade
```php ```php
use GhostZero\BitinflowAccounts\Facades\BitinflowAccounts; use Bitinflow\Accounts\Facades\BitinflowAccounts;
BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedUser(); BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedUser();
``` ```
@@ -161,7 +162,7 @@ composer test
``` ```
```shell ```shell
CLIENT_ID=xxxx CLIENT_KEY=yyyy CLIENT_ACCESS_TOKEN=zzzz composer test BASE_URL=xxxx CLIENT_ID=xxxx CLIENT_KEY=yyyy CLIENT_ACCESS_TOKEN=zzzz composer test
``` ```
#### Generate Documentation #### Generate Documentation

View File

@@ -1,34 +1,42 @@
{ {
"name": "ghostzero/bitinflow-accounts", "name": "bitinflow/accounts",
"description": "PHP bitinflow Accounts API Client for Laravel 5+", "description": "PHP bitinflow Accounts API Client for Laravel 5+",
"license": "MIT", "license": "MIT",
"authors": [ "authors": [
{ {
"name": "René Preuß", "name": "René Preuß",
"email": "rene@preuss.io" "email": "rene@bitinflow.com"
},
{
"name": "Maurice Preuß",
"email": "maurice@bitinflow.com"
} }
], ],
"require": { "require": {
"php": ">=7.2", "php": "^7.4|^8.0",
"ext-json": "*", "ext-json": "*",
"illuminate/support": "^6.0", "illuminate/support": "~5.4|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0",
"illuminate/console": "^6.0", "illuminate/console": "~5.4|~5.7.0|~5.8.0|^6.0|^7.0|^8.0|^9.0|^10.0",
"guzzlehttp/guzzle": "^6.3", "guzzlehttp/guzzle": "^6.3|^7.0",
"socialiteproviders/manager": "^3.4" "socialiteproviders/manager": "^3.4|^4.0.1",
"firebase/php-jwt": "^6.0"
}, },
"require-dev": { "require-dev": {
"phpunit/phpunit": "^8.0", "phpunit/phpunit": "^8.0|^9.0",
"orchestra/testbench": "~4.0", "orchestra/testbench": "^6.0",
"codedungeon/phpunit-result-printer": "^0.26.2" "codedungeon/phpunit-result-printer": "^0.31"
}, },
"autoload": { "autoload": {
"psr-4": { "psr-4": {
"GhostZero\\BitinflowAccounts\\": "src/GhostZero/BitinflowAccounts" "Bitinflow\\Accounts\\": "src/Accounts",
"Bitinflow\\Payments\\": "src/Payments",
"Bitinflow\\Support\\": "src/Support"
} }
}, },
"autoload-dev": { "autoload-dev": {
"psr-4": { "psr-4": {
"GhostZero\\BitinflowAccounts\\Tests\\": "tests/GhostZero/BitinflowAccounts" "Bitinflow\\Accounts\\Tests\\": "tests/Accounts",
"Bitinflow\\Payments\\Tests\\": "tests/Payments"
} }
}, },
"scripts": { "scripts": {
@@ -38,10 +46,10 @@
"extra": { "extra": {
"laravel": { "laravel": {
"providers": [ "providers": [
"GhostZero\\BitinflowAccounts\\Providers\\BitinflowAccountsServiceProvider" "Bitinflow\\Accounts\\Providers\\BitinflowAccountsServiceProvider"
], ],
"aliases": { "aliases": {
"BitinflowAccounts": "GhostZero\\BitinflowAccounts\\Facades\\BitinflowAccounts" "BitinflowAccounts": "Bitinflow\\Accounts\\Facades\\BitinflowAccounts"
} }
} }
} }

5001
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +0,0 @@
<?php
return [
'client_id' => env('BITINFLOW_ACCOUNTS_KEY', ''),
'client_secret' => env('BITINFLOW_ACCOUNTS_SECRET', ''),
'redirect_url' => env('BITINFLOW_ACCOUNTS_REDIRECT_URI', ''),
];

View File

@@ -0,0 +1,15 @@
<?php
return [
// Accounts
'client_id' => env('BITINFLOW_ACCOUNTS_KEY'),
'client_secret' => env('BITINFLOW_ACCOUNTS_SECRET'),
'redirect_url' => env('BITINFLOW_ACCOUNTS_REDIRECT_URI'),
'base_url' => env('BITINFLOW_ACCOUNTS_BASE_URL'),
// Payments
'payments' => [
'base_url' => env('BITINFLOW_PAYMENTS_BASE_URL', 'https://api.pay.bitinflow.com/v1/'),
'dashboard_url' => env('BITINFLOW_PAYMENTS_DASHBOARD_URL', 'https://pay.bitinflow.com/v1/'),
]
];

View File

@@ -2,7 +2,7 @@
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
use GhostZero\BitinflowAccounts\BitinflowAccounts; use Bitinflow\Accounts\BitinflowAccounts;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
$markdown = collect(class_uses(BitinflowAccounts::class)) $markdown = collect(class_uses(BitinflowAccounts::class))

14
oauth-public.key Normal file
View File

@@ -0,0 +1,14 @@
-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAscXfKPCQ1SaWB9W0aaeT
AXNAf7d3YLDBXtQTM+lEwLuQM3ZBiEaniliO3CVy5k0fTa7qQNpd81W05+bGKggX
cGicub+UamgF8MoubxSrLiV5gRJVt5mXkwKBa5ukb7qzUV+Lt4f7xOnTUjby6IkI
fiuLZyRuxC8El1nObUgh5DFnHtFJ5DwQTgrryKBjxnPxZzQMSqN2f1zGbQL6+YVz
+EJ9q4Qn4OZEsOO3s7u0kAbv+jyhlaU7La863rBjpIXdFPEHEUyKTQ958nGp3aSD
VvRVy7zWjWor98w+NMIqxMh0jpl22KhLRqY9kjfxT7y3NRhsxmlU5BUAQ5BgPXHJ
gjLmo6LdGIWjbKLd3zh2qQwfzfhBx4Ou1D7T+eqQErG0ykgPtmdS/tjIsi33HLpP
sbARd0czQGoP5CmKIqfpzQCx6jCc93TWmpkV+7es8nxD3mBt9OLLlGBv/6oT0WWJ
hVruTBfLhTvmVobRsB/qFoAVJm02ErBkMn9CLJFCbYb5Dme3uI9AFI1vbEbwQNbX
ufOJkiJ/ZN0pMv6G3Jnn0VmqLTfFswBgehTa9Y9I8xPxcaYvpLtRUTVYI0otHwE7
Cs20WGXlOL+w45aoaQF/rJ+Wvr/7l0QYbGJdX94V3kt7MP8IijIFD/VqZijHnmqm
opVHXHKaCKiiazuTcBx8p5MCAwEAAQ==
-----END PUBLIC KEY-----

View File

@@ -20,4 +20,11 @@
<directory suffix=".php">src</directory> <directory suffix=".php">src</directory>
</whitelist> </whitelist>
</filter> </filter>
<php>
<env name="BITINFLOW_ACCOUNTS_KEY" value="38"/>
<env name="BITINFLOW_ACCOUNTS_SECRET" value="2goNRF8x37HPVZVaa28ySZGVXJuksvxnxM7TcGzM"/>
<env name="BITINFLOW_PAYMENTS_BASE_URL" value="https://api.sandbox.pay.bitinflow.com/v1/"/>
<env name="BITINFLOW_PAYMENTS_DASHBOARD_URL" value="https://sandbox.pay.bitinflow.com/v1/"/>
<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>
</phpunit> </phpunit>

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\ApiOperations;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Accounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Delete
{
abstract public function delete(string $path = '', array $parameters = [], Paginator $paginator = null): Result;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\ApiOperations;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Accounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Get
{
abstract public function get(string $path = '', array $parameters = [], Paginator $paginator = null): Result;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\ApiOperations;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Accounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Post
{
abstract public function post(string $path = '', array $parameters = [], Paginator $paginator = null): Result;
}

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\ApiOperations;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Accounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Put
{
abstract public function put(string $path = '', array $parameters = [], Paginator $paginator = null): Result;
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Bitinflow\Accounts\ApiOperations;
use Bitinflow\Accounts\Exceptions\RequestRequiresMissingParametersException;
use Illuminate\Support\Arr;
trait Validation
{
/**
* @throws RequestRequiresMissingParametersException
*/
public function validateRequired(array $parameters, array $required)
{
if (!Arr::has($parameters, $required)) {
throw RequestRequiresMissingParametersException::fromValidateRequired($parameters, $required);
}
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Bitinflow\Accounts;
use Carbon\Carbon;
use Firebase\JWT\JWT;
use Illuminate\Contracts\Config\Repository as Config;
use Illuminate\Contracts\Encryption\Encrypter;
use Symfony\Component\HttpFoundation\Cookie;
class ApiTokenCookieFactory
{
/**
* The configuration repository implementation.
*
* @var Config
*/
protected $config;
/**
* The encrypter implementation.
*
* @var Encrypter
*/
protected $encrypter;
/**
* Create an API token cookie factory instance.
*
* @param Config $config
* @param Encrypter $encrypter
* @return void
*/
public function __construct(Config $config, Encrypter $encrypter)
{
$this->config = $config;
$this->encrypter = $encrypter;
}
/**
* Create a new API token cookie.
*
* @param mixed $userId
* @param string $csrfToken
* @return Cookie
*/
public function make($userId, string $csrfToken): Cookie
{
$config = $this->config->get('session');
$expiration = Carbon::now()->addMinutes($config['lifetime']);
return new Cookie(
BitinflowAccounts::cookie(),
$this->createToken($userId, $csrfToken, $expiration),
$expiration,
$config['path'],
$config['domain'],
$config['secure'],
true,
false,
$config['same_site'] ?? null
);
}
/**
* Create a new JWT token for the given user ID and CSRF token.
*
* @param mixed $userId
* @param string $csrfToken
* @param Carbon $expiration
* @return string
*/
protected function createToken($userId, string $csrfToken, Carbon $expiration): string
{
return JWT::encode([
'sub' => $userId,
'csrf' => $csrfToken,
'expiry' => $expiration->getTimestamp(),
], $this->encrypter->getKey(), 'HS256');
}
}

View File

@@ -0,0 +1,232 @@
<?php
namespace Bitinflow\Accounts\Auth;
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Helpers\JwtParser;
use Bitinflow\Accounts\Traits\HasBitinflowTokens;
use Exception;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Auth\GuardHelpers;
use Illuminate\Container\Container;
use Illuminate\Contracts\Container\BindingResolutionException;
use Illuminate\Contracts\Debug\ExceptionHandler;
use Illuminate\Contracts\Encryption\Encrypter;
use Illuminate\Cookie\CookieValuePrefix;
use Illuminate\Cookie\Middleware\EncryptCookies;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Http\Request;
use stdClass;
use Throwable;
class TokenGuard
{
use GuardHelpers;
/**
* @var Encrypter
*/
private $encrypter;
/**
* @var JwtParser
*/
private $jwtParser;
public function __construct(UserProvider $provider, Encrypter $encrypter, JwtParser $jwtParser)
{
$this->provider = $provider;
$this->encrypter = $encrypter;
$this->jwtParser = $jwtParser;
}
/**
* Get the user for the incoming request.
*
* @param Request $request
* @return mixed
* @throws BindingResolutionException
* @throws Throwable
*/
public function user(Request $request): ?Authenticatable
{
if ($request->bearerToken()) {
return $this->authenticateViaBearerToken($request);
} elseif ($request->cookie(BitinflowAccounts::cookie())) {
return $this->authenticateViaCookie($request);
}
return null;
}
/**
* Authenticate the incoming request via the Bearer token.
*
* @param Request $request
* @return Authenticatable
* @throws BindingResolutionException
* @throws Throwable
*/
protected function authenticateViaBearerToken(Request $request): ?Authenticatable
{
if (!$token = $this->validateRequestViaBearerToken($request)) {
return null;
}
// If the access token is valid we will retrieve the user according to the user ID
// associated with the token. We will use the provider implementation which may
// be used to retrieve users from Eloquent. Next, we'll be ready to continue.
/** @var Authenticatable|HasBitinflowTokens $user */
$user = $this->provider->retrieveById(
$request->attributes->get('oauth_user_id') ?: null
);
if (!$user) {
return null;
}
return $token ? $user->withBitinflowAccessToken($token) : null;
}
/**
* Authenticate and get the incoming request via the Bearer token.
*
* @param Request $request
* @return stdClass|null
* @throws BindingResolutionException
* @throws Throwable
*/
protected function validateRequestViaBearerToken(Request $request): ?stdClass
{
try {
$decoded = $this->jwtParser->decode($request);
$request->attributes->set('oauth_access_token_id', $decoded->jti);
$request->attributes->set('oauth_client_id', $decoded->aud);
$request->attributes->set('oauth_client_trusted', $decoded->client->trusted);
$request->attributes->set('oauth_user_id', $decoded->sub);
$request->attributes->set('oauth_scopes', $decoded->scopes);
return $decoded;
} catch (AuthenticationException $e) {
$request->headers->set('Authorization', '', true);
Container::getInstance()->make(
ExceptionHandler::class
)->report($e);
return null;
}
}
/**
* Authenticate the incoming request via the token cookie.
*
* @param Request $request
* @return mixed
*/
protected function authenticateViaCookie(Request $request)
{
if (!$token = $this->getTokenViaCookie($request)) {
return null;
}
// If this user exists, we will return this user and attach a "transient" token to
// the user model. The transient token assumes it has all scopes since the user
// is physically logged into the application via the application's interface.
/** @var Authenticatable|HasBitinflowTokens $user */
if ($user = $this->provider->retrieveById($token['sub'])) {
return $user->withBitinflowAccessToken((object)['scopes' => ['*']]);
}
return null;
}
/**
* Get the token cookie via the incoming request.
*
* @param Request $request
* @return array|null
*/
protected function getTokenViaCookie(Request $request): ?array
{
// If we need to retrieve the token from the cookie, it'll be encrypted so we must
// first decrypt the cookie and then attempt to find the token value within the
// database. If we can't decrypt the value we'll bail out with a null return.
try {
$token = $this->decodeJwtTokenCookie($request);
} catch (Exception $e) {
return null;
}
// We will compare the CSRF token in the decoded API token against the CSRF header
// sent with the request. If they don't match then this request isn't sent from
// a valid source and we won't authenticate the request for further handling.
if (!BitinflowAccounts::$ignoreCsrfToken && (!$this->validCsrf($token, $request) ||
time() >= $token['expiry'])) {
return null;
}
return $token;
}
/**
* Decode and decrypt the JWT token cookie.
*
* @param Request $request
* @return array
*/
protected function decodeJwtTokenCookie(Request $request): array
{
return (array)JWT::decode(
CookieValuePrefix::remove($this->encrypter->decrypt($request->cookie(BitinflowAccounts::cookie()), BitinflowAccounts::$unserializesCookies)),
new Key(
$this->encrypter->getKey(),
'HS256'
)
);
}
/**
* Determine if the CSRF / header are valid and match.
*
* @param array $token
* @param Request $request
* @return bool
*/
protected function validCsrf(array $token, Request $request): bool
{
return isset($token['csrf']) && hash_equals(
$token['csrf'], (string)$this->getTokenFromRequest($request)
);
}
/**
* Get the CSRF token from the request.
*
* @param Request $request
* @return string
*/
protected function getTokenFromRequest(Request $request): string
{
$token = $request->header('X-CSRF-TOKEN');
if (!$token && $header = $request->header('X-XSRF-TOKEN')) {
$token = CookieValuePrefix::remove($this->encrypter->decrypt($header, static::serialized()));
}
return $token;
}
/**
* Determine if the cookie contents should be serialized.
*
* @return bool
*/
public static function serialized(): bool
{
return EncryptCookies::serialized('XSRF-TOKEN');
}
}

View File

@@ -0,0 +1,86 @@
<?php
namespace Bitinflow\Accounts\Auth;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Contracts\Auth\UserProvider as Base;
class UserProvider implements Base
{
/**
* The user provider instance.
*
* @var Base
*/
protected $provider;
/**
* The user provider name.
*
* @var string
*/
protected $providerName;
/**
* Create a new Bitinflow Accounts user provider.
*
* @param Base $provider
* @param string $providerName
* @return void
*/
public function __construct(Base $provider, $providerName)
{
$this->provider = $provider;
$this->providerName = $providerName;
}
/**
* {@inheritdoc}
*/
public function retrieveById($identifier)
{
return $this->provider->retrieveById($identifier);
}
/**
* {@inheritdoc}
*/
public function retrieveByToken($identifier, $token)
{
return $this->provider->retrieveByToken($identifier, $token);
}
/**
* {@inheritdoc}
*/
public function updateRememberToken(Authenticatable $user, $token)
{
$this->provider->updateRememberToken($user, $token);
}
/**
* {@inheritdoc}
*/
public function retrieveByCredentials(array $credentials)
{
return $this->provider->retrieveByCredentials($credentials);
}
/**
* {@inheritdoc}
*/
public function validateCredentials(Authenticatable $user, array $credentials)
{
return $this->provider->validateCredentials($user, $credentials);
}
/**
* Get the name of the user provider.
*
* @return string
*/
public function getProviderName()
{
return $this->providerName;
}
}

View File

@@ -1,115 +1,162 @@
<?php <?php
namespace GhostZero\BitinflowAccounts; namespace Bitinflow\Accounts;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresAuthenticationException; use Bitinflow\Accounts\ApiOperations;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresClientIdException; use Bitinflow\Accounts\Exceptions\RequestRequiresAuthenticationException;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresRedirectUriException; use Bitinflow\Accounts\Exceptions\RequestRequiresClientIdException;
use GhostZero\BitinflowAccounts\Helpers\Paginator; use Bitinflow\Accounts\Exceptions\RequestRequiresRedirectUriException;
use GhostZero\BitinflowAccounts\Traits; use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Accounts\Traits;
use Bitinflow\Support\Query;
use GuzzleHttp\Client; use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException; use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Exception\RequestException;
use Illuminate\Contracts\Auth\Authenticatable;
/** /**
* @author René Preuß <rene@preuss.io> * @author René Preuß <rene@preuss.io>
*/ */
class BitinflowAccounts class BitinflowAccounts
{ {
use Traits\OauthTrait;
use Traits\ChargesTrait;
use Traits\CheckoutSessionsTrait;
use Traits\SshKeysTrait; use Traits\SshKeysTrait;
use Traits\UsersTrait; use Traits\UsersTrait;
const BASE_URI = 'https://accounts.bitinflow.com/api/'; use ApiOperations\Delete;
const OAUTH_BASE_URI = 'https://accounts.bitinflow.com/api/'; use ApiOperations\Get;
use ApiOperations\Post;
use ApiOperations\Put;
/**
* The name for API token cookies.
*
* @var string
*/
public static string $cookie = 'bitinflow_token';
/**
* Indicates if Bitinflow Accounts should ignore incoming CSRF tokens.
*/
public static bool $ignoreCsrfToken = false;
/**
* Indicates if Bitinflow Accounts should unserializes cookies.
*/
public static bool $unserializesCookies = false;
private static string $baseUrl = 'https://accounts.bitinflow.com/api/';
/** /**
* Guzzle is used to make http requests. * Guzzle is used to make http requests.
* @var \GuzzleHttp\Client
*/ */
protected $client; protected Client $client;
/** /**
* Paginator object. * Paginator object.
* @var Paginator
*/ */
protected $paginator; protected Paginator $paginator;
/** /**
* bitinflow Accounts OAuth token. * bitinflow Accounts OAuth token.
* @var string|null *
*/ */
protected $token = null; protected ?string $token = null;
/** /**
* bitinflow Accounts client id. * bitinflow Accounts client id.
* @var string|null *
*/ */
protected $clientId = null; protected ?string $clientId = null;
/** /**
* bitinflow Accounts client secret. * bitinflow Accounts client secret.
* @var string|null
*/ */
protected $clientSecret = null; protected ?string $clientSecret = null;
/** /**
* bitinflow Accounts OAuth redirect url. * bitinflow Accounts OAuth redirect url.
* @var string|null
*/ */
protected $redirectUri = null; protected ?string $redirectUri = null;
/** /**
* Constructor. * Constructor.
*/ */
public function __construct() public function __construct()
{ {
if ($clientId = config('bitinflow-accounts-api.client_id')) { if ($clientId = config('bitinflow-accounts.client_id')) {
$this->setClientId($clientId); $this->setClientId($clientId);
} }
if ($clientSecret = config('bitinflow-accounts-api.client_secret')) { if ($clientSecret = config('bitinflow-accounts.client_secret')) {
$this->setClientSecret($clientSecret); $this->setClientSecret($clientSecret);
} }
if ($redirectUri = config('bitinflow-accounts-api.redirect_url')) { if ($redirectUri = config('bitinflow-accounts.redirect_url')) {
$this->setRedirectUri($redirectUri); $this->setRedirectUri($redirectUri);
} }
if ($redirectUri = config('bitinflow-accounts.base_url')) {
self::setBaseUrl($redirectUri);
}
$this->client = new Client([ $this->client = new Client([
'base_uri' => self::BASE_URI, 'base_uri' => self::$baseUrl,
]); ]);
} }
/** /**
* Get client id. * @param string $baseUrl
* @return string *
* @throws RequestRequiresClientIdException * @internal only for internal and debug purposes.
*/ */
public function getClientId(): string public static function setBaseUrl(string $baseUrl): void
{ {
if (!$this->clientId) { self::$baseUrl = $baseUrl;
throw new RequestRequiresClientIdException;
}
return $this->clientId;
} }
/** /**
* Set client id. * Get or set the name for API token cookies.
* *
* @param string $clientId bitinflow Accounts client id * @param string|null $cookie
* * @return string|static
* @return void
*/ */
public function setClientId(string $clientId): void public static function cookie(string $cookie = null)
{ {
$this->clientId = $clientId; if (is_null($cookie)) {
return static::$cookie;
}
static::$cookie = $cookie;
return new static;
}
/**
* Set the current user for the application with the given scopes.
*
* @param Authenticatable|Traits\HasBitinflowTokens $user
* @param array $scopes
* @param string $guard
* @return Authenticatable
*/
public static function actingAs($user, $scopes = [], $guard = 'api')
{
$user->withBitinflowAccessToken((object)[
'scopes' => $scopes
]);
if (isset($user->wasRecentlyCreated) && $user->wasRecentlyCreated) {
$user->wasRecentlyCreated = false;
}
app('auth')->guard($guard)->setUser($user);
app('auth')->shouldUse($guard);
return $user;
} }
/** /**
* Fluid client id setter. * Fluid client id setter.
* *
* @param string $clientId bitinflow Accounts client id. * @param string $clientId bitinflow Accounts client id.
* *
* @return self * @return self
*/ */
@@ -122,6 +169,7 @@ class BitinflowAccounts
/** /**
* Get client secret. * Get client secret.
*
* @return string * @return string
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
@@ -137,7 +185,7 @@ class BitinflowAccounts
/** /**
* Set client secret. * Set client secret.
* *
* @param string $clientSecret bitinflow Accounts client secret * @param string $clientSecret bitinflow Accounts client secret
* *
* @return void * @return void
*/ */
@@ -149,7 +197,7 @@ class BitinflowAccounts
/** /**
* Fluid client secret setter. * Fluid client secret setter.
* *
* @param string $clientSecret bitinflow Accounts client secret * @param string $clientSecret bitinflow Accounts client secret
* *
* @return self * @return self
*/ */
@@ -162,6 +210,7 @@ class BitinflowAccounts
/** /**
* Get redirect url. * Get redirect url.
*
* @return string * @return string
* @throws RequestRequiresRedirectUriException * @throws RequestRequiresRedirectUriException
*/ */
@@ -177,7 +226,7 @@ class BitinflowAccounts
/** /**
* Set redirect url. * Set redirect url.
* *
* @param string $redirectUri * @param string $redirectUri
* *
* @return void * @return void
*/ */
@@ -189,7 +238,7 @@ class BitinflowAccounts
/** /**
* Fluid redirect url setter. * Fluid redirect url setter.
* *
* @param string $redirectUri * @param string $redirectUri
* *
* @return self * @return self
*/ */
@@ -202,11 +251,12 @@ class BitinflowAccounts
/** /**
* Get OAuth token. * Get OAuth token.
*
* @return string bitinflow Accounts token * @return string bitinflow Accounts token
* @return string|null * @return string|null
* @throws RequestRequiresAuthenticationException * @throws RequestRequiresAuthenticationException
*/ */
public function getToken() public function getToken(): ?string
{ {
if (!$this->token) { if (!$this->token) {
throw new RequestRequiresAuthenticationException; throw new RequestRequiresAuthenticationException;
@@ -218,7 +268,7 @@ class BitinflowAccounts
/** /**
* Set OAuth token. * Set OAuth token.
* *
* @param string $token bitinflow Accounts OAuth token * @param string $token bitinflow Accounts OAuth token
* *
* @return void * @return void
*/ */
@@ -230,7 +280,7 @@ class BitinflowAccounts
/** /**
* Fluid OAuth token setter. * Fluid OAuth token setter.
* *
* @param string $token bitinflow Accounts OAuth token * @param string $token bitinflow Accounts OAuth token
* *
* @return self * @return self
*/ */
@@ -242,87 +292,27 @@ class BitinflowAccounts
} }
/** /**
* @param string $path * @param string $path
* @param array $parameters * @param array $parameters
* @param Paginator|null $paginator * @param Paginator|null $paginator
* *
* @return Result * @return Result
* @throws GuzzleException * @throws GuzzleException
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
*/ */
public function get(string $path = '', array $parameters = [], Paginator $paginator = null) public function get(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{ {
return $this->query('GET', $path, $parameters, $paginator); return $this->query('GET', $path, $parameters, $paginator);
} }
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function post(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('POST', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function delete(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('DELETE', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function put(string $path = '', array $parameters = [], Paginator $paginator = null)
{
return $this->query('PUT', $path, $parameters, $paginator);
}
/**
* @param string $method
* @param string $path
* @param array|null $body
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function json(string $method, string $path = '', array $body = null)
{
if ($body) {
$body = json_encode(['data' => $body]);
}
return $this->query($method, $path, [], null, $body);
}
/** /**
* Build query & execute. * Build query & execute.
* *
* @param string $method HTTP method * @param string $method HTTP method
* @param string $path Query path * @param string $path Query path
* @param array $parameters Query parameters * @param array $parameters Query parameters
* @param Paginator $paginator Paginator object * @param Paginator|null $paginator Paginator object
* @param mixed|null $jsonBody JSON data * @param mixed|null $jsonBody JSON data
* *
* @return Result Result object * @return Result Result object
* @throws GuzzleException * @throws GuzzleException
@@ -330,14 +320,15 @@ class BitinflowAccounts
*/ */
public function query(string $method = 'GET', string $path = '', array $parameters = [], Paginator $paginator = null, $jsonBody = null): Result public function query(string $method = 'GET', string $path = '', array $parameters = [], Paginator $paginator = null, $jsonBody = null): Result
{ {
/** @noinspection DuplicatedCode */
if ($paginator !== null) { if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor(); $parameters[$paginator->action] = $paginator->cursor();
} }
try { try {
$response = $this->client->request($method, $path, [ $response = $this->client->request($method, $path, [
'headers' => $this->buildHeaders($jsonBody ? true : false), 'headers' => $this->buildHeaders((bool)$jsonBody),
'query' => $this->buildQuery($parameters), 'query' => Query::build($parameters),
'json' => $jsonBody ? $jsonBody : null, 'json' => $jsonBody ?: null,
]); ]);
$result = new Result($response, null, $paginator); $result = new Result($response, null, $paginator);
} catch (RequestException $exception) { } catch (RequestException $exception) {
@@ -348,30 +339,10 @@ class BitinflowAccounts
return $result; return $result;
} }
/**
* Build query with support for multiple smae first-dimension keys.
*
* @param array $query
*
* @return string
*/
public function buildQuery(array $query): string
{
$parts = [];
foreach ($query as $name => $value) {
$value = (array) $value;
array_walk_recursive($value, function ($value) use (&$parts, $name) {
$parts[] = urlencode($name) . '=' . urlencode($value);
});
}
return implode('&', $parts);
}
/** /**
* Build headers for request. * Build headers for request.
* *
* @param bool $json Body is JSON * @param bool $json Body is JSON
* *
* @return array * @return array
* @throws RequestRequiresClientIdException * @throws RequestRequiresClientIdException
@@ -380,15 +351,102 @@ class BitinflowAccounts
{ {
$headers = [ $headers = [
'Client-ID' => $this->getClientId(), 'Client-ID' => $this->getClientId(),
'Accept' => 'application/json',
]; ];
if ($this->token) { if ($this->token) {
$headers['Authorization'] = 'Bearer ' . $this->token; $headers['Authorization'] = 'Bearer ' . $this->token;
} }
if ($json) { if ($json) {
$headers['Content-Type'] = 'application/json'; $headers['Content-Type'] = 'application/json';
$headers['Accept'] = 'application/json';
} }
return $headers; return $headers;
} }
/**
* Get client id.
*
* @return string
* @throws RequestRequiresClientIdException
*/
public function getClientId(): string
{
if (!$this->clientId) {
throw new RequestRequiresClientIdException;
}
return $this->clientId;
}
/**
* Set client id.
*
* @param string $clientId bitinflow Accounts client id
*
* @return void
*/
public function setClientId(string $clientId): void
{
$this->clientId = $clientId;
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function post(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('POST', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function delete(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('DELETE', $path, $parameters, $paginator);
}
/**
* @param string $path
* @param array $parameters
* @param Paginator|null $paginator
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function put(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('PUT', $path, $parameters, $paginator);
}
/**
* @param string $method
* @param string $path
* @param array|null $body
*
* @return Result
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function json(string $method, string $path = '', array $body = null): Result
{
if ($body) {
$body = json_encode(['data' => $body]);
}
return $this->query($method, $path, [], null, $body);
}
} }

View File

@@ -0,0 +1,15 @@
<?php
namespace Bitinflow\Accounts\Contracts;
use Bitinflow\Accounts\Exceptions\RequestFreshAccessTokenException;
interface AppTokenRepository
{
/**
* @throws RequestFreshAccessTokenException
*
* @return string
*/
public function getAccessToken(): string;
}

View File

@@ -0,0 +1,89 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Enums;
/**
* @author René Preuß <rene@preuss.io>
*/
class Scope
{
/*
* v3 API
*/
public const USER = 'user';
public const USER_READ = 'user:read';
public const USERS = 'users';
public const USERS_READ = 'users:read';
public const USERS_CREATE = 'users:create';
public const PAYMENTS = 'payments';
public const PAYMENTS_READ = 'payments:read';
public const PAYMENTS_CREATE = 'payments:create';
public const PAYMENTS_CHECKOUT = 'payments:checkout';
public const PAYMENTS_REVOKE = 'payments:revoke';
public const PAYMENTS_RESUME = 'payments:resume';
public const PAYMENT_ORDERS = 'payment.orders';
public const PAYMENT_ORDERS_READ = 'payment.orders:read';
public const PAYMENT_ORDERS_CREATE = 'payment.orders:create';
public const PAYMENT_ORDERS_CHECKOUT = 'payment.orders:checkout';
public const PAYMENT_ORDERS_REVOKE = 'payment.orders:revoke';
public const PAYMENT_INVOICES = 'payment.invoices';
public const PAYMENT_INVOICES_READ = 'payment.invoices:read';
public const PAYMENT_SUBSCRIPTIONS = 'payment.subscriptions';
public const PAYMENT_SUBSCRIPTIONS_READ = 'payment.subscriptions:read';
public const PAYMENT_SUBSCRIPTIONS_CREATE = 'payment.subscriptions:create';
public const PAYMENT_SUBSCRIPTIONS_CHECKOUT = 'payment.subscriptions:checkout';
public const PAYMENT_SUBSCRIPTIONS_REVOKE = 'payment.subscriptions:revoke';
public const PAYMENT_SUBSCRIPTIONS_RESUME = 'payment.subscriptions:resume';
public const PAYMENT_WALLETS = 'payment.wallets';
public const PAYMENT_WALLETS_READ = 'payment.wallets:read';
public const PAYMENT_WALLETS_CREATE = 'payment.wallets:create';
public const PAYMENT_CHECKOUT_SESSIONS = 'payment.checkout-sessions';
public const PAYMENT_CHECKOUT_SESSIONS_READ = 'payment.checkout-sessions:read';
public const PAYMENT_CHECKOUT_SESSIONS_CREATE = 'payment.checkout-sessions:create';
public const PAYMENT_CHECKOUT_SESSIONS_CHECKOUT = 'payment.checkout-sessions:checkout';
public const PAYMENT_CHECKOUT_SESSIONS_REVOKE = 'payment.checkout-sessions:revoke';
/**
* v2 API
*/
/*
* v1 API
*/
// Read authorized user´s email address.
public const USERS_READ_EMAIL = 'users:read:email';
// Manage a authorized user object.
public const USERS_EDIT = 'users:edit';
// also available in v3
// public const USERS_CREATE = 'users:create';
// Read authorized user´s transactions.
public const TRANSACTIONS_READ = 'transactions:read';
// Create a new charge for the authorized user.
public const CHARGES_CREATE = 'charges:create';
/*
* v0 API
*/
// Deprecated scope.
public const API = 'api';
// Read nonpublic user information, including email address.
public const READ_USER = 'read_user';
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Bitinflow\Accounts\Exceptions;
use Illuminate\Auth\Access\AuthorizationException;
use Illuminate\Support\Arr;
class MissingScopeException extends AuthorizationException
{
/**
* The scopes that the user did not have.
*
* @var array
*/
protected $scopes;
/**
* Create a new missing scope exception.
*
* @param array|string $scopes
* @param string $message
*
* @return void
*/
public function __construct($scopes = [], $message = 'Invalid scope(s) provided.')
{
parent::__construct($message);
$this->scopes = Arr::wrap($scopes);
}
/**
* Get the scopes that the user did not have.
*
* @return array
*/
public function scopes()
{
return $this->scopes;
}
}

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions; namespace Bitinflow\Accounts\Exceptions;
use Exception; use Exception;

View File

@@ -0,0 +1,27 @@
<?php
namespace Bitinflow\Accounts\Exceptions;
use DomainException;
use Psr\Http\Message\ResponseInterface;
/**
* @author René Preuß <rene@bitinflow.com>
*/
class RequestFreshAccessTokenException extends DomainException
{
private ResponseInterface $response;
public static function fromResponse(ResponseInterface $response): self
{
$instance = new self(sprintf('Refresh token request from bitinflow accounts failed. Status Code is %s.', $response->getStatusCode()));
$instance->response = $response;
return $instance;
}
public function getResponse(): ResponseInterface
{
return $this->response;
}
}

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions; namespace Bitinflow\Accounts\Exceptions;
use Exception; use Exception;

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions; namespace Bitinflow\Accounts\Exceptions;
use Exception; use Exception;

View File

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Exceptions;
use Exception;
/**
* @author René Preuß <rene@preuss.io>
*/
class RequestRequiresMissingParametersException extends Exception
{
public function __construct($message = 'Request requires missing parameters', $code = 0, Exception $previous = null)
{
parent::__construct($message, $code, $previous);
}
public static function fromValidateRequired(array $given, array $required): RequestRequiresMissingParametersException
{
return new self(sprintf(
'Request requires missing parameters. Required: %s. Given: %s',
implode(', ', $required),
implode(', ', $given)
));
}
}

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions; namespace Bitinflow\Accounts\Exceptions;
use Exception; use Exception;

View File

@@ -2,7 +2,7 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Exceptions; namespace Bitinflow\Accounts\Exceptions;
use Exception; use Exception;

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Facades;
use Bitinflow\Accounts\BitinflowAccounts as BitinflowAccountsService;
use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Facade;
/**
* @method static string|static cookie(string $cookie = null)
* @method static Authenticatable actingAs($user, $scopes = [], $guard = 'api')
* @method static static withClientId(string $clientId): self
* @method static string getClientSecret(): string
*/
class BitinflowAccounts extends Facade
{
protected static function getFacadeAccessor()
{
return BitinflowAccountsService::class;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Bitinflow\Accounts\Helpers;
use Firebase\JWT\JWT;
use Firebase\JWT\Key;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use stdClass;
use Throwable;
class JwtParser
{
/**
* @param Request $request
* @return stdClass
* @throws AuthenticationException
*/
public function decode(Request $request): stdClass
{
JWT::$leeway = 60;
try {
return JWT::decode(
$request->bearerToken(),
new Key($this->getOauthPublicKey(), 'RS256')
);
} catch (Throwable $exception) {
throw (new AuthenticationException());
}
}
private function getOauthPublicKey()
{
return file_get_contents(dirname(__DIR__, 3) . '/oauth-public.key');
}
}

View File

@@ -2,9 +2,9 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Helpers; namespace Bitinflow\Accounts\Helpers;
use GhostZero\BitinflowAccounts\Result; use Bitinflow\Accounts\Result;
use stdClass; use stdClass;
/** /**
@@ -13,17 +13,18 @@ use stdClass;
class Paginator class Paginator
{ {
/**
* bitinflow Accounts response pagination cursor.
* @var null|stdClass
*/
private $pagination;
/** /**
* Next desired action (first, after, before). * Next desired action (first, after, before).
*
* @var null|string * @var null|string
*/ */
public $action = null; public $action = null;
/**
* bitinflow Accounts response pagination cursor.
*
* @var null|stdClass
*/
private $pagination;
/** /**
* Constructor. * Constructor.
@@ -38,7 +39,7 @@ class Paginator
/** /**
* Create Paginator from Result object. * Create Paginator from Result object.
* *
* @param Result $result Result object * @param Result $result Result object
* *
* @return self Paginator object * @return self Paginator object
*/ */
@@ -49,6 +50,7 @@ class Paginator
/** /**
* Return the current active cursor. * Return the current active cursor.
*
* @return string bitinflow Accounts cursor * @return string bitinflow Accounts cursor
*/ */
public function cursor(): string public function cursor(): string
@@ -58,6 +60,7 @@ class Paginator
/** /**
* Set the Paginator to fetch the next set of results. * Set the Paginator to fetch the next set of results.
*
* @return self * @return self
*/ */
public function first(): self public function first(): self
@@ -69,6 +72,7 @@ class Paginator
/** /**
* Set the Paginator to fetch the first set of results. * Set the Paginator to fetch the first set of results.
*
* @return self * @return self
*/ */
public function next(): self public function next(): self
@@ -80,6 +84,7 @@ class Paginator
/** /**
* Set the Paginator to fetch the last set of results. * Set the Paginator to fetch the last set of results.
*
* @return self * @return self
*/ */
public function back(): self public function back(): self

View File

@@ -0,0 +1,32 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\Exceptions\MissingScopeException;
use stdClass;
class CheckClientCredentials extends CheckCredentials
{
/**
* Validate token credentials.
*
* @param stdClass $token
* @param array $scopes
*
* @return void
* @throws MissingScopeException
*
*/
protected function validateScopes(stdClass $token, array $scopes)
{
if (in_array('*', $token->scopes)) {
return;
}
foreach ($scopes as $scope) {
if (!in_array($scope, $token->scopes)) {
throw new MissingScopeException($scopes);
}
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\Exceptions\MissingScopeException;
use stdClass;
class CheckClientCredentialsForAnyScope extends CheckCredentials
{
/**
* Validate token credentials.
*
* @param stdClass $token
* @param array $scopes
*
* @return void
* @throws MissingScopeException
*
*/
protected function validateScopes(stdClass $token, array $scopes)
{
if (in_array('*', $token->scopes)) {
return;
}
foreach ($scopes as $scope) {
if (in_array($scope, $token->scopes)) {
return;
}
}
throw new MissingScopeException($scopes);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\Exceptions\MissingScopeException;
use Bitinflow\Accounts\Helpers\JwtParser;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use stdClass;
abstract class CheckCredentials
{
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param mixed ...$scopes
*
* @return mixed
* @throws AuthenticationException|MissingScopeException
*
*/
public function handle($request, Closure $next, ...$scopes)
{
$decoded = $this->getJwtParser()->decode($request);
$request->attributes->set('oauth_access_token_id', $decoded->jti);
$request->attributes->set('oauth_client_id', $decoded->aud);
//$request->attributes->set('oauth_client_trusted', $decoded->client->trusted);
$request->attributes->set('oauth_user_id', $decoded->sub);
$request->attributes->set('oauth_scopes', $decoded->scopes);
$this->validateScopes($decoded, $scopes);
return $next($request);
}
private function getJwtParser(): JwtParser
{
return app(JwtParser::class);
}
abstract protected function validateScopes(stdClass $token, array $scopes);
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\Exceptions\MissingScopeException;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CheckForAnyScope
{
/**
* Handle the incoming request.
*
* @param Request $request
* @param Closure $next
* @param mixed ...$scopes
* @return Response
*
* @throws AuthenticationException|MissingScopeException
*/
public function handle($request, $next, ...$scopes)
{
if (!$request->user() || !$request->user()->bitinflowToken()) {
throw new AuthenticationException;
}
foreach ($scopes as $scope) {
if ($request->user()->bitinflowTokenCan($scope)) {
return $next($request);
}
}
throw new MissingScopeException($scopes);
}
}

View File

@@ -0,0 +1,37 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\Exceptions\MissingScopeException;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CheckScopes
{
/**
* Handle the incoming request.
*
* @param Request $request
* @param Closure $next
* @param mixed ...$scopes
* @return Response
*
* @throws AuthenticationException|MissingScopeException
*/
public function handle($request, $next, ...$scopes)
{
if (!$request->user() || !$request->user()->bitinflowToken()) {
throw new AuthenticationException;
}
foreach ($scopes as $scope) {
if (!$request->user()->bitinflowTokenCan($scope)) {
throw new MissingScopeException($scope);
}
}
return $next($request);
}
}

View File

@@ -0,0 +1,117 @@
<?php
namespace Bitinflow\Accounts\Http\Middleware;
use Bitinflow\Accounts\ApiTokenCookieFactory;
use Bitinflow\Accounts\BitinflowAccounts;
use Closure;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
class CreateFreshApiToken
{
/**
* The API token cookie factory instance.
*
* @var ApiTokenCookieFactory
*/
protected $cookieFactory;
/**
* The authentication guard.
*
* @var string
*/
protected $guard;
/**
* Create a new middleware instance.
*
* @param ApiTokenCookieFactory $cookieFactory
* @return void
*/
public function __construct(ApiTokenCookieFactory $cookieFactory)
{
$this->cookieFactory = $cookieFactory;
}
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param string|null $guard
* @return mixed
*/
public function handle($request, Closure $next, $guard = null)
{
$this->guard = $guard;
$response = $next($request);
if ($this->shouldReceiveFreshToken($request, $response)) {
$response->withCookie($this->cookieFactory->make(
$request->user($this->guard)->getAuthIdentifier(), $request->session()->token()
));
}
return $response;
}
/**
* Determine if the given request should receive a fresh token.
*
* @param Request $request
* @param Response $response
* @return bool
*/
protected function shouldReceiveFreshToken($request, $response): bool
{
return $this->requestShouldReceiveFreshToken($request) &&
$this->responseShouldReceiveFreshToken($response);
}
/**
* Determine if the request should receive a fresh token.
*
* @param Request $request
* @return bool
*/
protected function requestShouldReceiveFreshToken($request)
{
return $request->isMethod('GET') && $request->user($this->guard);
}
/**
* Determine if the response should receive a fresh token.
*
* @param Response $response
* @return bool
*/
protected function responseShouldReceiveFreshToken($response)
{
return ($response instanceof Response ||
$response instanceof JsonResponse) &&
!$this->alreadyContainsToken($response);
}
/**
* Determine if the given response already contains an API token.
*
* This avoids us overwriting a just "refreshed" token.
*
* @param Response $response
* @return bool
*/
protected function alreadyContainsToken($response): bool
{
foreach ($response->headers->getCookies() as $cookie) {
if ($cookie->getName() === BitinflowAccounts::cookie()) {
return true;
}
}
return false;
}
}

View File

@@ -0,0 +1,96 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Providers;
use Bitinflow\Accounts\Auth\TokenGuard;
use Bitinflow\Accounts\Auth\UserProvider;
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Helpers\JwtParser;
use Bitinflow\Accounts\Contracts;
use Bitinflow\Accounts\Repository;
use Bitinflow\Payments\BitinflowPayments;
use Illuminate\Auth\RequestGuard;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
class BitinflowAccountsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* @return void
*/
public function boot()
{
$this->publishes([
dirname(__DIR__, 3) . '/config/bitinflow-accounts.php' => config_path('bitinflow-accounts.php'),
], 'config');
}
/**
* Register the application services.
*
* @return void
*/
public function register()
{
$this->mergeConfigFrom(dirname(__DIR__, 3) . '/config/bitinflow-accounts.php', 'bitinflow-accounts');
$this->app->singleton(Contracts\AppTokenRepository::class, Repository\AppTokenRepository::class);
$this->app->singleton(BitinflowAccounts::class, function () {
return new BitinflowAccounts;
});
$this->app->singleton(BitinflowPayments::class, function () {
return new BitinflowPayments;
});
$this->registerGuard();
}
/**
* Register the token guard.
*
* @return void
*/
protected function registerGuard()
{
Auth::resolved(function ($auth) {
$auth->extend('bitinflow-accounts', function ($app, $name, array $config) {
return tap($this->makeGuard($config), function ($guard) {
$this->app->refresh('request', $guard, 'setRequest');
});
});
});
}
/**
* Make an instance of the token guard.
*
* @param array $config
* @return RequestGuard
*/
protected function makeGuard(array $config): RequestGuard
{
return new RequestGuard(function ($request) use ($config) {
return (new TokenGuard(
new UserProvider(Auth::createUserProvider($config['provider']), $config['provider']),
$this->app->make('encrypter'),
$this->app->make(JwtParser::class)
))->user($request);
}, $this->app['request']);
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [
BitinflowAccounts::class,
BitinflowPayments::class,
];
}
}

View File

@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Providers;
use Bitinflow\Accounts\BitinflowAccounts;
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 BitinflowAccountsSsoUserProvider implements UserProvider
{
private $bitinflowAccounts;
private $accessTokenField = null;
private $fields;
private $model;
private $request;
public function __construct(
BitinflowAccounts $bitinflowAccounts,
Request $request,
string $model,
array $fields,
?string $accessTokenField = null
) {
$this->request = $request;
$this->model = $model;
$this->fields = $fields;
$this->accessTokenField = $accessTokenField;
$this->bitinflowAccounts = $bitinflowAccounts;
}
/**
* @param mixed $identifier
* @return Builder|Model|object|null
*/
public function retrieveById($identifier)
{
$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->bitinflowAccounts->setToken($token);
$result = $this->bitinflowAccounts->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.
*
* @return Model
*/
public function createModel(): Model
{
$class = '\\' . ltrim($this->model, '\\');
return new $class;
}
/**
* Get a new query builder for the model instance.
*
* @param Model|null $model
* @return Builder
*/
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)
{
return false;
}
}

View File

@@ -0,0 +1,61 @@
<?php
namespace Bitinflow\Accounts\Repository;
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Contracts\AppTokenRepository as Repository;
use Bitinflow\Accounts\Exceptions\RequestFreshAccessTokenException;
use Illuminate\Support\Facades\Cache;
class AppTokenRepository implements Repository
{
public const ACCESS_TOKEN_CACHE_KEY = 'bitinflow-accounts:access_token';
private BitinflowAccounts $client;
public function __construct()
{
$this->client = app(BitinflowAccounts::class);
}
/**
* {@inheritDoc}
*/
public function getAccessToken(): string
{
$accessToken = Cache::get(self::ACCESS_TOKEN_CACHE_KEY);
if ($accessToken) {
return $accessToken;
}
return $this->requestFreshAccessToken('*');
}
/**
* @param string $scope
*
* @throws RequestFreshAccessTokenException
*
* @return mixed
*/
private function requestFreshAccessToken(string $scope)
{
$result = $this->getClient()->retrievingToken('client_credentials', [
'scope' => $scope,
]);
if ( ! $result->success()) {
throw RequestFreshAccessTokenException::fromResponse($result->response());
}
Cache::put(self::ACCESS_TOKEN_CACHE_KEY, $accessToken = $result->data()->access_token, now()->addWeek());
return $accessToken;
}
private function getClient(): BitinflowAccounts
{
return $this->client;
}
}

View File

@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts; namespace Bitinflow\Accounts;
use Bitinflow\Accounts\Helpers\Paginator;
use Exception; use Exception;
use GhostZero\BitinflowAccounts\Helpers\Paginator; use Psr\Http\Message\ResponseInterface;
use GuzzleHttp\Psr7\Response;
use stdClass; use stdClass;
@@ -17,55 +17,64 @@ class Result
{ {
/** /**
* Query successfull. * Query successful.
*
* @var boolean * @var boolean
*/ */
public $success = false; public $success = false;
/** /**
* Guzzle exception, if present. * Guzzle exception, if present.
*
* @var null|mixed * @var null|mixed
*/ */
public $exception = null; public $exception = null;
/** /**
* Query result data. * Query result data.
*
* @var array * @var array
*/ */
public $data = []; public $data = [];
/** /**
* Total amount of result data. * Total amount of result data.
*
* @var integer * @var integer
*/ */
public $total = 0; public $total = 0;
/** /**
* Status Code. * Status Code.
*
* @var integer * @var integer
*/ */
public $status = 0; public $status = 0;
/** /**
* bitinflow Accounts response pagination cursor. * bitinflow Accounts response pagination cursor.
* @var null|\stdClass *
* @var null|stdClass
*/ */
public $pagination; public $pagination;
/** /**
* Internal paginator. * Internal paginator.
*
* @var null|Paginator * @var null|Paginator
*/ */
public $paginator; public $paginator;
/** /**
* Original Guzzle HTTP Response. * Original Guzzle HTTP Response.
* @var Response *
* @var ResponseInterface|null
*/ */
public $response; public $response;
/** /**
* Original bitinflow Accounts instance. * Original bitinflow Accounts instance.
*
* @var BitinflowAccounts * @var BitinflowAccounts
*/ */
public $bitinflow; public $bitinflow;
@@ -73,17 +82,17 @@ class Result
/** /**
* Constructor, * Constructor,
* *
* @param Response $response HTTP response * @param ResponseInterface|null $response HTTP response
* @param Exception|mixed $exception Exception, if present * @param Exception|mixed $exception Exception, if present
* @param null|Paginator $paginator Paginator, if present * @param null|Paginator $paginator Paginator, if present
*/ */
public function __construct(Response $response, Exception $exception = null, Paginator $paginator = null) public function __construct(?ResponseInterface $response, Exception $exception = null, Paginator $paginator = null)
{ {
$this->response = $response; $this->response = $response;
$this->success = $exception === null; $this->success = $exception === null;
$this->exception = $exception; $this->exception = $exception;
$this->status = $response->getStatusCode(); $this->status = $response ? $response->getStatusCode() : 500;
$jsonResponse = @json_decode($response->getBody()->getContents()); $jsonResponse = $response ? @json_decode($response->getBody()->getContents(), false) : null;
if ($jsonResponse !== null) { if ($jsonResponse !== null) {
$this->setProperty($jsonResponse, 'data'); $this->setProperty($jsonResponse, 'data');
$this->setProperty($jsonResponse, 'total'); $this->setProperty($jsonResponse, 'total');
@@ -95,14 +104,14 @@ class Result
/** /**
* Sets a class attribute by given JSON Response Body. * Sets a class attribute by given JSON Response Body.
* *
* @param stdClass $jsonResponse Response Body * @param stdClass $jsonResponse Response Body
* @param string $responseProperty Response property name * @param string $responseProperty Response property name
* @param string|null $attribute Class property name * @param string|null $attribute Class property name
*/ */
private function setProperty(stdClass $jsonResponse, string $responseProperty, string $attribute = null) private function setProperty(stdClass $jsonResponse, string $responseProperty, string $attribute = null): void
{ {
$classAttribute = $attribute ?? $responseProperty; $classAttribute = $attribute ?? $responseProperty;
if (!empty($jsonResponse) && property_exists($jsonResponse, $responseProperty)) { if (property_exists($jsonResponse, $responseProperty)) {
$this->{$classAttribute} = $jsonResponse->{$responseProperty}; $this->{$classAttribute} = $jsonResponse->{$responseProperty};
} elseif ($responseProperty === 'data') { } elseif ($responseProperty === 'data') {
$this->{$classAttribute} = $jsonResponse; $this->{$classAttribute} = $jsonResponse;
@@ -110,7 +119,8 @@ class Result
} }
/** /**
* Returns wether the query was successfull. * Returns whether the query was successfully.
*
* @return bool Success state * @return bool Success state
*/ */
public function success(): bool public function success(): bool
@@ -120,6 +130,7 @@ class Result
/** /**
* Get the response data, also available as public attribute. * Get the response data, also available as public attribute.
*
* @return mixed * @return mixed
*/ */
public function data() public function data()
@@ -129,6 +140,7 @@ class Result
/** /**
* Returns the last HTTP or API error. * Returns the last HTTP or API error.
*
* @return string Error message * @return string Error message
*/ */
public function error(): string public function error(): string
@@ -137,7 +149,7 @@ class Result
if ($this->exception === null || !$this->exception->hasResponse()) { if ($this->exception === null || !$this->exception->hasResponse()) {
return 'bitinflow Accounts API Unavailable'; return 'bitinflow Accounts API Unavailable';
} }
$exception = (string) $this->exception->getResponse()->getBody(); $exception = (string)$this->exception->getResponse()->getBody();
$exception = @json_decode($exception); $exception = @json_decode($exception);
if (property_exists($exception, 'message') && !empty($exception->message)) { if (property_exists($exception, 'message') && !empty($exception->message)) {
return $exception->message; return $exception->message;
@@ -148,6 +160,7 @@ class Result
/** /**
* Shifts the current result (Use for single user/video etc. query). * Shifts the current result (Use for single user/video etc. query).
*
* @return mixed Shifted data * @return mixed Shifted data
*/ */
public function shift() public function shift()
@@ -163,36 +176,26 @@ class Result
/** /**
* Return the current count of items in dataset. * Return the current count of items in dataset.
*
* @return int Count * @return int Count
*/ */
public function count(): int public function count(): int
{ {
return count((array) $this->data); return count((array)$this->data);
}
/**
* Set the Paginator to fetch the first set of results.
* @return null|Paginator
*/
public function first()
{
return $this->paginator !== null ? $this->paginator->first() : null;
} }
/** /**
* Set the Paginator to fetch the next set of results. * Set the Paginator to fetch the next set of results.
* @return null|Paginator
*/ */
public function next() public function next(): ?Paginator
{ {
return $this->paginator !== null ? $this->paginator->next() : null; return $this->paginator !== null ? $this->paginator->next() : null;
} }
/** /**
* Set the Paginator to fetch the last set of results. * Set the Paginator to fetch the last set of results.
* @return null|Paginator
*/ */
public function back() public function back(): ?Paginator
{ {
return $this->paginator !== null ? $this->paginator->back() : null; return $this->paginator !== null ? $this->paginator->back() : null;
} }
@@ -200,7 +203,7 @@ class Result
/** /**
* Get rate limit information. * Get rate limit information.
* *
* @param string|null $key Get defined index * @param string|null $key Get defined index
* *
* @return string|array|null * @return string|array|null
*/ */
@@ -210,9 +213,9 @@ class Result
return null; return null;
} }
$rateLimit = [ $rateLimit = [
'limit' => (int) $this->response->getHeaderLine('X-RateLimit-Limit'), 'limit' => (int)$this->response->getHeaderLine('X-RateLimit-Limit'),
'remaining' => (int) $this->response->getHeaderLine('X-RateLimit-Remaining'), 'remaining' => (int)$this->response->getHeaderLine('X-RateLimit-Remaining'),
'reset' => (int) $this->response->getHeaderLine('Retry-After'), 'reset' => (int)$this->response->getHeaderLine('Retry-After'),
]; ];
if ($key === null) { if ($key === null) {
return $rateLimit; return $rateLimit;
@@ -224,10 +227,8 @@ class Result
/** /**
* Insert users in data response. * Insert users in data response.
* *
* @param string $identifierAttribute Attribute to identify the users * @param string $identifierAttribute Attribute to identify the users
* @param string $insertTo Data index to insert user data * @param string $insertTo Data index to insert user data
*
* @return self
*/ */
public function insertUsers(string $identifierAttribute = 'user_id', string $insertTo = 'user'): self public function insertUsers(string $identifierAttribute = 'user_id', string $insertTo = 'user'): self
{ {
@@ -235,7 +236,7 @@ class Result
$userIds = collect($data)->map(function ($item) use ($identifierAttribute) { $userIds = collect($data)->map(function ($item) use ($identifierAttribute) {
return $item->{$identifierAttribute}; return $item->{$identifierAttribute};
})->toArray(); })->toArray();
if (count($userIds) == 0) { if (count($userIds) === 0) {
return $this; return $this;
} }
$users = collect($this->bitinflow->getUsersByIds($userIds)->data); $users = collect($this->bitinflow->getUsersByIds($userIds)->data);
@@ -248,4 +249,22 @@ class Result
return $this; return $this;
} }
/**
* Set the Paginator to fetch the first set of results.
*/
public function first(): ?Paginator
{
return $this->paginator !== null ? $this->paginator->first() : null;
}
public function response(): ?ResponseInterface
{
return $this->response;
}
public function dump()
{
dump($this->data());
}
} }

View File

@@ -1,6 +1,6 @@
<?php <?php
namespace GhostZero\BitinflowAccounts\Socialite; namespace Bitinflow\Accounts\Socialite;
use SocialiteProviders\Manager\SocialiteWasCalled; use SocialiteProviders\Manager\SocialiteWasCalled;

View File

@@ -1,8 +1,8 @@
<?php <?php
namespace GhostZero\BitinflowAccounts\Socialite; namespace Bitinflow\Accounts\Socialite;
use GhostZero\BitinflowAccounts\Enums\Scope; use Bitinflow\Accounts\Enums\Scope;
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;
@@ -22,7 +22,7 @@ class Provider extends AbstractProvider implements ProviderInterface
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected $scopes = [Scope::READ_USER]; protected $scopes = [Scope::USER_READ];
/** /**
* {@inherticdoc}. * {@inherticdoc}.
@@ -53,14 +53,14 @@ class Provider extends AbstractProvider implements ProviderInterface
protected function getUserByToken($token) protected function getUserByToken($token)
{ {
$response = $this->getHttpClient()->get( $response = $this->getHttpClient()->get(
'https://accounts.bitinflow.com/api/user', [ 'https://accounts.bitinflow.com/api/v3/user', [
'headers' => [ 'headers' => [
'Accept' => 'application/json', 'Accept' => 'application/json',
'Authorization' => 'Bearer ' . $token, 'Authorization' => 'Bearer ' . $token,
], ],
]); ]);
return json_decode($response->getBody()->getContents(), true); return json_decode($response->getBody()->getContents(), true)['data'];
} }
/** /**

View File

@@ -0,0 +1,51 @@
<?php
namespace Bitinflow\Accounts\Traits;
use stdClass;
trait HasBitinflowTokens
{
/**
* The current access token for the authentication user.
*
* @var stdClass
*/
protected $accessToken;
/**
* Get the current access token being used by the user.
*
* @return stdClass|null
*/
public function bitinflowToken(): ?stdClass
{
return $this->accessToken;
}
/**
* Determine if the current API token has a given scope.
*
* @param string $scope
* @return bool
*/
public function bitinflowTokenCan(string $scope): bool
{
$scopes = $this->accessToken ? $this->accessToken->scopes : [];
return in_array('*', $scopes) || in_array($scope, $this->accessToken->scopes);
}
/**
* Set the current access token for the user.
*
* @param stdClass $accessToken
* @return $this
*/
public function withBitinflowAccessToken(stdClass $accessToken): self
{
$this->accessToken = $accessToken;
return $this;
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Traits;
use Bitinflow\Accounts\Result;
use GuzzleHttp\Exception\RequestException;
/**
* @author René Preuß <rene@preuss.io>
*/
trait OauthTrait
{
/**
* Retrieving a oauth token using a given grant type.
*
* @param string $grantType
* @param array $attributes
*
* @return Result
*/
public function retrievingToken(string $grantType, array $attributes): Result
{
try {
$response = $this->client->request('POST', '/oauth/token', [
'form_params' => $attributes + [
'grant_type' => $grantType,
'client_id' => $this->getClientId(),
'client_secret' => $this->getClientSecret(),
],
]);
$result = new Result($response, null);
} catch (RequestException $exception) {
$result = new Result($exception->getResponse(), $exception);
}
$result->bitinflow = $this;
return $result;
}
}

View File

@@ -2,12 +2,12 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits; namespace Bitinflow\Accounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Delete; use Bitinflow\Accounts\ApiOperations\Delete;
use GhostZero\BitinflowAccounts\ApiOperations\Get; use Bitinflow\Accounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\ApiOperations\Post; use Bitinflow\Accounts\ApiOperations\Post;
use GhostZero\BitinflowAccounts\Result; use Bitinflow\Accounts\Result;
trait SshKeysTrait trait SshKeysTrait
{ {
@@ -27,9 +27,9 @@ trait SshKeysTrait
} }
/** /**
* Get currently authed user with Bearer Token * Creates ssh key for the currently authed user
* *
* @param string $publicKey * @param string $publicKey
* @param string|null $name * @param string|null $name
* *
* @return Result Result object * @return Result Result object
@@ -43,7 +43,7 @@ trait SshKeysTrait
} }
/** /**
* Get currently authed user with Bearer Token * Deletes a given ssh key for the currently authed user
* *
* @param int $id * @param int $id
* *

View File

@@ -0,0 +1,50 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Traits;
use Bitinflow\Accounts\ApiOperations\Get;
use Bitinflow\Accounts\Result;
trait UsersTrait
{
use Get;
/**
* Get currently authed user with Bearer Token
*
* @return Result Result object
*/
public function getAuthedUser(): Result
{
return $this->get('v3/user');
}
/**
* Creates a new user on behalf of the current user.
*
* @param array $parameters
*
* @return Result
*/
public function createUser(array $parameters): Result
{
return $this->post('v3/users', $parameters);
}
/**
* Checks if the given email exists.
*
* @param string $email
*
* @return Result
*/
public function isEmailExisting(string $email): Result
{
return $this->post('v3/users/check', [
'email' => $email,
]);
}
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Delete
{
abstract public function delete(string $path = '', array $parameters = [], Paginator $paginator = null);
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Get
{
abstract public function get(string $path = '', array $parameters = [], Paginator $paginator = null);
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Post
{
abstract public function post(string $path = '', array $parameters = [], Paginator $paginator = null);
}

View File

@@ -1,16 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
/**
* @author René Preuß <rene@preuss.io>
*/
trait Put
{
abstract public function put(string $path = '', array $parameters = [], Paginator $paginator = null);
}

View File

@@ -1,38 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Enums;
/**
* @author René Preuß <rene@preuss.io>
*/
class Scope
{
/*
* v0 API
*/
// Deprecated scope.
const API = 'api';
// Read nonpublic user information, including email address.
const READ_USER = 'read_user';
/*
* v1 API
*/
// Read authorized user´s email address.
const USERS_READ_EMAIL = 'users:read:email';
// Manage a authorized user object.
const USERS_EDIT = 'users:edit';
// Read authorized user´s transactions.
const TRANSACTIONS_READ = 'transactions:read';
// Create a new charge for the authorized user.
const CHARGES_CREATE = 'charges:create';
}

View File

@@ -1,20 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Facades;
use GhostZero\BitinflowAccounts\BitinflowAccounts as BitinflowAccountsService;
use Illuminate\Support\Facades\Facade;
/**
* @author René Preuß <rene@preuss.io>
*/
class BitinflowAccounts extends Facade
{
protected static function getFacadeAccessor()
{
return BitinflowAccountsService::class;
}
}

View File

@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Providers;
use GhostZero\BitinflowAccounts\BitinflowAccounts;
use Illuminate\Support\ServiceProvider;
class BitinflowAccountsServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
* @return void
*/
public function boot()
{
$this->publishes([
dirname(__DIR__) . '/../../../config/bitinflow-accounts-api.php' => config_path('bitinflow-accounts-api.php'),
], 'config');
}
/**
* Register the application services.
* @return void
*/
public function register()
{
$this->mergeConfigFrom(
dirname(__DIR__) . '/../../../config/bitinflow-accounts-api.php', 'bitinflow-accounts-api'
);
$this->app->singleton(BitinflowAccounts::class, function () {
return new BitinflowAccounts;
});
}
/**
* Get the services provided by the provider.
* @return array
*/
public function provides()
{
return [BitinflowAccounts::class];
}
}

View File

@@ -1,69 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\ApiOperations\Post;
use GhostZero\BitinflowAccounts\ApiOperations\Put;
use GhostZero\BitinflowAccounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait ChargesTrait
{
use Get, Post, Put;
/**
* Create a Charge object
*
* @param array $parameters
*
* @return Result Result object
*/
public function createCharge(array $parameters): Result
{
return $this->post('charges', $parameters);
}
/**
* Get a Charge object
*
* @param string $id
*
* @return Result Result object
*/
public function getCharge(string $id): Result
{
return $this->get("charges/$id");
}
/**
* Update a Charge object
*
* @param string $id
* @param array $parameters
*
* @return Result Result object
*/
public function updateCharge(string $id, array $parameters): Result
{
return $this->put("charges/$id", $parameters);
}
/**
* Capture a Charge object
*
* @param string $id
* @param array $parameters
*
* @return Result Result object
*/
public function captureCharge(string $id, array $parameters = []): Result
{
return $this->post("charges/$id/capture", $parameters);
}
}

View File

@@ -1,42 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\ApiOperations\Post;
use GhostZero\BitinflowAccounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait CheckoutSessionsTrait
{
use Get, Post;
/**
* Get a Session object
*
* @param string $id
*
* @return Result Result object
*/
public function getCheckoutSession(string $id): Result
{
return $this->get("checkout/sessions/$id");
}
/**
* Create a Session object
*
* @param array $parameters
*
* @return Result
*/
public function createCheckoutSession(array $parameters): Result
{
return $this->post('payments/sessions', $parameters);
}
}

View File

@@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\Result;
trait UsersTrait
{
use Get;
/**
* Get currently authed user with Bearer Token
* @return Result Result object
*/
public function getAuthedUser(): Result
{
return $this->get('users/me', [], null);
}
}

View File

@@ -0,0 +1,260 @@
<?php /** @noinspection DuplicatedCode */
namespace Bitinflow\Payments;
use Bitinflow\Accounts\Exceptions\RequestRequiresAuthenticationException;
use Bitinflow\Accounts\Exceptions\RequestRequiresClientIdException;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Payments\Result;
use Bitinflow\Accounts\ApiOperations;
use Bitinflow\Payments\Traits;
use Bitinflow\Support\Query;
use GuzzleHttp\Client;
use GuzzleHttp\Exception\GuzzleException;
use GuzzleHttp\Exception\RequestException;
class BitinflowPayments
{
use Traits\Balance;
use Traits\Wallets;
use Traits\Orders;
use Traits\Subscriptions;
use Traits\CheckoutSessions;
use Traits\Taxation;
use ApiOperations\Validation;
private static string $baseUrl = 'https://api.pay.bitinflow.com/v1/';
/**
* bitinflow Payments OAuth token.
*/
protected ?string $token = null;
/**
* bitinflow Accounts client id.
*/
protected ?string $clientId = null;
/**
* bitinflow Accounts client secret.
*/
protected ?string $clientSecret = null;
private Client $client;
/**
* Constructor.
*/
public function __construct()
{
if ($clientId = config('bitinflow-accounts.client_id')) {
$this->setClientId($clientId);
}
if ($clientSecret = config('bitinflow-accounts.client_secret')) {
$this->setClientSecret($clientSecret);
}
if ($redirectUri = config('bitinflow-accounts.payments.base_url')) {
self::setBaseUrl($redirectUri);
}
$this->client = new Client([
'base_uri' => self::$baseUrl,
]);
}
/**
* @param string $baseUrl
*
* @internal only for internal and debug purposes.
*/
public static function setBaseUrl(string $baseUrl): void
{
self::$baseUrl = $baseUrl;
}
/**
* Get OAuth token.
*
* @return string bitinflow Accounts token
* @return string|null
* @throws RequestRequiresAuthenticationException
*/
public function getToken(): ?string
{
if (!$this->token) {
throw new RequestRequiresAuthenticationException;
}
return $this->token;
}
/**
* Set OAuth token.
*
* @param string $token bitinflow Accounts OAuth token
*
* @return void
*/
public function setToken(string $token): void
{
$this->token = $token;
}
/**
* Fluid OAuth token setter.
*
* @param string $token bitinflow Accounts OAuth token
*
* @return self
*/
public function withToken(string $token): self
{
$this->setToken($token);
return $this;
}
/**
* Build query & execute.
*
* @param string $method HTTP method
* @param string $path Query path
* @param array $parameters Query parameters
* @param Paginator|null $paginator Paginator object
* @param mixed|null $jsonBody JSON data
*
* @return Result Result object
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function query(string $method = 'GET', string $path = '', array $parameters = [], Paginator $paginator = null, $jsonBody = null): Result
{
if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor();
}
try {
$response = $this->client->request($method, $path, [
'headers' => $this->buildHeaders((bool)$jsonBody),
'query' => Query::build($parameters),
'json' => $jsonBody ?: null,
]);
$result = new Result($response, null, $paginator);
} catch (RequestException $exception) {
$result = new Result($exception->getResponse(), $exception, $paginator);
}
$result->bitinflow = $this;
return $result;
}
/**
* Build headers for request.
*
* @param bool $json Body is JSON
*
* @return array
* @throws RequestRequiresClientIdException
*/
private function buildHeaders(bool $json = false): array
{
$headers = [
'Client-ID' => $this->getClientId(),
'Accept' => 'application/json',
];
if ($this->token) {
$headers['Authorization'] = 'Bearer ' . $this->token;
}
if ($json) {
$headers['Content-Type'] = 'application/json';
}
return $headers;
}
/**
* Get client id.
*
* @return string
* @throws RequestRequiresClientIdException
*/
public function getClientId(): string
{
if (!$this->clientId) {
throw new RequestRequiresClientIdException;
}
return $this->clientId;
}
/**
* Set client id.
*
* @param string $clientId bitinflow Accounts client id
*
* @return void
*/
public function setClientId(string $clientId): void
{
$this->clientId = $clientId;
}
/**
* Fluid client id setter.
*
* @param string $clientId bitinflow Accounts client id.
*
* @return self
*/
public function withClientId(string $clientId): self
{
$this->setClientId($clientId);
return $this;
}
/**
* Get client secret.
*
* @return string
* @throws RequestRequiresClientIdException
*/
public function getClientSecret(): string
{
if (!$this->clientSecret) {
throw new RequestRequiresClientIdException;
}
return $this->clientSecret;
}
/**
* Set client secret.
*
* @param string $clientSecret bitinflow Accounts client secret
*
* @return void
*/
public function setClientSecret(string $clientSecret): void
{
$this->clientSecret = $clientSecret;
}
/**
* Fluid client secret setter.
*
* @param string $clientSecret bitinflow Accounts client secret
*
* @return self
*/
public function withClientSecret(string $clientSecret): self
{
$this->setClientSecret($clientSecret);
return $this;
}
public function getBaseUrl()
{
return self::$baseUrl;
}
}

View File

@@ -0,0 +1,19 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Payments\Facades;
use Bitinflow\Payments\BitinflowPayments as BitinflowPaymentsService;
use Illuminate\Support\Facades\Facade;
/**
* @author René Preuß <rene@preuss.io>
*/
class BitinflowPayments extends Facade
{
protected static function getFacadeAccessor()
{
return BitinflowPaymentsService::class;
}
}

10
src/Payments/Result.php Normal file
View File

@@ -0,0 +1,10 @@
<?php
namespace Bitinflow\Payments;
use Bitinflow\Accounts\Result as Base;
class Result extends Base
{
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Payments\Result;
use GuzzleHttp\Exception\GuzzleException;
trait Balance
{
/**
* Get balance from user.
*/
public function getUser(): Result
{
return $this->query('GET', 'user');
}
/**
* Deposit given amount from bank to account.
*/
public function deposit(float $amount, string $description): Result
{
return $this->query('PUT', 'wallet/deposit', [], null, [
'amount' => $amount,
'description' => $description,
]);
}
/**
* Charge given amount from account.
*
* @throws GuzzleException
*/
public function charge(float $amount, string $description): bool
{
$result = $this->createOrder([
'name' => $description,
'description' => 'one-time payment',
'amount' => 1,
'price' => $amount,
]);
return $this->checkoutOrder($result->data()->id);
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Accounts\Exceptions\RequestRequiresClientIdException;
use Bitinflow\Payments\Result;
use GuzzleHttp\Exception\GuzzleException;
trait CheckoutSessions
{
/**
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function createCheckoutSession(array $parameters): Result
{
return $this->query('POST', 'checkout-sessions', [], null, $parameters);
}
/**
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function getCheckoutSession(string $id): Result
{
return $this->query('GET', sprintf('checkout-sessions/%s', $id));
}
/**
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function checkoutCheckoutSession(string $id): Result
{
return $this->query('PUT', sprintf('checkout-sessions/%s/checkout', $id));
}
/**
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function revokeCheckoutSession(string $id): Result
{
return $this->query('PUT', sprintf('checkout-sessions/%s/revoke', $id));
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Payments\Result;
use Bitinflow\Payments\BitinflowPayments;
use GuzzleHttp\Exception\GuzzleException;
trait Orders
{
/**
* Get orders from user.
*
* @throws GuzzleException
*/
public function getOrders(): Result
{
return $this->query('GET', 'orders');
}
/**
* @param string $id
*/
public function getOrder(string $id): Result
{
return $this->query('GET', sprintf('orders/%s', $id));
}
/**
* Create a new order.
*
* @throws GuzzleException
*/
public function createOrder(array $parameters = []): Result
{
return $this->query('POST', 'orders', [], null, $parameters);
}
/**
* Checkout given subscription.
*
* @param string $id
*/
public function checkoutOrder(string $id):Result
{
return $this->query('PUT', sprintf('orders/%s/checkout', $id));
}
/**
* Revoke a running subscription.
*
* @param string $id
*/
public function revokeOrder(string $id):Result
{
return $this->query('PUT', sprintf('orders/%s/revoke', $id));
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Accounts\Exceptions\RequestRequiresClientIdException;
use Bitinflow\Accounts\Exceptions\RequestRequiresMissingParametersException;
use Bitinflow\Accounts\Helpers\Paginator;
use Bitinflow\Payments\Result;
use GuzzleHttp\Exception\GuzzleException;
trait Subscriptions
{
/**
* Get subscriptions from user.
*
* @return object|null
*/
public function getSubscriptions(array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('GET', 'subscriptions', $parameters, $paginator);
}
/**
* @param string $id
*/
public function getSubscription(string $id): Result
{
return $this->query('GET', sprintf('subscriptions/%s', $id));
}
/**
* Create a new subscription.
*
* @param array $parameters array which requires following attributes:
* name, description, period, price
* and following attributes are optional:
* vat, payload, ends_at, webhook_url, webhook_secret
* @return object|false the subscription object
* @throws GuzzleException
* @throws RequestRequiresClientIdException
* @throws RequestRequiresMissingParametersException
*/
public function createSubscription(array $parameters): Result
{
$this->validateRequired($parameters, ['name', 'description', 'period', 'price']);
return $this->query('POST', 'subscriptions', [], null, $parameters);
}
/**
* Force given subscription to check out (trusted apps only).
*
* @param string $id
* @return Result
* @throws RequestRequiresClientIdException
* @throws GuzzleException
*/
public function checkoutSubscription(string $id): Result
{
return $this->query('PUT', sprintf('subscriptions/%s/checkout', $id));
}
/**
* Revoke a running subscription.
*
* @param string $id
*/
public function revokeSubscription(string $id): Result
{
return $this->query('PUT', sprintf('subscriptions/%s/revoke', $id));
}
/**
* Resume a running subscription.
*
* @param string $id
*/
public function resumeSubscription(string $id): Result
{
return $this->query('PUT', sprintf('subscriptions/%s/resume', $id));
}
}

View File

@@ -0,0 +1,16 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Payments\Result;
trait Taxation
{
/**
* Get vat from user.
*/
public function getTaxation(): Result
{
return $this->query('GET', 'taxation');
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Bitinflow\Payments\Traits;
use Bitinflow\Payments\Result;
use Bitinflow\Payments\BitinflowPayments;
trait Wallets
{
/**
* Get all wallets that belong to the user.
*/
public function getWallets(): Result
{
return $this->query('GET', 'wallet');
}
/**
* Set default wallet to given wallet token.
*
* @param string $token default payment method token
*/
public function setDefaultWallet(string $token): Result
{
return $this->query('PUT', 'wallet/default', [], null, [
'token' => $token
]);
}
public function getWalletSetupIntent(string $successUrl): string
{
return sprintf('%swallet/?continue_url=%s', config('bitinflow-accounts.payments.dashboard_url'), urlencode($successUrl));
}
}

26
src/Support/Query.php Normal file
View File

@@ -0,0 +1,26 @@
<?php
namespace Bitinflow\Support;
class Query
{
/**
* Build query with support for multiple same first-dimension keys.
*
* @param array $query
*
* @return string
*/
public static function build(array $query): string
{
$parts = [];
foreach ($query as $name => $value) {
$value = (array)$value;
array_walk_recursive($value, function ($value) use (&$parts, $name) {
$parts[] = urlencode($name) . '=' . urlencode($value);
});
}
return implode('&', $parts);
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Tests;
use Bitinflow\Accounts\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiOauthTest extends ApiTestCase
{
public function testGetOauthToken(): void
{
$this->registerResult($result = $this->getClient()->retrievingToken('client_credentials', [
'scope' => '',
]));
$this->assertTrue($result->success());
$this->assertNotEmpty($result->data()->access_token);
}
}

View File

@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests; namespace Bitinflow\Accounts\Tests;
use GhostZero\BitinflowAccounts\Result; use Bitinflow\Accounts\Result;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase; use Bitinflow\Accounts\Tests\TestCases\ApiTestCase;
/** /**
* @author René Preuß <rene@preuss.io> * @author René Preuß <rene@preuss.io>
@@ -13,15 +13,14 @@ use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
class ApiSshKeysTest extends ApiTestCase class ApiSshKeysTest extends ApiTestCase
{ {
public function testGetSshKeyByUserId() public function testGetSshKeyByUserId(): void
{ {
$this->registerResult($result = $this->getClient()->getSshKeysByUserId(38)); $this->registerResult($result = $this->getClient()->getSshKeysByUserId(38));
$this->assertInstanceOf(Result::class, $result);
$this->assertEquals('rene.preuss@check24.de', $result->shift()->name); $this->assertEquals('rene.preuss@check24.de', $result->shift()->name);
$this->assertGreaterThanOrEqual(2, $result->count()); $this->assertGreaterThanOrEqual(2, $result->count());
} }
public function testSshKeyManagement() public function testSshKeyManagement(): void
{ {
$customName = 'Hello World!'; $customName = 'Hello World!';
$publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3H7sYVrVCwwYIuRm3on3S9n/BCd2mBJrgCk6xTerbNmt0RyUZ+RtGsK6UYkgnRR2WWq9/Pv2s3RXJXPxbsIEYmKCcTdLUvDk56x9385cIVUX4w016mpe/8lyu+mIdqWYKsJMoab0oReCDX8Y9qBcaffDh8AgmYVN+86gXgoP1ITe9BDYrFiR6U571VyLDVN3OYOYPMF3/L9f0knMfM0T4LrS8oi6faVBCxZHRoBGtGmsTBkE0KwplYQFN2aa4Mxab+rTUFmJr3LYEcJF0J8wNJ3eEDFNOR0254jrjbGGAXGsc+cxJoNzech+GBkRMKMpNU0lds6VxP0ZB25VfzjEmQ== René Preuß'; $publicKey = 'ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAQEA3H7sYVrVCwwYIuRm3on3S9n/BCd2mBJrgCk6xTerbNmt0RyUZ+RtGsK6UYkgnRR2WWq9/Pv2s3RXJXPxbsIEYmKCcTdLUvDk56x9385cIVUX4w016mpe/8lyu+mIdqWYKsJMoab0oReCDX8Y9qBcaffDh8AgmYVN+86gXgoP1ITe9BDYrFiR6U571VyLDVN3OYOYPMF3/L9f0knMfM0T4LrS8oi6faVBCxZHRoBGtGmsTBkE0KwplYQFN2aa4Mxab+rTUFmJr3LYEcJF0J8wNJ3eEDFNOR0254jrjbGGAXGsc+cxJoNzech+GBkRMKMpNU0lds6VxP0ZB25VfzjEmQ== René Preuß';

View File

@@ -0,0 +1,64 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Accounts\Tests;
use Bitinflow\Accounts\Enums\Scope;
use Bitinflow\Accounts\Tests\TestCases\ApiTestCase;
use Illuminate\Support\Str;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiUsersTest extends ApiTestCase
{
public function testGetAuthedUser(): void
{
$this->getClient()->withToken($this->getToken());
$this->registerResult($result = $this->getClient()->getAuthedUser());
$this->assertTrue($result->success());
$this->assertEquals('rene@preuss.io', $result->data()->email);
}
public function testEmailAvailabilityNonExisting(): void
{
$this->getClient()->withToken($this->getToken());
$this->registerResult($result = $this->getClient()->isEmailExisting('rene+non-existing@preuss.io'));
$this->assertTrue(!$result->success());
}
public function testEmailAvailabilityExisting(): void
{
$this->getClient()->withToken($this->getToken());
$this->registerResult($result = $this->getClient()->isEmailExisting('rene@preuss.io'));
$this->assertTrue($result->success());
}
public function testCreateUser(): void
{
$testEmailAddress = $this->createRandomEmail();
$this->registerResult($result = $this->getClient()->retrievingToken('client_credentials', [
'scope' => Scope::USERS_CREATE,
]));
$this->getClient()->withToken($result->data()->access_token);
$this->registerResult($result = $this->getClient()->createUser([
'first_name' => 'René',
'last_name' => 'Preuß',
'email' => $testEmailAddress,
'password' => 'Password1',
'password_confirmation' => 'Password1',
'terms_accepted' => true,
]));
$this->assertTrue($result->success(), $result->error());
$this->assertEquals($testEmailAddress, $result->data()->email);
}
private function createRandomEmail(): string
{
return sprintf('rene+unittest.%s@bitinflow.com', Str::random());
}
}

View File

@@ -2,11 +2,11 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests; namespace Bitinflow\Accounts\Tests;
use GhostZero\BitinflowAccounts\BitinflowAccounts; use Bitinflow\Accounts\BitinflowAccounts;
use GhostZero\BitinflowAccounts\Facades\BitinflowAccounts as BitinflowAccountsFacade; use Bitinflow\Accounts\Facades\BitinflowAccounts as BitinflowAccountsFacade;
use GhostZero\BitinflowAccounts\Tests\TestCases\TestCase; use Bitinflow\Accounts\Tests\TestCases\TestCase;
/** /**
* @author René Preuß <rene@preuss.io> * @author René Preuß <rene@preuss.io>
@@ -14,12 +14,12 @@ use GhostZero\BitinflowAccounts\Tests\TestCases\TestCase;
class ServiceInstantiationTest extends TestCase class ServiceInstantiationTest extends TestCase
{ {
public function testInstance() public function testInstance(): void
{ {
$this->assertInstanceOf(BitinflowAccounts::class, app(BitinflowAccounts::class)); $this->assertInstanceOf(BitinflowAccounts::class, app(BitinflowAccounts::class));
} }
public function testFacade() public function testFacade(): void
{ {
$this->assertInstanceOf(BitinflowAccounts::class, BitinflowAccountsFacade::getFacadeRoot()); $this->assertInstanceOf(BitinflowAccounts::class, BitinflowAccountsFacade::getFacadeRoot());
} }

View File

@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests\TestCases; namespace Bitinflow\Accounts\Tests\TestCases;
use GhostZero\BitinflowAccounts\BitinflowAccounts; use Bitinflow\Accounts\BitinflowAccounts;
use GhostZero\BitinflowAccounts\Result; use Bitinflow\Accounts\Result;
/** /**
* @author René Preuß <rene@preuss.io> * @author René Preuß <rene@preuss.io>
@@ -18,6 +18,11 @@ abstract class ApiTestCase extends TestCase
protected function setUp(): void protected function setUp(): void
{ {
parent::setUp(); parent::setUp();
if ($this->getBaseUrl()) {
BitinflowAccounts::setBaseUrl($this->getBaseUrl());
}
if (!$this->getClientId()) { if (!$this->getClientId()) {
$this->markTestSkipped('No Client-ID given'); $this->markTestSkipped('No Client-ID given');
} }
@@ -34,6 +39,11 @@ abstract class ApiTestCase extends TestCase
return $result; return $result;
} }
protected function getBaseUrl()
{
return getenv('BASE_URL');
}
protected function getClientId() protected function getClientId()
{ {
return getenv('CLIENT_ID'); return getenv('CLIENT_ID');

View File

@@ -2,10 +2,10 @@
declare(strict_types=1); declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests\TestCases; namespace Bitinflow\Accounts\Tests\TestCases;
use GhostZero\BitinflowAccounts\BitinflowAccounts; use Bitinflow\Accounts\BitinflowAccounts;
use GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider; use Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase; use Orchestra\Testbench\TestCase as BaseTestCase;
/** /**

View File

@@ -1,23 +0,0 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\Result;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiUsersTest extends ApiTestCase
{
public function testGetAuthedUser()
{
$this->getClient()->withToken($this->getToken());
$this->registerResult($result = $this->getClient()->getAuthedUser());
$this->assertInstanceOf(Result::class, $result);
$this->assertEquals('rene@preuss.io', $result->data()->email);
}
}

View File

@@ -0,0 +1,26 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Payments\Tests;
use Bitinflow\Accounts\Contracts\AppTokenRepository;
use Bitinflow\Payments\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiOauthTest extends ApiTestCase
{
public function testGetOauthToken(): void
{
$token = app(AppTokenRepository::class)->getAccessToken();
$this->getPaymentsClient()->withToken($this->getToken());
$this->registerResult($result = $this->getPaymentsClient()->createSubscription([
]));
$result->dump();
}
}

View File

@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Payments\Tests\TestCases;
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Result;
use Bitinflow\Payments\BitinflowPayments;
/**
* @author René Preuß <rene@preuss.io>
*/
abstract class ApiTestCase extends TestCase
{
protected static $rateLimitRemaining = null;
protected function setUp(): void
{
parent::setUp();
if ($this->getAccountsBaseUrl()) {
BitinflowAccounts::setBaseUrl($this->getAccountsBaseUrl());
}
if ($this->getPaymentsBaseUrl()) {
BitinflowPayments::setBaseUrl($this->getPaymentsBaseUrl());
}
if (!$this->getClientId()) {
$this->markTestSkipped('No Client-ID given');
}
if (self::$rateLimitRemaining !== null && self::$rateLimitRemaining < 3) {
$this->markTestSkipped('Rate Limit exceeded (' . self::$rateLimitRemaining . ')');
}
$this->getAccountsClient()->setClientId($this->getClientId());
$this->getPaymentsClient()->setClientId($this->getClientId());
}
protected function registerResult(Result $result): Result
{
self::$rateLimitRemaining = $result->rateLimit('remaining');
return $result;
}
protected function getAccountsBaseUrl()
{
return getenv('ACCOUNTS_BASE_URL');
}
protected function getPaymentsBaseUrl()
{
return getenv('PAYMENTS_BASE_URL');
}
protected function getClientId()
{
return getenv('CLIENT_ID');
}
protected function getClientSecret()
{
return getenv('CLIENT_KEY');
}
protected function getToken()
{
return getenv('CLIENT_ACCESS_TOKEN');
}
public function getPaymentsClient(): BitinflowPayments
{
return app(BitinflowPayments::class);
}
public function getAccountsClient(): BitinflowAccounts
{
return app(BitinflowAccounts::class);
}
}

View File

@@ -0,0 +1,30 @@
<?php
declare(strict_types=1);
namespace Bitinflow\Payments\Tests\TestCases;
use Bitinflow\Accounts\BitinflowAccounts;
use Bitinflow\Accounts\Providers\BitinflowAccountsServiceProvider;
use Orchestra\Testbench\TestCase as BaseTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
abstract class TestCase extends BaseTestCase
{
protected function getPackageProviders($app)
{
return [
BitinflowAccountsServiceProvider::class,
];
}
protected function getPackageAliases($app)
{
return [
'BitinflowAccounts' => BitinflowAccounts::class,
];
}
}