From 7c534853980c2981a37c284c60b71955f2bef24d Mon Sep 17 00:00:00 2001 From: criskell Date: Tue, 20 Jan 2026 18:24:04 -0300 Subject: [PATCH 1/2] feat(subaccounts): add resource for managing subaccounts --- src/Client.php | 9 + src/Resources/Subaccounts.php | 251 ++++++++++++++++++++++++++++ tests/Resources/SubaccountsTest.php | 251 ++++++++++++++++++++++++++++ 3 files changed, 511 insertions(+) create mode 100644 src/Resources/Subaccounts.php create mode 100644 tests/Resources/SubaccountsTest.php diff --git a/src/Client.php b/src/Client.php index a4c54c3..665310f 100644 --- a/src/Client.php +++ b/src/Client.php @@ -12,6 +12,7 @@ use OpenPix\PhpSdk\Resources\Webhooks; use OpenPix\PhpSdk\Resources\Payments; use OpenPix\PhpSdk\Resources\Refunds; +use OpenPix\PhpSdk\Resources\Subaccounts; /** * The client provides a list of "resources", objects that allow it to send requests to @@ -131,4 +132,12 @@ public function accounts(): Accounts { return new Accounts($this->requestTransport); } + + /** + * Returns operations for the `Subaccounts` resource. + */ + public function subaccounts(): Subaccounts + { + return new Subaccounts($this->requestTransport); + } } diff --git a/src/Resources/Subaccounts.php b/src/Resources/Subaccounts.php new file mode 100644 index 0000000..7194018 --- /dev/null +++ b/src/Resources/Subaccounts.php @@ -0,0 +1,251 @@ +requestTransport = $requestTransport; + } + + /** + * Get a list of subaccounts. + * + * ## Usage + * ```php + * $result = $client->subaccounts()->list(); + * + * foreach ($result["subAccounts"] as $subAccount) { + * $subAccount["name"]; // string + * $subAccount["pixKey"]; // string + * $subAccount["balance"]; // int + * } + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/GET/api/v1/subaccount + * + * @return array Result from API. + */ + public function list(): array + { + $request = (new Request()) + ->method("GET") + ->path("/api/v1/subaccount"); + + return $this->requestTransport->transport($request); + } + + /** + * Get an subaccount via pix key. + * + * ```php + * $result = $client->subaccounts()->getOne("pixKey"); + * + * $result["subaccount"]["name"]; // string + * $result["subaccount"]["pixKey"]; // boolean + * $result["subaccount"]["balance"]; // int + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/GET/api/v1/subaccount/{id} + * + * @param string $id Pix key registered to the subaccount. + * + * @return array Result from API. + */ + public function getOne(string $id): array + { + $request = (new Request()) + ->method("GET") + ->path("/api/v1/subaccount/" . $id); + + return $this->requestTransport->transport($request); + } + + /** + * Withdraw from a sub account and return the withdrawal transaction information. + * + * ```php + * $result = $client->subaccounts()->withdraw("subaccountId", [ + * "value" => 1000, // R$ 10,00 + * ]); + * + * $result["transaction"]["status"]; // string. e.g.: CREATED + * $result["transaction"]["value"]; // int + * $result["transaction"]["endToEndId"]; // string + * $result["transaction"]["correlationID"]; // string + * $result["transaction"]["destinationAlias"]; // string + * $result["transaction"]["comment"]; // string + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/{id}/withdraw + * + * @param string $id Pix key registered to the subaccount. + * @param array $data Data to make a withdraw partial. + * + * @return array Result from API. + */ + public function withdraw(string $id, array $data): array + { + $request = (new Request()) + ->method("POST") + ->path("/api/v1/subaccount/" . $id . "/withdraw") + ->body($data); + + return $this->requestTransport->transport($request); + } + + /** + * Create a sub account. + * + * ```php + * $result = $client->subaccounts()->create([ + * "name" => "Name of the sub account", + * "pixKey" => "The pix key of the sub account", + * ]); + * + * // Number in cents that represent the balance of the sub account + * $result["SubAccount"]["balance"]; // int + * // Name of the sub account + * $result["SubAccount"]["name"]; // string + * // The pix key for the sub account + * $result["SubAccount"]["pixKey"]; // string + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount + * + * @param array $data Data to create a new subAccount or retrieve existing one. + * + * @return array The Subccount created or retrieved if exists using the given pix key. + */ + public function create(array $data): array + { + $request = (new Request()) + ->method("POST") + ->path("/api/v1/subaccount") + ->body($data); + + return $this->requestTransport->transport($request); + } + + /** + * Delete a Sub Account​ if it has no remaining balance. + * + * ```php + * $result = $client->subaccounts()->delete("pixKey"); + * + * $result["subaccount"]["pixKey"]; // string + * $result["subaccount"]["status"]; // string + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/DELETE/api/v1/subaccount/{id} + * + * @param string $id Pix key registered to the subaccount + * + * @return array Sub Account successfully deleted. + */ + public function delete(string $id): array + { + $request = (new Request()) + ->method("DELETE") + ->path("/api/v1/subaccount/" . $id); + + return $this->requestTransport->transport($request); + } + + /** + * Debit from a Sub Account and send to the main account​. + * + * Transfers the amount from the subaccount to the main account. + * + * ```php + * $result = $client->subaccounts()->debitToMainAccount("sourceSubAccountPixKey", [ + * "value" => 1000, // R$ 10,00 + * // Optional description for the debit operation + * "description" => "Optional description", + * ]); + * + * $result["pixKey"]; // string. + * $result["description"]; // string. + * $result["success"]; // string. + * $result["value"]; // number. + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/{id}/debit + * + * @param string $id Pix key registered to the subaccount. + * @param array $data Data to make a debit from sub account to main account. + * + * @return array Result from API. + */ + public function debitToMainAccount(string $id, array $data): array + { + $request = (new Request()) + ->method("POST") + ->path("/api/v1/subaccount/" . $id . "/debit") + ->body($data); + + return $this->requestTransport->transport($request); + } + + /** + * Transfer between subaccounts​. + * + * ```php + * $result = $client->subaccounts()->transferBetweenSubaccounts([ + * "fromPixKey" => "3143da48-2bc7-49a4-89bd-4e22f73bfb0c", // string + * // Types: CPF, CNPJ, EMAIL, PHONE and RANDOM. + * "fromPixKeyType" => "RANDOM", // string + * + * "toPixKey" => "c4249323-b4ca-43f2-8139-874baab09b93", // string + * "toPixKeyType" => "RANDOM", // string + * + * "value" => 1000, // int + * "correlationID" => "correlation-id", // string + * ]); + * + * $result["value"]; // int. + * $result["destinationSubaccount"]["name"]; // string. + * $result["destinationSubaccount"]["pixKey"]; // string. + * $result["destinationSubaccount"]["balance"]; // int. + * $result["originSubaccount"]["name"]; // string. + * $result["originSubaccount"]["pixKey"]; // string. + * $result["originSubaccount"]["balance"]; // int. + * ``` + * + * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/{id}/transfer + * + * @param array $data Data to make a new transfer between subaccounts + * + * @return array Result from API. + */ + public function transferBetweenSubaccounts(array $data): array + { + $request = (new Request()) + ->method("POST") + ->path("/api/v1/subaccount/transfer") + ->body($data); + + return $this->requestTransport->transport($request); + } +} diff --git a/tests/Resources/SubaccountsTest.php b/tests/Resources/SubaccountsTest.php new file mode 100644 index 0000000..dcbd37f --- /dev/null +++ b/tests/Resources/SubaccountsTest.php @@ -0,0 +1,251 @@ + [ + "name" => "test-sub-account", + "pixKey" => "c4249323-b4ca-43f2-8139-8232aab09b93", + "balance" => 100 + ], + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($subaccountsResponse) { + $this->assertSame("GET", $request->getMethod()); + $this->assertSame("/api/v1/subaccount", $request->getPath()); + $this->assertSame($request->getBody(), null); + $this->assertSame($request->getQueryParams(), []); + + return $subaccountsResponse; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->list(); + + $this->assertSame($result, $subaccountsResponse); + } + + public function testGetOne(): void + { + $subaccountId = "356a192b7913b04c54574d18c28d46e6395428ab"; + $subaccount = [ + "SubAccount" => [ + "name" => "test-sub-account", + "pixKey" => $subaccountId, + "balance" => 100, + ], + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($subaccountId, $subaccount) { + $this->assertSame("GET", $request->getMethod()); + $this->assertSame("/api/v1/subaccount/" . $subaccountId, $request->getPath()); + $this->assertSame($request->getBody(), null); + $this->assertSame($request->getQueryParams(), []); + + return $subaccount; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->getOne($subaccountId); + + $this->assertSame($result, $subaccount); + } + + public function testDelete(): void + { + $subaccountId = "356a192b7913b04c54574d18c28d46e6395428ab"; + $response = [ + "status" => "OK", + "pixKey" => "destination@test.com", + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($subaccountId, $response) { + $this->assertSame("DELETE", $request->getMethod()); + $this->assertSame("/api/v1/subaccount/" . $subaccountId, $request->getPath()); + $this->assertSame($request->getBody(), null); + $this->assertSame($request->getQueryParams(), []); + + return $response; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->delete($subaccountId); + + $this->assertSame($result, $response); + } + + public function testWithdraw(): void + { + $subaccountId = "356a192b7913b04c54574d18c28d46e6395428ab"; + $value = 1000; // R$ 10,00 + + $payload = [ + 'value' => $value, + ]; + + $withdraw = [ + "transaction" => [ + "status" => "CREATED", + "value" => 100, + "endToEndId" => "ENDTOENDID_1234567890", + "correlationID" => "TESTING1323", + "destinationAlias" => "pixKeyTest@test.com", + "comment" => "testing-transaction", + ], + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($subaccountId, $payload, $withdraw) { + $this->assertSame("POST", $request->getMethod()); + $this->assertSame("/api/v1/subaccount/" . $subaccountId . "/withdraw", $request->getPath()); + $this->assertSame($request->getBody(), $payload); + $this->assertSame($request->getQueryParams(), []); + + return $withdraw; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->withdraw($subaccountId, $payload); + + $this->assertSame($result, $withdraw); + } + + public function testDebitToMainAccount(): void + { + $subaccountId = "356a192b7913b04c54574d18c28d46e6395428ab"; + $value = 1000; // R$ 10,00 + + $payload = [ + "value" => $value, + "description" => "Optional description for the debit operation", + ]; + + $debitResponse = [ + "pixKey" => "subaccount@test.com", + "value" => 50, + "description" => "Monthly payment", + "success" => "Sub-account withdrawal has been successfully debited, 50" + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($subaccountId, $payload, $debitResponse) { + $this->assertSame("POST", $request->getMethod()); + $this->assertSame("/api/v1/subaccount/" . $subaccountId . "/debit", $request->getPath()); + $this->assertSame($request->getBody(), $payload); + $this->assertSame($request->getQueryParams(), []); + + return $debitResponse; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->debitToMainAccount($subaccountId, $payload); + + $this->assertSame($result, $debitResponse); + } + + public function testTransferBetweenSubaccounts(): void + { + $value = 1000; // R$ 10,00 + + $payload = [ + "value" => $value, + "fromPixKey" => "3143da48-2bc7-49a4-89bd-4e22f73bfb0c", + "fromPixKeyType" => "RANDOM", + "toPixKey" => "c4249323-b4ca-43f2-8139-874baab09b93", + "toPixKeyType" => "RANDOM", + "correlationID" => "unique-id", + ]; + + $transferResponse = [ + "value" => $value, + "destinationSubaccount" => [ + "name" => "test-sub-account-1", + "pixKey" => "c4249323-b4ca-43f2-8139-874baab09b93", + "balance" => 1000 + ], + "originSubaccount" => [ + "name" => "test-sub-account-2", + "pixKey" => "3143da48-2bc7-49a4-89bd-4e22f73bfb0c", + "balance" => 0, + ], + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($payload, $transferResponse) { + $this->assertSame("POST", $request->getMethod()); + $this->assertSame("/api/v1/subaccount/transfer", $request->getPath()); + $this->assertSame($request->getBody(), $payload); + $this->assertSame($request->getQueryParams(), []); + + return $transferResponse; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->transferBetweenSubaccounts($payload); + + $this->assertSame($result, $transferResponse); + } + + public function testCreate(): void + { + $payload = [ + "name" => "Name of subaccount", + "pixKey" => "356a192b7913b04c54574d18c28d46e6395428ab", + ]; + + $createResponse = [ + "SubAccount" => [ + "name" => "Name of subaccount", + "pixKey" => "356a192b7913b04c54574d18c28d46e6395428ab", + ], + ]; + + $requestTransportMock = $this->createMock(RequestTransport::class); + $requestTransportMock->expects($this->once()) + ->method("transport") + ->willReturnCallback(function (Request $request) use ($payload, $createResponse) { + $this->assertSame("POST", $request->getMethod()); + $this->assertSame("/api/v1/subaccount", $request->getPath()); + $this->assertSame($request->getBody(), $payload); + $this->assertSame($request->getQueryParams(), []); + + return $createResponse; + }); + + $subaccounts = new Subaccounts($requestTransportMock); + + $result = $subaccounts->create($payload); + + $this->assertSame($result, $createResponse); + } +} From ceb29df7f557ba7970e601c6884d112d59c6d873 Mon Sep 17 00:00:00 2001 From: criskell Date: Sun, 16 Aug 2026 04:37:21 -0300 Subject: [PATCH 2/2] fix(subaccounts): expose real contract from api --- src/Resources/Subaccounts.php | 56 +++++++++++++++-------------- tests/Resources/SubaccountsTest.php | 31 ++++------------ 2 files changed, 37 insertions(+), 50 deletions(-) diff --git a/src/Resources/Subaccounts.php b/src/Resources/Subaccounts.php index 7194018..f295c9b 100644 --- a/src/Resources/Subaccounts.php +++ b/src/Resources/Subaccounts.php @@ -2,13 +2,14 @@ namespace OpenPix\PhpSdk\Resources; +use OpenPix\PhpSdk\Paginator; use OpenPix\PhpSdk\Request; use OpenPix\PhpSdk\RequestTransport; /** * Operations on subaccounts. * - * @link https://developers.openpix.com.br/api#tag/subaccount + * @link https://developers.woovi.com/api#tag/subaccount */ class Subaccounts { @@ -30,41 +31,47 @@ public function __construct(RequestTransport $requestTransport) } /** - * Get a list of subaccounts. + * Return an {@see Paginator} with subaccount list. * * ## Usage * ```php - * $result = $client->subaccounts()->list(); - * - * foreach ($result["subAccounts"] as $subAccount) { - * $subAccount["name"]; // string - * $subAccount["pixKey"]; // string - * $subAccount["balance"]; // int + * $paginator = $client->subaccounts()->list(); + * + * foreach ($paginator as $page) { + * foreach ($page["subAccounts"] as $subAccount) { + * $subAccount["name"]; // string + * $subAccount["pixKey"]; // string + * $subAccount["balance"]; // int + * } * } * ``` * * @link https://developers.woovi.com/api#tag/subaccount/GET/api/v1/subaccount * - * @return array Result from API. + * @param array $queryParams Query parameters. + * + * @return Paginator Paginated result from API. */ - public function list(): array + public function list(array $queryParams = []): Paginator { $request = (new Request()) ->method("GET") - ->path("/api/v1/subaccount"); + ->path("/api/v1/subaccount") + ->queryParams($queryParams); - return $this->requestTransport->transport($request); + return new Paginator($this->requestTransport, $request); } /** - * Get an subaccount via pix key. + * Get a subaccount via pix key. * * ```php * $result = $client->subaccounts()->getOne("pixKey"); * - * $result["subaccount"]["name"]; // string - * $result["subaccount"]["pixKey"]; // boolean - * $result["subaccount"]["balance"]; // int + * $result["subAccount"]["name"]; // string + * $result["subAccount"]["pixKey"]; // string + * $result["subAccount"]["balance"]; // int + * $result["subAccount"]["withdrawBlocked"]; // bool|null * ``` * * @link https://developers.woovi.com/api#tag/subaccount/GET/api/v1/subaccount/{id} @@ -101,11 +108,11 @@ public function getOne(string $id): array * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/{id}/withdraw * * @param string $id Pix key registered to the subaccount. - * @param array $data Data to make a withdraw partial. + * @param array $data Data to make a withdraw partial. Omit `value` to withdraw the full balance. * * @return array Result from API. */ - public function withdraw(string $id, array $data): array + public function withdraw(string $id, array $data = []): array { $request = (new Request()) ->method("POST") @@ -123,13 +130,10 @@ public function withdraw(string $id, array $data): array * "name" => "Name of the sub account", * "pixKey" => "The pix key of the sub account", * ]); - * - * // Number in cents that represent the balance of the sub account - * $result["SubAccount"]["balance"]; // int * // Name of the sub account - * $result["SubAccount"]["name"]; // string + * $result["subAccount"]["name"]; // string * // The pix key for the sub account - * $result["SubAccount"]["pixKey"]; // string + * $result["subAccount"]["pixKey"]; // string * ``` * * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount @@ -154,8 +158,8 @@ public function create(array $data): array * ```php * $result = $client->subaccounts()->delete("pixKey"); * - * $result["subaccount"]["pixKey"]; // string - * $result["subaccount"]["status"]; // string + * $result["pixKey"]; // string + * $result["status"]; // string. e.g.: OK * ``` * * @link https://developers.woovi.com/api#tag/subaccount/DELETE/api/v1/subaccount/{id} @@ -233,7 +237,7 @@ public function debitToMainAccount(string $id, array $data): array * $result["originSubaccount"]["balance"]; // int. * ``` * - * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/{id}/transfer + * @link https://developers.woovi.com/api#tag/subaccount/POST/api/v1/subaccount/transfer * * @param array $data Data to make a new transfer between subaccounts * diff --git a/tests/Resources/SubaccountsTest.php b/tests/Resources/SubaccountsTest.php index dcbd37f..b29fb97 100644 --- a/tests/Resources/SubaccountsTest.php +++ b/tests/Resources/SubaccountsTest.php @@ -1,6 +1,6 @@ [ - "name" => "test-sub-account", - "pixKey" => "c4249323-b4ca-43f2-8139-8232aab09b93", - "balance" => 100 - ], - ]; - $requestTransportMock = $this->createMock(RequestTransport::class); - $requestTransportMock->expects($this->once()) - ->method("transport") - ->willReturnCallback(function (Request $request) use ($subaccountsResponse) { - $this->assertSame("GET", $request->getMethod()); - $this->assertSame("/api/v1/subaccount", $request->getPath()); - $this->assertSame($request->getBody(), null); - $this->assertSame($request->getQueryParams(), []); - - return $subaccountsResponse; - }); $subaccounts = new Subaccounts($requestTransportMock); + $pagedRequest = $subaccounts->list()->getPagedRequest(); - $result = $subaccounts->list(); - - $this->assertSame($result, $subaccountsResponse); + $this->assertSame($pagedRequest->getPath(), "/api/v1/subaccount"); + $this->assertSame($pagedRequest->getMethod(), "GET"); + $this->assertSame($pagedRequest->getBody(), null); } public function testGetOne(): void { $subaccountId = "356a192b7913b04c54574d18c28d46e6395428ab"; $subaccount = [ - "SubAccount" => [ + "subAccount" => [ "name" => "test-sub-account", "pixKey" => $subaccountId, "balance" => 100, @@ -224,7 +207,7 @@ public function testCreate(): void ]; $createResponse = [ - "SubAccount" => [ + "subAccount" => [ "name" => "Name of subaccount", "pixKey" => "356a192b7913b04c54574d18c28d46e6395428ab", ],