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..f295c9b --- /dev/null +++ b/src/Resources/Subaccounts.php @@ -0,0 +1,255 @@ +requestTransport = $requestTransport; + } + + /** + * Return an {@see Paginator} with subaccount list. + * + * ## Usage + * ```php + * $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 + * + * @param array $queryParams Query parameters. + * + * @return Paginator Paginated result from API. + */ + public function list(array $queryParams = []): Paginator + { + $request = (new Request()) + ->method("GET") + ->path("/api/v1/subaccount") + ->queryParams($queryParams); + + return new Paginator($this->requestTransport, $request); + } + + /** + * Get a subaccount via pix key. + * + * ```php + * $result = $client->subaccounts()->getOne("pixKey"); + * + * $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} + * + * @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. Omit `value` to withdraw the full balance. + * + * @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", + * ]); + * // 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["pixKey"]; // string + * $result["status"]; // string. e.g.: OK + * ``` + * + * @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/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..b29fb97 --- /dev/null +++ b/tests/Resources/SubaccountsTest.php @@ -0,0 +1,234 @@ +createMock(RequestTransport::class); + + $subaccounts = new Subaccounts($requestTransportMock); + $pagedRequest = $subaccounts->list()->getPagedRequest(); + + $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" => [ + "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); + } +}