diff --git a/src/BlindPay.php b/src/BlindPay.php index 839a6fb..faad146 100644 --- a/src/BlindPay.php +++ b/src/BlindPay.php @@ -19,6 +19,7 @@ use BlindPay\SDK\Resources\Quotes\Quotes; use BlindPay\SDK\Resources\Receivers\Receivers; use BlindPay\SDK\Resources\Receivers\ReceiversWrapper; +use BlindPay\SDK\Resources\TermsOfService\TermsOfService; use BlindPay\SDK\Resources\VirtualAccounts\VirtualAccounts; use BlindPay\SDK\Resources\Wallets\BlockchainWallets; use BlindPay\SDK\Resources\Wallets\OfframpWallets; @@ -100,11 +101,13 @@ private function initializeInstances(): void $instancesResource = new Instances($this->instanceId, $this); $apiKeysResource = new ApiKeys($this->instanceId, $this); $webhooksResource = new Webhooks($this->instanceId, $this); + $termsOfServiceResource = new TermsOfService($this->instanceId, $this); $this->instances = new InstancesWrapper( $instancesResource, $apiKeysResource, - $webhooksResource + $webhooksResource, + $termsOfServiceResource ); } diff --git a/src/Resources/Instances/InstancesWrapper.php b/src/Resources/Instances/InstancesWrapper.php index b021a2b..a0daa5a 100644 --- a/src/Resources/Instances/InstancesWrapper.php +++ b/src/Resources/Instances/InstancesWrapper.php @@ -5,6 +5,7 @@ namespace BlindPay\SDK\Resources\Instances; use BlindPay\SDK\Resources\ApiKeys\ApiKeys; +use BlindPay\SDK\Resources\TermsOfService\TermsOfService; use BlindPay\SDK\Resources\Webhooks\Webhooks; use BlindPay\SDK\Types\BlindPayApiResponse; @@ -13,7 +14,8 @@ public function __construct( private Instances $base, public ApiKeys $apiKeys, - public Webhooks $webhookEndpoints + public Webhooks $webhookEndpoints, + public TermsOfService $tos ) {} /* diff --git a/src/Resources/TermsOfService/TermsOfService.php b/src/Resources/TermsOfService/TermsOfService.php new file mode 100644 index 0000000..62b8c16 --- /dev/null +++ b/src/Resources/TermsOfService/TermsOfService.php @@ -0,0 +1,84 @@ + $this->idempotencyKey, + ]; + + if ($this->receiverId !== null) { + $data['receiver_id'] = $this->receiverId; + } + + if ($this->redirectUrl !== null) { + $data['redirect_url'] = $this->redirectUrl; + } + + return $data; + } +} + +readonly class InitiateResponse +{ + public function __construct( + public string $url + ) {} + + public static function fromArray(array $data): self + { + return new self( + url: $data['url'] + ); + } +} + +class TermsOfService +{ + public function __construct( + private readonly string $instanceId, + private readonly ApiClientInterface $client + ) {} + + /* + * Initiate Terms of Service acceptance flow + * + * @param InitiateInput $input + * @return BlindPayApiResponse + */ + public function initiate(InitiateInput $input): BlindPayApiResponse + { + if (empty($input->idempotencyKey)) { + return BlindPayApiResponse::error( + new \BlindPay\SDK\Types\ErrorResponse('Idempotency key cannot be empty') + ); + } + + $response = $this->client->post( + "e/instances/{$this->instanceId}/tos", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + InitiateResponse::fromArray($response->data) + ); + } + + return $response; + } +} diff --git a/src/Resources/Wallets/BlockchainWallets.php b/src/Resources/Wallets/BlockchainWallets.php index 1389ad7..54edcb7 100644 --- a/src/Resources/Wallets/BlockchainWallets.php +++ b/src/Resources/Wallets/BlockchainWallets.php @@ -136,6 +136,74 @@ public function toArray(): array } } +readonly class MintUsdbSolanaInput +{ + public function __construct( + public string $address, + public string $amount + ) {} + + public function toArray(): array + { + return [ + 'address' => $this->address, + 'amount' => $this->amount, + ]; + } +} + +readonly class MintUsdbSolanaResponse +{ + public function __construct( + public bool $success, + public string $signature, + public string $error + ) {} + + public static function fromArray(array $data): self + { + return new self( + success: $data['success'], + signature: $data['signature'], + error: $data['error'] + ); + } +} + +readonly class PrepareSolanaDelegationTransactionInput +{ + public function __construct( + public string $tokenAddress, + public string $amount, + public string $ownerAddress + ) {} + + public function toArray(): array + { + return [ + 'token_address' => $this->tokenAddress, + 'amount' => $this->amount, + 'owner_address' => $this->ownerAddress, + ]; + } +} + +readonly class PrepareSolanaDelegationTransactionResponse +{ + public function __construct( + public bool $success, + public string $transaction + ) {} + + public static function fromArray(array $data): self + { + return new self( + success: $data['success'], + transaction: $data['transaction'] + ); + } +} + /** * Blockchain Wallets resource */ @@ -326,4 +394,46 @@ public function mintUsdbStellar(MintUsdbStellarInput $input): BlindPayApiRespons $input->toArray() ); } + + /** + * Mint USDB on Solana + * + * @return BlindPayApiResponse + */ + public function mintUsdbSolana(MintUsdbSolanaInput $input): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/mint-usdb-solana", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + MintUsdbSolanaResponse::fromArray($response->data) + ); + } + + return $response; + } + + /** + * Prepare Solana delegation transaction + * + * @return BlindPayApiResponse + */ + public function prepareSolanaDelegationTransaction(PrepareSolanaDelegationTransactionInput $input): BlindPayApiResponse + { + $response = $this->client->post( + "instances/{$this->instanceId}/prepare-delegate-solana", + $input->toArray() + ); + + if ($response->isSuccess() && is_array($response->data)) { + return BlindPayApiResponse::success( + PrepareSolanaDelegationTransactionResponse::fromArray($response->data) + ); + } + + return $response; + } } diff --git a/tests/Resources/TermsOfService/TermsOfServiceTest.php b/tests/Resources/TermsOfService/TermsOfServiceTest.php new file mode 100644 index 0000000..ff57337 --- /dev/null +++ b/tests/Resources/TermsOfService/TermsOfServiceTest.php @@ -0,0 +1,77 @@ +mockHandler = new MockHandler; + $handlerStack = HandlerStack::create($this->mockHandler); + $httpClient = new Client(['handler' => $handlerStack]); + + $this->blindpay = new BlindPay( + apiKey: 'test-key', + instanceId: 'in_000000000000' + ); + + $this->injectHttpClient($httpClient); + } + + private function injectHttpClient(Client $client): void + { + $reflection = new ReflectionClass($this->blindpay); + $property = $reflection->getProperty('httpClient'); + $property->setAccessible(true); + $property->setValue($this->blindpay, $client); + } + + private function mockResponse(array $body, int $status = 200): void + { + $this->mockHandler->append( + new Response( + $status, + ['Content-Type' => 'application/json'], + json_encode($body) + ) + ); + } + + #[Test] + public function it_initiates_terms_of_service_with_all_parameters(): void + { + $mockedResponse = [ + 'url' => 'https://app.blindpay.com/e/terms-of-service?session_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c', + ]; + + $this->mockResponse($mockedResponse); + + $input = new InitiateInput( + idempotencyKey: '123e4567-e89b-12d3-a456-426614174000', + receiverId: null, + redirectUrl: null + ); + + $response = $this->blindpay->instances->tos->initiate($input); + + $this->assertTrue($response->isSuccess()); + $this->assertNull($response->error); + $this->assertEquals($mockedResponse['url'], $response->data->url); + } +} diff --git a/tests/Resources/Wallets/BlockchainWalletsTest.php b/tests/Resources/Wallets/BlockchainWalletsTest.php index d5bc565..975ebe2 100644 --- a/tests/Resources/Wallets/BlockchainWalletsTest.php +++ b/tests/Resources/Wallets/BlockchainWalletsTest.php @@ -9,6 +9,8 @@ use BlindPay\SDK\Resources\Wallets\CreateBlockchainWalletWithHashInput; use BlindPay\SDK\Resources\Wallets\DeleteBlockchainWalletInput; use BlindPay\SDK\Resources\Wallets\GetBlockchainWalletInput; +use BlindPay\SDK\Resources\Wallets\MintUsdbSolanaInput; +use BlindPay\SDK\Resources\Wallets\PrepareSolanaDelegationTransactionInput; use BlindPay\SDK\Types\Network; use GuzzleHttp\Client; use GuzzleHttp\Handler\MockHandler; @@ -218,4 +220,53 @@ public function it_deletes_a_blockchain_wallet(): void $this->assertArrayHasKey('data', $response->data); $this->assertNull($response->data['data']); } + + #[Test] + public function it_mints_usdb_on_solana(): void + { + $mockedResponse = [ + 'success' => true, + 'signature' => '4wceVEQeJG4vpS4k2o1dHU5cFWeWTQU8iaCEpRaV5KkqSxPfbdAc8hzXa7nNYG6rvqgAmDkzBycbcXkKKAeK8Jtu', + 'error' => '', + ]; + + $this->mockResponse($mockedResponse); + + $input = new MintUsdbSolanaInput( + address: '7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5', + amount: '1000000' + ); + + $response = $this->blindpay->wallets->blockchain->mintUsdbSolana($input); + + $this->assertTrue($response->isSuccess()); + $this->assertNull($response->error); + $this->assertTrue($response->data->success); + $this->assertEquals('4wceVEQeJG4vpS4k2o1dHU5cFWeWTQU8iaCEpRaV5KkqSxPfbdAc8hzXa7nNYG6rvqgAmDkzBycbcXkKKAeK8Jtu', $response->data->signature); + $this->assertEquals('', $response->data->error); + } + + #[Test] + public function it_prepares_a_solana_delegation_transaction(): void + { + $mockedResponse = [ + 'success' => true, + 'transaction' => 'AAGBf4K95Gp5i6f0BAEYAgABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIw==', + ]; + + $this->mockResponse($mockedResponse); + + $input = new PrepareSolanaDelegationTransactionInput( + tokenAddress: 'TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA', + amount: '1000000', + ownerAddress: '7YttLkHDoNj9wyDur5pM1ejNaAvT9X4eqaYcHQqtj2G5' + ); + + $response = $this->blindpay->wallets->blockchain->prepareSolanaDelegationTransaction($input); + + $this->assertTrue($response->isSuccess()); + $this->assertNull($response->error); + $this->assertTrue($response->data->success); + $this->assertEquals('AAGBf4K95Gp5i6f0BAEYAgABAgMEBQYHCAkKCwwNDg8QERITFBUWFxgZGhscHR4fICEiIw==', $response->data->transaction); + } }