From f20acc90615f6ccfc2e592970c907caeb4faebcb Mon Sep 17 00:00:00 2001 From: Michel Bade Date: Fri, 19 Jan 2024 12:45:52 +0100 Subject: [PATCH 1/2] BRAIN-23 - Added turnover reporting --- .platform.app.yaml | 8 + composer.json | 20 +- composer.lock | 869 +++++++++++------- infection.json5 | 1 + ...sion20240118141652AddTransactionReport.php | 33 + phpunit.xml.dist | 3 +- .../Payment/BraintreePaymentService.php | 34 +- src/Braintree/Util/ReportService.php | 83 ++ src/Command/ReportTurnoverCommand.php | 43 + src/Entity/TransactionEntity.php | 15 + src/Entity/TransactionReportEntity.php | 58 ++ .../TransactionReportRepository.php | 22 + .../Payment/BraintreePaymentServiceTest.php | 38 +- .../unit/Braintree/Util/ReportServiceTest.php | 173 ++++ .../TransactionReportRepositoryTest.php | 47 + 15 files changed, 1093 insertions(+), 354 deletions(-) create mode 100644 migrations/Version20240118141652AddTransactionReport.php create mode 100644 src/Braintree/Util/ReportService.php create mode 100644 src/Command/ReportTurnoverCommand.php create mode 100644 src/Entity/TransactionReportEntity.php create mode 100644 src/Repository/TransactionReportRepository.php create mode 100644 tests/unit/Braintree/Util/ReportServiceTest.php create mode 100644 tests/unit/Repository/TransactionReportRepositoryTest.php diff --git a/.platform.app.yaml b/.platform.app.yaml index 38c9e87..abf824c 100644 --- a/.platform.app.yaml +++ b/.platform.app.yaml @@ -27,6 +27,14 @@ variables: CORS_ALLOW_ORIGIN: '*' DATABASE_URL: mysql://user:@database.internal:3306/main +crons: + snapshot: + spec: 0 0 * * * + cmd: | + # only run for the production environment, aka trunk + if [ "$PLATFORM_ENVIRONMENT_TYPE" = "production" ]; then + croncape bin/console report:turnover + fi # The hooks that will be performed when the package is deployed. hooks: diff --git a/composer.json b/composer.json index 535f16d..7284cda 100644 --- a/composer.json +++ b/composer.json @@ -11,21 +11,23 @@ "doctrine/doctrine-bundle": "^2.10", "doctrine/doctrine-migrations-bundle": "^3.2", "doctrine/orm": "^2.16", + "guzzlehttp/guzzle": "^7.8", "guzzlehttp/psr7": "^2.6", "nelmio/cors-bundle": "^2.3", "shopware/app-bundle": "^2.0", - "symfony/console": "7.0.*", - "symfony/dotenv": "7.0.*", - "symfony/http-foundation": "7.0.*", + "symfony/console": "^7.0", + "symfony/dotenv": "^7.0", + "symfony/http-foundation": "^7.0", "symfony/flex": "^2", - "symfony/framework-bundle": "7.0.*", + "symfony/framework-bundle": "^7.0", "symfony/monolog-bundle": "^3.8", - "symfony/property-access": "7.0.*", - "symfony/runtime": "7.0.*", - "symfony/serializer": "7.0.*", - "symfony/uid": "7.0.*", + "symfony/property-access": "^7.0", + "symfony/psr-http-message-bridge": "^7.0", + "symfony/runtime": "^7.0", + "symfony/serializer": "^7.0", + "symfony/uid": "^7.0", "symfony/webpack-encore-bundle": "^2.0", - "symfony/yaml": "7.0.*" + "symfony/yaml": "^7.0" }, "config": { "allow-plugins": { diff --git a/composer.lock b/composer.lock index cb64c29..f3130fe 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": "4bd7f67f98c6ba8778dc21ede5cd91ce", + "content-hash": "eadd4783150d67483c1279b64f90bc6f", "packages": [ { "name": "braintree/braintree_php", @@ -1378,6 +1378,215 @@ }, "time": "2022-05-23T21:33:49+00:00" }, + { + "name": "guzzlehttp/guzzle", + "version": "7.8.1", + "source": { + "type": "git", + "url": "https://github.com/guzzle/guzzle.git", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", + "shasum": "" + }, + "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0.1", + "guzzlehttp/psr7": "^1.9.1 || ^2.5.1", + "php": "^7.2.5 || ^8.0", + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" + }, + "provide": { + "psr/http-client-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", + "psr/log": "^1.1 || ^2.0 || ^3.0" + }, + "suggest": { + "ext-curl": "Required for CURL handler support", + "ext-intl": "Required for Internationalized Domain Name (IDN) support", + "psr/log": "Required for using the Log middleware" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "files": [ + "src/functions_include.php" + ], + "psr-4": { + "GuzzleHttp\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Jeremy Lindblom", + "email": "jeremeamia@gmail.com", + "homepage": "https://github.com/jeremeamia" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle is a PHP HTTP client library", + "keywords": [ + "client", + "curl", + "framework", + "http", + "http client", + "psr-18", + "psr-7", + "rest", + "web service" + ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:35:24+00:00" + }, + { + "name": "guzzlehttp/promises", + "version": "2.0.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/promises.git", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/promises/zipball/bbff78d96034045e58e13dedd6ad91b5d1253223", + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Promise\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + } + ], + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], + "support": { + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/promises", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:19:20+00:00" + }, { "name": "guzzlehttp/psr7", "version": "2.6.2", @@ -2643,31 +2852,31 @@ }, { "name": "symfony/cache", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "378e30a864c868d635353f103a5a5e7569f029ec" + "reference": "14a75869bbb41cb35bc5d9d322473928c6f3f978" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/378e30a864c868d635353f103a5a5e7569f029ec", - "reference": "378e30a864c868d635353f103a5a5e7569f029ec", + "url": "https://api.github.com/repos/symfony/cache/zipball/14a75869bbb41cb35bc5d9d322473928c6f3f978", + "reference": "14a75869bbb41cb35bc5d9d322473928c6f3f978", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "psr/cache": "^2.0|^3.0", "psr/log": "^1.1|^2|^3", "symfony/cache-contracts": "^2.5|^3", "symfony/service-contracts": "^2.5|^3", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^6.3.6|^7.0" }, "conflict": { - "doctrine/dbal": "<3.6", - "symfony/dependency-injection": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/var-dumper": "<6.4" + "doctrine/dbal": "<2.13.1", + "symfony/dependency-injection": "<5.4", + "symfony/http-kernel": "<5.4", + "symfony/var-dumper": "<5.4" }, "provide": { "psr/cache-implementation": "2.0|3.0", @@ -2676,15 +2885,15 @@ }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^2.13.1|^3|^4", "predis/predis": "^1.1|^2.0", "psr/simple-cache": "^1.0|^2.0|^3.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/filesystem": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2719,7 +2928,7 @@ "psr6" ], "support": { - "source": "https://github.com/symfony/cache/tree/v7.0.2" + "source": "https://github.com/symfony/cache/tree/v6.4.2" }, "funding": [ { @@ -2735,7 +2944,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2023-12-29T15:34:34+00:00" }, { "name": "symfony/cache-contracts", @@ -2815,34 +3024,34 @@ }, { "name": "symfony/config", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/config.git", - "reference": "8789646600f4e7e451dde9e1dc81cfa429f3857a" + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/config/zipball/8789646600f4e7e451dde9e1dc81cfa429f3857a", - "reference": "8789646600f4e7e451dde9e1dc81cfa429f3857a", + "url": "https://api.github.com/repos/symfony/config/zipball/5d33e0fb707d603330e0edfd4691803a1253572e", + "reference": "5d33e0fb707d603330e0edfd4691803a1253572e", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/filesystem": "^6.4|^7.0", + "symfony/filesystem": "^5.4|^6.0|^7.0", "symfony/polyfill-ctype": "~1.8" }, "conflict": { - "symfony/finder": "<6.4", + "symfony/finder": "<5.4", "symfony/service-contracts": "<2.5" }, "require-dev": { - "symfony/event-dispatcher": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/yaml": "^6.4|^7.0" + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2870,7 +3079,7 @@ "description": "Helps you find, load, combine, autofill and validate configuration values of any kind", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/config/tree/v7.0.0" + "source": "https://github.com/symfony/config/tree/v6.4.0" }, "funding": [ { @@ -2886,50 +3095,51 @@ "type": "tidelift" } ], - "time": "2023-11-09T08:30:23+00:00" + "time": "2023-11-09T08:28:32+00:00" }, { "name": "symfony/console", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c" + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/f8587c4cdc5acad67af71c37db34ef03af91e59c", - "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c", + "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", + "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^6.4|^7.0" + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { - "symfony/dependency-injection": "<6.4", - "symfony/dotenv": "<6.4", - "symfony/event-dispatcher": "<6.4", - "symfony/lock": "<6.4", - "symfony/process": "<6.4" + "symfony/dependency-injection": "<5.4", + "symfony/dotenv": "<5.4", + "symfony/event-dispatcher": "<5.4", + "symfony/lock": "<5.4", + "symfony/process": "<5.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/lock": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -2963,7 +3173,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v7.0.2" + "source": "https://github.com/symfony/console/tree/v6.4.2" }, "funding": [ { @@ -2979,43 +3189,44 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:54:46+00:00" + "time": "2023-12-10T16:15:48+00:00" }, { "name": "symfony/dependency-injection", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/dependency-injection.git", - "reference": "bd25ef7c937b9da12510bdc4f1c66728f19620e3" + "reference": "226ea431b1eda6f0d9f5a4b278757171960bb195" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/bd25ef7c937b9da12510bdc4f1c66728f19620e3", - "reference": "bd25ef7c937b9da12510bdc4f1c66728f19620e3", + "url": "https://api.github.com/repos/symfony/dependency-injection/zipball/226ea431b1eda6f0d9f5a4b278757171960bb195", + "reference": "226ea431b1eda6f0d9f5a4b278757171960bb195", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "psr/container": "^1.1|^2.0", "symfony/deprecation-contracts": "^2.5|^3", - "symfony/service-contracts": "^3.3", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/service-contracts": "^2.5|^3.0", + "symfony/var-exporter": "^6.2.10|^7.0" }, "conflict": { "ext-psr": "<1.1|>=2", - "symfony/config": "<6.4", - "symfony/finder": "<6.4", - "symfony/yaml": "<6.4" + "symfony/config": "<6.1", + "symfony/finder": "<5.4", + "symfony/proxy-manager-bridge": "<6.3", + "symfony/yaml": "<5.4" }, "provide": { "psr/container-implementation": "1.1|2.0", "symfony/service-implementation": "1.1|2.0|3.0" }, "require-dev": { - "symfony/config": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^6.1|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3043,7 +3254,7 @@ "description": "Allows you to standardize and centralize the way objects are constructed in your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/dependency-injection/tree/v7.0.2" + "source": "https://github.com/symfony/dependency-injection/tree/v6.4.2" }, "funding": [ { @@ -3059,7 +3270,7 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:18:20+00:00" + "time": "2023-12-28T19:16:56+00:00" }, { "name": "symfony/deprecation-contracts", @@ -3130,65 +3341,67 @@ }, { "name": "symfony/doctrine-bridge", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/doctrine-bridge.git", - "reference": "9c0ce8ff41c25fbee07cd3235e9d6f0d6505b8b3" + "reference": "da33f27c1dd9946afecfd1585b867551df71bf53" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/9c0ce8ff41c25fbee07cd3235e9d6f0d6505b8b3", - "reference": "9c0ce8ff41c25fbee07cd3235e9d6f0d6505b8b3", + "url": "https://api.github.com/repos/symfony/doctrine-bridge/zipball/da33f27c1dd9946afecfd1585b867551df71bf53", + "reference": "da33f27c1dd9946afecfd1585b867551df71bf53", "shasum": "" }, "require": { - "doctrine/event-manager": "^2", + "doctrine/event-manager": "^1.2|^2", "doctrine/persistence": "^3.1", - "php": ">=8.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3" }, "conflict": { - "doctrine/dbal": "<3.6", + "doctrine/dbal": "<2.13.1", "doctrine/lexer": "<1.1", "doctrine/orm": "<2.15", - "symfony/cache": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/form": "<6.4", - "symfony/http-foundation": "<6.4", - "symfony/http-kernel": "<6.4", - "symfony/lock": "<6.4", - "symfony/messenger": "<6.4", - "symfony/property-info": "<6.4", - "symfony/security-bundle": "<6.4", + "symfony/cache": "<5.4", + "symfony/dependency-injection": "<6.2", + "symfony/form": "<5.4.21|>=6,<6.2.7", + "symfony/http-foundation": "<6.3", + "symfony/http-kernel": "<6.2", + "symfony/lock": "<6.3", + "symfony/messenger": "<5.4", + "symfony/property-info": "<5.4", + "symfony/security-bundle": "<5.4", "symfony/security-core": "<6.4", "symfony/validator": "<6.4" }, "require-dev": { "doctrine/collections": "^1.0|^2.0", "doctrine/data-fixtures": "^1.1", - "doctrine/dbal": "^3.6|^4", + "doctrine/dbal": "^2.13.1|^3|^4", "doctrine/orm": "^2.15|^3", "psr/log": "^1|^2|^3", - "symfony/cache": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/doctrine-messenger": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/property-info": "^6.4|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^6.2|^7.0", + "symfony/doctrine-messenger": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4.21|^6.2.7|^7.0", + "symfony/http-kernel": "^6.3|^7.0", + "symfony/lock": "^6.3|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4|^6.0|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/proxy-manager-bridge": "^6.4", "symfony/security-core": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "symfony-bridge", "autoload": { @@ -3216,7 +3429,7 @@ "description": "Provides integration for Doctrine with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/doctrine-bridge/tree/v7.0.2" + "source": "https://github.com/symfony/doctrine-bridge/tree/v6.4.2" }, "funding": [ { @@ -3232,7 +3445,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T08:42:13+00:00" + "time": "2023-12-27T00:32:33+00:00" }, { "name": "symfony/dotenv", @@ -3310,22 +3523,22 @@ }, { "name": "symfony/error-handler", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "80b1258be1b84c12a345d0ec3881bbf2e5270cc2" + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/80b1258be1b84c12a345d0ec3881bbf2e5270cc2", - "reference": "80b1258be1b84c12a345d0ec3881bbf2e5270cc2", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", + "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", - "symfony/var-dumper": "^6.4|^7.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "conflict": { "symfony/deprecation-contracts": "<2.5", @@ -3334,7 +3547,7 @@ "require-dev": { "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-kernel": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0" + "symfony/serializer": "^5.4|^6.0|^7.0" }, "bin": [ "Resources/bin/patch-type-declarations" @@ -3365,7 +3578,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v7.0.0" + "source": "https://github.com/symfony/error-handler/tree/v6.4.0" }, "funding": [ { @@ -3381,28 +3594,28 @@ "type": "tidelift" } ], - "time": "2023-10-20T16:35:23+00:00" + "time": "2023-10-18T09:43:34+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a" + "reference": "e95216850555cd55e71b857eb9d6c2674124603a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/098b62ae81fdd6cbf941f355059f617db28f4f9a", - "reference": "098b62ae81fdd6cbf941f355059f617db28f4f9a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e95216850555cd55e71b857eb9d6c2674124603a", + "reference": "e95216850555cd55e71b857eb9d6c2674124603a", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<6.4", + "symfony/dependency-injection": "<5.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -3411,13 +3624,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/error-handler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/config": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3445,7 +3658,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.2" }, "funding": [ { @@ -3461,7 +3674,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:24:19+00:00" + "time": "2023-12-27T22:16:42+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -3541,20 +3754,20 @@ }, { "name": "symfony/filesystem", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7" + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/7da8ea2362a283771478c5f7729cfcb43a76b8b7", - "reference": "7da8ea2362a283771478c5f7729cfcb43a76b8b7", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/952a8cb588c3bc6ce76f6023000fb932f16a6e59", + "reference": "952a8cb588c3bc6ce76f6023000fb932f16a6e59", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-mbstring": "~1.8" }, @@ -3584,7 +3797,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.0" + "source": "https://github.com/symfony/filesystem/tree/v6.4.0" }, "funding": [ { @@ -3600,27 +3813,27 @@ "type": "tidelift" } ], - "time": "2023-07-27T06:33:22+00:00" + "time": "2023-07-26T17:27:13+00:00" }, { "name": "symfony/finder", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/finder.git", - "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56" + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", - "reference": "6e5688d69f7cfc4ed4a511e96007e06c2d34ce56", + "url": "https://api.github.com/repos/symfony/finder/zipball/11d736e97f116ac375a81f96e662911a34cd50ce", + "reference": "11d736e97f116ac375a81f96e662911a34cd50ce", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "require-dev": { - "symfony/filesystem": "^6.4|^7.0" + "symfony/filesystem": "^6.0|^7.0" }, "type": "library", "autoload": { @@ -3648,7 +3861,7 @@ "description": "Finds files and directories via an intuitive fluent interface", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/finder/tree/v7.0.0" + "source": "https://github.com/symfony/finder/tree/v6.4.0" }, "funding": [ { @@ -3664,7 +3877,7 @@ "type": "tidelift" } ], - "time": "2023-10-31T17:59:56+00:00" + "time": "2023-10-31T17:30:12+00:00" }, { "name": "symfony/flex", @@ -3879,27 +4092,28 @@ }, { "name": "symfony/http-client", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "db714986d3b84330bb6196fdb201c9f79b3a8853" + "reference": "fc0944665bd932cf32a7b8a1d009466afc16528f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/db714986d3b84330bb6196fdb201c9f79b3a8853", - "reference": "db714986d3b84330bb6196fdb201c9f79b3a8853", + "url": "https://api.github.com/repos/symfony/http-client/zipball/fc0944665bd932cf32a7b8a1d009466afc16528f", + "reference": "fc0944665bd932cf32a7b8a1d009466afc16528f", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/http-client-contracts": "^3", "symfony/service-contracts": "^2.5|^3" }, "conflict": { "php-http/discovery": "<1.15", - "symfony/http-foundation": "<6.4" + "symfony/http-foundation": "<6.3" }, "provide": { "php-http/async-client-implementation": "*", @@ -3916,11 +4130,11 @@ "nyholm/psr7": "^1.0", "php-http/httplug": "^1.0|^2.0", "psr/http-client": "^1.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/messenger": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0" + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/messenger": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -3951,7 +4165,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.0.2" + "source": "https://github.com/symfony/http-client/tree/v6.4.2" }, "funding": [ { @@ -3967,7 +4181,7 @@ "type": "tidelift" } ], - "time": "2023-12-02T12:51:19+00:00" + "time": "2023-12-02T12:49:56+00:00" }, { "name": "symfony/http-client-contracts", @@ -4126,71 +4340,72 @@ }, { "name": "symfony/http-kernel", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "237d3008bc3f5db3e066e348dc0a6435d70a52bb" + "reference": "13e8387320b5942d0dc408440c888e2d526efef4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/237d3008bc3f5db3e066e348dc0a6435d70a52bb", - "reference": "237d3008bc3f5db3e066e348dc0a6435d70a52bb", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/13e8387320b5942d0dc408440c888e2d526efef4", + "reference": "13e8387320b5942d0dc408440c888e2d526efef4", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "psr/log": "^1|^2|^3", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/error-handler": "^6.4|^7.0", - "symfony/event-dispatcher": "^6.4|^7.0", + "symfony/event-dispatcher": "^5.4|^6.0|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/browser-kit": "<6.4", - "symfony/cache": "<6.4", - "symfony/config": "<6.4", - "symfony/console": "<6.4", + "symfony/browser-kit": "<5.4", + "symfony/cache": "<5.4", + "symfony/config": "<6.1", + "symfony/console": "<5.4", "symfony/dependency-injection": "<6.4", - "symfony/doctrine-bridge": "<6.4", - "symfony/form": "<6.4", - "symfony/http-client": "<6.4", + "symfony/doctrine-bridge": "<5.4", + "symfony/form": "<5.4", + "symfony/http-client": "<5.4", "symfony/http-client-contracts": "<2.5", - "symfony/mailer": "<6.4", - "symfony/messenger": "<6.4", - "symfony/translation": "<6.4", + "symfony/mailer": "<5.4", + "symfony/messenger": "<5.4", + "symfony/translation": "<5.4", "symfony/translation-contracts": "<2.5", - "symfony/twig-bridge": "<6.4", + "symfony/twig-bridge": "<5.4", "symfony/validator": "<6.4", - "symfony/var-dumper": "<6.4", - "twig/twig": "<3.0.4" + "symfony/var-dumper": "<6.3", + "twig/twig": "<2.13" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/cache": "^1.0|^2.0|^3.0", - "symfony/browser-kit": "^6.4|^7.0", - "symfony/clock": "^6.4|^7.0", - "symfony/config": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/css-selector": "^6.4|^7.0", + "symfony/browser-kit": "^5.4|^6.0|^7.0", + "symfony/clock": "^6.2|^7.0", + "symfony/config": "^6.1|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/css-selector": "^5.4|^6.0|^7.0", "symfony/dependency-injection": "^6.4|^7.0", - "symfony/dom-crawler": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/dom-crawler": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", "symfony/http-client-contracts": "^2.5|^3", - "symfony/process": "^6.4|^7.0", - "symfony/property-access": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/serializer": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/property-access": "^5.4.5|^6.0.5|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/serializer": "^6.3|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3", - "symfony/uid": "^6.4|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", "symfony/validator": "^6.4|^7.0", - "symfony/var-exporter": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "symfony/var-exporter": "^6.2|^7.0", + "twig/twig": "^2.13|^3.0.4" }, "type": "library", "autoload": { @@ -4218,7 +4433,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v7.0.2" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.2" }, "funding": [ { @@ -4234,7 +4449,7 @@ "type": "tidelift" } ], - "time": "2023-12-30T15:41:17+00:00" + "time": "2023-12-30T15:31:44+00:00" }, { "name": "symfony/monolog-bridge", @@ -4884,33 +5099,33 @@ }, { "name": "symfony/property-info", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/property-info.git", - "reference": "ce627df05f5629ce4feec536ee827ad0a12689b6" + "reference": "288be71bae2ebc88676f5d3a03d23f70b278fcc1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/property-info/zipball/ce627df05f5629ce4feec536ee827ad0a12689b6", - "reference": "ce627df05f5629ce4feec536ee827ad0a12689b6", + "url": "https://api.github.com/repos/symfony/property-info/zipball/288be71bae2ebc88676f5d3a03d23f70b278fcc1", + "reference": "288be71bae2ebc88676f5d3a03d23f70b278fcc1", "shasum": "" }, "require": { - "php": ">=8.2", - "symfony/string": "^6.4|^7.0" + "php": ">=8.1", + "symfony/string": "^5.4|^6.0|^7.0" }, "conflict": { "phpdocumentor/reflection-docblock": "<5.2", "phpdocumentor/type-resolver": "<1.5.1", - "symfony/dependency-injection": "<6.4", + "symfony/dependency-injection": "<5.4", "symfony/serializer": "<6.4" }, "require-dev": { "phpdocumentor/reflection-docblock": "^5.2", "phpstan/phpdoc-parser": "^1.0", - "symfony/cache": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.4|^7.0" }, "type": "library", @@ -4947,7 +5162,7 @@ "validator" ], "support": { - "source": "https://github.com/symfony/property-info/tree/v7.0.0" + "source": "https://github.com/symfony/property-info/tree/v6.4.0" }, "funding": [ { @@ -4963,7 +5178,7 @@ "type": "tidelift" } ], - "time": "2023-11-25T08:38:27+00:00" + "time": "2023-11-25T16:57:46+00:00" }, { "name": "symfony/psr-http-message-bridge", @@ -5050,34 +5265,36 @@ }, { "name": "symfony/routing", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "78866be67255f42716271e33d1d8b64eb6e47bd9" + "reference": "98eab13a07fddc85766f1756129c69f207ffbc21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/78866be67255f42716271e33d1d8b64eb6e47bd9", - "reference": "78866be67255f42716271e33d1d8b64eb6e47bd9", + "url": "https://api.github.com/repos/symfony/routing/zipball/98eab13a07fddc85766f1756129c69f207ffbc21", + "reference": "98eab13a07fddc85766f1756129c69f207ffbc21", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3" }, "conflict": { - "symfony/config": "<6.4", - "symfony/dependency-injection": "<6.4", - "symfony/yaml": "<6.4" + "doctrine/annotations": "<1.12", + "symfony/config": "<6.2", + "symfony/dependency-injection": "<5.4", + "symfony/yaml": "<5.4" }, "require-dev": { + "doctrine/annotations": "^1.12|^2", "psr/log": "^1|^2|^3", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/config": "^6.2|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -5111,7 +5328,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v7.0.2" + "source": "https://github.com/symfony/routing/tree/v6.4.2" }, "funding": [ { @@ -5127,7 +5344,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:37:40+00:00" + "time": "2023-12-29T15:34:34+00:00" }, { "name": "symfony/runtime", @@ -5449,20 +5666,20 @@ }, { "name": "symfony/string", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5" + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/cc78f14f91f5e53b42044d0620961c48028ff9f5", - "reference": "cc78f14f91f5e53b42044d0620961c48028ff9f5", + "url": "https://api.github.com/repos/symfony/string/zipball/7cb80bc10bfcdf6b5492741c0b9357dac66940bc", + "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -5472,11 +5689,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^6.4|^7.0", - "symfony/http-client": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", + "symfony/error-handler": "^5.4|^6.0|^7.0", + "symfony/http-client": "^5.4|^6.0|^7.0", + "symfony/intl": "^6.2|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^6.4|^7.0" + "symfony/var-exporter": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -5515,7 +5732,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v7.0.2" + "source": "https://github.com/symfony/string/tree/v6.4.2" }, "funding": [ { @@ -5531,7 +5748,7 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:54:46+00:00" + "time": "2023-12-10T16:15:48+00:00" }, { "name": "symfony/uid", @@ -5609,32 +5826,34 @@ }, { "name": "symfony/var-dumper", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "5f6f1a527002068f6d40fda068399220eabebf71" + "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/5f6f1a527002068f6d40fda068399220eabebf71", - "reference": "5f6f1a527002068f6d40fda068399220eabebf71", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", + "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/polyfill-mbstring": "~1.0" }, "conflict": { - "symfony/console": "<6.4" + "symfony/console": "<5.4" }, "require-dev": { "ext-iconv": "*", - "symfony/console": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/process": "^6.4|^7.0", - "symfony/uid": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/error-handler": "^6.3|^7.0", + "symfony/http-kernel": "^5.4|^6.0|^7.0", + "symfony/process": "^5.4|^6.0|^7.0", + "symfony/uid": "^5.4|^6.0|^7.0", + "twig/twig": "^2.13|^3.0.4" }, "bin": [ "Resources/bin/var-dump-server" @@ -5672,7 +5891,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v7.0.2" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.2" }, "funding": [ { @@ -5688,27 +5907,28 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:18:20+00:00" + "time": "2023-12-28T19:16:56+00:00" }, { "name": "symfony/var-exporter", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "345c62fefe92243c3a06fc0cc65f2ec1a47e0764" + "reference": "5fe9a0021b8d35e67d914716ec8de50716a68e7e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/345c62fefe92243c3a06fc0cc65f2ec1a47e0764", - "reference": "345c62fefe92243c3a06fc0cc65f2ec1a47e0764", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/5fe9a0021b8d35e67d914716ec8de50716a68e7e", + "reference": "5fe9a0021b8d35e67d914716ec8de50716a68e7e", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3" }, "require-dev": { - "symfony/var-dumper": "^6.4|^7.0" + "symfony/var-dumper": "^5.4|^6.0|^7.0" }, "type": "library", "autoload": { @@ -5746,7 +5966,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v7.0.2" + "source": "https://github.com/symfony/var-exporter/tree/v6.4.2" }, "funding": [ { @@ -5762,7 +5982,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T08:42:13+00:00" + "time": "2023-12-27T08:18:35+00:00" }, { "name": "symfony/webpack-encore-bundle", @@ -8904,20 +9124,20 @@ }, { "name": "symfony/options-resolver", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/options-resolver.git", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f" + "reference": "22301f0e7fdeaacc14318928612dee79be99860e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/700ff4096e346f54cb628ea650767c8130f1001f", - "reference": "700ff4096e346f54cb628ea650767c8130f1001f", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/22301f0e7fdeaacc14318928612dee79be99860e", + "reference": "22301f0e7fdeaacc14318928612dee79be99860e", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", "symfony/deprecation-contracts": "^2.5|^3" }, "type": "library", @@ -8951,7 +9171,7 @@ "options" ], "support": { - "source": "https://github.com/symfony/options-resolver/tree/v7.0.0" + "source": "https://github.com/symfony/options-resolver/tree/v6.4.0" }, "funding": [ { @@ -8967,24 +9187,24 @@ "type": "tidelift" } ], - "time": "2023-08-08T10:20:21+00:00" + "time": "2023-08-08T10:16:24+00:00" }, { "name": "symfony/process", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a" + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a", - "reference": "acd3eb5cb02382c1cb0287ba29b2908cc6ffa83a", + "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", + "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", "shasum": "" }, "require": { - "php": ">=8.2" + "php": ">=8.1" }, "type": "library", "autoload": { @@ -9012,7 +9232,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v7.0.2" + "source": "https://github.com/symfony/process/tree/v6.4.2" }, "funding": [ { @@ -9028,7 +9248,7 @@ "type": "tidelift" } ], - "time": "2023-12-24T09:15:37+00:00" + "time": "2023-12-22T16:42:54+00:00" }, { "name": "symfony/translation-contracts", @@ -9110,64 +9330,65 @@ }, { "name": "symfony/twig-bridge", - "version": "v7.0.2", + "version": "v6.4.2", "source": { "type": "git", "url": "https://github.com/symfony/twig-bridge.git", - "reference": "d6236c6e75ee70317a27f0fd4c3f9bb956f22366" + "reference": "97af829e4733125ee70e806694d56165c60b4ee1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/d6236c6e75ee70317a27f0fd4c3f9bb956f22366", - "reference": "d6236c6e75ee70317a27f0fd4c3f9bb956f22366", + "url": "https://api.github.com/repos/symfony/twig-bridge/zipball/97af829e4733125ee70e806694d56165c60b4ee1", + "reference": "97af829e4733125ee70e806694d56165c60b4ee1", "shasum": "" }, "require": { - "php": ">=8.2", + "php": ">=8.1", + "symfony/deprecation-contracts": "^2.5|^3", "symfony/translation-contracts": "^2.5|^3", - "twig/twig": "^3.0.4" + "twig/twig": "^2.13|^3.0.4" }, "conflict": { "phpdocumentor/reflection-docblock": "<3.2.2", "phpdocumentor/type-resolver": "<1.4.0", - "symfony/console": "<6.4", - "symfony/form": "<6.4", - "symfony/http-foundation": "<6.4", + "symfony/console": "<5.4", + "symfony/form": "<6.3", + "symfony/http-foundation": "<5.4", "symfony/http-kernel": "<6.4", - "symfony/mime": "<6.4", + "symfony/mime": "<6.2", "symfony/serializer": "<6.4", - "symfony/translation": "<6.4", - "symfony/workflow": "<6.4" + "symfony/translation": "<5.4", + "symfony/workflow": "<5.4" }, "require-dev": { "egulias/email-validator": "^2.1.10|^3|^4", "league/html-to-markdown": "^5.0", "phpdocumentor/reflection-docblock": "^3.0|^4.0|^5.0", - "symfony/asset": "^6.4|^7.0", - "symfony/asset-mapper": "^6.4|^7.0", - "symfony/console": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/asset-mapper": "^6.3|^7.0", + "symfony/console": "^5.4|^6.0|^7.0", + "symfony/dependency-injection": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", "symfony/form": "^6.4|^7.0", - "symfony/html-sanitizer": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", + "symfony/html-sanitizer": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/intl": "^6.4|^7.0", - "symfony/mime": "^6.4|^7.0", + "symfony/intl": "^5.4|^6.0|^7.0", + "symfony/mime": "^6.2|^7.0", "symfony/polyfill-intl-icu": "~1.0", - "symfony/property-info": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", + "symfony/property-info": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", "symfony/security-acl": "^2.8|^3.0", - "symfony/security-core": "^6.4|^7.0", - "symfony/security-csrf": "^6.4|^7.0", - "symfony/security-http": "^6.4|^7.0", + "symfony/security-core": "^5.4|^6.0|^7.0", + "symfony/security-csrf": "^5.4|^6.0|^7.0", + "symfony/security-http": "^5.4|^6.0|^7.0", "symfony/serializer": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/workflow": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^6.1|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/workflow": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0", "twig/cssinliner-extra": "^2.12|^3", "twig/inky-extra": "^2.12|^3", "twig/markdown-extra": "^2.12|^3" @@ -9198,7 +9419,7 @@ "description": "Provides integration for Twig with various Symfony components", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bridge/tree/v7.0.2" + "source": "https://github.com/symfony/twig-bridge/tree/v6.4.2" }, "funding": [ { @@ -9214,47 +9435,47 @@ "type": "tidelift" } ], - "time": "2023-12-15T12:36:57+00:00" + "time": "2023-12-15T12:36:48+00:00" }, { "name": "symfony/twig-bundle", - "version": "v7.0.0", + "version": "v6.4.0", "source": { "type": "git", "url": "https://github.com/symfony/twig-bundle.git", - "reference": "42c4a60f1b83894cd85a6b00533f8216c413ac11" + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/42c4a60f1b83894cd85a6b00533f8216c413ac11", - "reference": "42c4a60f1b83894cd85a6b00533f8216c413ac11", + "url": "https://api.github.com/repos/symfony/twig-bundle/zipball/35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", + "reference": "35d84393e598dfb774e6a2bf49e5229a8a6dbe4c", "shasum": "" }, "require": { "composer-runtime-api": ">=2.1", - "php": ">=8.2", - "symfony/config": "^6.4|^7.0", - "symfony/dependency-injection": "^6.4|^7.0", - "symfony/http-foundation": "^6.4|^7.0", - "symfony/http-kernel": "^6.4|^7.0", - "symfony/twig-bridge": "^6.4|^7.0", - "twig/twig": "^3.0.4" + "php": ">=8.1", + "symfony/config": "^6.1|^7.0", + "symfony/dependency-injection": "^6.1|^7.0", + "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/http-kernel": "^6.2", + "symfony/twig-bridge": "^6.4", + "twig/twig": "^2.13|^3.0.4" }, "conflict": { - "symfony/framework-bundle": "<6.4", - "symfony/translation": "<6.4" + "symfony/framework-bundle": "<5.4", + "symfony/translation": "<5.4" }, "require-dev": { - "symfony/asset": "^6.4|^7.0", - "symfony/expression-language": "^6.4|^7.0", - "symfony/finder": "^6.4|^7.0", - "symfony/form": "^6.4|^7.0", - "symfony/framework-bundle": "^6.4|^7.0", - "symfony/routing": "^6.4|^7.0", - "symfony/stopwatch": "^6.4|^7.0", - "symfony/translation": "^6.4|^7.0", - "symfony/web-link": "^6.4|^7.0", - "symfony/yaml": "^6.4|^7.0" + "symfony/asset": "^5.4|^6.0|^7.0", + "symfony/expression-language": "^5.4|^6.0|^7.0", + "symfony/finder": "^5.4|^6.0|^7.0", + "symfony/form": "^5.4|^6.0|^7.0", + "symfony/framework-bundle": "^5.4|^6.0|^7.0", + "symfony/routing": "^5.4|^6.0|^7.0", + "symfony/stopwatch": "^5.4|^6.0|^7.0", + "symfony/translation": "^5.4|^6.0|^7.0", + "symfony/web-link": "^5.4|^6.0|^7.0", + "symfony/yaml": "^5.4|^6.0|^7.0" }, "type": "symfony-bundle", "autoload": { @@ -9282,7 +9503,7 @@ "description": "Provides a tight integration of Twig into the Symfony full-stack framework", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/twig-bundle/tree/v7.0.0" + "source": "https://github.com/symfony/twig-bundle/tree/v6.4.0" }, "funding": [ { @@ -9298,7 +9519,7 @@ "type": "tidelift" } ], - "time": "2023-11-26T15:16:53+00:00" + "time": "2023-11-07T14:57:07+00:00" }, { "name": "symfony/web-profiler-bundle", @@ -9882,5 +10103,5 @@ "ext-iconv": "*" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.3.0" } diff --git a/infection.json5 b/infection.json5 index b75476a..e648d7a 100644 --- a/infection.json5 +++ b/infection.json5 @@ -6,6 +6,7 @@ ], excludes: [ "Command/SetupUrlCommand.php", // internal utility command, that needs no testing + "Command/ReportTurnoverCommand.php", // wrapper around tested ReportService.php "Entity/", "Tests/" // changes that doesn't break tests are fine ] diff --git a/migrations/Version20240118141652AddTransactionReport.php b/migrations/Version20240118141652AddTransactionReport.php new file mode 100644 index 0000000..752dd4e --- /dev/null +++ b/migrations/Version20240118141652AddTransactionReport.php @@ -0,0 +1,33 @@ +addSql('CREATE TABLE transaction_report (transaction_id BINARY(16) NOT NULL COMMENT \'(DC2Type:uuid)\', currency_iso VARCHAR(3) NOT NULL, total_price NUMERIC(20, 2) NOT NULL, PRIMARY KEY(transaction_id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB'); + $this->addSql('ALTER TABLE transaction_report ADD CONSTRAINT FK_B25205C42FC0CB0F FOREIGN KEY (transaction_id) REFERENCES `transaction` (id)'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE transaction_report DROP FOREIGN KEY FK_B25205C42FC0CB0F'); + $this->addSql('DROP TABLE transaction_report'); + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 1a02e37..b9c4b7d 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -26,7 +26,8 @@ src/Entity - src/Command + src/Command/SetupUrlCommand.php + src/Command/ReportTurnoverCommand.php diff --git a/src/Braintree/Payment/BraintreePaymentService.php b/src/Braintree/Payment/BraintreePaymentService.php index 0528e9f..ff65cb1 100644 --- a/src/Braintree/Payment/BraintreePaymentService.php +++ b/src/Braintree/Payment/BraintreePaymentService.php @@ -5,11 +5,13 @@ use Braintree\Exception\NotFound; use Braintree\Gateway; use Braintree\Transaction; +use Doctrine\ORM\EntityManagerInterface; use Shopware\App\SDK\Context\Payment\PaymentPayAction; use Shopware\App\SDK\Shop\ShopInterface; use Swag\Braintree\Braintree\Exception\BraintreePaymentException; use Swag\Braintree\Braintree\Util\SalesChannelConfigService; -use Swag\Braintree\Entity\ShopEntity; +use Swag\Braintree\Entity\TransactionEntity; +use Swag\Braintree\Entity\TransactionReportEntity; use Swag\Braintree\Repository\TransactionRepository; class BraintreePaymentService @@ -24,6 +26,7 @@ public function __construct( private readonly OrderInformationService $orderInformationService, private readonly SalesChannelConfigService $salesChannelConfigService, private readonly TransactionRepository $transactionRepository, + private readonly EntityManagerInterface $em, ) { } @@ -53,9 +56,7 @@ public function handleTransaction(PaymentPayAction $payment): Transaction 'discountAmount' => $this->orderInformationService->extractDiscountAmount($payment), 'lineItems' => $this->orderInformationService->extractLineItems($payment), 'shipping' => $shipping['address'], - 'options' => [ - 'submitForSettlement' => true, - ], + 'options' => ['submitForSettlement' => true], 'paymentMethodNonce' => $nonce, 'purchaseOrderNumber' => $payment->order->getOrderNumber(), 'taxAmount' => $this->orderInformationService->extractTaxAmount($payment), @@ -134,16 +135,21 @@ public function getTransactionDetails(ShopInterface $shop, array $transactions): return $braintreeTransaction; } - private function saveTransaction(PaymentPayAction $payment, Transaction $braintreeResponse): void + private function saveTransaction(PaymentPayAction $payment, Transaction $braintreeTransaction): void { - /** @var ShopEntity $shop */ - $shop = $payment->shop; - - $this->transactionRepository->upsert([ - [ - 'orderTransactionId' => $payment->orderTransaction->getId(), - 'braintreeTransactionId' => $braintreeResponse->id, - ], - ], $shop); + $transaction = (new TransactionEntity()) + ->setBraintreeTransactionId($braintreeTransaction->id) + ->setOrderTransactionId($payment->orderTransaction->getId()) + ->setShop($payment->shop); + + $report = (new TransactionReportEntity()) + ->setCurrencyIso($braintreeTransaction->currencyIsoCode) + ->setTotalPrice((string) $braintreeTransaction->amount) + ->setTransaction($transaction); + + $this->em->persist($transaction); + $this->em->persist($report); + + $this->em->flush(); } } diff --git a/src/Braintree/Util/ReportService.php b/src/Braintree/Util/ReportService.php new file mode 100644 index 0000000..8eaa771 --- /dev/null +++ b/src/Braintree/Util/ReportService.php @@ -0,0 +1,83 @@ + 'https://api.shopware.com']), + ) { + } + + /** + * @return array List of currencies that could not be reported + */ + public function sendTurnoverReports(): array + { + $transactions = $this->transactionReportRepository->findAll(); + + $reports = []; + foreach ($transactions as $transaction) { + $reports[$transaction->getCurrencyIso()] ??= 0; + $reports[$transaction->getCurrencyIso()] += (float) $transaction->getTotalPrice(); + } + + $requests = []; + foreach ($reports as $currency => $turnover) { + $body = [ + 'identifier' => $this->apiIdentifier, + 'reportDate' => (new \DateTime())->format(\DateTimeInterface::ATOM), + 'reportDataKeys' => ['turnover' => $turnover], + 'currency' => $currency, + ]; + + $requests[$currency] = $this->client->postAsync( + '/shopwarepartners/reports/technology', + [RequestOptions::JSON => $body], + ); + } + + $rejectedCurrencies = []; + /** @var array{state: string, reason: ClientException} $response */ + foreach (Utils::settle($requests)->wait() as $currency => $response) { + if ($response['state'] !== Promise::REJECTED) { + continue; + } + + // @TODO - Implement logging + // $this->logger->warning(\sprintf( + // 'Failed to report turnover for "%s": %s', + // $currency, + // $response['reason']->getMessage() + // )); + + $rejectedCurrencies[] = $currency; + } + + foreach ($transactions as $transaction) { + // transaction has a rejected currency and shouldn't be deleted + if (\in_array($transaction->getCurrencyIso(), $rejectedCurrencies, true)) { + continue; + } + + $this->em->remove($transaction); + } + + $this->em->flush(); + + return $rejectedCurrencies; + } +} diff --git a/src/Command/ReportTurnoverCommand.php b/src/Command/ReportTurnoverCommand.php new file mode 100644 index 0000000..d463bbc --- /dev/null +++ b/src/Command/ReportTurnoverCommand.php @@ -0,0 +1,43 @@ +reportService->sendTurnoverReports(); + + if (\count($rejectedCurrencies) > 0) { + $io->warning(\sprintf( + 'The following currencies could not be reported: %s', + \implode(', ', $rejectedCurrencies) + )); + + return Command::FAILURE; + } + + return Command::SUCCESS; + } + + protected function configure(): void + { + $this->setDescription('Report the turnover of a given time period'); + } +} diff --git a/src/Entity/TransactionEntity.php b/src/Entity/TransactionEntity.php index f14ad43..46fe286 100644 --- a/src/Entity/TransactionEntity.php +++ b/src/Entity/TransactionEntity.php @@ -27,6 +27,9 @@ class TransactionEntity implements EntityInterface #[ORM\Column(type: Types::STRING, nullable: false)] private string $orderTransactionId; + #[ORM\OneToOne(targetEntity: TransactionReportEntity::class, mappedBy: 'transaction', cascade: ['persist', 'remove'])] + private ?TransactionReportEntity $transactionReport = null; + public function getBraintreeTransactionId(): string { return $this->braintreeTransactionId; @@ -50,4 +53,16 @@ public function setOrderTransactionId(string $orderTransactionId): self return $this; } + + public function getTransactionReport(): ?TransactionReportEntity + { + return $this->transactionReport; + } + + public function setTransactionReport(?TransactionReportEntity $transactionReport): self + { + $this->transactionReport = $transactionReport; + + return $this; + } } diff --git a/src/Entity/TransactionReportEntity.php b/src/Entity/TransactionReportEntity.php new file mode 100644 index 0000000..315a75f --- /dev/null +++ b/src/Entity/TransactionReportEntity.php @@ -0,0 +1,58 @@ +transaction; + } + + public function setTransaction(TransactionEntity $transaction): self + { + $this->transaction = $transaction; + + return $this; + } + + public function getCurrencyIso(): string + { + return $this->currencyIso; + } + + public function setCurrencyIso(string $currencyIso): self + { + $this->currencyIso = $currencyIso; + + return $this; + } + + public function getTotalPrice(): string + { + return $this->totalPrice; + } + + public function setTotalPrice(string $totalPrice): self + { + $this->totalPrice = $totalPrice; + + return $this; + } +} diff --git a/src/Repository/TransactionReportRepository.php b/src/Repository/TransactionReportRepository.php new file mode 100644 index 0000000..afe8401 --- /dev/null +++ b/src/Repository/TransactionReportRepository.php @@ -0,0 +1,22 @@ + + */ +class TransactionReportRepository extends ServiceEntityRepository +{ + public function __construct(ManagerRegistry $registry) + { + parent::__construct($registry, TransactionReportEntity::class); + } +} diff --git a/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php b/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php index c02462b..05331cd 100644 --- a/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php +++ b/tests/unit/Braintree/Payment/BraintreePaymentServiceTest.php @@ -9,6 +9,7 @@ use Braintree\Result; use Braintree\Transaction; use Braintree\TransactionGateway; +use Doctrine\ORM\EntityManagerInterface; use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -20,6 +21,7 @@ use Swag\Braintree\Braintree\Util\SalesChannelConfigService; use Swag\Braintree\Entity\ShopEntity; use Swag\Braintree\Entity\TransactionEntity; +use Swag\Braintree\Entity\TransactionReportEntity; use Swag\Braintree\Repository\TransactionRepository; use Swag\Braintree\Tests\Contract\PaymentPayActionHelperTrait; use Swag\Braintree\Tests\IdsCollection; @@ -45,6 +47,8 @@ class BraintreePaymentServiceTest extends TestCase private MockObject&TransactionRepository $transactionRepository; + private MockObject&EntityManagerInterface $entityManager; + protected function setUp(): void { $this->paymentMethodNonceGateway = $this->createMock(PaymentMethodNonceGateway::class); @@ -60,6 +64,8 @@ protected function setUp(): void $this->transactionRepository = $this->createMock(TransactionRepository::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + $this->orderIds = new IdsCollection(); $this->orderInformationService = new OrderInformationService(new TaxService()); $this->paymentService = new BraintreePaymentService( @@ -67,6 +73,7 @@ protected function setUp(): void $this->orderInformationService, $salesChannelConfigService, $this->transactionRepository, + $this->entityManager, ); $this->shop = new ShopEntity('this-is-shop-id', '', 'this-is-shop-secret'); } @@ -90,6 +97,8 @@ public function testHandleTransaction(): void $resultSuccess = new Result\Successful([ 'transaction' => Transaction::factory([ 'id' => 'this-is-transaction-id', + 'currencyIsoCode' => 'EUR', + 'amount' => 200, ]), ], ['transaction']); @@ -117,13 +126,29 @@ public function testHandleTransaction(): void BraintreePaymentService::BRAINTREE_DEVICE_DATA => 'this-is-device-data', ]); - $this->transactionRepository + $emMatcher = static::exactly(2); + $this->entityManager + ->expects($emMatcher) + ->method('persist') + ->willReturnCallback(function (object $entity) use (&$emMatcher): void { + switch ($emMatcher->numberOfInvocations()) { + case 1: + /** @var TransactionEntity $entity */ + static::assertInstanceOf(TransactionEntity::class, $entity); + static::assertEquals('this-is-transaction-id', $entity->getBraintreeTransactionId()); + break; + case 2: + /** @var TransactionReportEntity $entity */ + static::assertInstanceOf(TransactionReportEntity::class, $entity); + static::assertEquals('EUR', $entity->getCurrencyIso()); + static::assertEquals(200, $entity->getTotalPrice()); + break; + } + }); + + $this->entityManager ->expects(static::once()) - ->method('upsert') - ->with([[ - 'orderTransactionId' => $this->orderIds->get('order-transaction-id'), - 'braintreeTransactionId' => 'this-is-transaction-id', - ]], $this->shop); + ->method('flush'); $transaction = $this->paymentService->handleTransaction($paymentPayAction); @@ -281,6 +306,7 @@ public function testHandleTransactionWithoutMerchantIdThrowsException(): void $this->orderInformationService, $salesChannelConfigService, $this->transactionRepository, + $this->entityManager, ); $this->paymentMethodNonceGateway diff --git a/tests/unit/Braintree/Util/ReportServiceTest.php b/tests/unit/Braintree/Util/ReportServiceTest.php new file mode 100644 index 0000000..1263a96 --- /dev/null +++ b/tests/unit/Braintree/Util/ReportServiceTest.php @@ -0,0 +1,173 @@ + + */ + private array $clientHistory; + + private MockHandler $clientHandler; + + protected function setUp(): void + { + $this->transactionReportRepository = $this->createMock(TransactionReportRepository::class); + $this->entityManager = $this->createMock(EntityManagerInterface::class); + + $this->clientHistory = []; + $this->clientHandler = new MockHandler(); + $clientHandlerStack = HandlerStack::create($this->clientHandler); + $clientHandlerStack->push(Middleware::history($this->clientHistory)); + + $this->service = new ReportService( + $this->entityManager, + $this->transactionReportRepository, + 'test-id', + new Client(['handler' => $clientHandlerStack]), + ); + } + + public function testSendTurnoverReports(): void + { + $this->clientHandler->append(new Response(), new Response()); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52'), + + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('9.48'), + + (new TransactionReportEntity()) + ->setCurrencyIso('GBP') + ->setTotalPrice('100.00'), + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + + $this->entityManager + ->expects(static::once()) + ->method('flush'); + + $this->entityManager + ->expects(static::once()) + ->method('flush'); + + $this->entityManager + ->expects(static::exactly(3)) + ->method('remove'); + + $this->service->sendTurnoverReports(); + + static::assertEquals([[ + 'reportDataKeys' => ['turnover' => 20], + 'currency' => 'EUR', + ], [ + 'reportDataKeys' => ['turnover' => 100], + 'currency' => 'GBP', + ]], $this->extractTurnoverReports($this->clientHistory)); + } + + public function testSendTurnoverReportsWithRejectedReport(): void + { + $this->clientHandler->append(new Response(400), new Response()); + + $successfulReport = (new TransactionReportEntity()) + ->setCurrencyIso('GBP') + ->setTotalPrice('100.00'); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52'), + + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('9.48'), + + $successfulReport, + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + + $this->entityManager + ->expects(static::once()) + ->method('flush'); + + $this->entityManager + ->expects(static::once()) + ->method('flush'); + + // will only remove the successful reported transaction + $this->entityManager + ->expects(static::once()) + ->method('remove') + ->with($successfulReport); + + $rejectedCurrencies = $this->service->sendTurnoverReports(); + + static::assertEquals(['EUR'], $rejectedCurrencies); + + static::assertEquals([[ + 'reportDataKeys' => ['turnover' => 20], + 'currency' => 'EUR', + ], [ + 'reportDataKeys' => ['turnover' => 100], + 'currency' => 'GBP', + ]], $this->extractTurnoverReports($this->clientHistory)); + } + + /** + * Extracts all turnover reports, successful and failed ones + * + * @param array $history + * + * @return array> + */ + private function extractTurnoverReports(array $history): array + { + return \array_map( + function (array $entry) { + $body = \json_decode($entry['request']->getBody()->getContents(), true); + static::assertIsArray($body); + + unset($body['identifier']); + unset($body['reportDate']); + + return $body; + }, + $history, + ); + } +} diff --git a/tests/unit/Repository/TransactionReportRepositoryTest.php b/tests/unit/Repository/TransactionReportRepositoryTest.php new file mode 100644 index 0000000..a6dcbfb --- /dev/null +++ b/tests/unit/Repository/TransactionReportRepositoryTest.php @@ -0,0 +1,47 @@ +entityManager = $this->createMock(EntityManagerInterface::class); + $this->entityManager + ->method('getClassMetadata') + ->with(TransactionReportEntity::class) + ->willReturn(new ClassMetadata(TransactionReportEntity::class)); + + $this->registry = $this->createMock(ManagerRegistry::class); + $this->registry + ->method('getManagerForClass') + ->with(TransactionReportEntity::class) + ->willReturn($this->entityManager); + + $this->repository = new TransactionReportRepository($this->registry); + } + + public function testConstruct(): void + { + $this->entityManager->expects(static::once())->method('getClassMetadata'); + $this->registry->expects(static::once())->method('getManagerForClass'); + + static::assertSame(TransactionReportEntity::class, $this->repository->getClassName()); + } +} From e36b5d0b7f31dad0bdda146f1864e169adf5c706 Mon Sep 17 00:00:00 2001 From: Lennart Tinkloh Date: Mon, 22 Jan 2024 13:59:54 +0100 Subject: [PATCH 2/2] BRAIN-23 - fix mutations --- .github/workflows/js.yml | 2 +- .github/workflows/php.yml | 4 +- composer.lock | 79 +++++---- config/services/braintree.xml | 14 ++ src/Braintree/Util/ReportClientFactory.php | 16 ++ src/Braintree/Util/ReportService.php | 8 +- .../Util/ReportClientFactoryTest.php | 39 +++++ .../unit/Braintree/Util/ReportServiceTest.php | 153 +++++++++++++++++- 8 files changed, 260 insertions(+), 55 deletions(-) create mode 100644 src/Braintree/Util/ReportClientFactory.php create mode 100644 tests/unit/Braintree/Util/ReportClientFactoryTest.php diff --git a/.github/workflows/js.yml b/.github/workflows/js.yml index cd5d161..7b0eec5 100644 --- a/.github/workflows/js.yml +++ b/.github/workflows/js.yml @@ -13,7 +13,7 @@ on: - package.json - .github/workflows/js.yml branches: - - main + - trunk jobs: eslint: diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 5a21f32..300a848 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -11,7 +11,7 @@ on: - composer.json - .github/workflows/php.yml branches: - - main + - trunk workflow_dispatch: jobs: @@ -73,6 +73,6 @@ jobs: - name: Install dependencies run: composer install --prefer-dist --no-progress --no-suggest - name: Run Infection - run: composer infection + run: composer infection -- --min-msi=95 env: INFECTION_DASHBOARD_API_KEY: ${{ secrets.INFECTION_DASHBOARD_API_KEY }} \ No newline at end of file diff --git a/composer.lock b/composer.lock index f3130fe..878403c 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": "eadd4783150d67483c1279b64f90bc6f", + "content-hash": "7a6c3c4a4c2c703e15f5de85203b21ff", "packages": [ { "name": "braintree/braintree_php", @@ -327,16 +327,16 @@ }, { "name": "doctrine/dbal", - "version": "3.7.2", + "version": "3.7.3", "source": { "type": "git", "url": "https://github.com/doctrine/dbal.git", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2" + "reference": "ce594cbc39a4866c544f1a970d285ff0548221ad" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/dbal/zipball/0ac3c270590e54910715e9a1a044cc368df282b2", - "reference": "0ac3c270590e54910715e9a1a044cc368df282b2", + "url": "https://api.github.com/repos/doctrine/dbal/zipball/ce594cbc39a4866c544f1a970d285ff0548221ad", + "reference": "ce594cbc39a4866c544f1a970d285ff0548221ad", "shasum": "" }, "require": { @@ -352,14 +352,14 @@ "doctrine/coding-standard": "12.0.0", "fig/log-test": "^1", "jetbrains/phpstorm-stubs": "2023.1", - "phpstan/phpstan": "1.10.42", + "phpstan/phpstan": "1.10.56", "phpstan/phpstan-strict-rules": "^1.5", - "phpunit/phpunit": "9.6.13", + "phpunit/phpunit": "9.6.15", "psalm/plugin-phpunit": "0.18.4", "slevomat/coding-standard": "8.13.1", - "squizlabs/php_codesniffer": "3.7.2", - "symfony/cache": "^5.4|^6.0", - "symfony/console": "^4.4|^5.4|^6.0", + "squizlabs/php_codesniffer": "3.8.1", + "symfony/cache": "^5.4|^6.0|^7.0", + "symfony/console": "^4.4|^5.4|^6.0|^7.0", "vimeo/psalm": "4.30.0" }, "suggest": { @@ -420,7 +420,7 @@ ], "support": { "issues": "https://github.com/doctrine/dbal/issues", - "source": "https://github.com/doctrine/dbal/tree/3.7.2" + "source": "https://github.com/doctrine/dbal/tree/3.7.3" }, "funding": [ { @@ -436,7 +436,7 @@ "type": "tidelift" } ], - "time": "2023-11-19T08:06:58+00:00" + "time": "2024-01-21T07:53:09+00:00" }, { "name": "doctrine/deprecations", @@ -3099,47 +3099,46 @@ }, { "name": "symfony/console", - "version": "v6.4.2", + "version": "v7.0.2", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" + "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", + "url": "https://api.github.com/repos/symfony/console/zipball/f8587c4cdc5acad67af71c37db34ef03af91e59c", + "reference": "f8587c4cdc5acad67af71c37db34ef03af91e59c", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-mbstring": "~1.0", "symfony/service-contracts": "^2.5|^3", - "symfony/string": "^5.4|^6.0|^7.0" + "symfony/string": "^6.4|^7.0" }, "conflict": { - "symfony/dependency-injection": "<5.4", - "symfony/dotenv": "<5.4", - "symfony/event-dispatcher": "<5.4", - "symfony/lock": "<5.4", - "symfony/process": "<5.4" + "symfony/dependency-injection": "<6.4", + "symfony/dotenv": "<6.4", + "symfony/event-dispatcher": "<6.4", + "symfony/lock": "<6.4", + "symfony/process": "<6.4" }, "provide": { "psr/log-implementation": "1.0|2.0|3.0" }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/event-dispatcher": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/event-dispatcher": "^6.4|^7.0", "symfony/http-foundation": "^6.4|^7.0", "symfony/http-kernel": "^6.4|^7.0", - "symfony/lock": "^5.4|^6.0|^7.0", - "symfony/messenger": "^5.4|^6.0|^7.0", - "symfony/process": "^5.4|^6.0|^7.0", - "symfony/stopwatch": "^5.4|^6.0|^7.0", - "symfony/var-dumper": "^5.4|^6.0|^7.0" + "symfony/lock": "^6.4|^7.0", + "symfony/messenger": "^6.4|^7.0", + "symfony/process": "^6.4|^7.0", + "symfony/stopwatch": "^6.4|^7.0", + "symfony/var-dumper": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -3173,7 +3172,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.2" + "source": "https://github.com/symfony/console/tree/v7.0.2" }, "funding": [ { @@ -3189,7 +3188,7 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:15:48+00:00" + "time": "2023-12-10T16:54:46+00:00" }, { "name": "symfony/dependency-injection", @@ -6500,16 +6499,16 @@ }, { "name": "friendsofphp/php-cs-fixer", - "version": "v3.47.1", + "version": "v3.48.0", "source": { "type": "git", "url": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer.git", - "reference": "173c60d1eff911c9c54322704623a45561d3241d" + "reference": "a92472c6fb66349de25211f31c77eceae3df024e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/173c60d1eff911c9c54322704623a45561d3241d", - "reference": "173c60d1eff911c9c54322704623a45561d3241d", + "url": "https://api.github.com/repos/PHP-CS-Fixer/PHP-CS-Fixer/zipball/a92472c6fb66349de25211f31c77eceae3df024e", + "reference": "a92472c6fb66349de25211f31c77eceae3df024e", "shasum": "" }, "require": { @@ -6579,7 +6578,7 @@ ], "support": { "issues": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues", - "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.47.1" + "source": "https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/tree/v3.48.0" }, "funding": [ { @@ -6587,7 +6586,7 @@ "type": "github" } ], - "time": "2024-01-16T18:54:21+00:00" + "time": "2024-01-19T21:44:39+00:00" }, { "name": "infection/abstract-testframework-adapter", diff --git a/config/services/braintree.xml b/config/services/braintree.xml index 6688fe8..1294f7b 100644 --- a/config/services/braintree.xml +++ b/config/services/braintree.xml @@ -19,5 +19,19 @@ + + + + + https://api.shopware.com + + + + + + + %env(default::REPORT_IDENTIFIER)% + + diff --git a/src/Braintree/Util/ReportClientFactory.php b/src/Braintree/Util/ReportClientFactory.php new file mode 100644 index 0000000..db23023 --- /dev/null +++ b/src/Braintree/Util/ReportClientFactory.php @@ -0,0 +1,16 @@ + $config + */ + public static function createClient(array $config = []): Client + { + return new Client($config); + } +} diff --git a/src/Braintree/Util/ReportService.php b/src/Braintree/Util/ReportService.php index 8eaa771..979f949 100644 --- a/src/Braintree/Util/ReportService.php +++ b/src/Braintree/Util/ReportService.php @@ -9,16 +9,14 @@ use GuzzleHttp\Promise\Utils; use GuzzleHttp\RequestOptions; use Swag\Braintree\Repository\TransactionReportRepository; -use Symfony\Component\DependencyInjection\Attribute\Autowire; class ReportService { public function __construct( private readonly EntityManagerInterface $em, private readonly TransactionReportRepository $transactionReportRepository, - #[Autowire(env: 'REPORT_IDENTIFIER')] - private readonly string $apiIdentifier = '', - private readonly Client $client = new Client(['base_uri' => 'https://api.shopware.com']), + private readonly ?string $apiIdentifier, + private readonly Client $client, ) { } @@ -38,7 +36,7 @@ public function sendTurnoverReports(): array $requests = []; foreach ($reports as $currency => $turnover) { $body = [ - 'identifier' => $this->apiIdentifier, + 'identifier' => $this->apiIdentifier ?? '', 'reportDate' => (new \DateTime())->format(\DateTimeInterface::ATOM), 'reportDataKeys' => ['turnover' => $turnover], 'currency' => $currency, diff --git a/tests/unit/Braintree/Util/ReportClientFactoryTest.php b/tests/unit/Braintree/Util/ReportClientFactoryTest.php new file mode 100644 index 0000000..ad09833 --- /dev/null +++ b/tests/unit/Braintree/Util/ReportClientFactoryTest.php @@ -0,0 +1,39 @@ +append(new Response()); + $clientHandlerStack = HandlerStack::create($clientHandler); + $clientHandlerStack->push(Middleware::history($clientHistory)); + + $client = ReportClientFactory::createClient(['base_uri' => 'https://example.com', 'handler' => $clientHandlerStack]); + $client->get('/foo/bar'); + + static::assertCount(1, $clientHistory); + + static::assertArrayHasKey('request', $clientHistory[0]); + static::assertInstanceOf(Request::class, $clientHistory[0]['request']); + + $uri = $clientHistory[0]['request']->getUri(); + + static::assertInstanceOf(UriInterface::class, $uri); + static::assertSame('https://example.com/foo/bar', (string) $uri); + } +} diff --git a/tests/unit/Braintree/Util/ReportServiceTest.php b/tests/unit/Braintree/Util/ReportServiceTest.php index 1263a96..66a3a06 100644 --- a/tests/unit/Braintree/Util/ReportServiceTest.php +++ b/tests/unit/Braintree/Util/ReportServiceTest.php @@ -52,7 +52,7 @@ protected function setUp(): void public function testSendTurnoverReports(): void { - $this->clientHandler->append(new Response(), new Response()); + $this->clientHandler->append(new Response(), new Response(), new Response()); $reports = [ (new TransactionReportEntity()) @@ -77,10 +77,6 @@ public function testSendTurnoverReports(): void ->expects(static::once()) ->method('flush'); - $this->entityManager - ->expects(static::once()) - ->method('flush'); - $this->entityManager ->expects(static::exactly(3)) ->method('remove'); @@ -125,6 +121,50 @@ public function testSendTurnoverReportsWithRejectedReport(): void ->expects(static::once()) ->method('flush'); + // will only remove the successful reported transaction + $this->entityManager + ->expects(static::once()) + ->method('remove') + ->with($successfulReport); + + $rejectedCurrencies = $this->service->sendTurnoverReports(); + + static::assertEquals(['EUR'], $rejectedCurrencies); + + static::assertEquals([[ + 'reportDataKeys' => ['turnover' => 20], + 'currency' => 'EUR', + ], [ + 'reportDataKeys' => ['turnover' => 100], + 'currency' => 'GBP', + ]], $this->extractTurnoverReports($this->clientHistory)); + } + + public function testMultipleRejectedCurrencies(): void + { + $this->clientHandler->append(new Response(400), new Response(), new Response(400)); + + $successfulReport = (new TransactionReportEntity()) + ->setCurrencyIso('CHF') + ->setTotalPrice('9.48'); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52'), + + $successfulReport, + + (new TransactionReportEntity()) + ->setCurrencyIso('GBP') + ->setTotalPrice('100.00'), + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + $this->entityManager ->expects(static::once()) ->method('flush'); @@ -137,17 +177,116 @@ public function testSendTurnoverReportsWithRejectedReport(): void $rejectedCurrencies = $this->service->sendTurnoverReports(); - static::assertEquals(['EUR'], $rejectedCurrencies); + static::assertEquals(['EUR', 'GBP'], $rejectedCurrencies); static::assertEquals([[ - 'reportDataKeys' => ['turnover' => 20], + 'reportDataKeys' => ['turnover' => 10.52], 'currency' => 'EUR', + ], [ + 'reportDataKeys' => ['turnover' => 9.48], + 'currency' => 'CHF', ], [ 'reportDataKeys' => ['turnover' => 100], 'currency' => 'GBP', ]], $this->extractTurnoverReports($this->clientHistory)); } + public function testTransactionFloatCasting(): void + { + $this->clientHandler->append(new Response()); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52s'), + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + + $this->service->sendTurnoverReports(); + + static::assertNotNull($this->clientHandler->getLastRequest()); + + $requestBody = $this->clientHandler->getLastRequest()->getBody()->getContents(); + + static::assertJson($requestBody); + + $requestBodyArray = \json_decode($requestBody, true); + + static::assertIsArray($requestBodyArray); + + static::assertArrayHasKey('reportDataKeys', $requestBodyArray); + static::assertIsArray($requestBodyArray['reportDataKeys']); + + static::assertArrayHasKey('turnover', $requestBodyArray['reportDataKeys']); + static::assertIsFloat($requestBodyArray['reportDataKeys']['turnover']); + } + + public function testReportDateIsSent(): void + { + $this->clientHandler->append(new Response()); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52s'), + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + + $this->service->sendTurnoverReports(); + + static::assertNotNull($this->clientHandler->getLastRequest()); + + $requestBody = $this->clientHandler->getLastRequest()->getBody()->getContents(); + + static::assertJson($requestBody); + + $requestBodyArray = \json_decode($requestBody, true); + + static::assertArrayHasKey('reportDate', $requestBodyArray); + static::assertIsString($requestBodyArray['reportDate']); + + $reportDate = \DateTime::createFromFormat(\DateTimeInterface::ATOM, $requestBodyArray['reportDate']); + + static::assertInstanceOf(\DateTimeInterface::class, $reportDate); + } + + public function testApiIdentifierIsSent(): void + { + $this->clientHandler->append(new Response()); + + $reports = [ + (new TransactionReportEntity()) + ->setCurrencyIso('EUR') + ->setTotalPrice('10.52s'), + ]; + + $this->transactionReportRepository + ->expects(static::once()) + ->method('findAll') + ->willReturn($reports); + + $this->service->sendTurnoverReports(); + + static::assertNotNull($this->clientHandler->getLastRequest()); + + $requestBody = $this->clientHandler->getLastRequest()->getBody()->getContents(); + + static::assertJson($requestBody); + + $requestBodyArray = \json_decode($requestBody, true); + + static::assertArrayHasKey('identifier', $requestBodyArray); + static::assertSame('test-id', $requestBodyArray['identifier']); + } + /** * Extracts all turnover reports, successful and failed ones *