From 032c771e49f5c7786202ea240eb7874b15afdd6d Mon Sep 17 00:00:00 2001 From: envoyr Date: Sun, 8 May 2022 22:53:02 +0200 Subject: [PATCH] add bitinflow payments subscriptions --- AUTH.md | 61 ++++++ README.md | 18 +- config/bitinflow-accounts-api.php | 8 - config/bitinflow-accounts.php | 12 ++ .../BitinflowAccounts/BitinflowAccounts.php | 5 +- .../BitinflowAccounts/Enums/DocumentType.php | 17 -- .../BitinflowAccountsServiceProvider.php | 6 +- .../BitinflowAccounts/Traits/ChargesTrait.php | 69 ------ .../Traits/DocumentsTrait.php | 48 ----- .../Traits/HasBitinflowPaymentsWallet.php | 196 ++++++++++++++++++ .../Traits/PaymentIntentsTrait.php | 42 ---- .../BitinflowAccounts/ApiChargesTest.php | 62 ------ .../BitinflowAccounts/ApiDocumentsTest.php | 87 -------- .../ApiPaymentIntentsTest.php | 46 ---- 14 files changed, 274 insertions(+), 403 deletions(-) create mode 100644 AUTH.md delete mode 100644 config/bitinflow-accounts-api.php create mode 100644 config/bitinflow-accounts.php delete mode 100644 src/GhostZero/BitinflowAccounts/Enums/DocumentType.php delete mode 100644 src/GhostZero/BitinflowAccounts/Traits/ChargesTrait.php delete mode 100644 src/GhostZero/BitinflowAccounts/Traits/DocumentsTrait.php create mode 100644 src/GhostZero/BitinflowAccounts/Traits/HasBitinflowPaymentsWallet.php delete mode 100644 src/GhostZero/BitinflowAccounts/Traits/PaymentIntentsTrait.php delete mode 100644 tests/GhostZero/BitinflowAccounts/ApiChargesTest.php delete mode 100644 tests/GhostZero/BitinflowAccounts/ApiDocumentsTest.php delete mode 100644 tests/GhostZero/BitinflowAccounts/ApiPaymentIntentsTest.php diff --git a/AUTH.md b/AUTH.md new file mode 100644 index 0000000..8678997 --- /dev/null +++ b/AUTH.md @@ -0,0 +1,61 @@ +# Implementing Auth + +This method should typically be called in the `boot` method of your `AuthServiceProvider` class: + +```php +use GhostZero\BitinflowAccounts\BitinflowAccounts; +use GhostZero\BitinflowAccounts\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['assess_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'], + 'assess_token_field' => 'sso_access_token', + ], +], +``` \ No newline at end of file diff --git a/README.md b/README.md index 6ba47ff..8d475a2 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ protected $listen = [ Copy configuration to config folder: ``` -$ php artisan vendor:publish --provider="GhostZero\BitinflowAccounts\Providers\BitinflowAccountsServiceProvider" +$ bitinflow-accounts ``` Add environmental variables to your `.env` @@ -149,22 +149,6 @@ BitinflowAccounts::withClientId('abc123')->withToken('abcdef123456')->getAuthedU ## Documentation -### Charges - -```php -public function createCharge(array $parameters) -public function getCharge(string $id) -public function updateCharge(string $id, array $parameters) -public function captureCharge(string $id, array $parameters = array ()) -``` - -### Documents - -```php -public function createDocument(array $parameters) -public function createDocumentDownloadUrl(string $identifier, CarbonInterface $expiresAt = NULL) -``` - ### Oauth ```php diff --git a/config/bitinflow-accounts-api.php b/config/bitinflow-accounts-api.php deleted file mode 100644 index cb82da1..0000000 --- a/config/bitinflow-accounts-api.php +++ /dev/null @@ -1,8 +0,0 @@ - env('BITINFLOW_ACCOUNTS_KEY'), - 'client_secret' => env('BITINFLOW_ACCOUNTS_SECRET'), - 'redirect_url' => env('BITINFLOW_ACCOUNTS_REDIRECT_URI'), - 'base_url' => env('BITINFLOW_ACCOUNTS_BASE_URI'), -]; diff --git a/config/bitinflow-accounts.php b/config/bitinflow-accounts.php new file mode 100644 index 0000000..6fa552b --- /dev/null +++ b/config/bitinflow-accounts.php @@ -0,0 +1,12 @@ + 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' => [ + '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/'), + ] +]; diff --git a/src/GhostZero/BitinflowAccounts/BitinflowAccounts.php b/src/GhostZero/BitinflowAccounts/BitinflowAccounts.php index 180a66f..27a062a 100644 --- a/src/GhostZero/BitinflowAccounts/BitinflowAccounts.php +++ b/src/GhostZero/BitinflowAccounts/BitinflowAccounts.php @@ -18,13 +18,12 @@ use GuzzleHttp\Exception\RequestException; class BitinflowAccounts { - use Traits\ChargesTrait; - use Traits\DocumentsTrait; use Traits\OauthTrait; - use Traits\PaymentIntentsTrait; use Traits\SshKeysTrait; use Traits\UsersTrait; + use Traits\HasBitinflowPaymentsWallet; + use ApiOperations\Delete; use ApiOperations\Get; use ApiOperations\Post; diff --git a/src/GhostZero/BitinflowAccounts/Enums/DocumentType.php b/src/GhostZero/BitinflowAccounts/Enums/DocumentType.php deleted file mode 100644 index fe53985..0000000 --- a/src/GhostZero/BitinflowAccounts/Enums/DocumentType.php +++ /dev/null @@ -1,17 +0,0 @@ - - */ -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'; -} \ No newline at end of file diff --git a/src/GhostZero/BitinflowAccounts/Providers/BitinflowAccountsServiceProvider.php b/src/GhostZero/BitinflowAccounts/Providers/BitinflowAccountsServiceProvider.php index ffdb407..0560530 100644 --- a/src/GhostZero/BitinflowAccounts/Providers/BitinflowAccountsServiceProvider.php +++ b/src/GhostZero/BitinflowAccounts/Providers/BitinflowAccountsServiceProvider.php @@ -22,7 +22,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__, 4) . '/config/bitinflow-accounts.php' => config_path('bitinflow-accounts.php'), ], 'config'); } @@ -32,9 +32,7 @@ class BitinflowAccountsServiceProvider extends ServiceProvider */ public function register() { - $this->mergeConfigFrom( - dirname(__DIR__) . '/../../../config/bitinflow-accounts-api.php', 'bitinflow-accounts-api' - ); + $this->mergeConfigFrom(dirname(__DIR__, 4) . '/config/bitinflow-accounts.php', 'bitinflow-accounts'); $this->app->singleton(BitinflowAccounts::class, function () { return new BitinflowAccounts; }); diff --git a/src/GhostZero/BitinflowAccounts/Traits/ChargesTrait.php b/src/GhostZero/BitinflowAccounts/Traits/ChargesTrait.php deleted file mode 100644 index eb5a31e..0000000 --- a/src/GhostZero/BitinflowAccounts/Traits/ChargesTrait.php +++ /dev/null @@ -1,69 +0,0 @@ - - */ -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); - } -} \ No newline at end of file diff --git a/src/GhostZero/BitinflowAccounts/Traits/DocumentsTrait.php b/src/GhostZero/BitinflowAccounts/Traits/DocumentsTrait.php deleted file mode 100644 index 995e06b..0000000 --- a/src/GhostZero/BitinflowAccounts/Traits/DocumentsTrait.php +++ /dev/null @@ -1,48 +0,0 @@ - - */ -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(), - ]); - } -} \ No newline at end of file diff --git a/src/GhostZero/BitinflowAccounts/Traits/HasBitinflowPaymentsWallet.php b/src/GhostZero/BitinflowAccounts/Traits/HasBitinflowPaymentsWallet.php new file mode 100644 index 0000000..46ddd11 --- /dev/null +++ b/src/GhostZero/BitinflowAccounts/Traits/HasBitinflowPaymentsWallet.php @@ -0,0 +1,196 @@ + config('bitinflow-accounts.payments.base_url'), + ]); + + $response = $client->request($method, $url, [ + RequestOptions::JSON => $attributes, + RequestOptions::HEADERS => [ + 'Accept' => 'application/json', + 'Content-Type' => 'application/json', + 'Authorization' => sprintf('Bearer %s', $this->access_token), + ], + ]); + + return json_decode($response->getBody()); + } + + /** + * Get user from payments gateway. + * + * @return object|null + * @throws GuzzleException + */ + public function getPaymentsUser(): ?object + { + if (is_null($this->paymentsUser)) { + $this->paymentsUser = $this->paymentsGatewayRequest('GET', 'user'); + } + + return $this->paymentsUser; + } + + /** + * Check if user has an active wallet. + * + * @return bool + * @throws GuzzleException + */ + public function hasWallet(): bool + { + return $this->getPaymentsUser()->data->has_wallet; + } + + public function getWalletSetupIntent(string $success_path = ''): string + { + return sprintf('%swallet?continue_url=%s', config('bitinflow-accounts.payments.dashboard_url'), url($success_path)); + } + + /** + * Get balance from user. + * + * @return float + * @throws GuzzleException + */ + public function getBalance(): float + { + return $this->getPaymentsUser()->data->balance; + } + + /** + * Get vat from user. + * + * @return int|null + * @throws GuzzleException + */ + public function getVat(): ?int + { + return $this->getPaymentsUser()->data->taxation->vat; + } + + /** + * Get vat from user. + * + * @return array|null + * @throws GuzzleException + */ + public function getSubscriptions(): ?array + { + $subscriptions = $this->getPaymentsUser()->data->subscriptions; + + foreach ($subscriptions as $key => $subscription) { + if (!isset($subscription->payload->client_id) || $subscription->payload->client_id !== config('bitinflow-accounts.client_id')) { + unset($subscriptions[$key]); + } + } + + return $subscriptions; + } + + public function getSubscription($name = 'default'): ?object + { + foreach ($this->getSubscriptions() as $subscription) { + if (isset($subscription->payload->name) && $subscription->payload->name === $name) { + return $subscription; + } + } + + return null; + } + + public function hasSubscribed($name = 'default'): bool + { + $subscription = $this->getSubscription($name); + + return $subscription && $subscription->status === 'settled' || $subscription && $subscription->resumeable; + } + + /** + * Create a new subscription. + * + * @param array $attributes array which requires following attributes: + * name, description, period, price + * and following attributes are optional: + * vat, payload, ends_at, webhook_url, webhook_secret + * @param array $payload optional data that is stored in the subscription + * @param bool $checkout optional checkout it directly + * @return object the subscription object + * @throws GuzzleException + */ + public function createSubscription(string $name, array $attributes, array $payload = [], bool $checkout = false): object + { + $client = [ + 'name' => $name, + 'client_id' => config('bitinflow-accounts.client_id') + ]; + $defaults = ['period' => 'monthly']; + $attributes = array_merge(array_merge($defaults, $attributes), ['payload' => array_merge($payload, $client), 'checkout' => $checkout]); + + return $this->paymentsGatewayRequest('POST', 'subscriptions', $attributes)->data; + } + + /** + * Checkout given subscription. + * + * @param string $id + * @return void + * @throws GuzzleException + */ + public function checkoutSubscription(string $id): void + { + $this->paymentsGatewayRequest('PUT', sprintf('subscriptions/%s/checkout', $id)); + } + + /** + * Revoke a running subscription. + * + * @param $id + * @return void + * @throws GuzzleException + */ + public function revokeSubscription($id): void + { + $this->paymentsGatewayRequest('PUT', sprintf('subscriptions/%s/revoke', $id)); + } + + /** + * Resume a running subscription. + * + * @param $id + * @return void + * @throws GuzzleException + */ + public function resumeSubscription($id): void + { + $this->paymentsGatewayRequest('PUT', sprintf('subscriptions/%s/resume', $id)); + } +} \ No newline at end of file diff --git a/src/GhostZero/BitinflowAccounts/Traits/PaymentIntentsTrait.php b/src/GhostZero/BitinflowAccounts/Traits/PaymentIntentsTrait.php deleted file mode 100644 index 15e0410..0000000 --- a/src/GhostZero/BitinflowAccounts/Traits/PaymentIntentsTrait.php +++ /dev/null @@ -1,42 +0,0 @@ - - */ -trait PaymentIntentsTrait -{ - - use Get, Post; - - /** - * Get a Payment Intent object - * - * @param string $id - * - * @return Result Result object - */ - public function getPaymentIntent(string $id): Result - { - return $this->get("payment-intents/$id"); - } - - /** - * Create a Payment Intent object - * - * @param array $parameters - * - * @return Result - */ - public function createPaymentIntent(array $parameters): Result - { - return $this->post('payment-intents', $parameters); - } -} \ No newline at end of file diff --git a/tests/GhostZero/BitinflowAccounts/ApiChargesTest.php b/tests/GhostZero/BitinflowAccounts/ApiChargesTest.php deleted file mode 100644 index 6904319..0000000 --- a/tests/GhostZero/BitinflowAccounts/ApiChargesTest.php +++ /dev/null @@ -1,62 +0,0 @@ - - */ -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); - } -} \ No newline at end of file diff --git a/tests/GhostZero/BitinflowAccounts/ApiDocumentsTest.php b/tests/GhostZero/BitinflowAccounts/ApiDocumentsTest.php deleted file mode 100644 index e4fe037..0000000 --- a/tests/GhostZero/BitinflowAccounts/ApiDocumentsTest.php +++ /dev/null @@ -1,87 +0,0 @@ - - */ -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'), - ]; - } -} \ No newline at end of file diff --git a/tests/GhostZero/BitinflowAccounts/ApiPaymentIntentsTest.php b/tests/GhostZero/BitinflowAccounts/ApiPaymentIntentsTest.php deleted file mode 100644 index c611a16..0000000 --- a/tests/GhostZero/BitinflowAccounts/ApiPaymentIntentsTest.php +++ /dev/null @@ -1,46 +0,0 @@ - - */ -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); - } -} \ No newline at end of file