21 Commits
1.0.0 ... 3.1.0

Author SHA1 Message Date
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
René Preuß
da1b6b7796 Fix Directory Path 2019-10-16 16:16:59 +02:00
31 changed files with 659 additions and 5090 deletions

3
.gitignore vendored
View File

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

View File

@@ -155,14 +155,27 @@ BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedU
public function createCharge(array $parameters)
public function getCharge(string $id)
public function updateCharge(string $id, array $parameters)
public function captureCharge(string $id, array $parameters = [])
public function captureCharge(string $id, array $parameters = array ())
```
### CheckoutSessions
### Documents
```php
public function getCheckoutSession(string $id)
public function createCheckoutSession(array $parameters)
public function createDocument(array $parameters)
public function createDocumentDownloadUrl(string $identifier, CarbonInterface $expiresAt = NULL)
```
### Oauth
```php
public function retrievingToken(string $grantType, array $attributes)
```
### PaymentIntents
```php
public function getPaymentIntent(string $id)
public function createPaymentIntent(array $parameters)
```
### SshKeys
@@ -177,6 +190,7 @@ public function deleteSshKey(int $id)
```php
public function getAuthedUser()
public function createUser(array $parameters)
```
[**OAuth Scopes Enums**](https://github.com/ghostzero/bitinflow-accounts/blob/master/src/Enums/Scope.php)
@@ -190,7 +204,7 @@ composer test
```
```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

View File

@@ -9,9 +9,10 @@ PHP bitinflow Accounts API Client for Laravel 5+
## Table of contents
1. [Installation](#installation)
2. [Configuration](#configuration)
3. [Examples](#examples)
4. [Documentation](#documentation)
2. [Event Listener](#event-listener)
3. [Configuration](#configuration)
4. [Examples](#examples)
5. [Documentation](#documentation)
6. [Development](#Development)
## Installation
@@ -161,7 +162,7 @@ composer test
```
```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

View File

@@ -11,14 +11,15 @@
"require": {
"php": ">=7.2",
"ext-json": "*",
"illuminate/support": "^6.0",
"illuminate/console": "^6.0",
"guzzlehttp/guzzle": "^6.3",
"socialiteproviders/manager": "^3.4"
"illuminate/support": "^8.0",
"illuminate/console": "^8.0",
"guzzlehttp/guzzle": "^6.3|^7.0.1",
"socialiteproviders/manager": "^3.4",
"firebase/php-jwt": "^5.2"
},
"require-dev": {
"phpunit/phpunit": "^8.0",
"orchestra/testbench": "~4.0",
"orchestra/testbench": "^6.0",
"codedungeon/phpunit-result-printer": "^0.26.2"
},
"autoload": {

5001
composer.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,4 +4,5 @@ return [
'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_URI', ''),
];

0
oauth-public.key Normal file
View File

View File

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

View File

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

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
namespace GhostZero\BitinflowAccounts;
use GhostZero\BitinflowAccounts\ApiOperations;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresAuthenticationException;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresClientIdException;
use GhostZero\BitinflowAccounts\Exceptions\RequestRequiresRedirectUriException;
@@ -18,12 +19,18 @@ class BitinflowAccounts
{
use Traits\ChargesTrait;
use Traits\CheckoutSessionsTrait;
use Traits\DocumentsTrait;
use Traits\OauthTrait;
use Traits\PaymentIntentsTrait;
use Traits\SshKeysTrait;
use Traits\UsersTrait;
const BASE_URI = 'https://accounts.bitinflow.com/api/';
const OAUTH_BASE_URI = 'https://accounts.bitinflow.com/api/';
use ApiOperations\Delete;
use ApiOperations\Get;
use ApiOperations\Post;
use ApiOperations\Put;
private static $baseUrl = 'https://accounts.bitinflow.com/api/';
/**
* Guzzle is used to make http requests.
@@ -75,11 +82,24 @@ class BitinflowAccounts
if ($redirectUri = config('bitinflow-accounts-api.redirect_url')) {
$this->setRedirectUri($redirectUri);
}
if ($redirectUri = config('bitinflow-accounts-api.base_url')) {
self::setBaseUrl($redirectUri);
}
$this->client = new Client([
'base_uri' => self::BASE_URI,
'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 client id.
* @return string
@@ -97,7 +117,7 @@ class BitinflowAccounts
/**
* Set client id.
*
* @param string $clientId bitinflow Accounts client id
* @param string $clientId bitinflow Accounts client id
*
* @return void
*/
@@ -109,7 +129,7 @@ class BitinflowAccounts
/**
* Fluid client id setter.
*
* @param string $clientId bitinflow Accounts client id.
* @param string $clientId bitinflow Accounts client id.
*
* @return self
*/
@@ -137,7 +157,7 @@ class BitinflowAccounts
/**
* Set client secret.
*
* @param string $clientSecret bitinflow Accounts client secret
* @param string $clientSecret bitinflow Accounts client secret
*
* @return void
*/
@@ -149,7 +169,7 @@ class BitinflowAccounts
/**
* Fluid client secret setter.
*
* @param string $clientSecret bitinflow Accounts client secret
* @param string $clientSecret bitinflow Accounts client secret
*
* @return self
*/
@@ -177,7 +197,7 @@ class BitinflowAccounts
/**
* Set redirect url.
*
* @param string $redirectUri
* @param string $redirectUri
*
* @return void
*/
@@ -189,7 +209,7 @@ class BitinflowAccounts
/**
* Fluid redirect url setter.
*
* @param string $redirectUri
* @param string $redirectUri
*
* @return self
*/
@@ -218,7 +238,7 @@ class BitinflowAccounts
/**
* Set OAuth token.
*
* @param string $token bitinflow Accounts OAuth token
* @param string $token bitinflow Accounts OAuth token
*
* @return void
*/
@@ -230,7 +250,7 @@ class BitinflowAccounts
/**
* Fluid OAuth token setter.
*
* @param string $token bitinflow Accounts OAuth token
* @param string $token bitinflow Accounts OAuth token
*
* @return self
*/
@@ -250,7 +270,7 @@ class BitinflowAccounts
* @throws GuzzleException
* @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);
}
@@ -264,7 +284,7 @@ class BitinflowAccounts
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function post(string $path = '', array $parameters = [], Paginator $paginator = null)
public function post(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('POST', $path, $parameters, $paginator);
}
@@ -278,7 +298,7 @@ class BitinflowAccounts
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function delete(string $path = '', array $parameters = [], Paginator $paginator = null)
public function delete(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('DELETE', $path, $parameters, $paginator);
}
@@ -292,7 +312,7 @@ class BitinflowAccounts
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function put(string $path = '', array $parameters = [], Paginator $paginator = null)
public function put(string $path = '', array $parameters = [], Paginator $paginator = null): Result
{
return $this->query('PUT', $path, $parameters, $paginator);
}
@@ -306,7 +326,7 @@ class BitinflowAccounts
* @throws GuzzleException
* @throws RequestRequiresClientIdException
*/
public function json(string $method, string $path = '', array $body = null)
public function json(string $method, string $path = '', array $body = null): Result
{
if ($body) {
$body = json_encode(['data' => $body]);
@@ -318,11 +338,11 @@ class BitinflowAccounts
/**
* Build query & execute.
*
* @param string $method HTTP method
* @param string $path Query path
* @param array $parameters Query parameters
* @param Paginator $paginator Paginator object
* @param mixed|null $jsonBody JSON data
* @param string $method HTTP method
* @param string $path Query path
* @param array $parameters Query parameters
* @param Paginator $paginator Paginator object
* @param mixed|null $jsonBody JSON data
*
* @return Result Result object
* @throws GuzzleException
@@ -337,7 +357,7 @@ class BitinflowAccounts
$response = $this->client->request($method, $path, [
'headers' => $this->buildHeaders($jsonBody ? true : false),
'query' => $this->buildQuery($parameters),
'json' => $jsonBody ? $jsonBody : null,
'json' => $jsonBody ?: null,
]);
$result = new Result($response, null, $paginator);
} catch (RequestException $exception) {
@@ -351,7 +371,7 @@ class BitinflowAccounts
/**
* Build query with support for multiple smae first-dimension keys.
*
* @param array $query
* @param array $query
*
* @return string
*/
@@ -371,7 +391,7 @@ class BitinflowAccounts
/**
* Build headers for request.
*
* @param bool $json Body is JSON
* @param bool $json Body is JSON
*
* @return array
* @throws RequestRequiresClientIdException
@@ -380,13 +400,13 @@ class BitinflowAccounts
{
$headers = [
'Client-ID' => $this->getClientId(),
'Accept' => 'application/json',
];
if ($this->token) {
$headers['Authorization'] = 'Bearer ' . $this->token;
}
if ($json) {
$headers['Content-Type'] = 'application/json';
$headers['Accept'] = 'application/json';
}
return $headers;

View File

@@ -0,0 +1,17 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Enums;
/**
* @author René Preuß <rene@preuss.io>
*/
class DocumentType
{
// Read authorized user´s email address.
public const TYPE_PDF_INVOICE = 'pdf.invoice';
// Manage a authorized user object.
public const TYPE_PDF_ORDER = 'pdf.order';
}

View File

@@ -15,24 +15,26 @@ class Scope
*/
// Deprecated scope.
const API = 'api';
public const API = 'api';
// Read nonpublic user information, including email address.
const READ_USER = 'read_user';
public const READ_USER = 'read_user';
/*
* v1 API
*/
// Read authorized user´s email address.
const USERS_READ_EMAIL = 'users:read:email';
public const USERS_READ_EMAIL = 'users:read:email';
// Manage a authorized user object.
const USERS_EDIT = 'users:edit';
public const USERS_EDIT = 'users:edit';
public const USERS_CREATE = 'users:create';
// Read authorized user´s transactions.
const TRANSACTIONS_READ = 'transactions:read';
public const TRANSACTIONS_READ = 'transactions:read';
// Create a new charge for the authorized user.
const CHARGES_CREATE = 'charges:create';
public const CHARGES_CREATE = 'charges:create';
}

View File

@@ -0,0 +1,41 @@
<?php
namespace GhostZero\BitinflowAccounts\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

@@ -0,0 +1,82 @@
<?php
namespace GhostZero\BitinflowAccounts\Http\Middleware;
use Closure;
use Firebase\JWT\JWT;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Http\Request;
use GhostZero\BitinflowAccounts\Exceptions\MissingScopeException;
use stdClass;
use Throwable;
class CheckClientCredentials
{
public const ALLOWED_ALGORITHMS = ['RS256'];
/**
* Handle an incoming request.
*
* @param Request $request
* @param Closure $next
* @param mixed ...$scopes
*
* @throws AuthenticationException|MissingScopeException
*
* @return mixed
*/
public function handle($request, Closure $next, ...$scopes)
{
JWT::$leeway = 60;
try {
$decoded = JWT::decode(
$request->bearerToken(),
$this->getOauthPublicKey(),
self::ALLOWED_ALGORITHMS
);
} catch (Throwable $exception) {
throw new AuthenticationException();
}
$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 getOauthPublicKey()
{
return file_get_contents(__DIR__ . '/../../../../../oauth-public.key');
}
/**
* Validate token credentials.
*
* @param stdClass $token
* @param array $scopes
*
* @throws MissingScopeException
*
* @return void
*/
protected function validateScopes(stdClass $token, array $scopes)
{
if (empty($scopes) || in_array('*', $token->scopes)) {
return;
}
foreach ($scopes as $scope) {
if (in_array($scope, $token->scopes)) {
return;
}
}
throw new MissingScopeException($scopes);
}
}

View File

@@ -17,7 +17,7 @@ class BitinflowAccountsServiceProvider extends ServiceProvider
public function boot()
{
$this->publishes([
dirname(__DIR__) . '/../config/bitinflow-accounts-api.php' => config_path('bitinflow-accounts-api.php'),
dirname(__DIR__) . '/../../../config/bitinflow-accounts-api.php' => config_path('bitinflow-accounts-api.php'),
], 'config');
}
@@ -28,7 +28,7 @@ class BitinflowAccountsServiceProvider extends ServiceProvider
public function register()
{
$this->mergeConfigFrom(
dirname(__DIR__) . '/../config/bitinflow-accounts-api.php', 'bitinflow-accounts-api'
dirname(__DIR__) . '/../../../config/bitinflow-accounts-api.php', 'bitinflow-accounts-api'
);
$this->app->singleton(BitinflowAccounts::class, function () {
return new BitinflowAccounts;
@@ -43,4 +43,4 @@ class BitinflowAccountsServiceProvider extends ServiceProvider
{
return [BitinflowAccounts::class];
}
}
}

View File

@@ -6,7 +6,7 @@ namespace GhostZero\BitinflowAccounts;
use Exception;
use GhostZero\BitinflowAccounts\Helpers\Paginator;
use GuzzleHttp\Psr7\Response;
use Psr\Http\Message\ResponseInterface;
use stdClass;
@@ -60,7 +60,7 @@ class Result
/**
* Original Guzzle HTTP Response.
* @var Response
* @var ResponseInterface|null
*/
public $response;
@@ -73,17 +73,17 @@ class Result
/**
* Constructor,
*
* @param Response $response HTTP response
* @param ResponseInterface|null $response HTTP response
* @param Exception|mixed $exception Exception, 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->success = $exception === null;
$this->exception = $exception;
$this->status = $response->getStatusCode();
$jsonResponse = @json_decode($response->getBody()->getContents());
$this->status = $response ? $response->getStatusCode() : 500;
$jsonResponse = $response ? @json_decode($response->getBody()->getContents(), false) : null;
if ($jsonResponse !== null) {
$this->setProperty($jsonResponse, 'data');
$this->setProperty($jsonResponse, 'total');
@@ -99,10 +99,10 @@ class Result
* @param string $responseProperty Response 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;
if (!empty($jsonResponse) && property_exists($jsonResponse, $responseProperty)) {
if ($jsonResponse !== null && property_exists($jsonResponse, $responseProperty)) {
$this->{$classAttribute} = $jsonResponse->{$responseProperty};
} elseif ($responseProperty === 'data') {
$this->{$classAttribute} = $jsonResponse;
@@ -174,7 +174,7 @@ class Result
* Set the Paginator to fetch the first set of results.
* @return null|Paginator
*/
public function first()
public function first(): ?Paginator
{
return $this->paginator !== null ? $this->paginator->first() : null;
}
@@ -183,7 +183,7 @@ class Result
* 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;
}
@@ -192,7 +192,7 @@ class Result
* 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;
}
@@ -200,7 +200,7 @@ class Result
/**
* Get rate limit information.
*
* @param string|null $key Get defined index
* @param string|null $key Get defined index
*
* @return string|array|null
*/
@@ -224,8 +224,8 @@ class Result
/**
* Insert users in data response.
*
* @param string $identifierAttribute Attribute to identify the users
* @param string $insertTo Data index to insert user data
* @param string $identifierAttribute Attribute to identify the users
* @param string $insertTo Data index to insert user data
*
* @return self
*/
@@ -235,7 +235,7 @@ class Result
$userIds = collect($data)->map(function ($item) use ($identifierAttribute) {
return $item->{$identifierAttribute};
})->toArray();
if (count($userIds) == 0) {
if (count($userIds) === 0) {
return $this;
}
$users = collect($this->bitinflow->getUsersByIds($userIds)->data);

View File

@@ -0,0 +1,48 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use Carbon\CarbonInterface;
use GhostZero\BitinflowAccounts\ApiOperations\Get;
use GhostZero\BitinflowAccounts\ApiOperations\Post;
use GhostZero\BitinflowAccounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait DocumentsTrait
{
use Get, Post;
/**
* Create a Documents object
*
* @param array $parameters
*
* @return Result
*/
public function createDocument(array $parameters): Result
{
return $this->post('documents', $parameters);
}
/**
* Create a Documents download url
*
* @param string $identifier
* @param CarbonInterface|null $expiresAt
*
* @return Result
*/
public function createDocumentDownloadUrl(string $identifier, ?CarbonInterface $expiresAt = null): Result
{
return $this->post("documents/$identifier/download-url", [
'expires_at' => $expiresAt
? $expiresAt->toDateTimeString()
: now()->addHour()->toDateTimeString(),
]);
}
}

View File

@@ -0,0 +1,44 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Traits;
use GhostZero\BitinflowAccounts\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

@@ -11,32 +11,32 @@ use GhostZero\BitinflowAccounts\Result;
/**
* @author René Preuß <rene@preuss.io>
*/
trait CheckoutSessionsTrait
trait PaymentIntentsTrait
{
use Get, Post;
/**
* Get a Session object
* Get a Payment Intent object
*
* @param string $id
*
* @return Result Result object
*/
public function getCheckoutSession(string $id): Result
public function getPaymentIntent(string $id): Result
{
return $this->get("checkout/sessions/$id");
return $this->get("payment-intents/$id");
}
/**
* Create a Session object
* Create a Payment Intent object
*
* @param array $parameters
*
* @return Result
*/
public function createCheckoutSession(array $parameters): Result
public function createPaymentIntent(array $parameters): Result
{
return $this->post('payments/sessions', $parameters);
return $this->post('payment-intents', $parameters);
}
}

View File

@@ -27,7 +27,7 @@ trait SshKeysTrait
}
/**
* Get currently authed user with Bearer Token
* Creates ssh key for the currently authed user
*
* @param string $publicKey
* @param string|null $name
@@ -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
*

View File

@@ -18,6 +18,32 @@ trait UsersTrait
*/
public function getAuthedUser(): Result
{
return $this->get('users/me', [], null);
return $this->get('users/me');
}
}
/**
* Creates a new user on behalf of the current user.
*
* @param array $parameters
*
* @return Result
*/
public function createUser(array $parameters): Result
{
return $this->post('v2/users', $parameters);
}
/**
* Checks if the given email exists.
*
* @param string $email
*
* @return Result
*/
public function isEmailExisting(string $email): Result
{
return $this->post('v2/users/check-email', [
'email' => $email,
]);
}
}

View File

@@ -0,0 +1,62 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiChargesTest extends ApiTestCase
{
public function testCaptureWithoutCapture(): void
{
$this->getClient()->withToken($this->getToken());
$result = $this->getClient()->createCharge([
'amount' => 2000,
'currency' => 'usd',
'source' => 'tok_visa',
'description' => 'Charge for jenny.rosen@example.com',
]);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertEquals(2000, $result->data()->amount);
$this->assertTrue($result->data()->captured);
}
public function testChargeWithCapture(): void
{
$this->getClient()->withToken($this->getToken());
$result = $this->getClient()->createCharge([
'amount' => 2000,
'currency' => 'usd',
'source' => 'tok_visa',
'description' => 'Charge for jenny.rosen@example.com',
'capture' => false, // default is true for instant capture
'metadata' => [
'foo' => 'bar',
],
'receipt_email' => 'rene+unittest@bitinflow.com',
]);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertEquals(2000, $result->data()->amount);
$this->assertFalse($result->data()->captured);
$charge = $result->data();
$result = $this->getClient()->captureCharge($charge->id);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertEquals(2000, $result->data()->amount);
$this->assertTrue($result->data()->captured);
}
}

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\Enums\DocumentType;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiDocumentsTest extends ApiTestCase
{
public function testCreateDocument(): void
{
$this->getClient()->withToken($this->getToken());
$result = $this->getClient()->createDocument([
'branding' => [
'primary_color' => '#8284df',
'watermark_url' => 'https://fbs.streamkit.gg/img/pdf/wm.png',
'logo_url' => 'https://fbs.streamkit.gg/img/pdf/logo_dark_small.png',
],
'locale' => 'de',
'type' => DocumentType::TYPE_PDF_INVOICE,
'data' => $this->createDummyInvoiceData(),
'receipt_email' => 'rene+unittest@bitinflow.com',
]);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertArrayHasKey('download_url', $result->data());
$this->assertEquals(
'rene+unittest@bitinflow.com',
$result->data()->receipt_email
);
}
public function testGenerateDocumentStoragePath(): void
{
$this->getClient()->withToken($this->getToken());
$expiresAt = now()->addHours(2);
$result = $this->getClient()->createDocumentDownloadUrl('1', $expiresAt);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('download_url', $result->data());
$this->assertEquals(
$expiresAt->toDateTimeString(),
$result->data()->expires_at
);
}
private function createDummyInvoiceData(): array
{
return [
'id' => 'FBS-IN-1337',
'customer' => [
'name' => 'GhostZero',
'email' => 'rene@preuss.io',
'address' => [
'Example Street 123',
'50733 Cologne',
'GERMANY',
],
],
'line_items' => [
[
'name' => 'T-shirt',
'description' => 'Comfortable cotton t-shirt',
'unit' => 'T-shirt', // optional unit name
'amount' => 1500,
'currency' => 'usd',
'quantity' => 2,
],
],
'legal_notice' => 'According to the German §19 UStG no sales tax is calculated. However, the product is a digital good delivered via Internet we generally offer no refunds. The delivery date corresponds to the invoice date.',
'already_paid' => true,
'created_at' => now()->format('d.m.Y'),
];
}
}

View File

@@ -0,0 +1,23 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\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

@@ -0,0 +1,46 @@
<?php
declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
/**
* @author René Preuß <rene@preuss.io>
*/
class ApiPaymentIntentsTest extends ApiTestCase
{
private $paymentIntent;
public function testCreatePaymentIntent(): void
{
$this->getClient()->withToken($this->getToken());
$result = $this->getClient()->createPaymentIntent([
'payment_method_types' => ['card'],
'amount' => 1000,
'currency' => 'usd',
'application_fee_amount' => 123,
]);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertArrayHasKey('redirect_url', $result->data());
$this->assertEquals(1000, $result->data()->amount);
// use this payment intent for our next tests
$this->paymentIntent = $result->data();
}
public function testGetPaymentIntent(): void
{
$this->getClient()->withToken($this->getToken());
$result = $this->getClient()->getPaymentIntent($this->paymentIntent->id);
$this->registerResult($result);
$this->assertTrue($result->success());
$this->assertArrayHasKey('id', $result->data());
$this->assertEquals(1000, $result->data()->amount);
}
}

View File

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

View File

@@ -4,8 +4,9 @@ declare(strict_types=1);
namespace GhostZero\BitinflowAccounts\Tests;
use GhostZero\BitinflowAccounts\Result;
use GhostZero\BitinflowAccounts\Enums\Scope;
use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
use Illuminate\Support\Str;
/**
* @author René Preuß <rene@preuss.io>
@@ -13,11 +14,51 @@ use GhostZero\BitinflowAccounts\Tests\TestCases\ApiTestCase;
class ApiUsersTest extends ApiTestCase
{
public function testGetAuthedUser()
public function testGetAuthedUser(): void
{
$this->getClient()->withToken($this->getToken());
$this->registerResult($result = $this->getClient()->getAuthedUser());
$this->assertInstanceOf(Result::class, $result);
$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

@@ -14,12 +14,12 @@ use GhostZero\BitinflowAccounts\Tests\TestCases\TestCase;
class ServiceInstantiationTest extends TestCase
{
public function testInstance()
public function testInstance(): void
{
$this->assertInstanceOf(BitinflowAccounts::class, app(BitinflowAccounts::class));
}
public function testFacade()
public function testFacade(): void
{
$this->assertInstanceOf(BitinflowAccounts::class, BitinflowAccountsFacade::getFacadeRoot());
}

View File

@@ -18,6 +18,11 @@ abstract class ApiTestCase extends TestCase
protected function setUp(): void
{
parent::setUp();
if ($this->getBaseUrl()) {
BitinflowAccounts::setBaseUrl($this->getBaseUrl());
}
if (!$this->getClientId()) {
$this->markTestSkipped('No Client-ID given');
}
@@ -34,6 +39,11 @@ abstract class ApiTestCase extends TestCase
return $result;
}
protected function getBaseUrl()
{
return getenv('BASE_URL');
}
protected function getClientId()
{
return getenv('CLIENT_ID');