Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/BlindPay.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
);
}

Expand Down
4 changes: 3 additions & 1 deletion src/Resources/Instances/InstancesWrapper.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -13,7 +14,8 @@
public function __construct(
private Instances $base,
public ApiKeys $apiKeys,
public Webhooks $webhookEndpoints
public Webhooks $webhookEndpoints,
public TermsOfService $tos
) {}

/*
Expand Down
84 changes: 84 additions & 0 deletions src/Resources/TermsOfService/TermsOfService.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
<?php

declare(strict_types=1);

namespace BlindPay\SDK\Resources\TermsOfService;

use BlindPay\SDK\Internal\ApiClientInterface;
use BlindPay\SDK\Types\BlindPayApiResponse;

readonly class InitiateInput
{
public function __construct(
public string $idempotencyKey,
public ?string $receiverId = null,
public ?string $redirectUrl = null
) {}

public function toArray(): array
{
$data = [
'idempotency_key' => $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<InitiateResponse>
*/
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;
}
}
110 changes: 110 additions & 0 deletions src/Resources/Wallets/BlockchainWallets.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -326,4 +394,46 @@ public function mintUsdbStellar(MintUsdbStellarInput $input): BlindPayApiRespons
$input->toArray()
);
}

/**
* Mint USDB on Solana
*
* @return BlindPayApiResponse<MintUsdbSolanaResponse>
*/
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<PrepareSolanaDelegationTransactionResponse>
*/
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;
}
}
77 changes: 77 additions & 0 deletions tests/Resources/TermsOfService/TermsOfServiceTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
<?php

declare(strict_types=1);

namespace BlindPay\SDK\Tests\Resources;

use BlindPay\SDK\BlindPay;
use BlindPay\SDK\Resources\TermsOfService\InitiateInput;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use PHPUnit\Framework\Attributes\Test;
use PHPUnit\Framework\TestCase;
use ReflectionClass;

class TermsOfServiceTest extends TestCase
{
private BlindPay $blindpay;

private MockHandler $mockHandler;

protected function setUp(): void
{
$this->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);
}
}
51 changes: 51 additions & 0 deletions tests/Resources/Wallets/BlockchainWalletsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}