diff --git a/composer.json b/composer.json index 11921be..9e7907b 100644 --- a/composer.json +++ b/composer.json @@ -26,5 +26,15 @@ "fakerphp/faker": "^1.23", "phpunit/phpunit": "^9.6", "phpunit/php-code-coverage": "^9.2.13" + }, + "config": { + "platform": { + "php": "7.4.33" + }, + "audit": { + "ignore": { + "PKSA-z3gr-8qht-p93v": "*" + } + } } } \ No newline at end of file diff --git a/composer.lock b/composer.lock index 91708f1..0aeed5c 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "93504cc65f1b7c0693d8ed77bd392fe8", + "content-hash": "eec137d2a2f1b1e514f665e08fbc7a9a", "packages": [ { "name": "guzzlehttp/guzzle", @@ -531,30 +531,30 @@ }, { "name": "psr/log", - "version": "3.0.2", + "version": "1.1.4", "source": { "type": "git", "url": "https://github.com/php-fig/log.git", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3" + "reference": "d49695b909c3b7628b6289db5479a1c204601f11" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/log/zipball/f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", - "reference": "f16e1d5863e37f8d8c2a01719f5b34baa2b714d3", + "url": "https://api.github.com/repos/php-fig/log/zipball/d49695b909c3b7628b6289db5479a1c204601f11", + "reference": "d49695b909c3b7628b6289db5479a1c204601f11", "shasum": "" }, "require": { - "php": ">=8.0.0" + "php": ">=5.3.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "3.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { "psr-4": { - "Psr\\Log\\": "src" + "Psr\\Log\\": "Psr/Log/" } }, "notification-url": "https://packagist.org/downloads/", @@ -575,9 +575,9 @@ "psr-3" ], "support": { - "source": "https://github.com/php-fig/log/tree/3.0.2" + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "time": "2024-09-11T13:17:53+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ralouphie/getallheaders", @@ -625,20 +625,20 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v2.5.4", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/605389f2a7e5625f273b53960dc46aeaf9c62918", + "reference": "605389f2a7e5625f273b53960dc46aeaf9c62918", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=7.1" }, "type": "library", "extra": { @@ -647,7 +647,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "2.5-dev" } }, "autoload": { @@ -672,7 +672,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.4" }, "funding": [ { @@ -688,7 +688,7 @@ "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2024-09-25T14:11:13+00:00" }, { "name": "symfony/polyfill-php80", @@ -858,30 +858,30 @@ "packages-dev": [ { "name": "doctrine/instantiator", - "version": "2.0.0", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", - "reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { - "php": "^8.1" + "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^11", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", - "phpbench/phpbench": "^1.2", - "phpstan/phpstan": "^1.9.4", - "phpstan/phpstan-phpunit": "^1.3", - "phpunit/phpunit": "^9.5.27", - "vimeo/psalm": "^5.4" + "phpbench/phpbench": "^0.16 || ^1", + "phpstan/phpstan": "^1.4", + "phpstan/phpstan-phpunit": "^1", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -908,7 +908,7 @@ ], "support": { "issues": "https://github.com/doctrine/instantiator/issues", - "source": "https://github.com/doctrine/instantiator/tree/2.0.0" + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" }, "funding": [ { @@ -924,7 +924,7 @@ "type": "tidelift" } ], - "time": "2022-12-30T00:23:10+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "fakerphp/faker", @@ -1051,16 +1051,16 @@ }, { "name": "nikic/php-parser", - "version": "v5.6.2", + "version": "v5.7.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb" + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/3a454ca033b9e06b63282ce19562e892747449bb", - "reference": "3a454ca033b9e06b63282ce19562e892747449bb", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/dca41cd15c2ac9d055ad70dbfd011130757d1f82", + "reference": "dca41cd15c2ac9d055ad70dbfd011130757d1f82", "shasum": "" }, "require": { @@ -1103,9 +1103,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.6.2" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.7.0" }, - "time": "2025-10-21T19:32:17+00:00" + "time": "2025-12-06T11:56:16+00:00" }, { "name": "phar-io/manifest", @@ -1546,16 +1546,16 @@ }, { "name": "phpunit/phpunit", - "version": "9.6.29", + "version": "9.6.34", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3" + "reference": "b36f02317466907a230d3aa1d34467041271ef4a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", - "reference": "9ecfec57835a5581bc888ea7e13b51eb55ab9dd3", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/b36f02317466907a230d3aa1d34467041271ef4a", + "reference": "b36f02317466907a230d3aa1d34467041271ef4a", "shasum": "" }, "require": { @@ -1577,7 +1577,7 @@ "phpunit/php-timer": "^5.0.3", "sebastian/cli-parser": "^1.0.2", "sebastian/code-unit": "^1.0.8", - "sebastian/comparator": "^4.0.9", + "sebastian/comparator": "^4.0.10", "sebastian/diff": "^4.0.6", "sebastian/environment": "^5.1.5", "sebastian/exporter": "^4.0.8", @@ -1629,7 +1629,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.29" + "source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.34" }, "funding": [ { @@ -1653,7 +1653,7 @@ "type": "tidelift" } ], - "time": "2025-09-24T06:29:11+00:00" + "time": "2026-01-27T05:45:00+00:00" }, { "name": "psr/container", @@ -1877,16 +1877,16 @@ }, { "name": "sebastian/comparator", - "version": "4.0.9", + "version": "4.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/comparator.git", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5" + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/67a2df3a62639eab2cc5906065e9805d4fd5dfc5", - "reference": "67a2df3a62639eab2cc5906065e9805d4fd5dfc5", + "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/e4df00b9b3571187db2831ae9aada2c6efbd715d", + "reference": "e4df00b9b3571187db2831ae9aada2c6efbd715d", "shasum": "" }, "require": { @@ -1939,7 +1939,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/comparator/issues", - "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.9" + "source": "https://github.com/sebastianbergmann/comparator/tree/4.0.10" }, "funding": [ { @@ -1959,7 +1959,7 @@ "type": "tidelift" } ], - "time": "2025-08-10T06:51:50+00:00" + "time": "2026-01-24T09:22:56+00:00" }, { "name": "sebastian/complexity", @@ -2953,5 +2953,8 @@ "php": ">=7.4" }, "platform-dev": {}, - "plugin-api-version": "2.6.0" + "platform-overrides": { + "php": "7.4.33" + }, + "plugin-api-version": "2.9.0" } diff --git a/examples/payment_links_v2/00-payment-link-v2-create.php b/examples/payment_links_v2/00-payment-link-v2-create.php new file mode 100644 index 0000000..2838941 --- /dev/null +++ b/examples/payment_links_v2/00-payment-link-v2-create.php @@ -0,0 +1,44 @@ + 'PL_V2_001', + 'amount' => 150.00, + 'description' => 'Link de pagamento V2', + 'expires_at' => '05/02/2026 13:35:00', + 'additional_info' => 'Informações adicionais do link', + 'stock_limit' => 100, + ] +); + +try { + + $responsePaymentLink = $ipagClient->paymentLinksV2()->create($paymentLink); + $data = $responsePaymentLink->getData(); + + $link = $responsePaymentLink->getParsedPath('links.payment'); + + echo "Link de Pagamento: {$link}" . PHP_EOL; + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/payment_links_v2/01-payment-link-v2-create-with-checkout-settings.php b/examples/payment_links_v2/01-payment-link-v2-create-with-checkout-settings.php new file mode 100644 index 0000000..ad1940d --- /dev/null +++ b/examples/payment_links_v2/01-payment-link-v2-create-with-checkout-settings.php @@ -0,0 +1,52 @@ + 'PL_V2_002', + 'amount' => 250.00, + 'description' => 'Link de pagamento com configurações de checkout', + 'expires_at' => '05/02/2026 23:59:59', + 'additional_info' => 'Produto X - edição limitada', + 'stock_limit' => 50, + 'checkout_settings' => [ + 'max_installments' => 12, + 'min_installment_value' => 10.00, + 'interest' => 1.99, + 'interest_free_installments' => 3, + 'fixed_installment' => 0, + 'payment_method' => 'all', + ], + ] +); + +try { + + $responsePaymentLink = $ipagClient->paymentLinksV2()->create($paymentLink); + $data = $responsePaymentLink->getData(); + + $link = $responsePaymentLink->getParsedPath('links.payment'); + + echo "Link de Pagamento: {$link}" . PHP_EOL; + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/payment_links_v2/02-payment-link-v2-update.php b/examples/payment_links_v2/02-payment-link-v2-update.php new file mode 100644 index 0000000..fdaa2f8 --- /dev/null +++ b/examples/payment_links_v2/02-payment-link-v2-update.php @@ -0,0 +1,40 @@ + 199.90, + 'description' => 'Link atualizado', + 'expires_at' => '31/12/2026 23:59:59', + ] +); + +$paymentLinkId = 42; + +try { + + $responsePaymentLink = $ipagClient->paymentLinksV2()->update($paymentLink, $paymentLinkId); + $data = $responsePaymentLink->getData(); + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/subscription_v2/00-subscription-v2-create.php b/examples/subscription_v2/00-subscription-v2-create.php new file mode 100644 index 0000000..f8086ff --- /dev/null +++ b/examples/subscription_v2/00-subscription-v2-create.php @@ -0,0 +1,51 @@ + 'SUB_V2_001', + 'description' => 'Plano mensal premium', + 'starting_date' => '2026-07-01', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'frequency' => 'monthly', + 'interval' => 1, + 'amount' => 199.90, + 'cycles' => 12, + 'customer_id' => 100022, + ] +); + +try { + + $responseSubscription = $ipagClient->subscriptionV2()->create($subscription); + $data = $responseSubscription->getData(); + + $statusSubscription = $responseSubscription->getParsedPath('attributes.status'); + + echo "Status da Assinatura: {$statusSubscription}" . PHP_EOL; + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/subscription_v2/01-subscription-v2-create-with-plan-id.php b/examples/subscription_v2/01-subscription-v2-create-with-plan-id.php new file mode 100644 index 0000000..d969031 --- /dev/null +++ b/examples/subscription_v2/01-subscription-v2-create-with-plan-id.php @@ -0,0 +1,48 @@ + 'SUB_V2_002', + 'starting_date' => '2026-07-01', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'plan_id' => 2, + 'customer_id' => 100022, + ] +); + +try { + + $responseSubscription = $ipagClient->subscriptionV2()->create($subscription); + $data = $responseSubscription->getData(); + + $statusSubscription = $responseSubscription->getParsedPath('attributes.status'); + + echo "Status da Assinatura: {$statusSubscription}" . PHP_EOL; + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/subscription_v2/01-subscription-v2-create-with-plan.php b/examples/subscription_v2/01-subscription-v2-create-with-plan.php new file mode 100644 index 0000000..7d72813 --- /dev/null +++ b/examples/subscription_v2/01-subscription-v2-create-with-plan.php @@ -0,0 +1,63 @@ + 'SUB_V2_003', + 'starting_date' => '2026-07-01', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'customer_id' => 100022, + 'plan' => new \Ipag\Sdk\Model\SubscriptionV2\Plan([ + 'name' => 'Plano Semestral', + 'description' => 'Acesso completo por 6 meses', + 'amount' => 99.90, + 'frequency' => 'monthly', + 'interval' => 1, + 'cycles' => 6, + 'best_day' => true, + 'pro_rated_charge' => false, + 'grace_period' => 0, + 'callback_url' => 'https://minhaloja.com/callback', + 'installments' => 1, + 'trial' => new \Ipag\Sdk\Model\SubscriptionV2\PlanTrial([ + 'amount' => 0, + 'cycles' => 1, + ]), + ]), + ] +); + +try { + + $responseSubscription = $ipagClient->subscriptionV2()->create($subscription); + $data = $responseSubscription->getData(); + + $statusSubscription = $responseSubscription->getParsedPath('attributes.status'); + + echo "Status da Assinatura: {$statusSubscription}" . PHP_EOL; + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/examples/subscription_v2/02-subscription-v2-update.php b/examples/subscription_v2/02-subscription-v2-update.php new file mode 100644 index 0000000..4b74f80 --- /dev/null +++ b/examples/subscription_v2/02-subscription-v2-update.php @@ -0,0 +1,36 @@ + false, + 'amount' => 249.90, + 'description' => 'Plano atualizado', + ] +); + +$subscriptionId = 275; + +try { + + $responseSubscription = $ipagClient->subscriptionV2()->update($subscription, $subscriptionId); + $data = $responseSubscription->getData(); + + echo "
" . PHP_EOL;
+    print_r($data);
+    echo "
" . PHP_EOL; +} catch (Ipag\Sdk\Exception\HttpException $e) { + $code = $e->getResponse()->getStatusCode(); + $errors = $e->getErrors(); + + echo "
" . PHP_EOL;
+    var_dump($code, $errors);
+    echo "
" . PHP_EOL; +} catch (Exception $e) { + $error = $e->getMessage(); + + echo "
" . PHP_EOL;
+    var_dump($error);
+    echo "
" . PHP_EOL; +} diff --git a/src/Core/IpagClient.php b/src/Core/IpagClient.php index 224bdf2..4b03ed7 100644 --- a/src/Core/IpagClient.php +++ b/src/Core/IpagClient.php @@ -9,10 +9,12 @@ use Ipag\Sdk\Endpoint\EstablishmentEndpoint; use Ipag\Sdk\Endpoint\PaymentEndpoint; use Ipag\Sdk\Endpoint\PaymentLinksEndpoint; +use Ipag\Sdk\Endpoint\PaymentLinksV2Endpoint; use Ipag\Sdk\Endpoint\SellerEndpoint; use Ipag\Sdk\Endpoint\SplitRulesEndpoint; use Ipag\Sdk\Endpoint\SubscriptionEndpoint; use Ipag\Sdk\Endpoint\SubscriptionPlanEndpoint; +use Ipag\Sdk\Endpoint\SubscriptionV2Endpoint; use Ipag\Sdk\Endpoint\TokenEndpoint; use Ipag\Sdk\Endpoint\TransactionEndpoint; use Ipag\Sdk\Endpoint\TransferEndpoint; @@ -30,9 +32,7 @@ class IpagClient extends Client { - public function IpagClient() - { - } + public function IpagClient() {} /** * @param string $apiID API ID é a identificação do usuário. @@ -72,6 +72,11 @@ public function subscription(): SubscriptionEndpoint return SubscriptionEndpoint::make($this, $this); } + public function subscriptionV2(): SubscriptionV2Endpoint + { + return SubscriptionV2Endpoint::make($this, $this); + } + public function transaction(): TransactionEndpoint { return TransactionEndpoint::make($this, $this); @@ -102,6 +107,11 @@ public function paymentLinks(): PaymentLinksEndpoint return PaymentLinksEndpoint::make($this, $this); } + public function paymentLinksV2(): PaymentLinksV2Endpoint + { + return PaymentLinksV2Endpoint::make($this, $this); + } + public function webhook(): WebhookEndpoint { return WebhookEndpoint::make($this, $this); @@ -131,5 +141,4 @@ public function payment(): PaymentEndpoint { return PaymentEndpoint::make($this, $this); } - -} \ No newline at end of file +} diff --git a/src/Endpoint/PaymentLinksV2Endpoint.php b/src/Endpoint/PaymentLinksV2Endpoint.php new file mode 100644 index 0000000..f882422 --- /dev/null +++ b/src/Endpoint/PaymentLinksV2Endpoint.php @@ -0,0 +1,77 @@ +_POST($paymentLink->jsonSerialize()); + } + + /** + * Endpoint para atualizar um recurso `Payment Link V2` + * + * @param PaymentLinkV2 $paymentLink + * @param integer $id + * @return Response + * + * @codeCoverageIgnore + */ + public function update(PaymentLinkV2 $paymentLink, int $id): Response + { + return $this->_PUT($paymentLink->jsonSerialize(), ['id' => $id]); + } + + /** + * Endpoint para obter um recurso `Payment Link V2` + * + * @param integer $id + * @return Response + * + * @codeCoverageIgnore + */ + public function get(int $id): Response + { + return $this->_GET(['id' => $id]); + } + + /** + * Endpoint para listar recursos `Payment Link V2` + * + * @param array|null $filters + * @return Response + * + * @codeCoverageIgnore + */ + public function list(?array $filters = []): Response + { + return $this->_GET($filters ?? []); + } + + /** + * Endpoint para listar as transações de um recurso `Payment Link V2` + * + * @param integer $id + * @param array|null $filters + * @return Response + * + * @codeCoverageIgnore + */ + public function listTransactions(int $id, ?array $filters = []): Response + { + return $this->_GET($filters ?? [], [], "/{$id}/transactions"); + } +} diff --git a/src/Endpoint/SubscriptionV2Endpoint.php b/src/Endpoint/SubscriptionV2Endpoint.php new file mode 100644 index 0000000..319d813 --- /dev/null +++ b/src/Endpoint/SubscriptionV2Endpoint.php @@ -0,0 +1,37 @@ +_POST($subscription->jsonSerialize()); + } + + /** + * Endpoint para atualizar um recurso `Subscription` + * + * @param SubscriptionV2 $subscription + * @param integer $id + * @return Response + * + * @codeCoverageIgnore + */ + public function update(SubscriptionV2 $subscription, int $id): Response + { + return $this->_PUT($subscription->jsonSerialize(), ['id' => $id]); + } +} diff --git a/src/Model/Customer.php b/src/Model/Customer.php index 77cb42f..8248b26 100644 --- a/src/Model/Customer.php +++ b/src/Model/Customer.php @@ -79,6 +79,7 @@ protected function schema(SchemaBuilder $schema): Schema $schema->has('address', Address::class)->nullable(); $schema->has('billing_address', Address::class)->nullable(); $schema->has('shipping_address', Address::class)->nullable(); + $schema->has('owner', Owner::class)->nullable(); return $schema->build(); } @@ -129,7 +130,7 @@ public function setUuid(?string $uuid): self protected function name(): Mutator { - return new Mutator(null, fn ($value) => strval($value)); + return new Mutator(null, fn($value) => strval($value)); } /** @@ -156,8 +157,7 @@ public function setName(?string $name = null): self protected function is_active(): Mutator { - return new Mutator(null, fn ($value) => (bool) $value); - ; + return new Mutator(null, fn($value) => (bool) $value); } /** @@ -186,10 +186,10 @@ protected function email(): Mutator { return new Mutator( null, - fn ($value, $ctx) => + fn($value, $ctx) => is_null($value) ? - $value : - Assert::value($value)->email()->get() ?? $ctx->raise('inválido') + $value : + Assert::value($value)->email()->get() ?? $ctx->raise('inválido') ); } @@ -219,10 +219,10 @@ protected function phone(): Mutator { return new Mutator( null, - fn ($value, $ctx) => + fn($value, $ctx) => is_null($value) ? - $value : - Assert::value($value)->asDigits()->lbetween(10, 11)->get() ?? $ctx->raise('inválido') + $value : + Assert::value($value)->asDigits()->lbetween(10, 11)->get() ?? $ctx->raise('inválido') ); } @@ -252,10 +252,10 @@ protected function cpf_cnpj(): Mutator { return new Mutator( null, - fn ($value, $ctx) => + fn($value, $ctx) => is_null($value) ? - $value : - Assert::value($value)->asCpf(false)->or()->asCnpj(false)->get() ?? $ctx->raise('inválido') + $value : + Assert::value($value)->asCpf(false)->or()->asCnpj(false)->get() ?? $ctx->raise('inválido') ); } @@ -263,10 +263,10 @@ protected function tax_receipt(): Mutator { return new Mutator( null, - fn ($value, $ctx) => + fn($value, $ctx) => is_null($value) ? - $value : - Assert::value($value)->asCpf(false)->or()->asCnpj(false)->get() ?? $ctx->raise('inválido') + $value : + Assert::value($value)->asCpf(false)->or()->asCnpj(false)->get() ?? $ctx->raise('inválido') ); } @@ -487,4 +487,25 @@ public function setShippingAddress(?Address $address = null): self return $this; } + /** + * Retorna o objeto owner do cliente. + * + * @return Owner|null + */ + public function getOwner(): ?Owner + { + return $this->get('owner'); + } + + /** + * Seta o objeto owner do cliente. + * + * @param Owner|null $owner + * @return self + */ + public function setOwner(?Owner $owner = null): self + { + $this->set('owner', $owner); + return $this; + } } diff --git a/src/Model/PaymentLinkV2/CheckoutSettings.php b/src/Model/PaymentLinkV2/CheckoutSettings.php new file mode 100644 index 0000000..0a2e825 --- /dev/null +++ b/src/Model/PaymentLinkV2/CheckoutSettings.php @@ -0,0 +1,93 @@ +int('max_installments')->nullable(); + $schema->float('min_installment_value')->nullable(); + $schema->float('interest')->nullable(); + $schema->int('interest_free_installments')->nullable(); + $schema->int('fixed_installment')->nullable(); + $schema->string('payment_method')->nullable(); + + return $schema->build(); + } + + public function getMaxInstallments(): ?int + { + return $this->get('max_installments'); + } + + public function setMaxInstallments(?int $maxInstallments): self + { + $this->set('max_installments', $maxInstallments); + return $this; + } + + public function getMinInstallmentValue(): ?float + { + return $this->get('min_installment_value'); + } + + public function setMinInstallmentValue(?float $minInstallmentValue): self + { + $this->set('min_installment_value', $minInstallmentValue); + return $this; + } + + public function getInterest(): ?float + { + return $this->get('interest'); + } + + public function setInterest(?float $interest): self + { + $this->set('interest', $interest); + return $this; + } + + public function getInterestFreeInstallments(): ?int + { + return $this->get('interest_free_installments'); + } + + public function setInterestFreeInstallments(?int $interestFreeInstallments): self + { + $this->set('interest_free_installments', $interestFreeInstallments); + return $this; + } + + public function getFixedInstallment(): ?int + { + return $this->get('fixed_installment'); + } + + public function setFixedInstallment(?int $fixedInstallment): self + { + $this->set('fixed_installment', $fixedInstallment); + return $this; + } + + public function getPaymentMethod(): ?string + { + return $this->get('payment_method'); + } + + public function setPaymentMethod(?string $paymentMethod): self + { + $this->set('payment_method', $paymentMethod); + return $this; + } +} diff --git a/src/Model/PaymentLinkV2/PaymentLinkV2.php b/src/Model/PaymentLinkV2/PaymentLinkV2.php new file mode 100644 index 0000000..79297ce --- /dev/null +++ b/src/Model/PaymentLinkV2/PaymentLinkV2.php @@ -0,0 +1,125 @@ +string('external_code')->nullable(); + $schema->float('amount')->nullable(); + $schema->string('description')->nullable(); + $schema->string('expires_at')->nullable(); + $schema->string('additional_info')->nullable(); + $schema->int('stock_limit')->nullable(); + $schema->has('checkout_settings', CheckoutSettings::class)->nullable(); + + return $schema->build(); + } + + public function jsonSerialize(): array + { + return array_filter(parent::jsonSerialize(), fn($v) => !is_null($v)); + } + + protected function expires_at(): Mutator + { + return new Mutator( + null, + function ($value, $ctx) { + $d = \DateTime::createFromFormat('d/m/Y H:i:s', $value); + + return is_null($value) || + ($d && $d->format('d/m/Y H:i:s') === $value) ? + $value : $ctx->raise('inválido (formato esperado: dd/mm/aaaa hh:mm:ss)'); + } + ); + } + + public function getExternalCode(): ?string + { + return $this->get('external_code'); + } + + public function setExternalCode(?string $externalCode): self + { + $this->set('external_code', $externalCode); + return $this; + } + + public function getAmount(): ?float + { + return $this->get('amount'); + } + + public function setAmount(?float $amount): self + { + $this->set('amount', $amount); + return $this; + } + + public function getDescription(): ?string + { + return $this->get('description'); + } + + public function setDescription(?string $description): self + { + $this->set('description', $description); + return $this; + } + + public function getExpiresAt(): ?string + { + return $this->get('expires_at'); + } + + public function setExpiresAt(?string $expiresAt): self + { + $this->set('expires_at', $expiresAt); + return $this; + } + + public function getAdditionalInfo(): ?string + { + return $this->get('additional_info'); + } + + public function setAdditionalInfo(?string $additionalInfo): self + { + $this->set('additional_info', $additionalInfo); + return $this; + } + + public function getStockLimit(): ?int + { + return $this->get('stock_limit'); + } + + public function setStockLimit(?int $stockLimit): self + { + $this->set('stock_limit', $stockLimit); + return $this; + } + + public function getCheckoutSettings(): ?CheckoutSettings + { + return $this->get('checkout_settings'); + } + + public function setCheckoutSettings(?CheckoutSettings $checkoutSettings): self + { + $this->set('checkout_settings', $checkoutSettings); + return $this; + } +} diff --git a/src/Model/SubscriptionV2/Plan.php b/src/Model/SubscriptionV2/Plan.php new file mode 100644 index 0000000..fa46dd7 --- /dev/null +++ b/src/Model/SubscriptionV2/Plan.php @@ -0,0 +1,170 @@ +string('name')->limit(255)->nullable(); + $schema->string('description')->limit(1000)->nullable(); + $schema->float('amount')->nullable(); + $schema->string('frequency')->limit(20)->nullable(); + $schema->int('interval')->nullable(); + $schema->int('cycles')->nullable(); + $schema->bool('best_day')->nullable(); + $schema->bool('pro_rated_charge')->nullable(); + $schema->int('grace_period')->nullable(); + $schema->string('callback_url')->nullable(); + $schema->int('installments')->nullable(); + $schema->has('trial', PlanTrial::class)->nullable(); + + return $schema->build(); + } + + public function jsonSerialize(): array + { + return array_filter(parent::jsonSerialize(), fn($v) => !is_null($v)); + } + + public function getName(): ?string + { + return $this->get('name'); + } + + public function setName(?string $name): self + { + $this->set('name', $name); + return $this; + } + + public function getDescription(): ?string + { + return $this->get('description'); + } + + public function setDescription(?string $description): self + { + $this->set('description', $description); + return $this; + } + + public function getAmount(): ?float + { + return $this->get('amount'); + } + + public function setAmount(?float $amount): self + { + $this->set('amount', $amount); + return $this; + } + + public function getFrequency(): ?string + { + return $this->get('frequency'); + } + + public function setFrequency(?string $frequency): self + { + $this->set('frequency', $frequency); + return $this; + } + + public function getInterval(): ?int + { + return $this->get('interval'); + } + + public function setInterval(?int $interval): self + { + $this->set('interval', $interval); + return $this; + } + + public function getCycles(): ?int + { + return $this->get('cycles'); + } + + public function setCycles(?int $cycles): self + { + $this->set('cycles', $cycles); + return $this; + } + + public function getBestDay(): ?bool + { + return $this->get('best_day'); + } + + public function setBestDay(?bool $bestDay): self + { + $this->set('best_day', $bestDay); + return $this; + } + + public function getProRatedCharge(): ?bool + { + return $this->get('pro_rated_charge'); + } + + public function setProRatedCharge(?bool $proRatedCharge): self + { + $this->set('pro_rated_charge', $proRatedCharge); + return $this; + } + + public function getGracePeriod(): ?int + { + return $this->get('grace_period'); + } + + public function setGracePeriod(?int $gracePeriod): self + { + $this->set('grace_period', $gracePeriod); + return $this; + } + + public function getCallbackUrl(): ?string + { + return $this->get('callback_url'); + } + + public function setCallbackUrl(?string $callbackUrl): self + { + $this->set('callback_url', $callbackUrl); + return $this; + } + + public function getInstallments(): ?int + { + return $this->get('installments'); + } + + public function setInstallments(?int $installments): self + { + $this->set('installments', $installments); + return $this; + } + + public function getTrial(): ?PlanTrial + { + return $this->get('trial'); + } + + public function setTrial(?PlanTrial $trial): self + { + $this->set('trial', $trial); + return $this; + } +} diff --git a/src/Model/SubscriptionV2/PlanTrial.php b/src/Model/SubscriptionV2/PlanTrial.php new file mode 100644 index 0000000..5ea14e5 --- /dev/null +++ b/src/Model/SubscriptionV2/PlanTrial.php @@ -0,0 +1,50 @@ +float('amount')->nullable(); + $schema->int('cycles')->nullable(); + + return $schema->build(); + } + + public function jsonSerialize(): array + { + return array_filter(parent::jsonSerialize(), fn($v) => !is_null($v)); + } + + public function getAmount(): ?float + { + return $this->get('amount'); + } + + public function setAmount(?float $amount): self + { + $this->set('amount', $amount); + return $this; + } + + public function getCycles(): ?int + { + return $this->get('cycles'); + } + + public function setCycles(?int $cycles): self + { + $this->set('cycles', $cycles); + return $this; + } +} diff --git a/src/Model/SubscriptionV2/SubscriptionV2.php b/src/Model/SubscriptionV2/SubscriptionV2.php new file mode 100644 index 0000000..39b0e60 --- /dev/null +++ b/src/Model/SubscriptionV2/SubscriptionV2.php @@ -0,0 +1,222 @@ +string('profile_id')->nullable(); + $schema->string('description')->nullable(); + $schema->string('starting_date')->nullable(); + $schema->string('callback_url')->nullable(); + $schema->string('creditcard_token')->nullable(); + $schema->string('card_token')->nullable(); + $schema->bool('is_active')->nullable(); + $schema->int('plan_id')->nullable(); + $schema->int('customer_id')->nullable(); + $schema->has('plan', Plan::class)->nullable(); + $schema->string('frequency')->nullable(); + $schema->int('interval')->nullable(); + $schema->float('amount')->nullable(); + $schema->int('cycles')->nullable(); + $schema->has('customer', Customer::class)->nullable(); + + return $schema->build(); + } + + public function jsonSerialize(): array + { + return array_filter(parent::jsonSerialize(), fn($v) => !is_null($v)); + } + + protected function starting_date(): Mutator + { + return new Mutator( + null, + function ($value, $ctx) { + $d = \DateTime::createFromFormat('Y-m-d', $value); + + return is_null($value) || + ($d && $d->format('Y-m-d') === $value) ? + $value : $ctx->raise('inválido'); + } + ); + } + + public function getProfileId(): ?string + { + return $this->get('profile_id'); + } + + public function setProfileId(?string $profileId): self + { + $this->set('profile_id', $profileId); + return $this; + } + + public function getDescription(): ?string + { + return $this->get('description'); + } + + public function setDescription(?string $description): self + { + $this->set('description', $description); + return $this; + } + + public function getStartingDate(): ?string + { + return $this->get('starting_date'); + } + + public function setStartingDate(?string $startingDate): self + { + $this->set('starting_date', $startingDate); + return $this; + } + + public function getCallbackUrl(): ?string + { + return $this->get('callback_url'); + } + + public function setCallbackUrl(?string $callbackUrl): self + { + $this->set('callback_url', $callbackUrl); + return $this; + } + + public function getCreditCardToken(): ?string + { + return $this->get('creditcard_token'); + } + + public function setCreditCardToken(?string $creditCardToken): self + { + $this->set('creditcard_token', $creditCardToken); + return $this; + } + + public function getCardToken(): ?string + { + return $this->get('card_token'); + } + + public function setCardToken(?string $cardToken): self + { + $this->set('card_token', $cardToken); + return $this; + } + + public function getIsActive(): ?bool + { + return $this->get('is_active'); + } + + public function setIsActive(?bool $isActive): self + { + $this->set('is_active', $isActive); + return $this; + } + + public function getPlanId(): ?int + { + return $this->get('plan_id'); + } + + public function setPlanId(?int $planId): self + { + $this->set('plan_id', $planId); + return $this; + } + + public function getCustomerId(): ?int + { + return $this->get('customer_id'); + } + + public function setCustomerId(?int $customerId): self + { + $this->set('customer_id', $customerId); + return $this; + } + + public function getPlan(): ?Plan + { + return $this->get('plan'); + } + + public function setPlan(?Plan $plan): self + { + $this->set('plan', $plan); + return $this; + } + + public function getFrequency(): ?string + { + return $this->get('frequency'); + } + + public function setFrequency(?string $frequency): self + { + $this->set('frequency', $frequency); + return $this; + } + + public function getInterval(): ?int + { + return $this->get('interval'); + } + + public function setInterval(?int $interval): self + { + $this->set('interval', $interval); + return $this; + } + + public function getAmount(): ?float + { + return $this->get('amount'); + } + + public function setAmount(?float $amount): self + { + $this->set('amount', $amount); + return $this; + } + + public function getCycles(): ?int + { + return $this->get('cycles'); + } + + public function setCycles(?int $cycles): self + { + $this->set('cycles', $cycles); + return $this; + } + + public function getCustomer(): ?Customer + { + return $this->get('customer'); + } + + public function setCustomer(?Customer $customer): self + { + $this->set('customer', $customer); + return $this; + } +} diff --git a/tests/Endpoint/PaymentLinksV2EndpointTest.php b/tests/Endpoint/PaymentLinksV2EndpointTest.php new file mode 100644 index 0000000..0426b37 --- /dev/null +++ b/tests/Endpoint/PaymentLinksV2EndpointTest.php @@ -0,0 +1,380 @@ + 36, + "resource" => "payment_links", + "attributes" => [ + "uuid" => "177238cc-d5fe-4817-99e5-4419bd835ed1", + "external_code" => "PL_V2_001", + "is_active" => true, + "stock_limit" => 100, + "has_stock_limit" => true, + "amount" => 150.00, + "description" => "Link de pagamento V2", + "additional_info" => "Informações adicionais", + "expires_at" => "05/02/2026 13:35:00", + "created_at" => "2026-03-09 10:00:00", + "updated_at" => "2026-03-09 10:00:00", + ], + "checkout_settings" => null, + "links" => [ + "payment" => "https://api.ipag.test/link?t=177238cc-d5fe-4817-99e5-4419bd835ed1" + ] + ]; + } + + private function mockResponseList(): array + { + return [ + "data" => [ + [ + "id" => 27, + "resource" => "payment_links", + "attributes" => [ + "uuid" => "177238cc-d5fe-4817-99e5-4419bd835ed1", + "external_code" => "", + "is_active" => false, + "stock_limit" => 0, + "has_stock_limit" => true, + "amount" => 100, + "description" => "", + "additional_info" => "", + "expires_at" => null, + "created_at" => "2025-12-02 11:46:48", + "updated_at" => "2025-12-02 11:46:48", + ], + "checkout_settings" => null, + "links" => [ + "payment" => "https://api.ipag.test/link?t=177238cc-d5fe-4817-99e5-4419bd835ed1" + ] + ], + [ + "id" => 26, + "resource" => "payment_links", + "attributes" => [ + "uuid" => "4ba11da2-1bd9-43ee-84bd-f9a673c7e385", + "external_code" => "", + "is_active" => false, + "stock_limit" => 0, + "has_stock_limit" => true, + "amount" => 100, + "description" => "", + "additional_info" => "", + "expires_at" => null, + "created_at" => "2025-12-02 11:43:41", + "updated_at" => "2025-12-02 11:43:41", + ], + "checkout_settings" => null, + "links" => [ + "payment" => "https://api.ipag.test/link?t=4ba11da2-1bd9-43ee-84bd-f9a673c7e385" + ] + ], + ], + "links" => [ + "first" => "https://api.ipag.test/service/v2/payment_links?page=1", + "last" => "https://api.ipag.test/service/v2/payment_links?page=1", + "prev" => null, + "next" => null, + ], + "meta" => [ + "current_page" => 1, + "last_page" => 1, + "from" => 1, + "to" => 2, + "per_page" => 15, + "total" => 2, + ], + ]; + } + + // ========================================================================= + // CREATE + // ========================================================================= + + public function testShouldCreatePaymentLinkV2Successfully() + { + $this->instanceClient([ + new Response(201, [], json_encode($this->mockResponseCreate())) + ]); + + $paymentLink = new PaymentLinkV2([ + 'external_code' => 'PL_V2_001', + 'amount' => 150.00, + 'description' => 'Link de pagamento V2', + 'expires_at' => '05/02/2026 13:35:00', + 'additional_info' => 'Informações adicionais', + 'stock_limit' => 100, + ]); + + $response = $this->client->paymentLinksV2()->create($paymentLink); + + $this->assertIsObject($response); + $this->assertSame(36, $response->getParsedPath('id')); + $this->assertSame('payment_links', $response->getParsedPath('resource')); + $this->assertSame('PL_V2_001', $response->getParsedPath('attributes.external_code')); + $this->assertSame( + 'https://api.ipag.test/link?t=177238cc-d5fe-4817-99e5-4419bd835ed1', + $response->getParsedPath('links.payment') + ); + } + + public function testShouldCreatePaymentLinkV2WithCheckoutSettingsSuccessfully() + { + $mockResponse = $this->mockResponseCreate(); + $mockResponse['checkout_settings'] = [ + 'max_installments' => 12, + 'min_installment_value' => 10.00, + 'interest' => 1.99, + 'interest_free_installments' => 3, + 'fixed_installment' => 0, + 'payment_method' => 'all', + ]; + + $this->instanceClient([ + new Response(201, [], json_encode($mockResponse)) + ]); + + $paymentLink = new PaymentLinkV2([ + 'external_code' => 'PL_V2_002', + 'amount' => 250.00, + 'description' => 'Link com checkout', + 'expires_at' => '31/12/2026 23:59:59', + 'checkout_settings' => new CheckoutSettings([ + 'max_installments' => 12, + 'min_installment_value' => 10.00, + 'interest' => 1.99, + 'interest_free_installments' => 3, + 'fixed_installment' => 0, + 'payment_method' => 'all', + ]), + ]); + + $response = $this->client->paymentLinksV2()->create($paymentLink); + + $this->assertIsObject($response); + $this->assertSame(36, $response->getParsedPath('id')); + $this->assertSame(12, $response->getParsedPath('checkout_settings.max_installments')); + $this->assertSame('all', $response->getParsedPath('checkout_settings.payment_method')); + } + + // ========================================================================= + // UPDATE + // ========================================================================= + + public function testShouldUpdatePaymentLinkV2Successfully() + { + $mockResponse = $this->mockResponseCreate(); + $mockResponse['attributes']['amount'] = 199.90; + $mockResponse['attributes']['description'] = 'Link atualizado'; + $mockResponse['checkout_settings'] = [ + 'max_installments' => 11, + 'min_installment_value' => 10.00, + 'interest' => 20.00, + 'interest_free_installments' => 2, + 'fixed_installment' => 5, + 'payment_method' => 'creditcard', + ]; + + $this->instanceClient([ + new Response(200, [], json_encode($mockResponse)) + ]); + + $paymentLink = new PaymentLinkV2([ + 'checkout_settings' => [ + 'max_installments' => 11, + 'min_installment_value' => 10.00, + 'interest' => 20.00, + 'interest_free_installments' => 2, + 'fixed_installment' => 5, + 'payment_method' => 'creditcard', + ], + ]); + + $response = $this->client->paymentLinksV2()->update($paymentLink, 36); + + $this->assertIsObject($response); + $this->assertSame(36, $response->getParsedPath('id')); + $this->assertSame('creditcard', $response->getParsedPath('checkout_settings.payment_method')); + $this->assertSame(11, $response->getParsedPath('checkout_settings.max_installments')); + } + + // ========================================================================= + // GET + // ========================================================================= + + public function testShouldGetPaymentLinkV2ByIdSuccessfully() + { + $this->instanceClient([ + new Response(200, [], json_encode($this->mockResponseCreate())) + ]); + + $response = $this->client->paymentLinksV2()->get(36); + + $this->assertIsObject($response); + $this->assertSame(36, $response->getParsedPath('id')); + $this->assertSame('payment_links', $response->getParsedPath('resource')); + } + + // ========================================================================= + // LIST + // ========================================================================= + + public function testShouldListPaymentLinksV2Successfully() + { + $this->instanceClient([ + new Response(200, [], json_encode($this->mockResponseList())) + ]); + + $response = $this->client->paymentLinksV2()->list(); + $data = $response->getData(); + + $this->assertIsObject($response); + $this->assertArrayHasKey('data', $data); + $this->assertCount(2, $data['data']); + $this->assertSame(27, $data['data'][0]['id']); + $this->assertSame(26, $data['data'][1]['id']); + } + + public function testShouldListPaymentLinksV2WithMetaSuccessfully() + { + $this->instanceClient([ + new Response(200, [], json_encode($this->mockResponseList())) + ]); + + $response = $this->client->paymentLinksV2()->list(['page' => 1]); + $data = $response->getData(); + + $this->assertArrayHasKey('meta', $data); + $this->assertSame(1, $data['meta']['current_page']); + $this->assertSame(2, $data['meta']['total']); + $this->assertSame(15, $data['meta']['per_page']); + } + + public function testShouldListPaymentLinksV2WithPaginationLinksSuccessfully() + { + $this->instanceClient([ + new Response(200, [], json_encode($this->mockResponseList())) + ]); + + $response = $this->client->paymentLinksV2()->list(); + $data = $response->getData(); + + $this->assertArrayHasKey('links', $data); + $this->assertNotNull($data['links']['first']); + $this->assertNotNull($data['links']['last']); + $this->assertNull($data['links']['prev']); + $this->assertNull($data['links']['next']); + } + + // ========================================================================= + // LIST TRANSACTIONS + // ========================================================================= + + public function testShouldListPaymentLinkV2TransactionsSuccessfully() + { + $mockTransactions = [ + "data" => [ + ["id" => 1, "resource" => "transactions"], + ["id" => 2, "resource" => "transactions"], + ], + "meta" => [ + "current_page" => 1, + "total" => 2, + ], + ]; + + $this->instanceClient([ + new Response(200, [], json_encode($mockTransactions)) + ]); + + $response = $this->client->paymentLinksV2()->listTransactions(36); + $data = $response->getData(); + + $this->assertIsObject($response); + $this->assertArrayHasKey('data', $data); + $this->assertCount(2, $data['data']); + } + + // ========================================================================= + // Erros HTTP + // ========================================================================= + + public function testShouldResponseFailUnprocessableDataClient() + { + $this->instanceClient([ + new Response( + 406, + [], + json_encode((object) [ + "code" => "406", + "message" => [ + "external_code" => ["External Code is required"], + ] + ]) + ) + ]); + + try { + $this->client->paymentLinksV2()->create(new PaymentLinkV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(406, $th->getCode()); + } + } + + public function testShouldResponseFailUnauthenticatedClient() + { + $this->instanceClient([ + new Response( + 401, + [], + json_encode((object) [ + "code" => 401, + "message" => "Unauthorized", + "resource" => "authorization" + ]) + ) + ]); + + try { + $this->client->paymentLinksV2()->create(new PaymentLinkV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(401, $th->getCode()); + } + } + + public function testShouldResponseFailUnauthorizedClient() + { + $this->instanceClient([ + new Response( + 403, + [], + json_encode((object) [ + "code" => 403, + "message" => "Not Authorized", + "resource" => "payment_links" + ]) + ) + ]); + + try { + $this->client->paymentLinksV2()->create(new PaymentLinkV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(403, $th->getCode()); + } + } +} diff --git a/tests/Endpoint/SubscriptionV2EndpointTest.php b/tests/Endpoint/SubscriptionV2EndpointTest.php new file mode 100644 index 0000000..7d57122 --- /dev/null +++ b/tests/Endpoint/SubscriptionV2EndpointTest.php @@ -0,0 +1,244 @@ + 275, + "resource" => "subscriptions", + "attributes" => [ + "is_active" => true, + "status" => "pending", + "description" => "Plano para a ralé mensal", + "profile_id" => "fcf688dd-33a3-4225-9fe8-558fe43266ed", + "amount" => 200, + "frequency" => "monthly", + "interval" => 5, + "starting_date" => "2026-07-10", + "callback_url" => "https://minhaloja.com/callback", + "plan" => null, + "customer" => [ + "id" => 100022, + "resource" => "customers", + "attributes" => [ + "name" => "Gabriel César Brusarrosco", + "cpf_cnpj" => "077.588.670-06", + "email" => "gabriel.brusarrosco@ipag.com.br", + "phone" => "18997512082", + ] + ], + ] + ]; + } + + // ========================================================================= + // CREATE — Forma 1: campos diretos + customer_id + // ========================================================================= + + public function testShouldCreateSubscriptionV2WithDirectFieldsSuccessfully() + { + $this->instanceClient([ + new Response(201, [], json_encode($this->mockResponseCreate())) + ]); + + $subscription = new SubscriptionV2([ + 'profile_id' => 'SUB_V2_001', + 'description' => 'Plano mensal premium', + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'frequency' => 'monthly', + 'interval' => 5, + 'amount' => 200.00, + 'cycles' => 0, + 'customer_id' => 100022, + ]); + + $response = $this->client->subscriptionV2()->create($subscription); + + $this->assertIsObject($response); + $this->assertSame(275, $response->getParsedPath('id')); + $this->assertSame('subscriptions', $response->getParsedPath('resource')); + $this->assertSame('pending', $response->getParsedPath('attributes.status')); + } + + // ========================================================================= + // CREATE — Forma 2: plan_id + customer_id + // ========================================================================= + + public function testShouldCreateSubscriptionV2WithPlanIdSuccessfully() + { + $this->instanceClient([ + new Response(201, [], json_encode($this->mockResponseCreate())) + ]); + + $subscription = new SubscriptionV2([ + 'profile_id' => 'SUB_V2_002', + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'plan_id' => 2, + 'customer_id' => 100022, + ]); + + $response = $this->client->subscriptionV2()->create($subscription); + + $this->assertIsObject($response); + $this->assertSame(275, $response->getParsedPath('id')); + $this->assertSame('subscriptions', $response->getParsedPath('resource')); + } + + // ========================================================================= + // CREATE — Forma 3: plan object inline + customer_id + // ========================================================================= + + public function testShouldCreateSubscriptionV2WithPlanObjectSuccessfully() + { + $this->instanceClient([ + new Response(201, [], json_encode($this->mockResponseCreate())) + ]); + + $subscription = new SubscriptionV2([ + 'profile_id' => 'SUB_V2_003', + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'is_active' => true, + 'customer_id' => 100022, + 'plan' => new Plan([ + 'name' => 'Plano da Lojinha Básico', + 'description' => 'Plano para a ralé mensal', + 'amount' => 200.00, + 'frequency' => 'monthly', + 'interval' => 5, + 'cycles' => 0, + 'best_day' => true, + 'pro_rated_charge' => true, + 'grace_period' => 0, + 'callback_url' => 'https://minhaloja.com/callback', + 'installments' => 1, + 'trial' => new PlanTrial([ + 'amount' => 10.00, + 'cycles' => 1, + ]), + ]), + ]); + + $response = $this->client->subscriptionV2()->create($subscription); + + $this->assertIsObject($response); + $this->assertSame(275, $response->getParsedPath('id')); + $this->assertSame('subscriptions', $response->getParsedPath('resource')); + } + + // ========================================================================= + // UPDATE + // ========================================================================= + + public function testShouldUpdateSubscriptionV2Successfully() + { + $this->instanceClient([ + new Response(200, [], json_encode(array_merge( + $this->mockResponseCreate(), + ['attributes' => array_merge( + $this->mockResponseCreate()['attributes'], + ['is_active' => false, 'amount' => 249.90] + )] + ))) + ]); + + $subscription = new SubscriptionV2([ + 'is_active' => false, + 'amount' => 249.90, + ]); + + $response = $this->client->subscriptionV2()->update($subscription, 275); + + $this->assertIsObject($response); + $this->assertSame(275, $response->getParsedPath('id')); + $this->assertFalse($response->getParsedPath('attributes.is_active')); + } + + // ========================================================================= + // Erros HTTP + // ========================================================================= + + public function testShouldResponseFailUnprocessableDataClient() + { + $this->instanceClient([ + new Response( + 406, + [], + json_encode((object) [ + "code" => "406", + "message" => [ + "profile_id" => ["Profile Id is required"] + ] + ]) + ) + ]); + + try { + $this->client->subscriptionV2()->create(new SubscriptionV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(406, $th->getCode()); + } + } + + public function testShouldResponseFailUnauthenticatedClient() + { + $this->instanceClient([ + new Response( + 401, + [], + json_encode((object) [ + "code" => 401, + "message" => "Unauthorized", + "resource" => "authorization" + ]) + ) + ]); + + try { + $this->client->subscriptionV2()->create(new SubscriptionV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(401, $th->getCode()); + } + } + + public function testShouldResponseFailUnauthorizedClient() + { + $this->instanceClient([ + new Response( + 403, + [], + json_encode((object) [ + "code" => 403, + "message" => "Not Authorized", + "resource" => "subscriptions" + ]) + ) + ]); + + try { + $this->client->subscriptionV2()->create(new SubscriptionV2()); + } catch (\Throwable $th) { + $this->assertInstanceOf(HttpException::class, $th); + $this->assertSame(403, $th->getCode()); + } + } +} diff --git a/tests/Model/PaymentLinkV2Test.php b/tests/Model/PaymentLinkV2Test.php new file mode 100644 index 0000000..e264100 --- /dev/null +++ b/tests/Model/PaymentLinkV2Test.php @@ -0,0 +1,182 @@ + 'PL_V2_001', + 'amount' => 150.00, + 'description' => 'Link de pagamento V2', + 'expires_at' => '05/02/2026 13:35:00', + 'additional_info' => 'Informações adicionais', + 'stock_limit' => 100, + ]); + + $this->assertEquals('PL_V2_001', $paymentLink->getExternalCode()); + $this->assertEquals(150.00, $paymentLink->getAmount()); + $this->assertEquals('Link de pagamento V2', $paymentLink->getDescription()); + $this->assertEquals('05/02/2026 13:35:00', $paymentLink->getExpiresAt()); + $this->assertEquals('Informações adicionais', $paymentLink->getAdditionalInfo()); + $this->assertEquals(100, $paymentLink->getStockLimit()); + $this->assertNull($paymentLink->getCheckoutSettings()); + } + + public function testShouldCreatePaymentLinkV2WithCheckoutSettingsSuccessfully() + { + $paymentLink = new PaymentLinkV2([ + 'external_code' => 'PL_V2_002', + 'amount' => 250.00, + 'description' => 'Link com checkout settings', + 'expires_at' => '31/12/2026 23:59:59', + 'checkout_settings' => [ + 'max_installments' => 12, + 'min_installment_value' => 10.00, + 'interest' => 1.99, + 'interest_free_installments' => 3, + 'fixed_installment' => 0, + 'payment_method' => 'all', + ], + ]); + + $this->assertEquals('PL_V2_002', $paymentLink->getExternalCode()); + $this->assertEquals(250.00, $paymentLink->getAmount()); + $this->assertEquals('Link com checkout settings', $paymentLink->getDescription()); + $this->assertEquals('31/12/2026 23:59:59', $paymentLink->getExpiresAt()); + + $this->assertInstanceOf(CheckoutSettings::class, $paymentLink->getCheckoutSettings()); + $this->assertEquals(12, $paymentLink->getCheckoutSettings()->getMaxInstallments()); + $this->assertEquals(10.00, $paymentLink->getCheckoutSettings()->getMinInstallmentValue()); + $this->assertEquals(1.99, $paymentLink->getCheckoutSettings()->getInterest()); + $this->assertEquals(3, $paymentLink->getCheckoutSettings()->getInterestFreeInstallments()); + $this->assertEquals(0, $paymentLink->getCheckoutSettings()->getFixedInstallment()); + $this->assertEquals('all', $paymentLink->getCheckoutSettings()->getPaymentMethod()); + + $this->assertNull($paymentLink->getAdditionalInfo()); + $this->assertNull($paymentLink->getStockLimit()); + } + + public function testShouldCreatePaymentLinkV2AndSetTheValuesSuccessfully() + { + $checkoutSettings = new CheckoutSettings([ + 'max_installments' => 6, + 'min_installment_value' => 20.00, + 'interest' => 2.50, + 'interest_free_installments' => 2, + 'fixed_installment' => 0, + 'payment_method' => 'creditcard', + ]); + + $paymentLink = (new PaymentLinkV2()) + ->setExternalCode('PL_V2_003') + ->setAmount(300.00) + ->setDescription('Link via setters') + ->setExpiresAt('15/06/2026 18:00:00') + ->setAdditionalInfo('Detalhes do produto') + ->setStockLimit(25) + ->setCheckoutSettings($checkoutSettings); + + $this->assertEquals('PL_V2_003', $paymentLink->getExternalCode()); + $this->assertEquals(300.00, $paymentLink->getAmount()); + $this->assertEquals('Link via setters', $paymentLink->getDescription()); + $this->assertEquals('15/06/2026 18:00:00', $paymentLink->getExpiresAt()); + $this->assertEquals('Detalhes do produto', $paymentLink->getAdditionalInfo()); + $this->assertEquals(25, $paymentLink->getStockLimit()); + + $this->assertInstanceOf(CheckoutSettings::class, $paymentLink->getCheckoutSettings()); + $this->assertEquals(6, $paymentLink->getCheckoutSettings()->getMaxInstallments()); + $this->assertEquals('creditcard', $paymentLink->getCheckoutSettings()->getPaymentMethod()); + } + + public function testShouldCreateEmptyPaymentLinkV2ObjectSuccessfully() + { + $paymentLink = new PaymentLinkV2(); + + $this->assertNull($paymentLink->getExternalCode()); + $this->assertNull($paymentLink->getAmount()); + $this->assertNull($paymentLink->getDescription()); + $this->assertNull($paymentLink->getExpiresAt()); + $this->assertNull($paymentLink->getAdditionalInfo()); + $this->assertNull($paymentLink->getStockLimit()); + $this->assertNull($paymentLink->getCheckoutSettings()); + } + + public function testShouldThrowAMutatorExceptionOnThePaymentLinkV2ExpiresAtPropertyInvalidFormat() + { + $paymentLink = new PaymentLinkV2(); + + $this->expectException(MutatorAttributeException::class); + + $paymentLink->setExpiresAt('2026-02-05 13:35:00'); + } + + public function testShouldThrowAMutatorExceptionOnThePaymentLinkV2ExpiresAtPropertyDateOnly() + { + $paymentLink = new PaymentLinkV2(); + + $this->expectException(MutatorAttributeException::class); + + $paymentLink->setExpiresAt('05/02/2026'); + } + + public function testShouldThrowATypeExceptionOnThePaymentLinkV2StockLimitProperty() + { + $paymentLink = new PaymentLinkV2(); + + $this->expectException(\TypeError::class); + + $paymentLink->setStockLimit('não sou inteiro'); + } + + public function testShouldSerializeOnlyNonNullFieldsSuccessfully() + { + $paymentLink = (new PaymentLinkV2()) + ->setExternalCode('PL_V2_004') + ->setAmount(99.90) + ->setDescription('Link parcial'); + + $serialized = $paymentLink->jsonSerialize(); + + $this->assertArrayHasKey('external_code', $serialized); + $this->assertArrayHasKey('amount', $serialized); + $this->assertArrayHasKey('description', $serialized); + + $this->assertArrayNotHasKey('expires_at', $serialized); + $this->assertArrayNotHasKey('additional_info', $serialized); + $this->assertArrayNotHasKey('stock_limit', $serialized); + $this->assertArrayNotHasKey('checkout_settings', $serialized); + } + + public function testShouldSerializeCheckoutSettingsWhenPresent() + { + $paymentLink = (new PaymentLinkV2()) + ->setExternalCode('PL_V2_005') + ->setAmount(199.00) + ->setDescription('Com checkout') + ->setExpiresAt('01/01/2027 00:00:00') + ->setCheckoutSettings(new CheckoutSettings([ + 'max_installments' => 12, + 'payment_method' => 'pix', + ])); + + $serialized = $paymentLink->jsonSerialize(); + + $this->assertArrayHasKey('checkout_settings', $serialized); + $this->assertEquals(12, $serialized['checkout_settings']['max_installments']); + $this->assertEquals('pix', $serialized['checkout_settings']['payment_method']); + } + + public function testShouldAcceptNullExpiresAt() + { + $paymentLink = new PaymentLinkV2(['expires_at' => null]); + + $this->assertNull($paymentLink->getExpiresAt()); + } +} diff --git a/tests/Model/SubscriptionV2Test.php b/tests/Model/SubscriptionV2Test.php new file mode 100644 index 0000000..030bce2 --- /dev/null +++ b/tests/Model/SubscriptionV2Test.php @@ -0,0 +1,484 @@ + 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Plano para a ralé mensal', + 'is_active' => true, + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'amount' => 200.00, + 'frequency' => 'monthly', + 'interval' => 5, + 'cycles' => 0, + 'customer_id' => 100022, + ]); + + $this->assertEquals('a1b2c3d4-e5f6-7890-abcd-ef1234567890', $subscription->getProfileId()); + $this->assertEquals('Plano para a ralé mensal', $subscription->getDescription()); + $this->assertEquals(true, $subscription->getIsActive()); + $this->assertEquals('https://minhaloja.com/callback', $subscription->getCallbackUrl()); + $this->assertEquals('6a64c8c5-1249-4845-b34a-111b54b1beb2', $subscription->getCardToken()); + $this->assertEquals(200.00, $subscription->getAmount()); + $this->assertEquals('monthly', $subscription->getFrequency()); + $this->assertEquals(5, $subscription->getInterval()); + $this->assertEquals(0, $subscription->getCycles()); + $this->assertEquals(100022, $subscription->getCustomerId()); + + $this->assertEquals('2026-07-10', $subscription->getStartingDate()); + + $this->assertNull($subscription->getCreditCardToken()); + $this->assertNull($subscription->getPlanId()); + $this->assertNull($subscription->getPlan()); + $this->assertNull($subscription->getCustomer()); + } + + /** + * Cenário: plan_id + creditcard_token + customer_id (sem campos inline, sem plan object, sem card_token) + */ + public function testShouldCreateSubscriptionV2WithPlanIdAndCreditcardTokenSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Plano para a ralé mensal', + 'is_active' => true, + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'creditcard_token' => '123abc', + 'plan_id' => 2, + 'customer_id' => 100022, + ]); + + $this->assertEquals('a1b2c3d4-e5f6-7890-abcd-ef1234567890', $subscription->getProfileId()); + $this->assertEquals('Plano para a ralé mensal', $subscription->getDescription()); + $this->assertEquals(true, $subscription->getIsActive()); + $this->assertEquals('https://minhaloja.com/callback', $subscription->getCallbackUrl()); + $this->assertEquals('123abc', $subscription->getCreditCardToken()); + $this->assertEquals(2, $subscription->getPlanId()); + $this->assertEquals(100022, $subscription->getCustomerId()); + + $this->assertEquals('2026-07-10', $subscription->getStartingDate()); + + $this->assertNull($subscription->getCardToken()); + $this->assertNull($subscription->getPlan()); + $this->assertNull($subscription->getCustomer()); + $this->assertNull($subscription->getFrequency()); + $this->assertNull($subscription->getInterval()); + $this->assertNull($subscription->getAmount()); + $this->assertNull($subscription->getCycles()); + } + + /** + * Cenário: plan object + card_token (sem plan_id, sem customer_id, sem creditcard_token) + */ + public function testShouldCreateSubscriptionV2WithPlanObjectSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Plano para a ralé mensal', + 'is_active' => true, + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'customer_id' => 100022, + 'plan' => [ + 'name' => 'Plano da Lojinha Básico', + 'description' => 'Plano para a ralé mensal', + 'amount' => 200.00, + 'frequency' => 'monthly', + 'interval' => 5, + 'cycles' => 0, + 'best_day' => true, + 'pro_rated_charge' => true, + 'grace_period' => 0, + 'callback_url' => 'https://minhaloja.com/callback', + 'trial' => [ + 'amount' => 10.00, + 'cycles' => 1, + ], + ], + ]); + + $this->assertEquals('a1b2c3d4-e5f6-7890-abcd-ef1234567890', $subscription->getProfileId()); + $this->assertEquals('Plano para a ralé mensal', $subscription->getDescription()); + $this->assertEquals(true, $subscription->getIsActive()); + $this->assertEquals('6a64c8c5-1249-4845-b34a-111b54b1beb2', $subscription->getCardToken()); + $this->assertEquals(100022, $subscription->getCustomerId()); + + $this->assertInstanceOf(Plan::class, $subscription->getPlan()); + $this->assertEquals('Plano da Lojinha Básico', $subscription->getPlan()->getName()); + $this->assertEquals(200.00, $subscription->getPlan()->getAmount()); + $this->assertEquals('monthly', $subscription->getPlan()->getFrequency()); + $this->assertEquals(5, $subscription->getPlan()->getInterval()); + $this->assertEquals(0, $subscription->getPlan()->getCycles()); + $this->assertEquals(true, $subscription->getPlan()->getBestDay()); + $this->assertEquals(true, $subscription->getPlan()->getProRatedCharge()); + $this->assertEquals(0, $subscription->getPlan()->getGracePeriod()); + $this->assertEquals(10.00, $subscription->getPlan()->getTrial()->getAmount()); + $this->assertEquals(1, $subscription->getPlan()->getTrial()->getCycles()); + + $this->assertNull($subscription->getCreditCardToken()); + $this->assertNull($subscription->getPlanId()); + } + + /** + * Cenário: setters fluentes com customer_id + card_token + campos inline + */ + public function testShouldCreateSubscriptionV2AndSetTheValuesSuccessfully() + { + $subscription = (new SubscriptionV2()) + ->setProfileId('a1b2c3d4-e5f6-7890-abcd-ef1234567890') + ->setDescription('Plano para a ralé mensal') + ->setIsActive(true) + ->setStartingDate('2026-07-10') + ->setCallbackUrl('https://minhaloja.com/callback') + ->setCardToken('6a64c8c5-1249-4845-b34a-111b54b1beb2') + ->setAmount(200.00) + ->setFrequency('monthly') + ->setInterval(5) + ->setCycles(0) + ->setCustomerId(100022); + + $this->assertEquals('a1b2c3d4-e5f6-7890-abcd-ef1234567890', $subscription->getProfileId()); + $this->assertEquals('Plano para a ralé mensal', $subscription->getDescription()); + $this->assertEquals(true, $subscription->getIsActive()); + $this->assertEquals('https://minhaloja.com/callback', $subscription->getCallbackUrl()); + $this->assertEquals('6a64c8c5-1249-4845-b34a-111b54b1beb2', $subscription->getCardToken()); + $this->assertEquals(200.00, $subscription->getAmount()); + $this->assertEquals('monthly', $subscription->getFrequency()); + $this->assertEquals(5, $subscription->getInterval()); + $this->assertEquals(0, $subscription->getCycles()); + $this->assertEquals(100022, $subscription->getCustomerId()); + + $this->assertEquals('2026-07-10', $subscription->getStartingDate()); + } + + public function testShouldCreateEmptySubscriptionV2ObjectSuccessfully() + { + $subscription = new SubscriptionV2(); + + $this->assertNull($subscription->getProfileId()); + $this->assertNull($subscription->getDescription()); + $this->assertNull($subscription->getIsActive()); + $this->assertNull($subscription->getStartingDate()); + $this->assertNull($subscription->getCallbackUrl()); + $this->assertNull($subscription->getCardToken()); + $this->assertNull($subscription->getCreditCardToken()); + $this->assertNull($subscription->getPlanId()); + $this->assertNull($subscription->getCustomerId()); + $this->assertNull($subscription->getPlan()); + $this->assertNull($subscription->getFrequency()); + $this->assertNull($subscription->getInterval()); + $this->assertNull($subscription->getAmount()); + $this->assertNull($subscription->getCycles()); + $this->assertNull($subscription->getCustomer()); + } + + public function testShouldThrowATypeExceptionOnTheSubscriptionV2PlanIdProperty() + { + $subscription = new SubscriptionV2(); + + $this->expectException(\TypeError::class); + + $subscription->setPlanId('AAA'); + } + + public function testShouldThrowATypeExceptionOnTheSubscriptionV2CustomerIdProperty() + { + $subscription = new SubscriptionV2(); + + $this->expectException(\TypeError::class); + + $subscription->setCustomerId('AAA'); + } + + public function testShouldThrowATypeExceptionOnTheSubscriptionV2IntervalProperty() + { + $subscription = new SubscriptionV2(); + + $this->expectException(\TypeError::class); + + $subscription->setInterval('AAA'); + } + + public function testShouldThrowATypeExceptionOnTheSubscriptionV2CyclesProperty() + { + $subscription = new SubscriptionV2(); + + $this->expectException(\TypeError::class); + + $subscription->setCycles('AAA'); + } + + public function testShouldThrowAMutatorExceptionOnTheSubscriptionV2StartingDateProperty() + { + $subscription = new SubscriptionV2(); + + $this->expectException(MutatorAttributeException::class); + + $subscription->setStartingDate('10/07/2026'); + } + + public function testShouldSerializeOnlyNonNullFieldsSuccessfully() + { + $subscription = (new SubscriptionV2()) + ->setDescription('Plano para a ralé mensal') + ->setCardToken('6a64c8c5-1249-4845-b34a-111b54b1beb2') + ->setCustomerId(100022); + + $serialized = $subscription->jsonSerialize(); + + $this->assertArrayHasKey('description', $serialized); + $this->assertArrayHasKey('card_token', $serialized); + $this->assertArrayHasKey('customer_id', $serialized); + + $this->assertArrayNotHasKey('profile_id', $serialized); + $this->assertArrayNotHasKey('creditcard_token', $serialized); + $this->assertArrayNotHasKey('plan_id', $serialized); + $this->assertArrayNotHasKey('plan', $serialized); + $this->assertArrayNotHasKey('customer', $serialized); + $this->assertArrayNotHasKey('frequency', $serialized); + $this->assertArrayNotHasKey('interval', $serialized); + $this->assertArrayNotHasKey('amount', $serialized); + $this->assertArrayNotHasKey('cycles', $serialized); + } + + /** + * Cenário: customer object via constructor (relação has) + */ + public function testShouldCreateSubscriptionV2WithCustomerObjectSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Assinatura com customer object', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'customer' => [ + 'name' => 'João da Silva', + 'email' => 'joao@email.com', + 'cpf_cnpj' => '12345678909', + 'phone' => '11999999999', + ], + ]); + + $this->assertInstanceOf(Customer::class, $subscription->getCustomer()); + $this->assertEquals('João da Silva', $subscription->getCustomer()->getName()); + $this->assertEquals('joao@email.com', $subscription->getCustomer()->getEmail()); + $this->assertEquals('12345678909', $subscription->getCustomer()->getCpfCnpj()); + $this->assertEquals('11999999999', $subscription->getCustomer()->getPhone()); + + $this->assertNull($subscription->getCustomerId()); + } + + /** + * Cenário: customer object via setter (relação has) + */ + public function testShouldCreateSubscriptionV2WithCustomerObjectViaSetterSuccessfully() + { + $customer = new Customer([ + 'name' => 'Maria Souza', + 'email' => 'maria@email.com', + 'cpf_cnpj' => '98765432100', + ]); + + $subscription = (new SubscriptionV2()) + ->setDescription('Assinatura via setter') + ->setCardToken('6a64c8c5-1249-4845-b34a-111b54b1beb2') + ->setCustomer($customer); + + $this->assertInstanceOf(Customer::class, $subscription->getCustomer()); + $this->assertEquals('Maria Souza', $subscription->getCustomer()->getName()); + $this->assertEquals('maria@email.com', $subscription->getCustomer()->getEmail()); + $this->assertEquals('98765432100', $subscription->getCustomer()->getCpfCnpj()); + } + + /** + * Cenário: plan object via setter (relação has) + */ + public function testShouldCreateSubscriptionV2WithPlanObjectViaSetterSuccessfully() + { + $plan = new Plan([ + 'name' => 'Plano Premium', + 'description' => 'Plano premium mensal', + 'amount' => 99.90, + 'frequency' => 'monthly', + 'interval' => 1, + 'cycles' => 12, + 'trial' => [ + 'amount' => 0.00, + 'cycles' => 1, + ], + ]); + + $subscription = (new SubscriptionV2()) + ->setDescription('Assinatura com plano via setter') + ->setCardToken('6a64c8c5-1249-4845-b34a-111b54b1beb2') + ->setCustomerId(100022) + ->setPlan($plan); + + $this->assertInstanceOf(Plan::class, $subscription->getPlan()); + $this->assertEquals('Plano Premium', $subscription->getPlan()->getName()); + $this->assertEquals(99.90, $subscription->getPlan()->getAmount()); + $this->assertEquals('monthly', $subscription->getPlan()->getFrequency()); + $this->assertEquals(1, $subscription->getPlan()->getInterval()); + $this->assertEquals(12, $subscription->getPlan()->getCycles()); + $this->assertInstanceOf(PlanTrial::class, $subscription->getPlan()->getTrial()); + $this->assertEquals(0.00, $subscription->getPlan()->getTrial()->getAmount()); + $this->assertEquals(1, $subscription->getPlan()->getTrial()->getCycles()); + } + + /** + * Cenário: customer object com address aninhado (relação has -> has) + */ + public function testShouldCreateSubscriptionV2WithCustomerAndAddressObjectSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Assinatura com customer e address', + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'customer' => [ + 'name' => 'Gabriel César Brusarrosco', + 'cpf_cnpj' => '51526092077', + 'email' => 'gabriel.brusarrosco@ipag.com.br', + 'phone' => '18997512082', + 'address' => [ + 'street' => 'Rua Gino Piron', + 'number' => '697', + 'district' => 'Jd. Vale do Sol', + 'complement' => '', + 'city' => 'Presidente Prudente', + 'state' => 'SP', + 'zipcode' => '19030-130', + ], + ], + ]); + + $this->assertInstanceOf(Customer::class, $subscription->getCustomer()); + $this->assertEquals('Gabriel César Brusarrosco', $subscription->getCustomer()->getName()); + $this->assertEquals('51526092077', $subscription->getCustomer()->getCpfCnpj()); + $this->assertEquals('gabriel.brusarrosco@ipag.com.br', $subscription->getCustomer()->getEmail()); + $this->assertEquals('18997512082', $subscription->getCustomer()->getPhone()); + + $this->assertNotNull($subscription->getCustomer()->getAddress()); + $this->assertEquals('Rua Gino Piron', $subscription->getCustomer()->getAddress()->getStreet()); + $this->assertEquals('697', $subscription->getCustomer()->getAddress()->getNumber()); + $this->assertEquals('Jd. Vale do Sol', $subscription->getCustomer()->getAddress()->getDistrict()); + $this->assertEquals('Presidente Prudente', $subscription->getCustomer()->getAddress()->getCity()); + $this->assertEquals('SP', $subscription->getCustomer()->getAddress()->getState()); + $this->assertEquals('19030130', $subscription->getCustomer()->getAddress()->getZipcode()); + } + + /** + * Cenário: plan object com todos os campos do validator (installments, callback_url) + */ + public function testShouldCreateSubscriptionV2WithFullPlanObjectSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'customer_id' => 100022, + 'plan' => [ + 'name' => 'Plano Completo', + 'description' => 'Plano com todos os campos', + 'amount' => 500.00, + 'frequency' => 'monthly', + 'interval' => 1, + 'cycles' => 12, + 'best_day' => true, + 'pro_rated_charge' => false, + 'grace_period' => 5, + 'callback_url' => 'https://webhook.site/callback', + 'installments' => 3, + 'trial' => [ + 'amount' => 0.00, + 'cycles' => 2, + ], + ], + ]); + + $plan = $subscription->getPlan(); + $this->assertInstanceOf(Plan::class, $plan); + $this->assertEquals('Plano Completo', $plan->getName()); + $this->assertEquals('Plano com todos os campos', $plan->getDescription()); + $this->assertEquals(500.00, $plan->getAmount()); + $this->assertEquals('monthly', $plan->getFrequency()); + $this->assertEquals(1, $plan->getInterval()); + $this->assertEquals(12, $plan->getCycles()); + $this->assertEquals(true, $plan->getBestDay()); + $this->assertEquals(false, $plan->getProRatedCharge()); + $this->assertEquals(5, $plan->getGracePeriod()); + $this->assertEquals('https://webhook.site/callback', $plan->getCallbackUrl()); + $this->assertEquals(3, $plan->getInstallments()); + $this->assertInstanceOf(PlanTrial::class, $plan->getTrial()); + $this->assertEquals(0.00, $plan->getTrial()->getAmount()); + $this->assertEquals(2, $plan->getTrial()->getCycles()); + } + + /** + * Cenário: preencher campos e depois setar null (nullable) + */ + public function testShouldCreateAndSetNullPropertiesSubscriptionV2ObjectSuccessfully() + { + $subscription = new SubscriptionV2([ + 'profile_id' => 'a1b2c3d4-e5f6-7890-abcd-ef1234567890', + 'description' => 'Plano para a ralé mensal', + 'is_active' => true, + 'starting_date' => '2026-07-10', + 'callback_url' => 'https://minhaloja.com/callback', + 'card_token' => '6a64c8c5-1249-4845-b34a-111b54b1beb2', + 'amount' => 200.00, + 'frequency' => 'monthly', + 'interval' => 5, + 'cycles' => 0, + 'customer_id' => 100022, + ]); + + $subscription + ->setProfileId(null) + ->setDescription(null) + ->setIsActive(null) + ->setStartingDate(null) + ->setCallbackUrl(null) + ->setCardToken(null) + ->setCreditCardToken(null) + ->setAmount(null) + ->setFrequency(null) + ->setInterval(null) + ->setCycles(null) + ->setCustomerId(null) + ->setPlanId(null); + + $this->assertNull($subscription->getProfileId()); + $this->assertNull($subscription->getDescription()); + $this->assertNull($subscription->getIsActive()); + $this->assertNull($subscription->getStartingDate()); + $this->assertNull($subscription->getCallbackUrl()); + $this->assertNull($subscription->getCardToken()); + $this->assertNull($subscription->getCreditCardToken()); + $this->assertNull($subscription->getAmount()); + $this->assertNull($subscription->getFrequency()); + $this->assertNull($subscription->getInterval()); + $this->assertNull($subscription->getCycles()); + $this->assertNull($subscription->getCustomerId()); + $this->assertNull($subscription->getPlanId()); + } +}