diff --git a/composer.json b/composer.json
index 7d0e926..0fc7bd7 100644
--- a/composer.json
+++ b/composer.json
@@ -28,12 +28,15 @@
},
"autoload": {
"psr-4": {
- "Bitinflow\\Accounts\\": "src/Accounts"
+ "Bitinflow\\Accounts\\": "src/Accounts",
+ "Bitinflow\\Payments\\": "src/Payments",
+ "Bitinflow\\Support\\": "src/Support"
}
},
"autoload-dev": {
"psr-4": {
- "Bitinflow\\Accounts\\Tests\\": "tests/Accounts"
+ "Bitinflow\\Accounts\\Tests\\": "tests/Accounts",
+ "Bitinflow\\Payments\\Tests\\": "tests/Payments"
}
},
"scripts": {
diff --git a/phpunit.xml b/phpunit.xml
index 723c89e..b1466ba 100644
--- a/phpunit.xml
+++ b/phpunit.xml
@@ -20,4 +20,11 @@
src
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Accounts/ApiOperations/Validation.php b/src/Accounts/ApiOperations/Validation.php
new file mode 100644
index 0000000..7105de5
--- /dev/null
+++ b/src/Accounts/ApiOperations/Validation.php
@@ -0,0 +1,19 @@
+token) {
throw new RequestRequiresAuthenticationException;
@@ -323,7 +311,7 @@ class BitinflowAccounts
* @param string $method HTTP method
* @param string $path Query path
* @param array $parameters Query parameters
- * @param Paginator $paginator Paginator object
+ * @param Paginator|null $paginator Paginator object
* @param mixed|null $jsonBody JSON data
*
* @return Result Result object
@@ -332,13 +320,14 @@ class BitinflowAccounts
*/
public function query(string $method = 'GET', string $path = '', array $parameters = [], Paginator $paginator = null, $jsonBody = null): Result
{
+ /** @noinspection DuplicatedCode */
if ($paginator !== null) {
$parameters[$paginator->action] = $paginator->cursor();
}
try {
$response = $this->client->request($method, $path, [
- 'headers' => $this->buildHeaders($jsonBody ? true : false),
- 'query' => $this->buildQuery($parameters),
+ 'headers' => $this->buildHeaders((bool)$jsonBody),
+ 'query' => Query::build($parameters),
'json' => $jsonBody ?: null,
]);
$result = new Result($response, null, $paginator);
@@ -401,26 +390,6 @@ class BitinflowAccounts
$this->clientId = $clientId;
}
- /**
- * 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);
- }
-
/**
* @param string $path
* @param array $parameters
diff --git a/src/Accounts/Exceptions/RequestRequiresMissingParametersException.php b/src/Accounts/Exceptions/RequestRequiresMissingParametersException.php
new file mode 100644
index 0000000..3c4d570
--- /dev/null
+++ b/src/Accounts/Exceptions/RequestRequiresMissingParametersException.php
@@ -0,0 +1,27 @@
+
+ */
+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)
+ ));
+ }
+}
\ No newline at end of file
diff --git a/src/Accounts/Facades/BitinflowAccounts.php b/src/Accounts/Facades/BitinflowAccounts.php
index 66915a3..0ee466e 100644
--- a/src/Accounts/Facades/BitinflowAccounts.php
+++ b/src/Accounts/Facades/BitinflowAccounts.php
@@ -5,14 +5,17 @@ declare(strict_types=1);
namespace Bitinflow\Accounts\Facades;
use Bitinflow\Accounts\BitinflowAccounts as BitinflowAccountsService;
+use Illuminate\Contracts\Auth\Authenticatable;
use Illuminate\Support\Facades\Facade;
/**
- * @author René Preuß
+ * @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;
diff --git a/src/Accounts/Providers/BitinflowAccountsServiceProvider.php b/src/Accounts/Providers/BitinflowAccountsServiceProvider.php
index aae0cda..785a014 100644
--- a/src/Accounts/Providers/BitinflowAccountsServiceProvider.php
+++ b/src/Accounts/Providers/BitinflowAccountsServiceProvider.php
@@ -10,13 +10,13 @@ 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.
*
@@ -41,6 +41,9 @@ class BitinflowAccountsServiceProvider extends ServiceProvider
$this->app->singleton(BitinflowAccounts::class, function () {
return new BitinflowAccounts;
});
+ $this->app->singleton(BitinflowPayments::class, function () {
+ return new BitinflowPayments;
+ });
$this->registerGuard();
}
@@ -85,6 +88,9 @@ class BitinflowAccountsServiceProvider extends ServiceProvider
*/
public function provides()
{
- return [BitinflowAccounts::class];
+ return [
+ BitinflowAccounts::class,
+ BitinflowPayments::class,
+ ];
}
}
diff --git a/src/Accounts/Result.php b/src/Accounts/Result.php
index 3095702..4335028 100644
--- a/src/Accounts/Result.php
+++ b/src/Accounts/Result.php
@@ -262,4 +262,9 @@ class Result
{
return $this->response;
}
+
+ public function dump()
+ {
+ dump($this->data());
+ }
}
\ No newline at end of file
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/Balance.php b/src/Accounts/Traits/BitinflowPaymentsWallet/Balance.php
deleted file mode 100644
index ab2c40f..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/Balance.php
+++ /dev/null
@@ -1,57 +0,0 @@
-user->getPaymentsUser()->data->balance;
- }
-
- /**
- * Deposit given amount from bank to account.
- *
- * @param float $amount
- * @param string $description
- * @return bool
- */
- public function deposit(float $amount, string $decription): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('wallet/deposit', [
- 'amount' => $amount,
- 'decription' => $decription,
- ]));
- }
-
- /**
- * Charge given amount from account.
- *
- * @param float $amount
- * @param string $decription
- * @return bool
- */
- public function charge(float $amount, string $decription): bool
- {
- $order = $this->user->orders()->create([
- 'name' => $decription,
- 'description' => 'one-time payment',
- 'amount' => 1,
- 'price' => $amount,
- ]);
-
- return $this->user->orders()->checkout($order->id);
- }
-}
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/CheckoutSessions.php b/src/Accounts/Traits/BitinflowPaymentsWallet/CheckoutSessions.php
deleted file mode 100644
index 15245be..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/CheckoutSessions.php
+++ /dev/null
@@ -1,33 +0,0 @@
-user->asPaymentsUser('POST', 'checkout-sessions', $payload);
- }
-
- public function get(string $id)
- {
- return $this->user->asPaymentsUser('GET', sprintf('checkout-sessions/%s', $id));
- }
-
- public function checkout(string $id)
- {
- return $this->user->asPaymentsUser('PUT', sprintf('checkout-sessions/%s/checkout', $id));
- }
-
- public function revoke(string $id)
- {
- return $this->user->asPaymentsUser('PUT', sprintf('checkout-sessions/%s/revoke', $id));
- }
-}
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/Orders.php b/src/Accounts/Traits/BitinflowPaymentsWallet/Orders.php
deleted file mode 100644
index 32835c2..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/Orders.php
+++ /dev/null
@@ -1,69 +0,0 @@
-user->asPaymentsUser('GET', 'orders');
- }
-
- /**
- * @param string $id
- * @return object|null
- */
- public function get(string $id): ?object
- {
- return $this->user->asPaymentsUser('GET', sprintf('orders/%s', $id));
- }
-
- /**
- * Create a new order.
- *
- * @param array $attributes
- * @return object|false
- */
- public function create(array $attributes = []): object|false
- {
- return $this->user->asPaymentsUser('POST', 'orders', $attributes)->data;
- }
-
- /**
- * Checkout given subscription.
- *
- * @param string $id
- * @return bool
- */
- public function checkout(string $id): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('orders/%s/checkout', $id));
-
- return true;
- }
-
- /**
- * Revoke a running subscription.
- *
- * @param string $id
- * @return bool
- */
- public function revoke(string $id): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('orders/%s/revoke', $id));
-
- return true;
- }
-}
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/Subscriptions.php b/src/Accounts/Traits/BitinflowPaymentsWallet/Subscriptions.php
deleted file mode 100644
index 41469be..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/Subscriptions.php
+++ /dev/null
@@ -1,97 +0,0 @@
-user->asPaymentsUser('GET', 'subscriptions');
- }
-
- /**
- * @param string $id
- * @return object|null
- */
- public function get(string $id): ?object
- {
- return $this->user->asPaymentsUser('GET', sprintf('subscriptions/%s', $id));
- }
-
- /**
- * 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
- * @return object|false the subscription object
- * @throws GuzzleException
- */
- public function create(array $attributes): object|false
- {
- return $this->user->asPaymentsUser('POST', 'subscriptions', $attributes)->data;
- }
-
- /**
- * @param string $name
- * @return bool
- */
- public function has(string $name = 'default'): bool
- {
- $subscription = $this->get($name);
-
- return $subscription && $subscription->status === 'settled' || $subscription && $subscription->resumeable;
- }
-
- /**
- * Checkout given subscription.
- *
- * @param string $id
- * @return bool
- */
- public function checkout(string $id): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('subscriptions/%s/checkout', $id));
-
- return true;
- }
-
- /**
- * Revoke a running subscription.
- *
- * @param string $id
- * @return bool
- */
- public function revoke(string $id): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('subscriptions/%s/revoke', $id));
-
- return true;
- }
-
- /**
- * Resume a running subscription.
- *
- * @param string $id
- * @return bool
- */
- public function resume(string $id): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('subscriptions/%s/resume', $id));
-
- return true;
- }
-}
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/Taxation.php b/src/Accounts/Traits/BitinflowPaymentsWallet/Taxation.php
deleted file mode 100644
index 06d95df..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/Taxation.php
+++ /dev/null
@@ -1,23 +0,0 @@
-user->getPaymentsUser()->data->taxation->vat;
- }
-}
diff --git a/src/Accounts/Traits/BitinflowPaymentsWallet/Wallets.php b/src/Accounts/Traits/BitinflowPaymentsWallet/Wallets.php
deleted file mode 100644
index ead2e66..0000000
--- a/src/Accounts/Traits/BitinflowPaymentsWallet/Wallets.php
+++ /dev/null
@@ -1,54 +0,0 @@
-user->getPaymentsUser()->data->wallets;
- }
-
- /**
- * Check if user has an active wallet.
- *
- * @return bool
- * @throws GuzzleException
- */
- public function has(): ?bool
- {
- return $this->user->getPaymentsUser()->data->has_wallet;
- }
-
- /**
- * Set default wallet to given wallet token.
- *
- * @param string $token default payment method token
- * @return bool
- */
- public function setDefault(string $token): bool
- {
- $this->user->asPaymentsUser('PUT', sprintf('wallets/default', [
- 'token' => $token
- ]));
-
- return true;
- }
-
- public function setupIntent()
- {
- return sprintf('%swallet/?continue_url=%s', config('bitinflow-accounts.payments.dashboard_url'), urlencode(url()->to($success_path)));
- }
-}
diff --git a/src/Payments/Facades/BitinflowPayments.php b/src/Payments/Facades/BitinflowPayments.php
new file mode 100644
index 0000000..4dd78f6
--- /dev/null
+++ b/src/Payments/Facades/BitinflowPayments.php
@@ -0,0 +1,19 @@
+
+ */
+class BitinflowPayments extends Facade
+{
+ protected static function getFacadeAccessor()
+ {
+ return BitinflowPaymentsService::class;
+ }
+}
\ No newline at end of file
diff --git a/src/Payments/Result.php b/src/Payments/Result.php
new file mode 100644
index 0000000..19e2393
--- /dev/null
+++ b/src/Payments/Result.php
@@ -0,0 +1,10 @@
+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);
+ }
+}
diff --git a/src/Payments/Traits/CheckoutSessions.php b/src/Payments/Traits/CheckoutSessions.php
new file mode 100644
index 0000000..b8a07be
--- /dev/null
+++ b/src/Payments/Traits/CheckoutSessions.php
@@ -0,0 +1,46 @@
+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));
+ }
+}
diff --git a/src/Payments/Traits/Orders.php b/src/Payments/Traits/Orders.php
new file mode 100644
index 0000000..e37d64c
--- /dev/null
+++ b/src/Payments/Traits/Orders.php
@@ -0,0 +1,58 @@
+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', $parameters)->data;
+ }
+
+ /**
+ * 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));
+ }
+}
diff --git a/src/Payments/Traits/Subscriptions.php b/src/Payments/Traits/Subscriptions.php
new file mode 100644
index 0000000..ed4c473
--- /dev/null
+++ b/src/Payments/Traits/Subscriptions.php
@@ -0,0 +1,82 @@
+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): ?object
+ {
+ $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));
+ }
+}
diff --git a/src/Payments/Traits/Taxation.php b/src/Payments/Traits/Taxation.php
new file mode 100644
index 0000000..23ad5df
--- /dev/null
+++ b/src/Payments/Traits/Taxation.php
@@ -0,0 +1,16 @@
+query('GET', 'taxation');
+ }
+}
diff --git a/src/Payments/Traits/Wallets.php b/src/Payments/Traits/Wallets.php
new file mode 100644
index 0000000..a998881
--- /dev/null
+++ b/src/Payments/Traits/Wallets.php
@@ -0,0 +1,34 @@
+query('GET', 'wallets');
+ }
+
+ /**
+ * Set default wallet to given wallet token.
+ *
+ * @param string $token default payment method token
+ */
+ public function setDefaultWallet(string $token): Result
+ {
+ return $this->query('PUT', 'wallets/default', [], null, [
+ 'token' => $token
+ ]);
+ }
+
+ public function getWalletSetupIntent(string $successUrl): string
+ {
+ return sprintf('%swallet/?continue_url=%s', config('bitinflow-accounts.payments.dashboard_url'), urlencode($successUrl));
+ }
+}
diff --git a/src/Support/Query.php b/src/Support/Query.php
new file mode 100644
index 0000000..179594d
--- /dev/null
+++ b/src/Support/Query.php
@@ -0,0 +1,26 @@
+ $value) {
+ $value = (array)$value;
+ array_walk_recursive($value, function ($value) use (&$parts, $name) {
+ $parts[] = urlencode($name) . '=' . urlencode($value);
+ });
+ }
+
+ return implode('&', $parts);
+ }
+}
\ No newline at end of file
diff --git a/tests/Payments/ApiOauthTest.php b/tests/Payments/ApiOauthTest.php
new file mode 100644
index 0000000..f53cea0
--- /dev/null
+++ b/tests/Payments/ApiOauthTest.php
@@ -0,0 +1,26 @@
+
+ */
+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();
+ }
+}
\ No newline at end of file
diff --git a/tests/Payments/TestCases/ApiTestCase.php b/tests/Payments/TestCases/ApiTestCase.php
new file mode 100644
index 0000000..3a311b2
--- /dev/null
+++ b/tests/Payments/TestCases/ApiTestCase.php
@@ -0,0 +1,80 @@
+
+ */
+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);
+ }
+}
\ No newline at end of file
diff --git a/tests/Payments/TestCases/TestCase.php b/tests/Payments/TestCases/TestCase.php
new file mode 100644
index 0000000..036b2ec
--- /dev/null
+++ b/tests/Payments/TestCases/TestCase.php
@@ -0,0 +1,30 @@
+
+ */
+abstract class TestCase extends BaseTestCase
+{
+
+ protected function getPackageProviders($app)
+ {
+ return [
+ BitinflowAccountsServiceProvider::class,
+ ];
+ }
+
+ protected function getPackageAliases($app)
+ {
+ return [
+ 'BitinflowAccounts' => BitinflowAccounts::class,
+ ];
+ }
+}
\ No newline at end of file