From 71d24e0c68b7953b15fb1b6a044979c6a1315aa4 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 6 Mar 2023 12:29:16 +0100 Subject: [PATCH 001/343] Changes for PrestaShop 8.0 --- .github/release-drafter.yml | 10 +- .github/workflows/build-release.yml | 12 +- .github/workflows/php.yml | 66 +- .github/workflows/publish-to-marketplace.yml | 4 +- Makefile | 11 +- composer.json | 7 - composer.lock | 1418 +++++++++++------ config.xml | 2 +- ps_checkout.php | 4 +- src/Api/GenericClient.php | 2 +- src/Api/Payment/Client/PaymentClient.php | 8 +- .../FundingSourceCollectionTest.php | 7 - tests/phpstan/phpstan-PS-8.neon | 37 + 13 files changed, 1030 insertions(+), 558 deletions(-) create mode 100644 tests/phpstan/phpstan-PS-8.neon diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 2b5177c77..64dcdccff 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,16 +1,14 @@ name-template: v$NEXT_PATCH_VERSION tag-template: v$NEXT_PATCH_VERSION categories: - - title: 🔨 Improvements + - title: Improvements label: enhancement - - title: 🐛 Bug Fixes + - title: Bug Fixes label: bug - - title: 🚀 New Features + - title: New Features label: Feature -change-template: '- #$NUMBER: $TITLE by @$AUTHOR' +change-template: '- #$NUMBER: $TITLE' template: | # Changes $CHANGES - - diff --git a/.github/workflows/build-release.yml b/.github/workflows/build-release.yml index 3d61f698c..37bf5ee14 100644 --- a/.github/workflows/build-release.yml +++ b/.github/workflows/build-release.yml @@ -1,10 +1,12 @@ name: Build & Release draft on: - pull_request: push: branches: - - master + - 'master' + - 'prestashop/8.x' + pull_request: + types: [ opened, reopened, synchronize, edited ] env: GCLOUD_TOKEN_PATH: ./token.json @@ -68,7 +70,7 @@ jobs: - name: Prepare for Release run: | cd ${{ github.event.repository.name }} - zip -r ${{ github.event.repository.name }}.zip ${{ github.event.repository.name }} + zip -r ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.zip ${{ github.event.repository.name }} -x '*.git*' - name: Clean existing assets shell: bash run: | @@ -86,6 +88,6 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ steps.release_info.outputs.upload_url }} - asset_path: ./${{ github.event.repository.name }}/${{ github.event.repository.name }}.zip - asset_name: ${{ github.event.repository.name }}.zip + asset_path: ./${{ github.event.repository.name }}/${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.zip + asset_name: ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.zip asset_content_type: application/zip diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index 22201c6a1..c781a322d 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -1,18 +1,53 @@ name: PHP tests -on: [push, pull_request] +on: + push: + branches: + - 'master' + - 'prestashop/8.x' + pull_request: + types: [opened, reopened, synchronize, edited] + jobs: + header-stamp: + name: Check license headers + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP 7.4 + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + + - name: Cache vendor folder + uses: actions/cache@v1 + with: + path: vendor + key: php-${{ hashFiles('composer.lock') }} + + - name: Cache composer folder + uses: actions/cache@v1 + with: + path: ~/.composer/cache + key: php-composer-cache + + - run: composer install + + - name: Run Header Stamp in Dry Run mode + run: php vendor/bin/header-stamp --license=vendor/prestashop/header-stamp/assets/afl.txt --exclude=.github,node_modules,vendor,tests,_dev --dry-run php-linter: - name: PHP Syntax check 5.6|7.2|7.3 + name: PHP Syntax check 7.2|7.3|7.4 runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2.0.0 - - name: PHP syntax checker 5.6 - uses: prestashop/github-action-php-lint/5.6@master - name: PHP syntax checker 7.2 uses: prestashop/github-action-php-lint/7.2@master - name: PHP syntax checker 7.3 uses: prestashop/github-action-php-lint/7.3@master + - name: PHP syntax checker 7.4 + uses: prestashop/github-action-php-lint/7.4@master php-cs-fixer: name: PHP-CS-Fixer runs-on: ubuntu-latest @@ -26,7 +61,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - presta-versions: ['1.6.1.0', '1.6.1.21', '1.7.0.3', 'latest'] + presta-versions: ['8.0.0', 'latest'] steps: - name: Checkout uses: actions/checkout@v2.0.0 @@ -48,26 +83,15 @@ jobs: - name: Pull PrestaShop files (Tag ${{ matrix.presta-versions }}) run: docker run -tid --rm -v ps-volume:/var/www/html --name temp-ps prestashop/prestashop:${{ matrix.presta-versions }} - - name: Select .neon file to run with PHPStan - id: neon - run: | - PS_VERSION=$(docker exec temp-ps bash -c 'echo "$PS_VERSION"') - [[ "${PS_VERSION:0:3}" != '1.7' ]] && echo ::set-output name=filename::phpstan-PS-1.6.neon || echo ::set-output name=filename::phpstan-PS-1.7.neon - - name : Run PHPStan - run: docker run --rm --volumes-from temp-ps -v $PWD:/web/module -e _PS_ROOT_DIR_=/var/www/html --workdir=/web/module phpstan/phpstan:0.12 analyse --configuration=/web/module/tests/phpstan/${{steps.neon.outputs.filename}} --error-format github - header-stamp: - name: Check license headers + run: docker run --rm --volumes-from temp-ps -v $PWD:/web/module -e _PS_ROOT_DIR_=/var/www/html --workdir=/web/module phpstan/phpstan:0.12 analyse --configuration=/web/module/tests/phpstan/phpstan-PS-8.neon --error-format github + phpunit: + name: PHPUnit runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v2 - - name: Setup PHP 7.4 - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - - name: Cache vendor folder uses: actions/cache@v1 with: @@ -82,5 +106,5 @@ jobs: - run: composer install - - name: Run Header Stamp in Dry Run mode - run: php vendor/bin/header-stamp --license=vendor/prestashop/header-stamp/assets/afl.txt --exclude=.github,node_modules,vendor,tests,_dev --dry-run + - name: Run PHPUnit + run: php vendor/bin/phpunit tests/Unit diff --git a/.github/workflows/publish-to-marketplace.yml b/.github/workflows/publish-to-marketplace.yml index d7e6bd27b..10dafec58 100644 --- a/.github/workflows/publish-to-marketplace.yml +++ b/.github/workflows/publish-to-marketplace.yml @@ -16,7 +16,7 @@ jobs: with: repo: ${{ github.event.repository.full_name }} version: ${{ github.event.release.id }} - file: ${{ github.event.repository.name }}.zip + file: ${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -26,6 +26,6 @@ jobs: - name: Release zip run: | - ~/.composer/vendor/bin/publish-on-marketplace --archive=$PWD/${{ github.event.repository.name }}.zip --metadata-json=$PWD/.github/mktp-metadata.json --changelog="${{ github.event.release.body }}" --debug + ~/.composer/vendor/bin/publish-on-marketplace --archive=$PWD/${{ github.event.repository.name }}-${{ github.event.release.tag_name }}.zip --metadata-json=$PWD/.github/mktp-metadata.json --changelog="${{ github.event.release.body }}" --debug env: MARKETPLACE_API_KEY: ${{ secrets.MARKETPLACE_API_KEY }} diff --git a/Makefile b/Makefile index f4f19483d..23e0ff4b5 100644 --- a/Makefile +++ b/Makefile @@ -12,16 +12,16 @@ build-back-prod: docker-compose run --rm php sh -c "composer install --no-dev -o" build-front: - docker-compose run --rm node sh -c "yarn --cwd _dev/ install" - docker-compose run --rm node sh -c "yarn --cwd _dev/ build" + docker-compose run --rm node sh -c "npm i --prefix ./_dev/js/front" + docker-compose run --rm node sh -c "npm run build --prefix ./_dev/js/front" # target: watch-front - Watcher for the vueJS files watch-front: - docker-compose run --rm node sh -c "yarn --cwd _dev/ dev" + docker-compose run --rm node sh -c "npm run watch --prefix ./_dev/js/front" # target: test-front - Launch the front test suite test-front: - docker-compose run --rm node sh -c "yarn --cwd _dev/ lint" + docker-compose run --rm node sh -c "npm test --prefix ./_dev/js/front" build-zip: cp -Ra $(PWD) /tmp/ps_checkout @@ -30,6 +30,9 @@ build-zip: rm -rf /tmp/ps_checkout/.travis.yml rm -rf /tmp/ps_checkout/cloudbuild.yaml rm -rf /tmp/ps_checkout/composer.* + rm -rf /tmp/ps_checkout/package.json + rm -rf /tmp/ps_checkout/.npmrc + rm -rf /tmp/ps_checkout/package-lock.json rm -rf /tmp/ps_checkout/.gitignore rm -rf /tmp/ps_checkout/deploy.sh rm -rf /tmp/ps_checkout/.editorconfig diff --git a/composer.json b/composer.json index e3ff11be8..6cef34418 100755 --- a/composer.json +++ b/composer.json @@ -22,9 +22,7 @@ "require": { "php": ">=7.2", "giggsey/libphonenumber-for-php": "^8.12", - "guzzlehttp/guzzle": "~5.0", "guzzlehttp/log-subscriber": "~1.0", - "monolog/monolog": "1.25.3", "prestashop/decimal": "^1.3", "prestashop/module-lib-guzzle-adapter": "^1.0", "prestashop/module-lib-service-container": "^1.0", @@ -32,11 +30,6 @@ "ramsey/uuid": "^3.8", "segmentio/analytics-php": "^1.5", "sentry/sentry": "^1.0", - "symfony/cache": "^3.4", - "symfony/config": "^3.4", - "symfony/dependency-injection": "^3.4", - "symfony/finder": "^3.4", - "symfony/options-resolver": "^3.4", "webmozart/assert": "^1.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 376d7146c..5940a1e5c 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": "057da90f654b8f699de7d06e30b94a20", + "content-hash": "60b2d09fa06fbf665b582ab406bb22dd", "packages": [ { "name": "clue/stream-filter", @@ -56,6 +56,10 @@ "stream_filter_append", "stream_filter_register" ], + "support": { + "issues": "https://github.com/clue/stream-filter/issues", + "source": "https://github.com/clue/stream-filter/tree/v1.6.0" + }, "funding": [ { "url": "https://clue.engineering/support", @@ -70,16 +74,16 @@ }, { "name": "giggsey/libphonenumber-for-php", - "version": "8.12.53", + "version": "8.13.7", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "2e39201ca4b88c1ec0594cd78128d21e94929d04" + "reference": "281b12d9091fcd04e9379a67f723179c44b13ec8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/2e39201ca4b88c1ec0594cd78128d21e94929d04", - "reference": "2e39201ca4b88c1ec0594cd78128d21e94929d04", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/281b12d9091fcd04e9379a67f723179c44b13ec8", + "reference": "281b12d9091fcd04e9379a67f723179c44b13ec8", "shasum": "" }, "require": { @@ -134,20 +138,24 @@ "phonenumber", "validation" ], - "time": "2022-08-08T06:53:30+00:00" + "support": { + "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", + "source": "https://github.com/giggsey/libphonenumber-for-php" + }, + "time": "2023-03-03T14:16:43+00:00" }, { "name": "giggsey/locale", - "version": "2.2", + "version": "2.3", "source": { "type": "git", "url": "https://github.com/giggsey/Locale.git", - "reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635" + "reference": "5f035523740be40d40ac768a123c9bcc1ae12f56" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/Locale/zipball/9c1dca769253f6a3e81f9a5c167f53b6a54ab635", - "reference": "9c1dca769253f6a3e81f9a5c167f53b6a54ab635", + "url": "https://api.github.com/repos/giggsey/Locale/zipball/5f035523740be40d40ac768a123c9bcc1ae12f56", + "reference": "5f035523740be40d40ac768a123c9bcc1ae12f56", "shasum": "" }, "require": { @@ -161,10 +169,10 @@ "phing/phing": "^2.7", "php-coveralls/php-coveralls": "^2.0", "phpunit/phpunit": "^8.5|^9.5", - "symfony/console": "^5.0", - "symfony/filesystem": "^5.0", - "symfony/finder": "^5.0", - "symfony/process": "^5.0" + "symfony/console": "^5.0|^6.0", + "symfony/filesystem": "^5.0|^6.0", + "symfony/finder": "^5.0|^6.0", + "symfony/process": "^5.0|^6.0" }, "type": "library", "autoload": { @@ -184,7 +192,11 @@ } ], "description": "Locale functions required by libphonenumber-for-php", - "time": "2022-04-06T07:33:59+00:00" + "support": { + "issues": "https://github.com/giggsey/Locale/issues", + "source": "https://github.com/giggsey/Locale/tree/2.3" + }, + "time": "2022-10-19T20:03:30+00:00" }, { "name": "guzzlehttp/guzzle", @@ -237,6 +249,10 @@ "rest", "web service" ], + "support": { + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/5.3" + }, "time": "2019-10-30T09:32:00+00:00" }, { @@ -290,21 +306,25 @@ "log", "plugin" ], + "support": { + "issues": "https://github.com/guzzle/log-subscriber/issues", + "source": "https://github.com/guzzle/log-subscriber/tree/master" + }, "abandoned": true, "time": "2014-10-13T03:31:43+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.4.0", + "version": "2.4.3", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "13388f00956b1503577598873fffb5ae994b5737" + "reference": "67c26b443f348a51926030c83481b85718457d3d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/13388f00956b1503577598873fffb5ae994b5737", - "reference": "13388f00956b1503577598873fffb5ae994b5737", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", + "reference": "67c26b443f348a51926030c83481b85718457d3d", "shasum": "" }, "require": { @@ -318,15 +338,19 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.4.1", + "bamarni/composer-bin-plugin": "^1.8.1", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.8 || ^9.3.10" + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + }, "branch-alias": { "dev-master": "2.4-dev" } @@ -388,6 +412,10 @@ "uri", "url" ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.4.3" + }, "funding": [ { "url": "https://github.com/GrahamCampbell", @@ -402,7 +430,7 @@ "type": "tidelift" } ], - "time": "2022-06-20T21:43:11+00:00" + "time": "2022-10-26T14:07:24+00:00" }, { "name": "guzzlehttp/ringphp", @@ -453,6 +481,10 @@ } ], "description": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "support": { + "issues": "https://github.com/guzzle/RingPHP/issues", + "source": "https://github.com/guzzle/RingPHP/tree/1.1.1" + }, "abandoned": true, "time": "2018-07-31T13:22:33+00:00" }, @@ -504,87 +536,13 @@ "Guzzle", "stream" ], + "support": { + "issues": "https://github.com/guzzle/streams/issues", + "source": "https://github.com/guzzle/streams/tree/master" + }, "abandoned": true, "time": "2014-10-12T19:18:40+00:00" }, - { - "name": "monolog/monolog", - "version": "1.25.3", - "source": { - "type": "git", - "url": "https://github.com/Seldaek/monolog.git", - "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Seldaek/monolog/zipball/fa82921994db851a8becaf3787a9e73c5976b6f1", - "reference": "fa82921994db851a8becaf3787a9e73c5976b6f1", - "shasum": "" - }, - "require": { - "php": ">=5.3.0", - "psr/log": "~1.0" - }, - "provide": { - "psr/log-implementation": "1.0.0" - }, - "require-dev": { - "aws/aws-sdk-php": "^2.4.9 || ^3.0", - "doctrine/couchdb": "~1.0@dev", - "graylog2/gelf-php": "~1.0", - "jakub-onderka/php-parallel-lint": "0.9", - "php-amqplib/php-amqplib": "~2.4", - "php-console/php-console": "^3.1.3", - "phpunit/phpunit": "~4.5", - "phpunit/phpunit-mock-objects": "2.3.0", - "ruflin/elastica": ">=0.90 <3.0", - "sentry/sentry": "^0.13", - "swiftmailer/swiftmailer": "^5.3|^6.0" - }, - "suggest": { - "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", - "doctrine/couchdb": "Allow sending log messages to a CouchDB server", - "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", - "ext-mongo": "Allow sending log messages to a MongoDB server", - "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", - "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", - "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", - "php-console/php-console": "Allow sending log messages to Google Chrome", - "rollbar/rollbar": "Allow sending log messages to Rollbar", - "ruflin/elastica": "Allow sending log messages to an Elastic Search server", - "sentry/sentry": "Allow sending log messages to a Sentry server" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Monolog\\": "src/Monolog" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "Sends your logs to files, sockets, inboxes, databases and various web services", - "homepage": "http://github.com/Seldaek/monolog", - "keywords": [ - "log", - "logging", - "psr-3" - ], - "time": "2019-12-20T14:15:16+00:00" - }, { "name": "paragonie/random_compat", "version": "v9.99.100", @@ -628,6 +586,11 @@ "pseudorandom", "random" ], + "support": { + "email": "info@paragonie.com", + "issues": "https://github.com/paragonie/random_compat/issues", + "source": "https://github.com/paragonie/random_compat" + }, "time": "2020-10-15T08:29:30+00:00" }, { @@ -686,6 +649,10 @@ "client", "http" ], + "support": { + "issues": "https://github.com/php-http/httplug/issues", + "source": "https://github.com/php-http/httplug/tree/2.3.0" + }, "time": "2022-02-21T09:52:22+00:00" }, { @@ -756,6 +723,10 @@ "message", "psr-7" ], + "support": { + "issues": "https://github.com/php-http/message/issues", + "source": "https://github.com/php-http/message/tree/1.13.0" + }, "time": "2022-02-11T13:41:14+00:00" }, { @@ -806,6 +777,10 @@ "stream", "uri" ], + "support": { + "issues": "https://github.com/php-http/message-factory/issues", + "source": "https://github.com/php-http/message-factory/tree/master" + }, "time": "2015-12-19T14:08:53+00:00" }, { @@ -859,6 +834,10 @@ "keywords": [ "promise" ], + "support": { + "issues": "https://github.com/php-http/promise/issues", + "source": "https://github.com/php-http/promise/tree/1.1.0" + }, "time": "2020-07-07T09:29:14+00:00" }, { @@ -908,6 +887,10 @@ "precision", "prestashop" ], + "support": { + "issues": "https://github.com/PrestaShop/decimal/issues", + "source": "https://github.com/PrestaShop/decimal/tree/1.5.0" + }, "time": "2022-02-02T09:04:37+00:00" }, { @@ -953,6 +936,10 @@ "package", "prestashop" ], + "support": { + "issues": "https://github.com/PrestaShopCorp/module-lib-cache-directory-provider/issues", + "source": "https://github.com/PrestaShopCorp/module-lib-cache-directory-provider/tree/master" + }, "time": "2020-09-08T14:13:23+00:00" }, { @@ -1057,20 +1044,24 @@ "package", "prestashop" ], + "support": { + "issues": "https://github.com/PrestaShopCorp/module-lib-service-container/issues", + "source": "https://github.com/PrestaShopCorp/module-lib-service-container/tree/1.4.0" + }, "time": "2021-06-01T15:21:20+00:00" }, { "name": "prestashop/prestashop-accounts-installer", - "version": "v1.0.1", + "version": "v1.0.2", "source": { "type": "git", "url": "https://github.com/PrestaShopCorp/prestashop-accounts-installer.git", - "reference": "f038af2408968d1045330b32aa1fed65fcaf4c9b" + "reference": "b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/f038af2408968d1045330b32aa1fed65fcaf4c9b", - "reference": "f038af2408968d1045330b32aa1fed65fcaf4c9b", + "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b", + "reference": "b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b", "shasum": "" }, "require": { @@ -1093,7 +1084,11 @@ "MIT" ], "description": "Utility package to install `ps_accounts` module or present data to trigger manual install from psx configuration page.", - "time": "2021-07-23T15:40:36+00:00" + "support": { + "issues": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/issues", + "source": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/tree/v1.0.2" + }, + "time": "2022-11-08T17:56:00+00:00" }, { "name": "psr/cache", @@ -1139,6 +1134,9 @@ "psr", "psr-6" ], + "support": { + "source": "https://github.com/php-fig/cache/tree/master" + }, "time": "2016-08-06T20:24:11+00:00" }, { @@ -1183,6 +1181,10 @@ "container-interop", "psr" ], + "support": { + "issues": "https://github.com/php-fig/container/issues", + "source": "https://github.com/php-fig/container/tree/1.1.1" + }, "time": "2021-03-05T17:36:06+00:00" }, { @@ -1232,6 +1234,9 @@ "psr", "psr-18" ], + "support": { + "source": "https://github.com/php-fig/http-client/tree/master" + }, "time": "2020-06-29T06:28:15+00:00" }, { @@ -1284,6 +1289,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/master" + }, "time": "2019-04-30T12:38:16+00:00" }, { @@ -1334,6 +1342,9 @@ "request", "response" ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/master" + }, "time": "2016-08-06T14:39:51+00:00" }, { @@ -1381,55 +1392,10 @@ "psr", "psr-3" ], - "time": "2021-05-03T11:20:27+00:00" - }, - { - "name": "psr/simple-cache", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/php-fig/simple-cache.git", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-fig/simple-cache/zipball/408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "reference": "408d5eafb83c57f6365a3ca330ff23aa4a5fa39b", - "shasum": "" - }, - "require": { - "php": ">=5.3.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0.x-dev" - } - }, - "autoload": { - "psr-4": { - "Psr\\SimpleCache\\": "src/" - } + "support": { + "source": "https://github.com/php-fig/log/tree/1.1.4" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" - } - ], - "description": "Common interfaces for simple caching", - "keywords": [ - "cache", - "caching", - "psr", - "psr-16", - "simple-cache" - ], - "time": "2017-10-23T01:57:42+00:00" + "time": "2021-05-03T11:20:27+00:00" }, { "name": "ralouphie/getallheaders", @@ -1469,20 +1435,24 @@ } ], "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, "time": "2019-03-08T08:55:37+00:00" }, { "name": "ramsey/uuid", - "version": "3.9.6", + "version": "3.9.7", "source": { "type": "git", "url": "https://github.com/ramsey/uuid.git", - "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3" + "reference": "dc75aa439eb4c1b77f5379fd958b3dc0e6014178" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/ramsey/uuid/zipball/ffa80ab953edd85d5b6c004f96181a538aad35a3", - "reference": "ffa80ab953edd85d5b6c004f96181a538aad35a3", + "url": "https://api.github.com/repos/ramsey/uuid/zipball/dc75aa439eb4c1b77f5379fd958b3dc0e6014178", + "reference": "dc75aa439eb4c1b77f5379fd958b3dc0e6014178", "shasum": "" }, "require": { @@ -1519,11 +1489,6 @@ "ramsey/uuid-doctrine": "Allows the use of Ramsey\\Uuid\\Uuid as Doctrine field type." }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, "autoload": { "files": [ "src/functions.php" @@ -1558,6 +1523,12 @@ "identifier", "uuid" ], + "support": { + "issues": "https://github.com/ramsey/uuid/issues", + "rss": "https://github.com/ramsey/uuid/releases.atom", + "source": "https://github.com/ramsey/uuid", + "wiki": "https://github.com/ramsey/uuid/wiki" + }, "funding": [ { "url": "https://github.com/ramsey", @@ -1568,7 +1539,7 @@ "type": "tidelift" } ], - "time": "2021-09-25T23:07:42+00:00" + "time": "2022-12-19T21:55:10+00:00" }, { "name": "react/promise", @@ -1630,6 +1601,10 @@ "promise", "promises" ], + "support": { + "issues": "https://github.com/reactphp/promise/issues", + "source": "https://github.com/reactphp/promise/tree/v2.9.0" + }, "funding": [ { "url": "https://github.com/WyriHaximus", @@ -1691,6 +1666,10 @@ "segment", "segmentio" ], + "support": { + "issues": "https://github.com/segmentio/analytics-php/issues", + "source": "https://github.com/segmentio/analytics-php/tree/1.8.0" + }, "time": "2021-05-31T22:44:22+00:00" }, { @@ -1755,41 +1734,58 @@ "log", "logging" ], + "support": { + "issues": "https://github.com/getsentry/sentry-php/issues", + "source": "https://github.com/getsentry/sentry-php/tree/1.11.0" + }, "time": "2020-02-12T18:38:11+00:00" }, { "name": "symfony/cache", - "version": "v3.4.47", + "version": "v4.4.48", "source": { "type": "git", "url": "https://github.com/symfony/cache.git", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813" + "reference": "3b98ed664887ad197b8ede3da2432787212eb915" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/cache/zipball/a7a14c4832760bd1fbd31be2859ffedc9b6ff813", - "reference": "a7a14c4832760bd1fbd31be2859ffedc9b6ff813", + "url": "https://api.github.com/repos/symfony/cache/zipball/3b98ed664887ad197b8ede3da2432787212eb915", + "reference": "3b98ed664887ad197b8ede3da2432787212eb915", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "psr/cache": "~1.0", - "psr/log": "~1.0", - "psr/simple-cache": "^1.0", - "symfony/polyfill-apcu": "~1.1" + "php": ">=7.1.3", + "psr/cache": "^1.0|^2.0", + "psr/log": "^1|^2|^3", + "symfony/cache-contracts": "^1.1.7|^2", + "symfony/polyfill-php73": "^1.9", + "symfony/polyfill-php80": "^1.16", + "symfony/service-contracts": "^1.1|^2", + "symfony/var-exporter": "^4.2|^5.0" }, "conflict": { - "symfony/var-dumper": "<3.3" + "doctrine/dbal": "<2.7", + "symfony/dependency-injection": "<3.4", + "symfony/http-kernel": "<4.4|>=5.0", + "symfony/var-dumper": "<4.4" }, "provide": { - "psr/cache-implementation": "1.0", - "psr/simple-cache-implementation": "1.0" + "psr/cache-implementation": "1.0|2.0", + "psr/simple-cache-implementation": "1.0|2.0", + "symfony/cache-implementation": "1.0|2.0" }, "require-dev": { "cache/integration-tests": "dev-master", - "doctrine/cache": "^1.6", - "doctrine/dbal": "^2.4|^3.0", - "predis/predis": "^1.0" + "doctrine/cache": "^1.6|^2.0", + "doctrine/dbal": "^2.7|^3.0", + "predis/predis": "^1.1", + "psr/simple-cache": "^1.0|^2.0", + "symfony/config": "^4.2|^5.0", + "symfony/dependency-injection": "^3.4|^4.1|^5.0", + "symfony/filesystem": "^4.4|^5.0", + "symfony/http-kernel": "^4.4", + "symfony/var-dumper": "^4.4|^5.0" }, "type": "library", "autoload": { @@ -1814,12 +1810,15 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Cache component with PSR-6, PSR-16, and tags", + "description": "Provides extended PSR-6, PSR-16 (and tags) implementations", "homepage": "https://symfony.com", "keywords": [ "caching", "psr6" ], + "support": { + "source": "https://github.com/symfony/cache/tree/v4.4.48" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1834,7 +1833,86 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-10-17T20:21:54+00:00" + }, + { + "name": "symfony/cache-contracts", + "version": "v2.5.2", + "source": { + "type": "git", + "url": "https://github.com/symfony/cache-contracts.git", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/cache-contracts/zipball/64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "reference": "64be4a7acb83b6f2bf6de9a02cee6dad41277ebc", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "psr/cache": "^1.0|^2.0|^3.0" + }, + "suggest": { + "symfony/cache-implementation": "" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" + }, + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "psr-4": { + "Symfony\\Contracts\\Cache\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to caching", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/cache-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-01-02T09:53:40+00:00" }, { "name": "symfony/config", @@ -1893,6 +1971,9 @@ ], "description": "Symfony Config Component", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/config/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1973,6 +2054,9 @@ ], "description": "Symfony DependencyInjection Component", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/dependency-injection/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -1990,32 +2074,36 @@ "time": "2020-10-24T10:57:07+00:00" }, { - "name": "symfony/expression-language", - "version": "v3.4.47", + "name": "symfony/deprecation-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/expression-language.git", - "reference": "de38e66398fca1fcb9c48e80279910e6889cb28f" + "url": "https://github.com/symfony/deprecation-contracts.git", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/expression-language/zipball/de38e66398fca1fcb9c48e80279910e6889cb28f", - "reference": "de38e66398fca1fcb9c48e80279910e6889cb28f", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", + "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8", - "symfony/cache": "~3.1|~4.0", - "symfony/polyfill-php70": "~1.6" + "php": ">=7.1" }, "type": "library", - "autoload": { - "psr-4": { - "Symfony\\Component\\ExpressionLanguage\\": "" + "extra": { + "branch-alias": { + "dev-main": "2.5-dev" }, - "exclude-from-classmap": [ - "/Tests/" - ] + "thanks": { + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" + } + }, + "autoload": { + "files": [ + "function.php" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2023,16 +2111,19 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony ExpressionLanguage Component", + "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/deprecation-contracts/tree/v2.5.2" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2047,31 +2138,31 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-01-02T09:53:40+00:00" }, { - "name": "symfony/filesystem", - "version": "v4.4.42", + "name": "symfony/expression-language", + "version": "v3.4.47", "source": { "type": "git", - "url": "https://github.com/symfony/filesystem.git", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" + "url": "https://github.com/symfony/expression-language.git", + "reference": "de38e66398fca1fcb9c48e80279910e6889cb28f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", - "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "url": "https://api.github.com/repos/symfony/expression-language/zipball/de38e66398fca1fcb9c48e80279910e6889cb28f", + "reference": "de38e66398fca1fcb9c48e80279910e6889cb28f", "shasum": "" }, "require": { - "php": ">=7.1.3", - "symfony/polyfill-ctype": "~1.8", - "symfony/polyfill-php80": "^1.16" + "php": "^5.5.9|>=7.0.8", + "symfony/cache": "~3.1|~4.0", + "symfony/polyfill-php70": "~1.6" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Filesystem\\": "" + "Symfony\\Component\\ExpressionLanguage\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2091,8 +2182,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Provides basic utilities for the filesystem", + "description": "Symfony ExpressionLanguage Component", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/expression-language/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2107,29 +2201,31 @@ "type": "tidelift" } ], - "time": "2022-05-20T08:49:14+00:00" + "time": "2020-10-24T10:57:07+00:00" }, { - "name": "symfony/finder", - "version": "v3.4.47", + "name": "symfony/filesystem", + "version": "v4.4.42", "source": { "type": "git", - "url": "https://github.com/symfony/finder.git", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e" + "url": "https://github.com/symfony/filesystem.git", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/finder/zipball/b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", - "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/815412ee8971209bd4c1eecd5f4f481eacd44bf5", + "reference": "815412ee8971209bd4c1eecd5f4f481eacd44bf5", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1.3", + "symfony/polyfill-ctype": "~1.8", + "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Finder\\": "" + "Symfony\\Component\\Filesystem\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -2149,8 +2245,11 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony Finder Component", + "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/filesystem/tree/v4.4.42" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2165,33 +2264,48 @@ "type": "tidelift" } ], - "time": "2020-11-16T17:02:08+00:00" + "time": "2022-05-20T08:49:14+00:00" }, { - "name": "symfony/options-resolver", - "version": "v3.4.47", + "name": "symfony/polyfill-ctype", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/options-resolver.git", - "reference": "c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744" + "url": "https://github.com/symfony/polyfill-ctype.git", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/options-resolver/zipball/c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744", - "reference": "c7efc97a47b2ebaabc19d5b6c6b50f5c37c92744", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", + "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", "shasum": "" }, "require": { - "php": "^5.5.9|>=7.0.8" + "php": ">=7.1" + }, + "provide": { + "ext-ctype": "*" + }, + "suggest": { + "ext-ctype": "For best performance" }, "type": "library", + "extra": { + "branch-alias": { + "dev-main": "1.27-dev" + }, + "thanks": { + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" + } + }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Component\\OptionsResolver\\": "" - }, - "exclude-from-classmap": [ - "/Tests/" - ] + "Symfony\\Polyfill\\Ctype\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2199,21 +2313,25 @@ ], "authors": [ { - "name": "Fabien Potencier", - "email": "fabien@symfony.com" + "name": "Gert de Pagter", + "email": "BackEndTea@gmail.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony OptionsResolver Component", + "description": "Symfony polyfill for ctype functions", "homepage": "https://symfony.com", "keywords": [ - "config", - "configuration", - "options" + "compatibility", + "ctype", + "polyfill", + "portable" ], + "support": { + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2228,29 +2346,35 @@ "type": "tidelift" } ], - "time": "2020-10-24T10:57:07+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { - "name": "symfony/polyfill-apcu", - "version": "v1.26.0", + "name": "symfony/polyfill-mbstring", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-apcu.git", - "reference": "43273a33c46f9d5a08dac76859f63d6814242e81" + "url": "https://github.com/symfony/polyfill-mbstring.git", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-apcu/zipball/43273a33c46f9d5a08dac76859f63d6814242e81", - "reference": "43273a33c46f9d5a08dac76859f63d6814242e81", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", "shasum": "" }, "require": { "php": ">=7.1" }, + "provide": { + "ext-mbstring": "*" + }, + "suggest": { + "ext-mbstring": "For best performance" + }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2262,7 +2386,7 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Apcu\\": "" + "Symfony\\Polyfill\\Mbstring\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -2279,15 +2403,18 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting apcu_* functions to lower PHP versions", + "description": "Symfony polyfill for the Mbstring extension", "homepage": "https://symfony.com", "keywords": [ - "apcu", "compatibility", + "mbstring", "polyfill", "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2302,71 +2429,60 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { - "name": "symfony/polyfill-ctype", - "version": "v1.26.0", + "name": "symfony/polyfill-php70", + "version": "v1.20.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4" + "url": "https://github.com/symfony/polyfill-php70.git", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", - "reference": "6fd1b9a79f6e3cf65f9e679b23af304cd9e010d4", + "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", + "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", "shasum": "" }, "require": { "php": ">=7.1" }, - "provide": { - "ext-ctype": "*" - }, - "suggest": { - "ext-ctype": "For best performance" - }, - "type": "library", + "type": "metapackage", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.20-dev" }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, - "autoload": { - "files": [ - "bootstrap.php" - ], - "psr-4": { - "Symfony\\Polyfill\\Ctype\\": "" - } - }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ { - "name": "Gert de Pagter", - "email": "BackEndTea@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for ctype functions", + "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "ctype", "polyfill", - "portable" + "portable", + "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php70/tree/v1.20.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2381,35 +2497,29 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2020-10-23T14:02:19+00:00" }, { - "name": "symfony/polyfill-mbstring", - "version": "v1.26.0", + "name": "symfony/polyfill-php73", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e" + "url": "https://github.com/symfony/polyfill-php73.git", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", - "reference": "9344f9cb97f3b19424af1a21a3b0e75b0a7d8d7e", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", "shasum": "" }, "require": { "php": ">=7.1" }, - "provide": { - "ext-mbstring": "*" - }, - "suggest": { - "ext-mbstring": "For best performance" - }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", @@ -2421,8 +2531,11 @@ "bootstrap.php" ], "psr-4": { - "Symfony\\Polyfill\\Mbstring\\": "" - } + "Symfony\\Polyfill\\Php73\\": "" + }, + "classmap": [ + "Resources/stubs" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2438,15 +2551,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill for the Mbstring extension", + "description": "Symfony polyfill backporting some PHP 7.3+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", - "mbstring", "polyfill", "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2461,40 +2576,55 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { - "name": "symfony/polyfill-php70", - "version": "v1.20.0", + "name": "symfony/polyfill-php80", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php70.git", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644" + "url": "https://github.com/symfony/polyfill-php80.git", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php70/zipball/5f03a781d984aae42cebd18e7912fa80f02ee644", - "reference": "5f03a781d984aae42cebd18e7912fa80f02ee644", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", "shasum": "" }, "require": { "php": ">=7.1" }, - "type": "metapackage", + "type": "library", "extra": { "branch-alias": { - "dev-main": "1.20-dev" + "dev-main": "1.27-dev" }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" } }, + "autoload": { + "files": [ + "bootstrap.php" + ], + "psr-4": { + "Symfony\\Polyfill\\Php80\\": "" + }, + "classmap": [ + "Resources/stubs" + ] + }, "notification-url": "https://packagist.org/downloads/", "license": [ "MIT" ], "authors": [ + { + "name": "Ion Bazan", + "email": "ion.bazan@gmail.com" + }, { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2504,7 +2634,7 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.0+ features to lower PHP versions", + "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ "compatibility", @@ -2512,6 +2642,9 @@ "portable", "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2526,45 +2659,47 @@ "type": "tidelift" } ], - "time": "2020-10-23T14:02:19+00:00" + "time": "2022-11-03T14:55:06+00:00" }, { - "name": "symfony/polyfill-php80", - "version": "v1.26.0", + "name": "symfony/service-contracts", + "version": "v2.5.2", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace" + "url": "https://github.com/symfony/service-contracts.git", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/cfa0ae98841b9e461207c13ab093d76b0fa7bace", - "reference": "cfa0ae98841b9e461207c13ab093d76b0fa7bace", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", "shasum": "" }, "require": { - "php": ">=7.1" + "php": ">=7.2.5", + "psr/container": "^1.1", + "symfony/deprecation-contracts": "^2.1|^3" + }, + "conflict": { + "ext-psr": "<1.1|>=2" + }, + "suggest": { + "symfony/service-implementation": "" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "1.26-dev" + "dev-main": "2.5-dev" }, "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" + "name": "symfony/contracts", + "url": "https://github.com/symfony/contracts" } }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php80\\": "" - }, - "classmap": [ - "Resources/stubs" - ] + "Symfony\\Contracts\\Service\\": "" + } }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -2572,9 +2707,78 @@ ], "authors": [ { - "name": "Ion Bazan", - "email": "ion.bazan@gmail.com" + "name": "Nicolas Grekas", + "email": "p@tchwork.com" }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Generic abstractions related to writing services", + "homepage": "https://symfony.com", + "keywords": [ + "abstractions", + "contracts", + "decoupling", + "interfaces", + "interoperability", + "standards" + ], + "support": { + "source": "https://github.com/symfony/service-contracts/tree/v2.5.2" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2022-05-30T19:17:29+00:00" + }, + { + "name": "symfony/var-exporter", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/var-exporter.git", + "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/be74908a6942fdd331554b3cec27ff41b45ccad4", + "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "require-dev": { + "symfony/var-dumper": "^4.4.9|^5.0.9|^6.0" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\VarExporter\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ { "name": "Nicolas Grekas", "email": "p@tchwork.com" @@ -2584,14 +2788,19 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions", + "description": "Allows exporting any serializable PHP data structure to plain PHP code", "homepage": "https://symfony.com", "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" + "clone", + "construct", + "export", + "hydrate", + "instantiate", + "serialize" ], + "support": { + "source": "https://github.com/symfony/var-exporter/tree/v5.4.21" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2606,7 +2815,7 @@ "type": "tidelift" } ], - "time": "2022-05-10T07:21:04+00:00" + "time": "2023-02-21T19:46:44+00:00" }, { "name": "symfony/yaml", @@ -2660,6 +2869,9 @@ ], "description": "Symfony Yaml Component", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/yaml/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -2728,6 +2940,10 @@ "check", "validate" ], + "support": { + "issues": "https://github.com/webmozarts/assert/issues", + "source": "https://github.com/webmozarts/assert/tree/1.11.0" + }, "time": "2022-06-03T18:03:27+00:00" } ], @@ -2783,6 +2999,10 @@ "regex", "regular expression" ], + "support": { + "issues": "https://github.com/composer/pcre/issues", + "source": "https://github.com/composer/pcre/tree/1.0.1" + }, "funding": [ { "url": "https://packagist.com", @@ -2859,6 +3079,11 @@ "validation", "versioning" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/semver/issues", + "source": "https://github.com/composer/semver/tree/3.3.2" + }, "funding": [ { "url": "https://packagist.com", @@ -2920,6 +3145,11 @@ "Xdebug", "performance" ], + "support": { + "irc": "irc://irc.freenode.org/composer", + "issues": "https://github.com/composer/xdebug-handler/issues", + "source": "https://github.com/composer/xdebug-handler/tree/2.0.5" + }, "funding": [ { "url": "https://packagist.com", @@ -2938,32 +3168,35 @@ }, { "name": "doctrine/annotations", - "version": "1.13.3", + "version": "1.14.3", "source": { "type": "git", "url": "https://github.com/doctrine/annotations.git", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0" + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/annotations/zipball/648b0343343565c4a056bfc8392201385e8d89f0", - "reference": "648b0343343565c4a056bfc8392201385e8d89f0", + "url": "https://api.github.com/repos/doctrine/annotations/zipball/fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", + "reference": "fb0d71a7393298a7b232cbf4c8b1f73f3ec3d5af", "shasum": "" }, "require": { - "doctrine/lexer": "1.*", + "doctrine/lexer": "^1 || ^2", "ext-tokenizer": "*", "php": "^7.1 || ^8.0", "psr/cache": "^1 || ^2 || ^3" }, "require-dev": { "doctrine/cache": "^1.11 || ^2.0", - "doctrine/coding-standard": "^6.0 || ^8.1", - "phpstan/phpstan": "^1.4.10 || ^1.8.0", - "phpunit/phpunit": "^7.5 || ^8.0 || ^9.1.5", - "symfony/cache": "^4.4 || ^5.2", + "doctrine/coding-standard": "^9 || ^10", + "phpstan/phpstan": "~1.4.10 || ^1.8.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "symfony/cache": "^4.4 || ^5.4 || ^6", "vimeo/psalm": "^4.10" }, + "suggest": { + "php": "PHP 8.0 or higher comes with attributes, a native replacement for annotations" + }, "type": "library", "autoload": { "psr-4": { @@ -3003,34 +3236,81 @@ "docblock", "parser" ], - "time": "2022-07-02T10:48:51+00:00" + "support": { + "issues": "https://github.com/doctrine/annotations/issues", + "source": "https://github.com/doctrine/annotations/tree/1.14.3" + }, + "time": "2023-02-01T09:20:38+00:00" + }, + { + "name": "doctrine/deprecations", + "version": "v1.0.0", + "source": { + "type": "git", + "url": "https://github.com/doctrine/deprecations.git", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "shasum": "" + }, + "require": { + "php": "^7.1|^8.0" + }, + "require-dev": { + "doctrine/coding-standard": "^9", + "phpunit/phpunit": "^7.5|^8.5|^9.5", + "psr/log": "^1|^2|^3" + }, + "suggest": { + "psr/log": "Allows logging deprecations via PSR-3 logger implementation" + }, + "type": "library", + "autoload": { + "psr-4": { + "Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.", + "homepage": "https://www.doctrine-project.org/", + "support": { + "issues": "https://github.com/doctrine/deprecations/issues", + "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + }, + "time": "2022-05-02T15:47:09+00:00" }, { "name": "doctrine/instantiator", - "version": "1.4.1", + "version": "1.5.0", "source": { "type": "git", "url": "https://github.com/doctrine/instantiator.git", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc" + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc", - "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc", + "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b", + "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9", + "doctrine/coding-standard": "^9 || ^11", "ext-pdo": "*", "ext-phar": "*", "phpbench/phpbench": "^0.16 || ^1", "phpstan/phpstan": "^1.4", "phpstan/phpstan-phpunit": "^1", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.22" + "vimeo/psalm": "^4.30 || ^5.4" }, "type": "library", "autoload": { @@ -3055,6 +3335,10 @@ "constructor", "instantiate" ], + "support": { + "issues": "https://github.com/doctrine/instantiator/issues", + "source": "https://github.com/doctrine/instantiator/tree/1.5.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -3069,35 +3353,37 @@ "type": "tidelift" } ], - "time": "2022-03-03T08:28:38+00:00" + "time": "2022-12-30T00:15:36+00:00" }, { "name": "doctrine/lexer", - "version": "1.2.3", + "version": "2.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229" + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/c268e882d4dbdd85e36e4ad69e02dc284f89d229", - "reference": "c268e882d4dbdd85e36e4ad69e02dc284f89d229", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", "shasum": "" }, "require": { + "doctrine/deprecations": "^1.0", "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9.0", + "doctrine/coding-standard": "^9 || ^10", "phpstan/phpstan": "^1.3", "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", - "vimeo/psalm": "^4.11" + "psalm/plugin-phpunit": "^0.18.3", + "vimeo/psalm": "^4.11 || ^5.0" }, "type": "library", "autoload": { "psr-4": { - "Doctrine\\Common\\Lexer\\": "lib/Doctrine/Common/Lexer" + "Doctrine\\Common\\Lexer\\": "src" } }, "notification-url": "https://packagist.org/downloads/", @@ -3127,6 +3413,10 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/doctrine/lexer/issues", + "source": "https://github.com/doctrine/lexer/tree/2.1.0" + }, "funding": [ { "url": "https://www.doctrine-project.org/sponsorship.html", @@ -3141,7 +3431,7 @@ "type": "tidelift" } ], - "time": "2022-02-28T11:07:21+00:00" + "time": "2022-12-14T08:49:07+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -3240,6 +3530,10 @@ } ], "description": "A tool to automatically fix PHP code style", + "support": { + "issues": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/issues", + "source": "https://github.com/FriendsOfPHP/PHP-CS-Fixer/tree/v2.19.3" + }, "funding": [ { "url": "https://github.com/keradus", @@ -3295,6 +3589,10 @@ "object", "object graph" ], + "support": { + "issues": "https://github.com/myclabs/DeepCopy/issues", + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + }, "funding": [ { "url": "https://tidelift.com/funding/github/packagist/myclabs/deep-copy", @@ -3352,6 +3650,10 @@ "parser", "php" ], + "support": { + "issues": "https://github.com/nikic/PHP-Parser/issues", + "source": "https://github.com/nikic/PHP-Parser/tree/v3.1.5" + }, "time": "2018-02-28T20:30:58+00:00" }, { @@ -3403,6 +3705,11 @@ "keywords": [ "diff" ], + "support": { + "issues": "https://github.com/PHP-CS-Fixer/diff/issues", + "source": "https://github.com/PHP-CS-Fixer/diff/tree/v1.3.1" + }, + "abandoned": true, "time": "2020-10-14T08:39:05+00:00" }, { @@ -3452,6 +3759,10 @@ "reflection", "static analysis" ], + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionCommon/issues", + "source": "https://github.com/phpDocumentor/ReflectionCommon/tree/2.x" + }, "time": "2020-06-27T09:03:43+00:00" }, { @@ -3505,6 +3816,10 @@ } ], "description": "With this component, a library can provide support for annotations via DocBlocks or otherwise retrieve information that is embedded in a DocBlock.", + "support": { + "issues": "https://github.com/phpDocumentor/ReflectionDocBlock/issues", + "source": "https://github.com/phpDocumentor/ReflectionDocBlock/tree/5.3.0" + }, "time": "2021-10-19T17:43:47+00:00" }, { @@ -3551,6 +3866,10 @@ } ], "description": "A PSR-5 based resolver of Class names, Types and Structural Element Names", + "support": { + "issues": "https://github.com/phpDocumentor/TypeResolver/issues", + "source": "https://github.com/phpDocumentor/TypeResolver/tree/1.6.1" + }, "time": "2022-03-15T21:29:03+00:00" }, { @@ -3614,6 +3933,10 @@ "spy", "stub" ], + "support": { + "issues": "https://github.com/phpspec/prophecy/issues", + "source": "https://github.com/phpspec/prophecy/tree/v1.10.3" + }, "time": "2020-03-05T15:02:03+00:00" }, { @@ -3677,6 +4000,11 @@ "testing", "xunit" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/4.0" + }, "time": "2017-04-02T07:44:40+00:00" }, { @@ -3724,6 +4052,11 @@ "filesystem", "iterator" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/php-file-iterator/issues", + "source": "https://github.com/sebastianbergmann/php-file-iterator/tree/1.4.5" + }, "time": "2017-11-27T13:52:08+00:00" }, { @@ -3765,6 +4098,10 @@ "keywords": [ "template" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-text-template/issues", + "source": "https://github.com/sebastianbergmann/php-text-template/tree/1.2.1" + }, "time": "2015-06-21T13:50:34+00:00" }, { @@ -3814,6 +4151,10 @@ "keywords": [ "timer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-timer/issues", + "source": "https://github.com/sebastianbergmann/php-timer/tree/master" + }, "time": "2017-02-26T11:10:40+00:00" }, { @@ -3863,6 +4204,10 @@ "keywords": [ "tokenizer" ], + "support": { + "issues": "https://github.com/sebastianbergmann/php-token-stream/issues", + "source": "https://github.com/sebastianbergmann/php-token-stream/tree/master" + }, "abandoned": true, "time": "2017-11-27T05:48:46+00:00" }, @@ -3946,6 +4291,10 @@ "testing", "xunit" ], + "support": { + "issues": "https://github.com/sebastianbergmann/phpunit/issues", + "source": "https://github.com/sebastianbergmann/phpunit/tree/5.7.27" + }, "time": "2018-02-01T05:50:59+00:00" }, { @@ -4005,6 +4354,11 @@ "mock", "xunit" ], + "support": { + "irc": "irc://irc.freenode.net/phpunit", + "issues": "https://github.com/sebastianbergmann/phpunit-mock-objects/issues", + "source": "https://github.com/sebastianbergmann/phpunit-mock-objects/tree/3.4" + }, "abandoned": true, "time": "2017-06-30T09:13:00+00:00" }, @@ -4049,6 +4403,9 @@ ], "description": "Automatically add an 'index.php' in all the current or specified directories and all sub-directories.", "homepage": "https://github.com/PrestaShopCorp/autoindex", + "support": { + "source": "https://github.com/PrestaShopCorp/autoindex/tree/v1.0.0" + }, "time": "2020-03-11T13:37:03+00:00" }, { @@ -4095,6 +4452,10 @@ ], "description": "Rewrite your file headers to add the license or to make them up-to-date", "homepage": "https://github.com/PrestaShopCorp/header-stamp", + "support": { + "issues": "https://github.com/PrestaShopCorp/header-stamp/issues", + "source": "https://github.com/PrestaShopCorp/header-stamp/tree/v1.7" + }, "time": "2020-12-09T16:40:38+00:00" }, { @@ -4137,6 +4498,10 @@ "MIT" ], "description": "PrestaShop coding standards", + "support": { + "issues": "https://github.com/PrestaShop/php-dev-tools/issues", + "source": "https://github.com/PrestaShop/php-dev-tools/tree/v3.16.1" + }, "time": "2021-10-18T07:48:21+00:00" }, { @@ -4182,6 +4547,10 @@ ], "description": "Looks up which function or method a line of code belongs to", "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", + "support": { + "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + }, "funding": [ { "url": "https://github.com/sebastianbergmann", @@ -4252,6 +4621,10 @@ "compare", "equality" ], + "support": { + "issues": "https://github.com/sebastianbergmann/comparator/issues", + "source": "https://github.com/sebastianbergmann/comparator/tree/1.2" + }, "time": "2017-01-29T09:50:25+00:00" }, { @@ -4304,6 +4677,10 @@ "keywords": [ "diff" ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/1.4" + }, "time": "2017-05-22T07:24:03+00:00" }, { @@ -4354,6 +4731,10 @@ "environment", "hhvm" ], + "support": { + "issues": "https://github.com/sebastianbergmann/environment/issues", + "source": "https://github.com/sebastianbergmann/environment/tree/master" + }, "time": "2016-11-26T07:53:53+00:00" }, { @@ -4421,6 +4802,10 @@ "export", "exporter" ], + "support": { + "issues": "https://github.com/sebastianbergmann/exporter/issues", + "source": "https://github.com/sebastianbergmann/exporter/tree/master" + }, "time": "2016-11-19T08:54:04+00:00" }, { @@ -4472,6 +4857,10 @@ "keywords": [ "global state" ], + "support": { + "issues": "https://github.com/sebastianbergmann/global-state/issues", + "source": "https://github.com/sebastianbergmann/global-state/tree/1.1.1" + }, "time": "2015-10-12T03:26:01+00:00" }, { @@ -4518,6 +4907,10 @@ ], "description": "Traverses array structures and object graphs to enumerate all referenced objects", "homepage": "https://github.com/sebastianbergmann/object-enumerator/", + "support": { + "issues": "https://github.com/sebastianbergmann/object-enumerator/issues", + "source": "https://github.com/sebastianbergmann/object-enumerator/tree/master" + }, "time": "2017-02-18T15:18:39+00:00" }, { @@ -4571,6 +4964,10 @@ ], "description": "Provides functionality to recursively process PHP variables", "homepage": "http://www.github.com/sebastianbergmann/recursion-context", + "support": { + "issues": "https://github.com/sebastianbergmann/recursion-context/issues", + "source": "https://github.com/sebastianbergmann/recursion-context/tree/master" + }, "time": "2016-11-19T07:33:16+00:00" }, { @@ -4613,6 +5010,10 @@ ], "description": "Provides a list of PHP built-in functions that operate on resources", "homepage": "https://www.github.com/sebastianbergmann/resource-operations", + "support": { + "issues": "https://github.com/sebastianbergmann/resource-operations/issues", + "source": "https://github.com/sebastianbergmann/resource-operations/tree/master" + }, "time": "2015-07-28T20:34:47+00:00" }, { @@ -4656,20 +5057,24 @@ ], "description": "Library that helps with managing the version number of Git-hosted PHP projects", "homepage": "https://github.com/sebastianbergmann/version", + "support": { + "issues": "https://github.com/sebastianbergmann/version/issues", + "source": "https://github.com/sebastianbergmann/version/tree/master" + }, "time": "2016-10-03T07:35:21+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.1", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619", - "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -4705,9 +5110,15 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], - "time": "2022-06-18T07:21:10+00:00" + "support": { + "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", + "source": "https://github.com/squizlabs/PHP_CodeSniffer", + "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + }, + "time": "2023-02-22T23:07:41+00:00" }, { "name": "symfony/console", @@ -4774,6 +5185,9 @@ ], "description": "Symfony Console Component", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/console/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4839,6 +5253,9 @@ ], "description": "Provides tools to ease debugging PHP code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/debug/tree/v4.4.44" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -4856,70 +5273,6 @@ "abandoned": "symfony/error-handler", "time": "2022-07-28T16:29:46+00:00" }, - { - "name": "symfony/deprecation-contracts", - "version": "v2.5.2", - "source": { - "type": "git", - "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "reference": "e8b495ea28c1d97b5e0c121748d6f9b53d075c66", - "shasum": "" - }, - "require": { - "php": ">=7.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "2.5-dev" - }, - "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" - } - }, - "autoload": { - "files": [ - "function.php" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" - }, - { - "name": "Symfony Community", - "homepage": "https://symfony.com/contributors" - } - ], - "description": "A generic function and convention to trigger deprecation notices", - "homepage": "https://symfony.com", - "funding": [ - { - "url": "https://symfony.com/sponsor", - "type": "custom" - }, - { - "url": "https://github.com/fabpot", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", - "type": "tidelift" - } - ], - "time": "2022-01-02T09:53:40+00:00" - }, { "name": "symfony/event-dispatcher", "version": "v4.4.44", @@ -4985,6 +5338,9 @@ ], "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/v4.4.44" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5061,6 +5417,9 @@ "interoperability", "standards" ], + "support": { + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5078,39 +5437,30 @@ "time": "2022-01-02T09:41:36+00:00" }, { - "name": "symfony/polyfill-php72", - "version": "v1.26.0", + "name": "symfony/finder", + "version": "v3.4.47", "source": { "type": "git", - "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2" + "url": "https://github.com/symfony/finder.git", + "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/bf44a9fd41feaac72b074de600314a93e2ae78e2", - "reference": "bf44a9fd41feaac72b074de600314a93e2ae78e2", + "url": "https://api.github.com/repos/symfony/finder/zipball/b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", + "reference": "b6b6ad3db3edb1b4b1c1896b1975fb684994de6e", "shasum": "" }, "require": { - "php": ">=7.1" + "php": "^5.5.9|>=7.0.8" }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.26-dev" - }, - "thanks": { - "name": "symfony/polyfill", - "url": "https://github.com/symfony/polyfill" - } - }, "autoload": { - "files": [ - "bootstrap.php" - ], "psr-4": { - "Symfony\\Polyfill\\Php72\\": "" - } + "Symfony\\Component\\Finder\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] }, "notification-url": "https://packagist.org/downloads/", "license": [ @@ -5118,22 +5468,19 @@ ], "authors": [ { - "name": "Nicolas Grekas", - "email": "p@tchwork.com" + "name": "Fabien Potencier", + "email": "fabien@symfony.com" }, { "name": "Symfony Community", "homepage": "https://symfony.com/contributors" } ], - "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", + "description": "Symfony Finder Component", "homepage": "https://symfony.com", - "keywords": [ - "compatibility", - "polyfill", - "portable", - "shim" - ], + "support": { + "source": "https://github.com/symfony/finder/tree/v3.4.47" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5148,30 +5495,32 @@ "type": "tidelift" } ], - "time": "2022-05-24T11:49:31+00:00" + "time": "2020-11-16T17:02:08+00:00" }, { - "name": "symfony/process", - "version": "v5.4.11", + "name": "symfony/options-resolver", + "version": "v5.4.21", "source": { "type": "git", - "url": "https://github.com/symfony/process.git", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1" + "url": "https://github.com/symfony/options-resolver.git", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/6e75fe6874cbc7e4773d049616ab450eff537bf1", - "reference": "6e75fe6874cbc7e4773d049616ab450eff537bf1", + "url": "https://api.github.com/repos/symfony/options-resolver/zipball/4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", + "reference": "4fe5cf6ede71096839f0e4b4444d65dd3a7c1eb9", "shasum": "" }, "require": { "php": ">=7.2.5", + "symfony/deprecation-contracts": "^2.1|^3", + "symfony/polyfill-php73": "~1.0", "symfony/polyfill-php80": "^1.16" }, "type": "library", "autoload": { "psr-4": { - "Symfony\\Component\\Process\\": "" + "Symfony\\Component\\OptionsResolver\\": "" }, "exclude-from-classmap": [ "/Tests/" @@ -5191,8 +5540,16 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Executes commands in sub-processes", + "description": "Provides an improved replacement for the array_replace PHP function", "homepage": "https://symfony.com", + "keywords": [ + "config", + "configuration", + "options" + ], + "support": { + "source": "https://github.com/symfony/options-resolver/tree/v5.4.21" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5207,46 +5564,41 @@ "type": "tidelift" } ], - "time": "2022-06-27T16:58:25+00:00" + "time": "2023-02-14T08:03:56+00:00" }, { - "name": "symfony/service-contracts", - "version": "v2.5.2", + "name": "symfony/polyfill-php72", + "version": "v1.27.0", "source": { "type": "git", - "url": "https://github.com/symfony/service-contracts.git", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c" + "url": "https://github.com/symfony/polyfill-php72.git", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/4b426aac47d6427cc1a1d0f7e2ac724627f5966c", - "reference": "4b426aac47d6427cc1a1d0f7e2ac724627f5966c", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", + "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", "shasum": "" }, "require": { - "php": ">=7.2.5", - "psr/container": "^1.1", - "symfony/deprecation-contracts": "^2.1|^3" - }, - "conflict": { - "ext-psr": "<1.1|>=2" - }, - "suggest": { - "symfony/service-implementation": "" + "php": ">=7.1" }, "type": "library", "extra": { "branch-alias": { - "dev-main": "2.5-dev" + "dev-main": "1.27-dev" }, "thanks": { - "name": "symfony/contracts", - "url": "https://github.com/symfony/contracts" + "name": "symfony/polyfill", + "url": "https://github.com/symfony/polyfill" } }, "autoload": { + "files": [ + "bootstrap.php" + ], "psr-4": { - "Symfony\\Contracts\\Service\\": "" + "Symfony\\Polyfill\\Php72\\": "" } }, "notification-url": "https://packagist.org/downloads/", @@ -5263,16 +5615,17 @@ "homepage": "https://symfony.com/contributors" } ], - "description": "Generic abstractions related to writing services", + "description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions", "homepage": "https://symfony.com", "keywords": [ - "abstractions", - "contracts", - "decoupling", - "interfaces", - "interoperability", - "standards" + "compatibility", + "polyfill", + "portable", + "shim" ], + "support": { + "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5287,20 +5640,82 @@ "type": "tidelift" } ], - "time": "2022-05-30T19:17:29+00:00" + "time": "2022-11-03T14:55:06+00:00" + }, + { + "name": "symfony/process", + "version": "v5.4.21", + "source": { + "type": "git", + "url": "https://github.com/symfony/process.git", + "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/symfony/process/zipball/d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", + "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", + "shasum": "" + }, + "require": { + "php": ">=7.2.5", + "symfony/polyfill-php80": "^1.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Symfony\\Component\\Process\\": "" + }, + "exclude-from-classmap": [ + "/Tests/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "https://symfony.com/contributors" + } + ], + "description": "Executes commands in sub-processes", + "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/process/tree/v5.4.21" + }, + "funding": [ + { + "url": "https://symfony.com/sponsor", + "type": "custom" + }, + { + "url": "https://github.com/fabpot", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", + "type": "tidelift" + } + ], + "time": "2023-02-21T19:46:44+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.5", + "version": "v5.4.21", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30" + "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", - "reference": "4d04b5c24f3c9a1a168a131f6cbe297155bc0d30", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f83692cd869a6f2391691d40a01e8acb89e76fee", + "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee", "shasum": "" }, "require": { @@ -5332,6 +5747,9 @@ ], "description": "Provides a way to profile code", "homepage": "https://symfony.com", + "support": { + "source": "https://github.com/symfony/stopwatch/tree/v5.4.21" + }, "funding": [ { "url": "https://symfony.com/sponsor", @@ -5346,7 +5764,7 @@ "type": "tidelift" } ], - "time": "2022-02-18T16:06:09+00:00" + "time": "2023-02-14T08:03:56+00:00" } ], "aliases": [], diff --git a/config.xml b/config.xml index 1ef050928..ae05a4631 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index b07daf604..ef9532118 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -130,7 +130,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '3.0.2'; + const VERSION = '8.3.1.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -156,7 +156,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '3.0.2'; + $this->version = '8.3.1.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; diff --git a/src/Api/GenericClient.php b/src/Api/GenericClient.php index 086bc1c0a..7df12518a 100755 --- a/src/Api/GenericClient.php +++ b/src/Api/GenericClient.php @@ -170,7 +170,7 @@ protected function setRoute($route) * * @param ClientInterface $client */ - protected function setClient(ClientInterface $client) + protected function setClient($client) { $this->client = $client; } diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 2caab8327..1946e305a 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -20,20 +20,24 @@ namespace PrestaShop\Module\PrestashopCheckout\Api\Payment\Client; -use GuzzleHttp\Client; use PrestaShop\Module\PrestashopCheckout\Api\GenericClient; use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\ShopContext; use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; +use Psr\Http\Client\ClientInterface; /** * Construct the client used to make call to maasland */ class PaymentClient extends GenericClient { - public function __construct(\Link $link, Client $client = null) + /** + * @param \Link $link + * @param ClientInterface|null $client + */ + public function __construct(\Link $link, $client = null) { parent::__construct(); diff --git a/tests/Unit/FundingSource/FundingSourceCollectionTest.php b/tests/Unit/FundingSource/FundingSourceCollectionTest.php index 0364c6011..18b64f290 100644 --- a/tests/Unit/FundingSource/FundingSourceCollectionTest.php +++ b/tests/Unit/FundingSource/FundingSourceCollectionTest.php @@ -26,13 +26,6 @@ class FundingSourceCollectionTest extends TestCase { - protected function setUp() - { - $collection = new FundingSourceCollection($this->getFundingSourceEntityList()); - - $this->assertContainsOnlyInstancesOf(FundingSourceEntity::class, $collection->get()); - } - public function testSortFundingSourcesByPosition() { $collection = new FundingSourceCollection($this->getFundingSourceEntityList()); diff --git a/tests/phpstan/phpstan-PS-8.neon b/tests/phpstan/phpstan-PS-8.neon new file mode 100644 index 000000000..bb2f93ac4 --- /dev/null +++ b/tests/phpstan/phpstan-PS-8.neon @@ -0,0 +1,37 @@ +includes: + - %currentWorkingDirectory%/vendor/prestashop/php-dev-tools/phpstan/ps-module-extension.neon + +parameters: + paths: + # From PHPStan 0.12, paths to check are relative to the neon file + - ../../classes + - ../../controllers + - ../../src + - ../../ps_checkout.php + reportUnmatchedIgnoredErrors: false + ignoreErrors: + - '#Cannot assign offset "merchant…" to string\|true.#' + - '#Property ModuleCore::\$version \(float\) does not accept string.#' + - '#Strict comparison using === between false and string will always evaluate to false.#' + - '#Call to function is_array\(\) with Currency will always evaluate to false.#' + - '#Parameter \#1 \$id of class Customer constructor expects null, bool\|int<1, max>\|int given.#' + - '#Parameter \#1 \$id of class Customer constructor expects null, int\|int<1, max> given.#' + - '#Parameter \#1 \$id of class Customer constructor expects null, int given.#' + - '#Parameter \#1 \$hook_name of method ModuleCore::registerHook\(\) expects string, array given.#' + - '#Parameter \#6 \$idShop of method LinkCore::getModuleLink\(\) expects null, int given.#' + - '#Call to an undefined method\(\) AdminController|FrontController::getCheckoutProcess\(\).#' + - '#Parameter \#1 \$id_hook of method ModuleCore::updatePosition\(\) expects bool, int given.#' + - '#Property TabCore::\$name \(string\) does not accept array.#' + - '#Access to an undefined property PaymentModule::\$currentOrderReference.#' + - '#Property CustomerCore::\$passwd \(int\) does not accept bool\|string.#' + - '#Property CustomerCore::\$passwd \(int\) does not accept string.#' + - '#Parameter \#4 \$ssl of method LinkCore::getModuleLink\(\) expects null, true given.#' + - '#Parameter \#1 \$id of class Customer constructor expects null, int<1, max>\|int given.#' + - '#Property CustomerMessageCore::\$ip_address \(string\) does not accept int.#' + - '#Left side of \&\& is always true.#' + - '#Parameter \#7 \$currency_special of method PaymentModuleCore::validateOrder\(\) expects null, int given.#' + - '#Parameter \#9 \$secure_key of method PaymentModuleCore::validateOrder\(\) expects bool, string given.#' + - '#Property CustomerMessageCore::\$private \(int\) does not accept true.#' + - '#Parameter \#1 \$amount_paid of method OrderCore::addOrderPayment\(\) expects string, float\|int given.#' + + level: 5 From b659cbc7d7131bfb2cef3fad491bc763531e3f68 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:48:52 +0100 Subject: [PATCH 002/343] Update marketplace metadata --- .github/mktp-metadata.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/mktp-metadata.json b/.github/mktp-metadata.json index 0c2d10552..bb2948cb6 100644 --- a/.github/mktp-metadata.json +++ b/.github/mktp-metadata.json @@ -5,5 +5,5 @@ "channel" : "stable", "type_upgrade" : "updatemin", "product_type" : "module", - "compatible_from" : "1.6.1.0" -} \ No newline at end of file + "compatible_from" : "8.0.0" +} From 7bec9b00a56043508ccd0e2a7554444ecd1f4839 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 25 Apr 2023 15:27:27 +0200 Subject: [PATCH 003/343] Bump version prestashop-accounts-installer --- composer.lock | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/composer.lock b/composer.lock index 5940a1e5c..57eb9de61 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": "60b2d09fa06fbf665b582ab406bb22dd", + "content-hash": "5a10a643aeceddd78dd585ca3cb79640", "packages": [ { "name": "clue/stream-filter", @@ -1052,16 +1052,16 @@ }, { "name": "prestashop/prestashop-accounts-installer", - "version": "v1.0.2", + "version": "v1.0.3", "source": { "type": "git", "url": "https://github.com/PrestaShopCorp/prestashop-accounts-installer.git", - "reference": "b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b" + "reference": "b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b", - "reference": "b8f82c0f1c16f2dc9a7e284c0d3883d721231c3b", + "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7", + "reference": "b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7", "shasum": "" }, "require": { @@ -1086,9 +1086,9 @@ "description": "Utility package to install `ps_accounts` module or present data to trigger manual install from psx configuration page.", "support": { "issues": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/issues", - "source": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/tree/v1.0.2" + "source": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/tree/v1.0.3" }, - "time": "2022-11-08T17:56:00+00:00" + "time": "2023-04-18T11:54:57+00:00" }, { "name": "psr/cache", From 6ac02b3cc0dd50c0c2934a72f75f3f93ad302314 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 18 Apr 2023 18:51:55 +0300 Subject: [PATCH 004/343] Changed logos --- README.md | 2 +- logo.png | Bin 117232 -> 3257 bytes views/css/adminAfterHeader.css | 5 +++-- views/img/icon-blue.svg | 10 ++++++++++ views/img/icon-white.svg | 10 ++++++++++ views/img/icon.svg | 10 ++++++++++ views/img/logo-blue.svg | 9 +++++++++ views/img/logo-white.svg | 9 +++++++++ views/img/logo.svg | 9 +++++++++ 9 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 views/img/icon-blue.svg create mode 100644 views/img/icon-white.svg create mode 100644 views/img/icon.svg create mode 100644 views/img/logo-blue.svg create mode 100644 views/img/logo-white.svg create mode 100644 views/img/logo.svg diff --git a/README.md b/README.md index 9163057c3..0aa0823cb 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -

PrestaShop Checkout

+

PrestaShop Checkout

# PrestaShop Checkout diff --git a/logo.png b/logo.png index 633fb798ccae8744f333e5c5f3a3203068b093ee..1b0229c7ee4f11bf11bd81873955371b707d3d44 100644 GIT binary patch literal 3257 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 literal 117232 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 z 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 diff --git a/views/css/adminAfterHeader.css b/views/css/adminAfterHeader.css index 5003d065d..eed6294ce 100755 --- a/views/css/adminAfterHeader.css +++ b/views/css/adminAfterHeader.css @@ -20,15 +20,16 @@ .img { width: 100%; height: 100%; - background-image: url('../img/prestashop_brand.png'); + background-image: url('../img/logo.svg'); background-repeat: no-repeat; background-size: contain; + background-position: center; } @media screen and (max-width: 768px) { .img { width: 34px; height: 34px; - background-image: url('../img/logo.png'); + background-image: url('../img/icon.svg'); } } .payment-icon { diff --git a/views/img/icon-blue.svg b/views/img/icon-blue.svg new file mode 100644 index 000000000..17915746e --- /dev/null +++ b/views/img/icon-blue.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/views/img/icon-white.svg b/views/img/icon-white.svg new file mode 100644 index 000000000..f5de129ff --- /dev/null +++ b/views/img/icon-white.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/views/img/icon.svg b/views/img/icon.svg new file mode 100644 index 000000000..b584e1f6b --- /dev/null +++ b/views/img/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + diff --git a/views/img/logo-blue.svg b/views/img/logo-blue.svg new file mode 100644 index 000000000..2b5b1496b --- /dev/null +++ b/views/img/logo-blue.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/views/img/logo-white.svg b/views/img/logo-white.svg new file mode 100644 index 000000000..a98f63e1c --- /dev/null +++ b/views/img/logo-white.svg @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/views/img/logo.svg b/views/img/logo.svg new file mode 100644 index 000000000..8c4ce05f7 --- /dev/null +++ b/views/img/logo.svg @@ -0,0 +1,9 @@ + + + + + + + + From 379d5fd74f795a08b80133308f3df64ef50db978 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Fri, 21 Apr 2023 17:54:56 +0300 Subject: [PATCH 005/343] replaced logo in img directory --- views/img/logo.png | Bin 117232 -> 3257 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/views/img/logo.png b/views/img/logo.png index 633fb798ccae8744f333e5c5f3a3203068b093ee..1b0229c7ee4f11bf11bd81873955371b707d3d44 100644 GIT binary patch literal 3257 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 literal 117232 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 z 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 From b5e81793e3da5e2c298439679a0ae9b7cfcccb9a Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 15 May 2023 10:56:58 +0200 Subject: [PATCH 006/343] Bump module version to 8.3.2.0 --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index ae05a4631..cd8930ee2 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 33958b489..b4338e7bd 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -121,7 +121,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.1.0'; + const VERSION = '8.3.2.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -140,7 +140,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.1.0'; + $this->version = '8.3.2.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From 1a1f4bf529b10b3cf34b991d8cc40c72ca0c4063 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 1 Jun 2023 10:24:30 +0200 Subject: [PATCH 007/343] Changes for PS 8.x --- composer.json | 3 +- composer.lock | 658 ++++++++++------------- config.xml | 2 +- ps_checkout.php | 4 +- src/Api/GenericClient.php | 84 +-- src/Api/Payment/Client/PaymentClient.php | 15 +- upgrade/upgrade-8.3.3.0.php | 52 ++ 7 files changed, 362 insertions(+), 456 deletions(-) create mode 100644 upgrade/upgrade-8.3.3.0.php diff --git a/composer.json b/composer.json index 6cef34418..8e81235e6 100755 --- a/composer.json +++ b/composer.json @@ -22,7 +22,8 @@ "require": { "php": ">=7.2", "giggsey/libphonenumber-for-php": "^8.12", - "guzzlehttp/log-subscriber": "~1.0", + "gmponos/guzzle_logger": "^2.2", + "guzzlehttp/guzzle": "^7.4", "prestashop/decimal": "^1.3", "prestashop/module-lib-guzzle-adapter": "^1.0", "prestashop/module-lib-service-container": "^1.0", diff --git a/composer.lock b/composer.lock index 57eb9de61..acb07e15d 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": "5a10a643aeceddd78dd585ca3cb79640", + "content-hash": "83e53d19b0dc27f33a825b790f2f8fd2", "packages": [ { "name": "clue/stream-filter", @@ -74,16 +74,16 @@ }, { "name": "giggsey/libphonenumber-for-php", - "version": "8.13.7", + "version": "8.13.12", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "281b12d9091fcd04e9379a67f723179c44b13ec8" + "reference": "218caeeeb224bf2f553597b5c3a1647ff936db64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/281b12d9091fcd04e9379a67f723179c44b13ec8", - "reference": "281b12d9091fcd04e9379a67f723179c44b13ec8", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/218caeeeb224bf2f553597b5c3a1647ff936db64", + "reference": "218caeeeb224bf2f553597b5c3a1647ff936db64", "shasum": "" }, "require": { @@ -142,20 +142,20 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "source": "https://github.com/giggsey/libphonenumber-for-php" }, - "time": "2023-03-03T14:16:43+00:00" + "time": "2023-05-22T07:19:16+00:00" }, { "name": "giggsey/locale", - "version": "2.3", + "version": "2.4", "source": { "type": "git", "url": "https://github.com/giggsey/Locale.git", - "reference": "5f035523740be40d40ac768a123c9bcc1ae12f56" + "reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/Locale/zipball/5f035523740be40d40ac768a123c9bcc1ae12f56", - "reference": "5f035523740be40d40ac768a123c9bcc1ae12f56", + "url": "https://api.github.com/repos/giggsey/Locale/zipball/a6b33dfc9e8949b7e28133c4628b29cd9f1850bb", + "reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb", "shasum": "" }, "require": { @@ -194,98 +194,50 @@ "description": "Locale functions required by libphonenumber-for-php", "support": { "issues": "https://github.com/giggsey/Locale/issues", - "source": "https://github.com/giggsey/Locale/tree/2.3" + "source": "https://github.com/giggsey/Locale/tree/2.4" }, - "time": "2022-10-19T20:03:30+00:00" + "time": "2023-04-13T07:40:58+00:00" }, { - "name": "guzzlehttp/guzzle", - "version": "5.3.4", + "name": "gmponos/guzzle_logger", + "version": "v2.2.0", "source": { "type": "git", - "url": "https://github.com/guzzle/guzzle.git", - "reference": "b87eda7a7162f95574032da17e9323c9899cb6b2" + "url": "https://github.com/gmponos/guzzle-log-middleware.git", + "reference": "8784459b18f332beb49363a4d71399e784d8c298" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/b87eda7a7162f95574032da17e9323c9899cb6b2", - "reference": "b87eda7a7162f95574032da17e9323c9899cb6b2", + "url": "https://api.github.com/repos/gmponos/guzzle-log-middleware/zipball/8784459b18f332beb49363a4d71399e784d8c298", + "reference": "8784459b18f332beb49363a4d71399e784d8c298", "shasum": "" }, "require": { - "guzzlehttp/ringphp": "^1.1", - "php": ">=5.4.0", - "react/promise": "^2.2" + "ext-json": "*", + "guzzlehttp/guzzle": "^6.1 || ^7.0.1", + "guzzlehttp/psr7": "^1.7 || ^2.0", + "php": "^7.2 || ^8.0", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "^4.0" - }, - "type": "library", - "autoload": { - "psr-4": { - "GuzzleHttp\\": "src/" - } + "phpstan/phpstan": "~0.12.32", + "phpstan/phpstan-phpunit": "^0.12.11", + "phpstan/phpstan-strict-rules": "^0.12.2", + "phpunit/phpunit": "^8.5.8 || ^9.2.5", + "squizlabs/php_codesniffer": "^3.5.5" }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" - } - ], - "description": "Guzzle is a PHP HTTP client library and framework for building RESTful web service clients", - "homepage": "http://guzzlephp.org/", - "keywords": [ - "client", - "curl", - "framework", - "http", - "http client", - "rest", - "web service" - ], - "support": { - "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/5.3" - }, - "time": "2019-10-30T09:32:00+00:00" - }, - { - "name": "guzzlehttp/log-subscriber", - "version": "1.0.1", - "source": { - "type": "git", - "url": "https://github.com/guzzle/log-subscriber.git", - "reference": "99c3c0004165db721d8ef7bbef60c996210e538a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/guzzle/log-subscriber/zipball/99c3c0004165db721d8ef7bbef60c996210e538a", - "reference": "99c3c0004165db721d8ef7bbef60c996210e538a", - "shasum": "" - }, - "require": { - "guzzlehttp/guzzle": "~4.0 | ~5.0", - "php": ">=5.4.0", - "psr/log": "~1.0" - }, - "require-dev": { - "phpunit/phpunit": "~4.0" + "suggest": { + "monolog/monolog": "A PSR-3 logger to send your logs to files, sockets and various web services" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0-dev" + "dev-master": "2.0-dev" } }, "autoload": { "psr-4": { - "GuzzleHttp\\Subscriber\\Log\\": "src/" + "GuzzleLogMiddleware\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -294,70 +246,81 @@ ], "authors": [ { - "name": "Michael Dowling", - "email": "mtdowling@gmail.com", - "homepage": "https://github.com/mtdowling" + "name": "George Mponos", + "email": "gmponos@gmail.com" } ], - "description": "Logs HTTP requests and responses as they are sent over the wire (Guzzle 4+)", - "homepage": "http://guzzlephp.org/", + "description": "A Guzzle middleware to log request and responses automatically", + "homepage": "https://github.com/gmponos/guzzle-log-middleware", "keywords": [ "Guzzle", + "PSR3", "log", - "plugin" + "logging" ], "support": { - "issues": "https://github.com/guzzle/log-subscriber/issues", - "source": "https://github.com/guzzle/log-subscriber/tree/master" + "issues": "https://github.com/gmponos/guzzle-log-middleware/issues", + "source": "https://github.com/gmponos/guzzle-log-middleware/tree/v2.2.0" }, - "abandoned": true, - "time": "2014-10-13T03:31:43+00:00" + "funding": [ + { + "url": "https://github.com/gmponos", + "type": "github" + } + ], + "time": "2022-04-04T08:50:42+00:00" }, { - "name": "guzzlehttp/psr7", - "version": "2.4.3", + "name": "guzzlehttp/guzzle", + "version": "7.7.0", "source": { "type": "git", - "url": "https://github.com/guzzle/psr7.git", - "reference": "67c26b443f348a51926030c83481b85718457d3d" + "url": "https://github.com/guzzle/guzzle.git", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/67c26b443f348a51926030c83481b85718457d3d", - "reference": "67c26b443f348a51926030c83481b85718457d3d", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", + "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", "shasum": "" }, "require": { + "ext-json": "*", + "guzzlehttp/promises": "^1.5.3 || ^2.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", "php": "^7.2.5 || ^8.0", - "psr/http-factory": "^1.0", - "psr/http-message": "^1.0", - "ralouphie/getallheaders": "^3.0" + "psr/http-client": "^1.0", + "symfony/deprecation-contracts": "^2.2 || ^3.0" }, "provide": { - "psr/http-factory-implementation": "1.0", - "psr/http-message-implementation": "1.0" + "psr/http-client-implementation": "1.0" }, "require-dev": { "bamarni/composer-bin-plugin": "^1.8.1", - "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "ext-curl": "*", + "php-http/client-integration-tests": "dev-master#2c025848417c1135031fdf9c728ee53d0a7ceaee as 3.0.999", + "php-http/message-factory": "^1.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23", + "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { - "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + "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 - }, - "branch-alias": { - "dev-master": "2.4-dev" } }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { - "GuzzleHttp\\Psr7\\": "src/" + "GuzzleHttp\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -375,6 +338,11 @@ "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", @@ -394,27 +362,23 @@ "name": "Tobias Schultze", "email": "webmaster@tubo-world.de", "homepage": "https://github.com/Tobion" - }, - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com", - "homepage": "https://sagikazarmark.hu" } ], - "description": "PSR-7 message implementation that also provides common utility methods", + "description": "Guzzle is a PHP HTTP client library", "keywords": [ + "client", + "curl", + "framework", "http", - "message", + "http client", + "psr-18", "psr-7", - "request", - "response", - "stream", - "uri", - "url" + "rest", + "web service" ], "support": { - "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.4.3" + "issues": "https://github.com/guzzle/guzzle/issues", + "source": "https://github.com/guzzle/guzzle/tree/7.7.0" }, "funding": [ { @@ -426,47 +390,43 @@ "type": "github" }, { - "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/guzzle", "type": "tidelift" } ], - "time": "2022-10-26T14:07:24+00:00" + "time": "2023-05-21T14:04:53+00:00" }, { - "name": "guzzlehttp/ringphp", - "version": "1.1.1", + "name": "guzzlehttp/promises", + "version": "2.0.0", "source": { "type": "git", - "url": "https://github.com/guzzle/RingPHP.git", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b" + "url": "https://github.com/guzzle/promises.git", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/RingPHP/zipball/5e2a174052995663dd68e6b5ad838afd47dd615b", - "reference": "5e2a174052995663dd68e6b5ad838afd47dd615b", + "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", "shasum": "" }, "require": { - "guzzlehttp/streams": "~3.0", - "php": ">=5.4.0", - "react/promise": "~2.0" + "php": "^7.2.5 || ^8.0" }, "require-dev": { - "ext-curl": "*", - "phpunit/phpunit": "~4.0" - }, - "suggest": { - "ext-curl": "Guzzle will use specific adapters if cURL is present" + "bamarni/composer-bin-plugin": "^1.8.1", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.1-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "GuzzleHttp\\Ring\\": "src/" + "GuzzleHttp\\Promise\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -474,49 +434,93 @@ "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": "Provides a simple API and specification that abstracts away the details of HTTP into a single PHP function.", + "description": "Guzzle promises library", + "keywords": [ + "promise" + ], "support": { - "issues": "https://github.com/guzzle/RingPHP/issues", - "source": "https://github.com/guzzle/RingPHP/tree/1.1.1" + "issues": "https://github.com/guzzle/promises/issues", + "source": "https://github.com/guzzle/promises/tree/2.0.0" }, - "abandoned": true, - "time": "2018-07-31T13:22:33+00:00" + "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-05-21T13:50:22+00:00" }, { - "name": "guzzlehttp/streams", - "version": "3.0.0", + "name": "guzzlehttp/psr7", + "version": "2.5.0", "source": { "type": "git", - "url": "https://github.com/guzzle/streams.git", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5" + "url": "https://github.com/guzzle/psr7.git", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/streams/zipball/47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", - "reference": "47aaa48e27dae43d39fc1cea0ccf0d84ac1a2ba5", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", + "reference": "b635f279edd83fc275f822a1188157ffea568ff6", "shasum": "" }, "require": { - "php": ">=5.4.0" + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" }, "require-dev": { - "phpunit/phpunit": "~4.0" + "bamarni/composer-bin-plugin": "^1.8.1", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.29 || ^9.5.23" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.0-dev" + "bamarni-bin": { + "bin-links": true, + "forward-command": false } }, "autoload": { "psr-4": { - "GuzzleHttp\\Stream\\": "src/" + "GuzzleHttp\\Psr7\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -524,24 +528,72 @@ "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": "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" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" } ], - "description": "Provides a simple abstraction over streams of data", - "homepage": "http://guzzlephp.org/", + "description": "PSR-7 message implementation that also provides common utility methods", "keywords": [ - "Guzzle", - "stream" + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" ], "support": { - "issues": "https://github.com/guzzle/streams/issues", - "source": "https://github.com/guzzle/streams/tree/master" + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.5.0" }, - "abandoned": true, - "time": "2014-10-12T19:18:40+00:00" + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-04-17T16:11:26+00:00" }, { "name": "paragonie/random_compat", @@ -595,34 +647,29 @@ }, { "name": "php-http/httplug", - "version": "2.3.0", + "version": "2.4.0", "source": { "type": "git", "url": "https://github.com/php-http/httplug.git", - "reference": "f640739f80dfa1152533976e3c112477f69274eb" + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/httplug/zipball/f640739f80dfa1152533976e3c112477f69274eb", - "reference": "f640739f80dfa1152533976e3c112477f69274eb", + "url": "https://api.github.com/repos/php-http/httplug/zipball/625ad742c360c8ac580fcc647a1541d29e257f67", + "reference": "625ad742c360c8ac580fcc647a1541d29e257f67", "shasum": "" }, "require": { "php": "^7.1 || ^8.0", "php-http/promise": "^1.1", "psr/http-client": "^1.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.1", - "phpspec/phpspec": "^5.1 || ^6.0" + "friends-of-phpspec/phpspec-code-coverage": "^4.1 || ^5.0 || ^6.0", + "phpspec/phpspec": "^5.1 || ^6.0 || ^7.0" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - } - }, "autoload": { "psr-4": { "Http\\Client\\": "src/" @@ -651,29 +698,28 @@ ], "support": { "issues": "https://github.com/php-http/httplug/issues", - "source": "https://github.com/php-http/httplug/tree/2.3.0" + "source": "https://github.com/php-http/httplug/tree/2.4.0" }, - "time": "2022-02-21T09:52:22+00:00" + "time": "2023-04-14T15:10:03+00:00" }, { "name": "php-http/message", - "version": "1.13.0", + "version": "1.16.0", "source": { "type": "git", "url": "https://github.com/php-http/message.git", - "reference": "7886e647a30a966a1a8d1dad1845b71ca8678361" + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/message/zipball/7886e647a30a966a1a8d1dad1845b71ca8678361", - "reference": "7886e647a30a966a1a8d1dad1845b71ca8678361", + "url": "https://api.github.com/repos/php-http/message/zipball/47a14338bf4ebd67d317bf1144253d7db4ab55fd", + "reference": "47a14338bf4ebd67d317bf1144253d7db4ab55fd", "shasum": "" }, "require": { "clue/stream-filter": "^1.5", - "php": "^7.1 || ^8.0", - "php-http/message-factory": "^1.0.2", - "psr/http-message": "^1.0" + "php": "^7.2 || ^8.0", + "psr/http-message": "^1.1 || ^2.0" }, "provide": { "php-http/message-factory-implementation": "1.0" @@ -681,8 +727,9 @@ "require-dev": { "ergebnis/composer-normalize": "^2.6", "ext-zlib": "*", - "guzzlehttp/psr7": "^1.0", - "laminas/laminas-diactoros": "^2.0", + "guzzlehttp/psr7": "^1.0 || ^2.0", + "laminas/laminas-diactoros": "^2.0 || ^3.0", + "php-http/message-factory": "^1.0.2", "phpspec/phpspec": "^5.1 || ^6.3 || ^7.1", "slim/slim": "^3.0" }, @@ -693,11 +740,6 @@ "slim/slim": "Used with Slim Framework PSR-7 implementation" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.10-dev" - } - }, "autoload": { "files": [ "src/filters.php" @@ -725,63 +767,9 @@ ], "support": { "issues": "https://github.com/php-http/message/issues", - "source": "https://github.com/php-http/message/tree/1.13.0" + "source": "https://github.com/php-http/message/tree/1.16.0" }, - "time": "2022-02-11T13:41:14+00:00" - }, - { - "name": "php-http/message-factory", - "version": "v1.0.2", - "source": { - "type": "git", - "url": "https://github.com/php-http/message-factory.git", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/php-http/message-factory/zipball/a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "reference": "a478cb11f66a6ac48d8954216cfed9aa06a501a1", - "shasum": "" - }, - "require": { - "php": ">=5.4", - "psr/http-message": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev" - } - }, - "autoload": { - "psr-4": { - "Http\\Message\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Márk Sági-Kazár", - "email": "mark.sagikazar@gmail.com" - } - ], - "description": "Factory interfaces for PSR-7 HTTP Message", - "homepage": "http://php-http.org", - "keywords": [ - "factory", - "http", - "message", - "stream", - "uri" - ], - "support": { - "issues": "https://github.com/php-http/message-factory/issues", - "source": "https://github.com/php-http/message-factory/tree/master" - }, - "time": "2015-12-19T14:08:53+00:00" + "time": "2023-05-17T06:43:38+00:00" }, { "name": "php-http/promise", @@ -1052,16 +1040,16 @@ }, { "name": "prestashop/prestashop-accounts-installer", - "version": "v1.0.3", + "version": "v1.0.4", "source": { "type": "git", "url": "https://github.com/PrestaShopCorp/prestashop-accounts-installer.git", - "reference": "b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7" + "reference": "0ad934bc540558db3944a9a1e5b08b852bb46e8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7", - "reference": "b69a6a0d1f2ecbb1e53e61b22a4140462e77bdf7", + "url": "https://api.github.com/repos/PrestaShopCorp/prestashop-accounts-installer/zipball/0ad934bc540558db3944a9a1e5b08b852bb46e8b", + "reference": "0ad934bc540558db3944a9a1e5b08b852bb46e8b", "shasum": "" }, "require": { @@ -1086,9 +1074,9 @@ "description": "Utility package to install `ps_accounts` module or present data to trigger manual install from psx configuration page.", "support": { "issues": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/issues", - "source": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/tree/v1.0.3" + "source": "https://github.com/PrestaShopCorp/prestashop-accounts-installer/tree/v1.0.4" }, - "time": "2023-04-18T11:54:57+00:00" + "time": "2023-05-04T07:24:48+00:00" }, { "name": "psr/cache", @@ -1189,21 +1177,21 @@ }, { "name": "psr/http-client", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621" + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", - "reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", "shasum": "" }, "require": { "php": "^7.0 || ^8.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -1223,7 +1211,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interface for HTTP clients", @@ -1235,27 +1223,27 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/master" + "source": "https://github.com/php-fig/http-client/tree/1.0.2" }, - "time": "2020-06-29T06:28:15+00:00" + "time": "2023-04-10T20:12:12+00:00" }, { "name": "psr/http-factory", - "version": "1.0.1", + "version": "1.0.2", "source": { "type": "git", "url": "https://github.com/php-fig/http-factory.git", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be" + "reference": "e616d01114759c4c489f93b099585439f795fe35" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-factory/zipball/12ac7fcd07e5b077433f5f2bee95b3a771bf61be", - "reference": "12ac7fcd07e5b077433f5f2bee95b3a771bf61be", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", "shasum": "" }, "require": { "php": ">=7.0.0", - "psr/http-message": "^1.0" + "psr/http-message": "^1.0 || ^2.0" }, "type": "library", "extra": { @@ -1275,7 +1263,7 @@ "authors": [ { "name": "PHP-FIG", - "homepage": "http://www.php-fig.org/" + "homepage": "https://www.php-fig.org/" } ], "description": "Common interfaces for PSR-7 HTTP message factories", @@ -1290,31 +1278,31 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-factory/tree/master" + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" }, - "time": "2019-04-30T12:38:16+00:00" + "time": "2023-04-10T20:10:41+00:00" }, { "name": "psr/http-message", - "version": "1.0.1", + "version": "1.1", "source": { "type": "git", "url": "https://github.com/php-fig/http-message.git", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363" + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-message/zipball/f6561bf28d520154e4b0ec72be95418abe6d9363", - "reference": "f6561bf28d520154e4b0ec72be95418abe6d9363", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/cb6ce4845ce34a8ad9e68117c10ee90a29919eba", + "reference": "cb6ce4845ce34a8ad9e68117c10ee90a29919eba", "shasum": "" }, "require": { - "php": ">=5.3.0" + "php": "^7.2 || ^8.0" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.0.x-dev" + "dev-master": "1.1.x-dev" } }, "autoload": { @@ -1343,9 +1331,9 @@ "response" ], "support": { - "source": "https://github.com/php-fig/http-message/tree/master" + "source": "https://github.com/php-fig/http-message/tree/1.1" }, - "time": "2016-08-06T14:39:51+00:00" + "time": "2023-04-04T09:50:52+00:00" }, { "name": "psr/log", @@ -1541,82 +1529,6 @@ ], "time": "2022-12-19T21:55:10+00:00" }, - { - "name": "react/promise", - "version": "v2.9.0", - "source": { - "type": "git", - "url": "https://github.com/reactphp/promise.git", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/reactphp/promise/zipball/234f8fd1023c9158e2314fa9d7d0e6a83db42910", - "reference": "234f8fd1023c9158e2314fa9d7d0e6a83db42910", - "shasum": "" - }, - "require": { - "php": ">=5.4.0" - }, - "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" - }, - "type": "library", - "autoload": { - "files": [ - "src/functions_include.php" - ], - "psr-4": { - "React\\Promise\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jan Sorgalla", - "email": "jsorgalla@gmail.com", - "homepage": "https://sorgalla.com/" - }, - { - "name": "Christian Lück", - "email": "christian@clue.engineering", - "homepage": "https://clue.engineering/" - }, - { - "name": "Cees-Jan Kiewiet", - "email": "reactphp@ceesjankiewiet.nl", - "homepage": "https://wyrihaximus.net/" - }, - { - "name": "Chris Boden", - "email": "cboden@gmail.com", - "homepage": "https://cboden.dev/" - } - ], - "description": "A lightweight implementation of CommonJS Promises/A for PHP", - "keywords": [ - "promise", - "promises" - ], - "support": { - "issues": "https://github.com/reactphp/promise/issues", - "source": "https://github.com/reactphp/promise/tree/v2.9.0" - }, - "funding": [ - { - "url": "https://github.com/WyriHaximus", - "type": "github" - }, - { - "url": "https://github.com/clue", - "type": "github" - } - ], - "time": "2022-02-11T10:27:51+00:00" - }, { "name": "segmentio/analytics-php", "version": "1.8.0", @@ -3244,16 +3156,16 @@ }, { "name": "doctrine/deprecations", - "version": "v1.0.0", + "version": "v1.1.0", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de" + "reference": "8cffffb2218e01f3b370bf763e00e81697725259" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", - "reference": "0e2a4f1f8cdfc7a92ec3b01c9334898c806b30de", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/8cffffb2218e01f3b370bf763e00e81697725259", + "reference": "8cffffb2218e01f3b370bf763e00e81697725259", "shasum": "" }, "require": { @@ -3281,9 +3193,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.0.0" + "source": "https://github.com/doctrine/deprecations/tree/v1.1.0" }, - "time": "2022-05-02T15:47:09+00:00" + "time": "2023-05-29T18:55:17+00:00" }, { "name": "doctrine/instantiator", @@ -3544,16 +3456,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.11.0", + "version": "1.11.1", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614" + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614", - "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", + "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c", "shasum": "" }, "require": { @@ -3591,7 +3503,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0" + "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1" }, "funding": [ { @@ -3599,7 +3511,7 @@ "type": "tidelift" } ], - "time": "2022-03-03T13:19:32+00:00" + "time": "2023-03-08T13:26:56+00:00" }, { "name": "nikic/php-parser", @@ -5644,16 +5556,16 @@ }, { "name": "symfony/process", - "version": "v5.4.21", + "version": "v5.4.24", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd" + "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", - "reference": "d4ce417ebcb0b7d090b4c178ed6d3accc518e8bd", + "url": "https://api.github.com/repos/symfony/process/zipball/e3c46cc5689c8782944274bb30702106ecbe3b64", + "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64", "shasum": "" }, "require": { @@ -5686,7 +5598,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.21" + "source": "https://github.com/symfony/process/tree/v5.4.24" }, "funding": [ { @@ -5702,7 +5614,7 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2023-05-17T11:26:05+00:00" }, { "name": "symfony/stopwatch", diff --git a/config.xml b/config.xml index cd8930ee2..3ca9df5c0 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 976878983..d588ee5d1 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.2.0'; + const VERSION = '8.3.3.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -142,7 +142,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.2.0'; + $this->version = '8.3.3.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; diff --git a/src/Api/GenericClient.php b/src/Api/GenericClient.php index 0b8b7380e..0a6a78ce8 100755 --- a/src/Api/GenericClient.php +++ b/src/Api/GenericClient.php @@ -23,14 +23,12 @@ use GuzzleHttp\Exception\RequestException; use GuzzleHttp\Psr7\Request; use GuzzleHttp\Ring\Exception\RingException; -use GuzzleHttp\Subscriber\Log\Formatter; -use GuzzleHttp\Subscriber\Log\LogSubscriber; +use Link; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; -use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFactory; use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; +use Ps_checkout; use Psr\Http\Client\ClientInterface; -use Psr\Log\LoggerInterface; /** * Construct the client used to make call to maasland @@ -47,7 +45,7 @@ class GenericClient /** * Class Link in order to generate module link * - * @var \Link + * @var Link */ protected $link; @@ -86,7 +84,7 @@ class GenericClient public function __construct() { - /** @var \Ps_checkout $module */ + /** @var Ps_checkout $module */ $module = \Module::getInstanceByName('ps_checkout'); /** @var PsAccountRepository $psAccountRepository */ $psAccountRepository = $module->getService('ps_checkout.repository.prestashop.account'); @@ -104,21 +102,6 @@ public function __construct() */ protected function post(array $options = []) { - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - - if (method_exists($this->client, 'getEmitter') && true === (bool) $this->getConfiguration(LoggerFactory::PS_CHECKOUT_LOGGER_HTTP, true)) { - /** @var LoggerInterface $logger */ - $logger = $module->getService('ps_checkout.logger'); - - $subscriber = new LogSubscriber( - $logger, - $this->getLogFormatter() - ); - - $this->client->getEmitter()->attach($subscriber); - } - try { $response = $this->client->sendRequest( new Request('POST', $this->getRoute(), [], json_encode($options)) @@ -144,9 +127,8 @@ protected function post(array $options = []) } $responseHandler = new ResponseApiHandler(); - $response = $responseHandler->handleResponse($response); - return $response; + return $responseHandler->handleResponse($response); } /** @@ -172,9 +154,9 @@ protected function setClient($client) /** * Setter for link * - * @param \Link $link + * @param Link $link */ - protected function setLink(\Link $link) + protected function setLink(Link $link) { $this->link = $link; } @@ -222,7 +204,7 @@ protected function getClient() /** * Getter for Link * - * @return \Link + * @return Link */ protected function getLink() { @@ -239,56 +221,6 @@ protected function getTimeout() return $this->timeout; } - /** - * Getter for exceptions mode - * - * @return bool - */ - protected function getExceptionsMode() - { - return $this->catchExceptions; - } - - /** - * @todo To be moved elsewhere - * - * @param string $key - * @param mixed $defaultValue - * - * @return mixed - */ - private function getConfiguration($key, $defaultValue) - { - if (false === \Configuration::hasKey($key)) { - return $defaultValue; - } - - return \Configuration::get( - $key, - null, - null, - (int) \Context::getContext()->shop->id - ); - } - - /** - * @return string - */ - private function getLogFormatter() - { - $formatter = $this->getConfiguration(LoggerFactory::PS_CHECKOUT_LOGGER_HTTP_FORMAT, 'DEBUG'); - - if ('CLF' === $formatter) { - return Formatter::CLF; - } - - if ('SHORT' === $formatter) { - return Formatter::SHORT; - } - - return Formatter::DEBUG; - } - private function handleException(\Exception $exception) { $body = ''; diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 8782e0d0d..49674be11 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -26,6 +26,7 @@ use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFactory; use PrestaShop\Module\PrestashopCheckout\ShopContext; use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; use Psr\Http\Client\ClientInterface; @@ -53,10 +54,18 @@ public function __construct(\Link $link, $client = null) /** @var \PrestaShop\Module\PrestashopCheckout\Version\Version $version */ $version = $module->getService('ps_checkout.module.version'); + /** @var \PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration $configuration */ + $configuration = $module->getService('ps_checkout.configuration'); + $handlerStack = null; - if ( - defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION') + $isLoggerEnabled = (bool) $configuration->get(LoggerFactory::PS_CHECKOUT_LOGGER_HTTP, [ + 'default' => true, + 'global' => true, + ]); + + if ($isLoggerEnabled + && defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION') && class_exists(HandlerStack::class) && class_exists(LogMiddleware::class) ) { @@ -80,7 +89,7 @@ public function __construct(\Link $link, $client = null) 'DispatchWebHook', [], true, - (int) \Configuration::get('PS_LANG_DEFAULT'), + (int) $configuration->get('PS_LANG_DEFAULT'), (int) \Context::getContext()->shop->id ), 'Bn-Code' => (new ShopContext())->getBnCode(), diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php new file mode 100644 index 000000000..337d12132 --- /dev/null +++ b/upgrade/upgrade-8.3.3.0.php @@ -0,0 +1,52 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Update main function for module version 8.3.3.0 + * + * @param Ps_checkout $module + * + * @return bool + */ +function upgrade_module_8_3_3_0($module) +{ + // Force PrestaShop to upgrade for all shop to avoid issues + $savedShopContext = Shop::getContext(); + $savedShopId = Shop::getContextShopID(); + $savedGroupShopId = Shop::getContextShopGroupID(); + Shop::setContext(Shop::CONTEXT_ALL); + + $module->registerHook('displayPaymentReturn'); + $module->registerHook('displayOrderDetail'); + + // Restore initial PrestaShop shop context + if (Shop::CONTEXT_SHOP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedShopId); + } elseif (Shop::CONTEXT_GROUP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedGroupShopId); + } else { + Shop::setContext($savedShopContext); + } + + return true; +} From 307b2130605087edbd9336681b4e8dee60c1b509 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 1 Jun 2023 13:26:29 +0200 Subject: [PATCH 008/343] Fix exception messages --- src/Checkout/CheckoutChecker.php | 8 +- .../CapturePayPalOrderCommandHandler.php | 2 +- .../UpdatePayPalOrderCacheCommandHandler.php | 4 +- .../PayPalOrderEventSubscriber.php | 4 +- ...etCurrentPayPalOrderStatusQueryHandler.php | 10 - ...lOrderForCheckoutCompletedQueryHandler.php | 4 +- ...lOrderForOrderConfirmationQueryHandler.php | 6 +- .../PayPalCaptureEventSubscriber.php | 16 +- .../Unit/Builder/OrderPayloadBuilderTest.php | 183 -------------- .../CheckTransitionStateServiceTest.php | 232 ------------------ 10 files changed, 17 insertions(+), 452 deletions(-) delete mode 100644 tests/Unit/Builder/OrderPayloadBuilderTest.php delete mode 100644 tests/Unit/Order/State/Service/CheckTransitionStateServiceTest.php diff --git a/src/Checkout/CheckoutChecker.php b/src/Checkout/CheckoutChecker.php index f5f8edf5e..96c5d6f26 100644 --- a/src/Checkout/CheckoutChecker.php +++ b/src/Checkout/CheckoutChecker.php @@ -54,7 +54,7 @@ public function __construct(LoggerInterface $logger) public function continueWithAuthorization($cartId, $orderPayPal) { if ($orderPayPal['status'] === 'COMPLETED') { - throw new PsCheckoutException('PayPal Order is already captured'); + throw new PsCheckoutException(sprintf('PayPal Order %s is already captured', $orderPayPal['id'])); } if (isset($orderPayPal['payment_source']['card'])) { @@ -98,18 +98,18 @@ public function continueWithAuthorization($cartId, $orderPayPal) $cart = new Cart($cartId); if (!Validate::isLoadedObject($cart)) { - throw new PsCheckoutException(sprintf('Cart with id #%s not found.', var_export($cartId, true)), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('Cart with id %s not found.', var_export($cartId, true)), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } if (!$cart->hasProducts()) { - throw new PsCheckoutException(sprintf('Cart with id #%s has no product. Cannot capture the order.', var_export($cart->id, true)), PsCheckoutException::CART_PRODUCT_MISSING); + throw new PsCheckoutException(sprintf('Cart with id %s has no product. Cannot capture the order.', var_export($cart->id, true)), PsCheckoutException::CART_PRODUCT_MISSING); } if ($cart->isAllProductsInStock() !== true || (method_exists($cart, 'checkAllProductsAreStillAvailableInThisState') && $cart->checkAllProductsAreStillAvailableInThisState() !== true) || (method_exists($cart, 'checkAllProductsHaveMinimalQuantities') && $cart->checkAllProductsHaveMinimalQuantities() !== true) ) { - throw new PsCheckoutException(sprintf('Cart with id #%s contains products unavailable. Cannot capture the order.', var_export($cart->id, true)), PsCheckoutException::CART_PRODUCT_UNAVAILABLE); + throw new PsCheckoutException(sprintf('Cart with id %s contains products unavailable. Cannot capture the order.', var_export($cart->id, true)), PsCheckoutException::CART_PRODUCT_UNAVAILABLE); } if (!Customer::customerHasAddress($cart->id_customer, $cart->id_address_invoice)) { diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 723a91a22..b5bec5d1b 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -105,7 +105,7 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) $this->paypalOrderEventDispatcher->dispatch($response['body']); $this->paypalCaptureEventDispatcher->dispatch($response['body']['id'], $capturePayPal); } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to capture PayPal Order #%d', $capturePayPalOrderCommand->getOrderId()->getValue()), PayPalOrderException::CANNOT_CAPTURE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to capture PayPal Order %s', $capturePayPalOrderCommand->getOrderId()->getValue()), PayPalOrderException::CANNOT_CAPTURE_ORDER, $exception); } } } diff --git a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php index f6ee79287..d8de13a8c 100644 --- a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php @@ -41,7 +41,7 @@ public function __construct($paypalOrderCache) public function handle(UpdatePayPalOrderCacheCommand $updatePayPalOrderCacheCommand) { - $responseBody = $updatePayPalOrderCacheCommand->getOrderPayPal(); - $this->paypalOrderCache->set($responseBody['id'], $responseBody); + $orderPayPal = $updatePayPalOrderCacheCommand->getOrderPayPal(); + $this->paypalOrderCache->set($orderPayPal['id'], $orderPayPal); } } diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index ae4545dd4..2f3f40b66 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -133,7 +133,7 @@ public function savePayPalOrder($event) $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('order #%s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } switch (get_class($event)) { @@ -185,7 +185,7 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('order #%s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } if ($psCheckoutCart->isExpressCheckout()) { diff --git a/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php b/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php index f59fe01c5..add46d5dc 100644 --- a/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php @@ -57,16 +57,6 @@ public function handle(GetCurrentPayPalOrderStatusQuery $getPayPalOrderQuery) throw new PayPalOrderException('Cannot retrieve cart', PayPalOrderException::PRESTASHOP_CART_NOT_FOUND, $exception); } - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - $module->getLogger()->debug( - 'Get from PsCheckoutCart', - [ - 'PayPalOrderId' => $psCheckoutCart->getPaypalOrderId(), - 'PayPalOrderStatus' => $psCheckoutCart->getPaypalStatus(), - ] - ); - return new GetCurrentPayPalOrderStatusQueryResult( $psCheckoutCart->getPaypalOrderId(), $psCheckoutCart->getPaypalStatus() diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index ef254a7c1..346f61d2a 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -87,11 +87,11 @@ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQu try { $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderId()->getValue()); } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order #%d', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); } if (!$orderPayPal->isLoaded()) { - throw new PayPalOrderException(sprintf('No data for PayPal Order #%d', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); + throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); } $this->eventDispatcher->dispatch( diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php index 2aa531e0f..1b5bc9c1b 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php @@ -73,15 +73,13 @@ public function handle(GetPayPalOrderForOrderConfirmationQuery $query) try { $orderPayPal = new PaypalOrder($query->getOrderId()->getValue()); } catch (\Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order #%d', $query->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $query->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); } if (!$orderPayPal->isLoaded()) { - throw new PayPalOrderException(sprintf('No data for PayPal Order #%d', $query->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); + throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $query->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); } - // $this->orderPayPalCache->set(CacheSettings::PAYPAL_ORDER_ID . $query->getOrderId()->getValue(), $orderPayPal->getOrder()); - $this->eventDispatcher->dispatch( new PayPalOrderFetchedEvent($query->getOrderId()->getValue(), $orderPayPal->getOrder()) ); diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index 9a1ef237a..fe7f55561 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -311,7 +311,7 @@ public function createOrderPayment(PayPalCaptureCompletedEvent $event) /** @var PsCheckoutCart|false $psCheckoutCart */ $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('Order #%s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } /** @var GetOrderQueryResult $order */ @@ -373,7 +373,7 @@ public function setPaymentCompletedOrderStatus(PayPalCaptureCompletedEvent $even $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('Order #%s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } /** @var GetOrderForPaymentCompletedQueryResult $order */ @@ -466,7 +466,7 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('Order #%s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } /** @var GetOrderForPaymentRefundedQueryResult $order */ $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($psCheckoutCart->getIdCart())); @@ -498,7 +498,7 @@ public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('Order #%s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } /** @var GetOrderForPaymentReversedQueryResult $order */ @@ -524,14 +524,6 @@ public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) */ public function updateCache(PayPalCaptureEvent $event) { - $this->logger->info( - __CLASS__ . ':' . __FUNCTION__, - [ - 'PayPalOrderId' => $event->getPayPalOrderId()->getValue(), - 'PayPalCaptureId' => $event->getPayPalCaptureId()->getValue(), - 'PayPalCapture' => $event->getCapture(), - ] - ); $this->capturePayPalCache->set($event->getPayPalCaptureId()->getValue(), $event->getCapture()); } } diff --git a/tests/Unit/Builder/OrderPayloadBuilderTest.php b/tests/Unit/Builder/OrderPayloadBuilderTest.php deleted file mode 100644 index 3678154bb..000000000 --- a/tests/Unit/Builder/OrderPayloadBuilderTest.php +++ /dev/null @@ -1,183 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace Tests\Unit\Builder; - -use PHPUnit\Framework\TestCase; -use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; -use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfigurationOptionsResolver; -use PrestaShop\Module\PrestashopCheckout\Repository\PaypalAccountRepository; -use PrestaShop\Module\PrestashopCheckout\Temp\Adapter\OrderDataAdapter; -use PrestaShop\Module\PrestashopCheckout\Temp\Factory\OrderDataFactory; - -class OrderPayloadBuilderTest extends TestCase -{ - public function testPayloadCreation() - { - $orderDataAdapterMock = $this->createMock(OrderDataAdapter::class); - $orderDataAdapterMock->method('getGenderName')->willReturn('M'); - $orderDataAdapterMock->method('getStateName')->willReturn('Ile de France'); - $orderDataAdapterMock->method('getIsoCountry')->willReturn('FR'); - - $accountRepository = new PaypalAccountRepository(new PrestaShopConfiguration(new PrestaShopConfigurationOptionsResolver(1))); - $orderFactory = new OrderDataFactory($accountRepository, $orderDataAdapterMock); - $payload = $orderFactory->createFromArray($this->getDataPayload()); - $this->checkPayloadPayer($payload); - $this->checkPayloadApplicationContext($payload); - $this->checkPayloadPurchaseUnits($payload); - } - - public function testPayloadChecksum() - { - $orderDataAdapterMock = $this->createMock(OrderDataAdapter::class); - $orderDataAdapterMock->method('getGenderName')->willReturn('M'); - $orderDataAdapterMock->method('getStateName')->willReturn('Ile de France'); - $orderDataAdapterMock->method('getIsoCountry')->willReturn('FR'); - - $accountRepository = new PaypalAccountRepository(new PrestaShopConfiguration(new PrestaShopConfigurationOptionsResolver(1))); - $orderFactory = new OrderDataFactory($accountRepository, $orderDataAdapterMock); - $dataPayload = $this->getDataPayload(); - $payload = $orderFactory->createFromArray($dataPayload, false); - $dataPayload['payer']['address_line_1'] = '79 avenue des Champs'; - $payload2 = $orderFactory->createFromArray($dataPayload, false); - $this->checkPayloadChecksumPayer($payload, $payload2); - } - - /** - * @return array - */ - public function getDataPayload() - { - return [ - 'cart' => [ - 'id' => 3, - 'id_lang' => 1, - 'items' => $this->getCartItems(), - 'shipping_cost' => 5.70, - 'subtotals' => [ - 'gift_wrapping' => [ - 'amount' => 0, - ], - ], - 'total_with_taxes' => 83, - ], - 'currency' => [ - 'iso_code' => 1, - ], - 'customer' => [ - 'birthday' => '1990-01-01', - 'email_address' => 'john.doe@mail.fr', - 'id_gender' => 1, - ], - 'payee' => [ - 'email_address' => 'nhoj.eod@prestatest.fr', - 'merchant_id' => '716537O08', - ], - 'payer' => [ - 'address_line_1' => '16 rue des champs', - 'address_line_2' => 'Appartement 23', - 'admin_area_2' => 'Paris', - 'given_name' => 'John', - 'id_country' => 1, - 'id_state' => 1, - 'surname' => 'Doe', - 'payer_id' => '', - 'phone' => '', - 'phone_mobile' => '0612345678', - 'postcode' => '75000', - ], - 'psCheckout' => [ - 'isExpressCheckout' => false, - ], - 'shipping' => [ - 'address_line_1' => '5 rue du port', - 'address_line_2' => 'Appartement 4', - 'admin_area_2' => 'Le Mans', - 'given_name' => 'Johnny', - 'id_country' => 1, - 'id_state' => 1, - 'surname' => 'Doe', - 'postcode' => '72000', - ], - 'shop' => [ - 'name' => 'PrestaTest', - ], - ]; - } - - /** - * @return array - */ - public function getCartItems() - { - return [ - [ - 'attributes' => 'Coupe classique, col rond, manches courtes', - 'is_virtual' => '0', - 'name' => 'Pull imprime', - 'quantity' => 2, - 'total' => 37.80, - 'total_wt' => 40, - ], - [ - 'attributes' => 'Mug rouge en ceramique, avec une anse', - 'is_virtual' => '0', - 'name' => 'Mug', - 'quantity' => 1, - 'total' => 2.60, - 'total_wt' => 3, - ], - ]; - } - - /** - * @param array $payload - */ - private function checkPayloadPayer($payload) - { - echo 'Payer : ' . json_encode($payload['payer']) . PHP_EOL; - } - - /** - * @param array $payload - */ - private function checkPayloadApplicationContext($payload) - { - echo 'ApplicationContext : ' . json_encode($payload['application_context']) . PHP_EOL; - } - - /** - * @param array $payload - */ - private function checkPayloadPurchaseUnits($payload) - { - echo 'PurchaseUnits : ' . json_encode($payload['purchase_units']) . PHP_EOL; - } - - /** - * @param array $payload - */ - private function checkPayloadChecksumPayer($payload, $payload2) - { - $checksum = $payload['payer']->generateChecksum(); - $checksum2 = $payload2['payer']->generateChecksum(); - $this->assertTrue($checksum !== $checksum2, 'Two different checksum for the Payer object'); - } -} diff --git a/tests/Unit/Order/State/Service/CheckTransitionStateServiceTest.php b/tests/Unit/Order/State/Service/CheckTransitionStateServiceTest.php deleted file mode 100644 index c25da750f..000000000 --- a/tests/Unit/Order/State/Service/CheckTransitionStateServiceTest.php +++ /dev/null @@ -1,232 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace Tests\Unit\Order\State\Service; - -use PHPUnit\Framework\TestCase; -use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\Order\State\Service\CheckOrderState; -use PrestaShop\Module\PrestashopCheckout\Order\State\Service\CheckTransitionStateService; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CheckTransitionPayPalOrderStatusService; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; - -class CheckTransitionStateServiceTest extends TestCase -{ - /** - * @dataProvider dataProvider - */ - public function testGetNewOrderState($data, $expectedResult) - { - $checkTransition = new CheckTransitionStateService(new CheckTransitionPayPalOrderStatusService(), new CheckOrderState(), new CheckOrderAmount()); - $result = $checkTransition->getNewOrderState($data); - $this->assertEquals($expectedResult, $result); - } - - public function dataProvider() - { - return [ - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::COMPLETED, - 'amount' => '10', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::CREATED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::COMPLETED, - 'amount' => '5', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::CREATED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::PARTIALLY_PAID, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::PARTIALLY_REFUNDED, - 'amount' => '5', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::COMPLETED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::PARTIALLY_REFUNDED, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::PARTIALLY_REFUNDED, - 'amount' => '10', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::COMPLETED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::REFUNDED, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::REFUND, - 'amount' => '5', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::COMPLETED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::PARTIALLY_REFUNDED, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::REFUND, - 'amount' => '10', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::COMPLETED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::REFUNDED, - ], - [ - [ - 'cart' => ['amount' => 10], - 'Order' => [ - 'currentOrderStatus' => OrderStateConfigurationKeys::WAITING_CAPTURE, - 'totalAmountPaid' => '0', - 'totalAmount' => '10', - 'totalRefunded' => '0', - ], - 'PayPalRefund' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalCapture' => [ // NULL si pas de refund dans l'order PayPal - 'status' => PayPalCaptureStatus::PENDING, - 'amount' => '10', - ], - 'PayPalAuthorization' => [ // NULL si pas de refund dans l'order PayPal - null, - ], - 'PayPalOrder' => [ - 'oldStatus' => PayPalOrderStatus::CREATED, - 'newStatus' => PayPalOrderStatus::COMPLETED, - ], - ], - OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, - ], - ]; - } -} From e154ba6e7d7df14ce44362bd72faac0261f92e9a Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 12 Jun 2023 09:34:50 +0200 Subject: [PATCH 009/343] Run php-cs-fixer --- controllers/front/DispatchWebHook.php | 2 +- src/Order/AbstractOrderHandler.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/controllers/front/DispatchWebHook.php b/controllers/front/DispatchWebHook.php index a5bc36761..a48c22f36 100755 --- a/controllers/front/DispatchWebHook.php +++ b/controllers/front/DispatchWebHook.php @@ -321,7 +321,7 @@ protected function canonicalRedirection($canonical_url = '') private function handleException(Exception $exception) { $this->module->getLogger()->log( - PsCheckoutException::PRESTASHOP_ORDER_NOT_FOUND === $exception->getCode() || OrderNotFoundException::NOT_FOUND? Logger::NOTICE : Logger::ERROR, + PsCheckoutException::PRESTASHOP_ORDER_NOT_FOUND === $exception->getCode() || OrderNotFoundException::NOT_FOUND ? Logger::NOTICE : Logger::ERROR, 'Webhook exception ' . $exception->getCode(), [ 'merchantId' => $this->merchantId, diff --git a/src/Order/AbstractOrderHandler.php b/src/Order/AbstractOrderHandler.php index 1d6333af6..da03392b3 100644 --- a/src/Order/AbstractOrderHandler.php +++ b/src/Order/AbstractOrderHandler.php @@ -40,7 +40,7 @@ protected function getOrder(OrderId $orderId) try { $order = new Order($orderId->getValue()); } catch (Exception $exception) { - throw new OrderNotFoundException(sprintf('Error occurred when trying to get order object #%s', $orderId->getValue()),OrderNotFoundException::NOT_FOUND, $exception); + throw new OrderNotFoundException(sprintf('Error occurred when trying to get order object #%s', $orderId->getValue()), OrderNotFoundException::NOT_FOUND, $exception); } if ($order->id !== $orderId->getValue()) { From a34060bdada236cfaae196a0cfd6ef16c3f5179c Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Jun 2023 11:58:34 +0200 Subject: [PATCH 010/343] Refacto --- classes/PsCheckoutCart.php | 20 +- config/common.yml | 124 +-- .../AdminAjaxPrestashopCheckoutController.php | 3 +- controllers/front/create.php | 2 +- controllers/front/validate.php | 227 ++--- src/Api/Payment/Order.php | 18 +- .../Order => Cart}/Cache/CacheSettings.php | 2 +- .../Exception/CartNotFoundException.php} | 4 +- .../UpdatePaymentMethodSelectedCommand.php | 2 +- ...atePaymentMethodSelectedCommandHandler.php | 38 +- .../CheckoutEventSubscriber.php | 61 +- .../Exception/PsCheckoutSessionException.php | 2 +- src/Dispatcher/OrderDispatcher.php | 42 +- src/Exception/PayPalException.php | 2 + src/Order/Command/AddOrderPaymentCommand.php | 5 + src/Order/Command/CreateOrderCommand.php | 95 +- .../AddOrderPaymentCommandHandler.php | 34 +- .../CreateOrderCommandHandler.php | 148 ++- .../UpdateOrderStatusCommandHandler.php | 28 +- .../EventSubscriber/OrderEventSubscriber.php | 3 +- .../Payment/Query/GetOrderPaymentQuery.php | 62 -- .../Query/GetOrderPaymentQueryResult.php | 95 -- .../GetOrderPaymentQueryHandler.php | 64 -- .../GetOrderForPaymentCompletedQuery.php | 37 +- ...GetOrderForPaymentCompletedQueryResult.php | 91 +- .../Query/GetOrderForPaymentDeniedQuery.php | 22 +- .../GetOrderForPaymentDeniedQueryResult.php | 34 +- .../Query/GetOrderForPaymentPendingQuery.php | 22 +- .../GetOrderForPaymentPendingQueryResult.php | 56 +- .../Query/GetOrderForPaymentRefundedQuery.php | 22 +- .../GetOrderForPaymentRefundedQueryResult.php | 38 +- .../Query/GetOrderForPaymentReversedQuery.php | 37 +- .../GetOrderForPaymentReversedQueryResult.php | 38 +- src/Order/Query/GetOrderQuery.php | 50 - src/Order/Query/GetOrderQueryResult.php | 205 ---- ...etOrderForPaymentCompletedQueryHandler.php | 71 +- .../GetOrderForPaymentDeniedQueryHandler.php | 67 +- .../GetOrderForPaymentPendingQueryHandler.php | 72 +- ...GetOrderForPaymentRefundedQueryHandler.php | 64 +- ...GetOrderForPaymentReversedQueryHandler.php | 69 +- .../QueryHandler/GetOrderQueryHandler.php | 104 -- src/Order/Service/CheckOrderAmount.php | 8 +- .../GetOrderStateConfigurationQueryResult.php | 258 ----- src/Order/State/Query/GetOrderStateQuery.php | 46 - .../State/Query/GetOrderStateQueryResult.php | 50 - ...GetOrderStateConfigurationQueryHandler.php | 56 -- .../GetOrderStateQueryHandler.php | 33 - src/Order/State/Service/CheckOrderState.php | 109 --- src/Order/State/Service/OrderStateMapper.php | 98 ++ .../ValueObject/OrderStateConfiguration.php | 64 -- .../PayPalIdentityEventSubscriber.php | 42 +- .../Command/PrunePayPalOrderCacheCommand.php | 50 - .../Command/RemovePayPalOrderCacheCommand.php | 51 - .../Order/Command/SavePayPalOrderCommand.php | 4 +- .../Command/UpdatePayPalOrderCacheCommand.php | 66 -- .../CapturePayPalOrderCommandHandler.php | 115 ++- .../PrunePayPalOrderCacheCommandHandler.php | 74 -- .../RemovePayPalOrderCacheCommandHandler.php | 75 -- .../SavePayPalOrderCommandHandler.php | 19 +- .../UpdatePayPalOrderCacheCommandHandler.php | 47 - .../Comparator/PayPalOrderComparator.php | 116 --- .../Event/PayPalOrderCacheUpdatedEvent.php | 51 - .../Order/Event/PayPalOrderFetchedEvent.php | 25 - .../Event/PayPalOrderNotApprovedEvent.php | 25 - .../Order/Event/PayPalOrderSavedEvent.php | 52 - .../PayPalOrderEventSubscriber.php | 157 +-- .../Order/PayPalOrderEventDispatcher.php | 163 ---- src/PayPal/Order/PayPalOrderStatus.php | 18 +- src/PayPal/Order/PayPalOrderSummaryView.php | 2 +- .../Order/PayPalOrderSummaryViewBuilder.php | 8 +- .../GetCurrentPayPalOrderStatusQuery.php | 12 +- ...GetCurrentPayPalOrderStatusQueryResult.php | 20 +- ...etPayPalOrderForCheckoutCompletedQuery.php | 12 +- ...etPayPalOrderForOrderConfirmationQuery.php | 12 +- ...alOrderForOrderConfirmationQueryResult.php | 12 +- ...etCurrentPayPalOrderStatusQueryHandler.php | 2 +- ...lOrderForCheckoutCompletedQueryHandler.php | 40 +- ...lOrderForOrderConfirmationQueryHandler.php | 27 +- src/PayPal/PayPalClientTokenProvider.php | 41 +- src/PayPal/PayPalConfiguration.php | 10 + .../Comparator/PayPalCaptureComparator.php | 124 --- .../PayPalCaptureEventSubscriber.php | 368 ++----- .../Capture/PayPalCaptureEventDispatcher.php | 155 --- .../Payment/Capture/PayPalCaptureStatus.php | 1 - src/PayPalError.php | 4 + src/Repository/PsCheckoutCartRepository.php | 2 +- .../UpdatePsCheckoutSessionCommand.php | 198 ---- .../UpdatePsCheckoutSessionCommandHandler.php | 92 -- .../Event/PsCheckoutSessionUpdatedEvent.php | 51 - src/Translations/Translations.php | 2 + src/ValidateOrder.php | 6 +- .../State/Service/CheckOrderStateTest.php | 165 ---- ...TransitionPayPalOrderStatusServiceTest.php | 32 +- .../Comparator/PayPalOrderComparatorTest.php | 894 ------------------ ...onPayPalAuthorizationStatusServiceTest.php | 3 +- ...ansitionPayPalCaptureStatusServiceTest.php | 3 +- .../PayPalCaptureComparatorTest.php | 393 -------- ...ransitionPayPalRefundStatusServiceTest.php | 3 +- views/templates/admin/ajaxPayPalOrder.tpl | 30 +- .../templates/admin/ajaxPayPalOrderLegacy.tpl | 30 +- .../hook/displayOrderConfirmation.tpl | 2 +- views/templates/hook/displayOrderDetail.tpl | 5 + views/templates/hook/displayPaymentReturn.tpl | 5 + 103 files changed, 1348 insertions(+), 5470 deletions(-) rename src/{PayPal/Order => Cart}/Cache/CacheSettings.php (93%) rename src/{Order/State/Query/GetOrderStateConfigurationQuery.php => Cart/Exception/CartNotFoundException.php} (88%) rename src/{Session => Checkout}/Command/UpdatePaymentMethodSelectedCommand.php (97%) rename src/{Session => Checkout}/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php (61%) rename src/{Session => Checkout}/Exception/PsCheckoutSessionException.php (93%) delete mode 100644 src/Order/Payment/Query/GetOrderPaymentQuery.php delete mode 100644 src/Order/Payment/Query/GetOrderPaymentQueryResult.php delete mode 100644 src/Order/Payment/QueryHandler/GetOrderPaymentQueryHandler.php delete mode 100644 src/Order/Query/GetOrderQuery.php delete mode 100644 src/Order/Query/GetOrderQueryResult.php delete mode 100644 src/Order/QueryHandler/GetOrderQueryHandler.php delete mode 100644 src/Order/State/Query/GetOrderStateConfigurationQueryResult.php delete mode 100644 src/Order/State/Query/GetOrderStateQuery.php delete mode 100644 src/Order/State/Query/GetOrderStateQueryResult.php delete mode 100644 src/Order/State/QueryHandler/GetOrderStateConfigurationQueryHandler.php delete mode 100644 src/Order/State/QueryHandler/GetOrderStateQueryHandler.php delete mode 100644 src/Order/State/Service/CheckOrderState.php create mode 100644 src/Order/State/Service/OrderStateMapper.php delete mode 100644 src/Order/State/ValueObject/OrderStateConfiguration.php delete mode 100644 src/PayPal/Order/Command/PrunePayPalOrderCacheCommand.php delete mode 100644 src/PayPal/Order/Command/RemovePayPalOrderCacheCommand.php delete mode 100644 src/PayPal/Order/Command/UpdatePayPalOrderCacheCommand.php delete mode 100644 src/PayPal/Order/CommandHandler/PrunePayPalOrderCacheCommandHandler.php delete mode 100644 src/PayPal/Order/CommandHandler/RemovePayPalOrderCacheCommandHandler.php delete mode 100644 src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php delete mode 100644 src/PayPal/Order/Comparator/PayPalOrderComparator.php delete mode 100644 src/PayPal/Order/Event/PayPalOrderCacheUpdatedEvent.php delete mode 100644 src/PayPal/Order/Event/PayPalOrderFetchedEvent.php delete mode 100644 src/PayPal/Order/Event/PayPalOrderNotApprovedEvent.php delete mode 100644 src/PayPal/Order/Event/PayPalOrderSavedEvent.php delete mode 100644 src/PayPal/Order/PayPalOrderEventDispatcher.php delete mode 100644 src/PayPal/Payment/Capture/Comparator/PayPalCaptureComparator.php delete mode 100644 src/PayPal/Payment/Capture/PayPalCaptureEventDispatcher.php delete mode 100644 src/Session/Command/UpdatePsCheckoutSessionCommand.php delete mode 100644 src/Session/CommandHandler/UpdatePsCheckoutSessionCommandHandler.php delete mode 100644 src/Session/Event/PsCheckoutSessionUpdatedEvent.php delete mode 100644 tests/Unit/Order/State/Service/CheckOrderStateTest.php delete mode 100644 tests/Unit/PayPal/Order/Comparator/PayPalOrderComparatorTest.php delete mode 100644 tests/Unit/PayPal/Payment/Capture/Comparator/PayPalCaptureComparatorTest.php diff --git a/classes/PsCheckoutCart.php b/classes/PsCheckoutCart.php index b9d447e09..7c4bfd8ce 100644 --- a/classes/PsCheckoutCart.php +++ b/classes/PsCheckoutCart.php @@ -227,7 +227,15 @@ public function getPaypalClientToken() */ public function isPaypalClientTokenExpired() { - return empty($this->paypal_token_expire) || strtotime($this->paypal_token_expire) > time(); + if (empty($this->paypal_token_expire)) { + return true; + } + + try { + return (new DateTime())->diff(new DateTime($this->paypal_token_expire))->invert === 1; + } catch (Exception $e) { + return true; + } } /** @@ -243,7 +251,15 @@ public function getPaypalAuthorizationExpireDate() */ public function isPaypalAuthorizationExpired() { - return empty($this->paypal_authorization_expire) || strtotime($this->paypal_authorization_expire) > time(); + if (empty($this->paypal_authorization_expire)) { + return true; + } + + try { + return (new DateTime())->diff(new DateTime($this->paypal_authorization_expire))->invert === 1; + } catch (Exception $e) { + return true; + } } /** diff --git a/config/common.yml b/config/common.yml index 5d9b18447..d651a2641 100644 --- a/config/common.yml +++ b/config/common.yml @@ -354,6 +354,8 @@ services: ps_checkout.paypal.provider.client_token: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\PayPalClientTokenProvider' public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.prestashop.router: class: 'PrestaShop\Module\PrestashopCheckout\Routing\Router' @@ -399,9 +401,11 @@ services: class: 'PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount' public: true - ps_checkout.order.state.service.check_order_state: - class: 'PrestaShop\Module\PrestashopCheckout\Order\State\Service\CheckOrderState' + ps_checkout.order.state.service.order_state_mapper: + class: 'PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper' public: true + arguments: + - "@ps_checkout.configuration" ps_checkout.paypal.order.service.paypal_order_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus' @@ -452,10 +456,7 @@ services: PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand: "ps_checkout.command.handler.order.update_order_status" PrestaShop\Module\PrestashopCheckout\Order\Matrice\Command\UpdateOrderMatriceCommand: "ps_checkout.command.handler.order.matrice.update_order_matrice" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.capture_paypal_order" - PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\PrunePayPalOrderCacheCommand: "ps_checkout.command.handler.paypal.order.prune_paypal_order_cache" - PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\RemovePayPalOrderCacheCommand: "ps_checkout.command.handler.paypal.order.remove_paypal_order_cache" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.save_paypal_order" - PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCacheCommand: "ps_checkout.command.handler.paypal.order.update_paypal_order_cache" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQuery: "ps_checkout.query.handler.order.get_order_for_payment_completed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQuery: "ps_checkout.query.handler.order.get_order_for_payment_denied" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery: "ps_checkout.query.handler.order.get_order_for_payment_pending" @@ -465,11 +466,7 @@ services: PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery: "ps_checkout.query.handler.paypal.order.get_current_paypal_order_status" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_order_confirmation" - PrestaShop\Module\PrestashopCheckout\Session\Command\UpdatePaymentMethodSelectedCommand: "ps_checkout.query.handler.session.update_payment_method_selected" - PrestaShop\Module\PrestashopCheckout\Session\Command\UpdatePsCheckoutSessionCommand: "ps_checkout.query.handler.session.update_ps_checkout_session" - PrestaShop\Module\PrestashopCheckout\Order\Payment\Query\GetOrderPaymentQuery: "ps_checkout.query.handler.order.payment.get_payment_order" - PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderQuery: "ps_checkout.query.handler.order.get_order" - PrestaShop\Module\PrestashopCheckout\Order\State\Query\GetOrderStateConfigurationQuery: "ps_checkout.query.handler.order.state.get_order_state_configuration" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand: "ps_checkout.query.handler.checkout.update_payment_method_selected" ps_checkout.event.dispatcher.factory: class: 'PrestaShop\Module\PrestashopCheckout\Event\SymfonyEventDispatcherFactory' @@ -496,19 +493,19 @@ services: public: true arguments: - "@ps_checkout.module" - - "@ps_checkout.logger" - "@ps_checkout.repository.pscheckoutcart" - "@ps_checkout.cache.paypal.order" + - "@ps_checkout.checkout.checker" + - "@ps_checkout.paypal.order.service.check_transition_paypal_order_status" ps_checkout.event.subscriber.paypal.capture: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\EventSubscriber\PayPalCaptureEventSubscriber' public: true arguments: - "@ps_checkout.module" - - "@ps_checkout.logger" - - "@ps_checkout.repository.pscheckoutcart" - "@ps_checkout.order.service.check_order_amount" - "@ps_checkout.cache.paypal.capture" + - "@ps_checkout.order.state.service.order_state_mapper" ps_checkout.event.dispatcher.symfony: class: 'Symfony\Component\EventDispatcher\EventDispatcherInterface' @@ -532,6 +529,8 @@ services: public: true arguments: - "@ps_checkout.event.dispatcher" + - "@ps_checkout.funding_source.translation" + - "@ps_checkout.paypal.configuration" ps_checkout.command.handler.order.create_order: class: 'PrestaShop\Module\PrestashopCheckout\Order\CommandHandler\CreateOrderCommandHandler' @@ -539,13 +538,16 @@ services: arguments: - "@ps_checkout.context.state.manager" - "@ps_checkout.event.dispatcher" + - "@ps_checkout.repository.pscheckoutcart" + - "@ps_checkout.order.state.service.order_state_mapper" + - "@ps_checkout.module" + - "@ps_checkout.order.service.check_order_amount" ps_checkout.command.handler.order.update_order_status: class: 'PrestaShop\Module\PrestashopCheckout\Order\CommandHandler\UpdateOrderStatusCommandHandler' public: true arguments: - "@ps_checkout.event.dispatcher" - - "@ps_checkout.order.state.service.check_order_state" ps_checkout.command.handler.order.matrice.update_order_matrice: class: 'PrestaShop\Module\PrestashopCheckout\Order\Matrice\CommandHandler\UpdateOrderMatriceCommandHandler' @@ -556,76 +558,50 @@ services: ps_checkout.command.handler.paypal.order.capture_paypal_order: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler' public: true - arguments: - - "@ps_checkout.paypal.order.dispatcher" - - "@ps_checkout.paypal.capture.dispatcher" - - ps_checkout.command.handler.paypal.order.prune_paypal_order_cache: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\PrunePayPalOrderCacheCommandHandler' - public: true - arguments: - - "@ps_checkout.event.dispatcher" - - "@ps_checkout.cache.paypal.order" - - ps_checkout.command.handler.paypal.order.remove_paypal_order_cache: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\RemovePayPalOrderCacheCommandHandler' - public: true arguments: - "@ps_checkout.event.dispatcher" - - "@ps_checkout.cache.paypal.order" ps_checkout.command.handler.paypal.order.save_paypal_order: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\SavePayPalOrderCommandHandler' public: true arguments: - - "@ps_checkout.event.dispatcher" - "@ps_checkout.repository.pscheckoutcart" - ps_checkout.command.handler.paypal.order.update_paypal_order_cache: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\UpdatePayPalOrderCacheCommandHandler' - public: true - arguments: - - "@ps_checkout.cache.paypal.order" - ps_checkout.query.handler.paypal.order.get_current_paypal_order_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetCurrentPayPalOrderStatusQueryHandler' public: true arguments: - "@ps_checkout.repository.pscheckoutcart" - ps_checkout.query.handler.order.get_order: - class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderQueryHandler' - public: true - ps_checkout.query.handler.order.get_order_for_payment_completed: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentCompletedQueryHandler' public: true arguments: - - "@ps_checkout.cache.order" + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.query.handler.order.get_order_for_payment_denied: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentDeniedQueryHandler' public: true arguments: - - "@ps_checkout.cache.order" + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.query.handler.order.get_order_for_payment_pending: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentPendingQueryHandler' public: true arguments: - - "@ps_checkout.cache.order" + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.query.handler.order.get_order_for_payment_refunded: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentRefundedQueryHandler' public: true arguments: - - "@ps_checkout.cache.order" + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.query.handler.order.get_order_for_payment_reversed: class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForPaymentReversedQueryHandler' public: true arguments: - - "@ps_checkout.cache.order" + - "@ps_checkout.repository.pscheckoutcart" ps_checkout.query.handler.paypal.identity.get_client_token: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Identity\QueryHandler\GetClientTokenPayPalQueryHandler' @@ -637,78 +613,24 @@ services: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCheckoutCompletedQueryHandler' public: true arguments: - - "@ps_checkout.event.dispatcher" - "@ps_checkout.cache.paypal.order" - - "@ps_checkout.paypal.order.dispatcher" ps_checkout.query.handler.paypal.order.get_paypal_order_for_order_confirmation: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForOrderConfirmationQueryHandler' public: true arguments: - - "@ps_checkout.event.dispatcher" - "@ps_checkout.cache.paypal.order" - ps_checkout.query.handler.session.update_payment_method_selected: - class: 'PrestaShop\Module\PrestashopCheckout\Session\CommandHandler\UpdatePaymentMethodSelectedCommandHandler' + ps_checkout.query.handler.checkout.update_payment_method_selected: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\UpdatePaymentMethodSelectedCommandHandler' public: true arguments: - - "@ps_checkout.event.dispatcher" - "@ps_checkout.repository.pscheckoutcart" - ps_checkout.query.handler.session.update_ps_checkout_session: - class: 'PrestaShop\Module\PrestashopCheckout\Session\CommandHandler\UpdatePsCheckoutSessionCommandHandler' - public: true - arguments: - - "@ps_checkout.event.dispatcher" - - "@ps_checkout.repository.pscheckoutcart" - - ps_checkout.query.handler.order.payment.get_payment_order: - class: 'PrestaShop\Module\PrestashopCheckout\Order\Payment\QueryHandler\GetOrderPaymentQueryHandler' - public: true - - ps_checkout.query.handler.order.state.get_order_state_configuration: - class: 'PrestaShop\Module\PrestashopCheckout\Order\State\QueryHandler\GetOrderStateConfigurationQueryHandler' - public: true - - ps_checkout.query.handler.order.state.get_order_state: - class: 'PrestaShop\Module\PrestashopCheckout\Order\State\QueryHandler\GetOrderStateQueryHandler' - public: true - - ps_checkout.paypal.order.comparator: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\Comparator\PayPalOrderComparator' - public: true - arguments: - - "@ps_checkout.cache.paypal.order" - - ps_checkout.paypal.order.dispatcher: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderEventDispatcher' - public: true - arguments: - - "@ps_checkout.event.dispatcher" - - "@ps_checkout.bus.command" - - "@ps_checkout.paypal.order.service.check_transition_paypal_order_status" - - "@ps_checkout.paypal.order.comparator" - - "@ps_checkout.cache.paypal.order" - ps_checkout.paypal.capture.service.check_transition_paypal_capture_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\CheckTransitionPayPalCaptureStatusService' public: true - ps_checkout.paypal.capture.comparator: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Comparator\PayPalCaptureComparator' - public: true - arguments: - - "@ps_checkout.cache.paypal.capture" - - ps_checkout.paypal.capture.dispatcher: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureEventDispatcher' - public: true - arguments: - - "@ps_checkout.event.dispatcher" - - "@ps_checkout.paypal.capture.service.check_transition_paypal_capture_status" - - "@ps_checkout.paypal.capture.comparator" - - "@ps_checkout.cache.paypal.capture" - ps_checkout.checkout.checker: class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker' public: true diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index bdc49e9a1..7913cd1d3 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -344,7 +344,7 @@ public function ajaxProcessFetchOrder() $psCheckoutCartCollection = new PrestaShopCollection('PsCheckoutCart'); $psCheckoutCartCollection->where('id_cart', '=', (int) $order->id_cart); - $psCheckoutCartCollection->orderBy('date_upd', 'DESC'); + $psCheckoutCartCollection->orderBy('date_upd', 'ASC'); if (!$psCheckoutCartCollection->count()) { http_response_code(500); @@ -390,6 +390,7 @@ public function ajaxProcessFetchOrder() 'moduleLogoUri' => $this->module->getPathUri() . 'logo.png', 'orderPaymentDisplayName' => $fundingSourceTranslationProvider->getPaymentMethodName($psCheckoutCart->paypal_funding), 'orderPaymentLogoUri' => $this->module->getPathUri() . 'views/img/' . $psCheckoutCart->paypal_funding . '.svg', + 'psCheckoutCart' => $psCheckoutCart, ]); $this->ajaxDie(json_encode([ diff --git a/controllers/front/create.php b/controllers/front/create.php index a5185d909..6decffa68 100644 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -119,7 +119,7 @@ public function postProcess() // If we have a PayPal Order Id with a status CREATED or APPROVED or PAYER_ACTION_REQUIRED we mark it as CANCELED and create new one // This is needed because cart gets updated so we need to update paypal order too if ( - false !== $psCheckoutCart + false !== $psCheckoutCart && $psCheckoutCart->getPaypalOrderId() ) { $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; $psCheckoutCartRepository->save($psCheckoutCart); diff --git a/controllers/front/validate.php b/controllers/front/validate.php index 85f2f31c6..fe9d3f1a5 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -20,12 +20,14 @@ */ use PrestaShop\Module\PrestashopCheckout\Checkout\Event\CheckoutCompletedEvent; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQueryResult; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; /** * This controller receive ajax call to capture/authorize payment and create a PrestaShop Order @@ -44,28 +46,10 @@ class Ps_CheckoutValidateModuleFrontController extends AbstractFrontController /** * @see FrontController::postProcess() - * - * @todo Move logic to a Service */ public function postProcess() { try { - if (false === $this->checkIfContextIsValid()) { - throw new PsCheckoutException('The context is not valid', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); - } - - if (false === $this->checkIfPaymentOptionIsAvailable()) { - throw new PsCheckoutException('This payment method is not available.', PsCheckoutException::PRESTASHOP_PAYMENT_UNAVAILABLE); - } - - $cart = $this->context->cart; - - $customer = new Customer($cart->id_customer); - - if (false === Validate::isLoadedObject($customer)) { - throw new PsCheckoutException('Unable to load Customer', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); - } - $bodyContent = file_get_contents('php://input'); if (empty($bodyContent)) { @@ -84,11 +68,19 @@ public function postProcess() $this->paypalOrderId = $bodyValues['orderID']; + /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ + $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->paypalOrderId); + + if (!Validate::isLoadedObject($psCheckoutCart)) { + throw new PsCheckoutException('The cart cannot be found', PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + } + /** @var EventDispatcherInterface $eventDispatcher */ $eventDispatcher = $this->module->getService('ps_checkout.event.dispatcher'); $eventDispatcher->dispatch(new CheckoutCompletedEvent( - $cart->id, + $psCheckoutCart->getIdCart(), $this->paypalOrderId, (isset($bodyValues['fundingSource']) && Validate::isGenericName($bodyValues['fundingSource'])) ? $bodyValues['fundingSource'] : null, isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout'], @@ -97,6 +89,11 @@ public function postProcess() $this->sendOkResponse($this->generateResponse()); } catch (Exception $exception) { + $this->module->getLogger()->error('CheckoutCompletedEvent failed', [ + 'exception_class' => get_class($exception), + 'exception_message' => $exception->getMessage(), + 'exception_code' => $exception->getCode(), + ]); $response = $this->generateResponse(); if (!empty($response)) { @@ -109,45 +106,46 @@ public function postProcess() private function generateResponse() { - if (!$this->module->currentOrder) { + if (empty($this->paypalOrderId)) { return null; } try { - /** @var \PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface $commandBus */ + /** @var CommandBusInterface $commandBus */ $commandBus = $this->module->getService('ps_checkout.bus.command'); - /** @var \PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository $psCheckoutCartRepository */ + /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); - $psCheckoutCart = $psCheckoutCartRepository->findOneByCartId($this->context->cart->id); + $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->paypalOrderId); if (!Validate::isLoadedObject($psCheckoutCart)) { return null; } - /** @var GetPayPalOrderForOrderConfirmationQueryResult $paypalOrder */ - $paypalOrder = $commandBus->handle(new GetPayPalOrderForOrderConfirmationQuery( - $psCheckoutCart->paypal_order - )); + $paypalOrder = null; + + try { + /** @var GetPayPalOrderForOrderConfirmationQueryResult $paypalOrder */ + $paypalOrder = $commandBus->handle(new GetPayPalOrderForOrderConfirmationQuery( + $psCheckoutCart->paypal_order + )); + } catch (Exception $exception) { + } $response = [ 'status' => $psCheckoutCart->paypal_status, 'paypalOrderId' => $psCheckoutCart->paypal_order, - 'transactionIdentifier' => isset($paypalOrder->getOrder()['purchase_units'][0]['payments']['captures'][0]) ? $paypalOrder->getOrder()['purchase_units'][0]['payments']['captures'][0]['id'] : null, + 'transactionIdentifier' => $paypalOrder && isset($paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]) ? $paypalOrder->getOrderPayPal()['purchase_units'][0]['payments']['captures'][0]['id'] : null, ]; return $response; } catch (Exception $exception) { $this->handleException($exception); } + + return null; } - /** - * Redirect to checkout page - * - * @param string $exceptionMessageForCustomer - * @param Exception $exception - */ private function sendBadRequestError($exceptionMessageForCustomer, Exception $exception) { $this->exitWithResponse([ @@ -171,6 +169,30 @@ private function sendBadRequestError($exceptionMessageForCustomer, Exception $ex */ private function sendOkResponse($response) { + if (empty($this->paypalOrderId)) { + return null; + } + + /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ + $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->paypalOrderId); + + if (!Validate::isLoadedObject($psCheckoutCart)) { + return null; + } + + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); + + if (!$orders->count()) { + return null; + } + + /** @var Order $order */ + $order = $orders->getFirst(); + + $cart = new Cart($psCheckoutCart->getIdCart()); + $this->exitWithResponse([ 'status' => true, 'httpCode' => 200, @@ -178,58 +200,16 @@ private function sendOkResponse($response) 'paypal_status' => $response['status'], 'paypal_order' => $response['paypalOrderId'], 'paypal_transaction' => $response['transactionIdentifier'], - 'id_cart' => (int) $this->context->cart->id, + 'id_cart' => $psCheckoutCart->getIdCart(), 'id_module' => (int) $this->module->id, - 'id_order' => (int) $this->module->currentOrder, - 'secure_key' => $this->context->customer->secure_key, + 'id_order' => (int) $order->id, + 'secure_key' => $cart->secure_key, ], 'exceptionCode' => null, 'exceptionMessage' => null, ]); } - /** - * Check if the context is valid and if the module is active - * - * @return bool - */ - private function checkIfContextIsValid() - { - return true === (bool) $this->module->active - && true === Validate::isLoadedObject($this->context->cart) - && true === Validate::isUnsignedInt($this->context->cart->id_customer) - && true === Validate::isUnsignedInt($this->context->cart->id_address_delivery) - && true === Validate::isUnsignedInt($this->context->cart->id_address_invoice); - } - - /** - * Check that this payment option is still available in case the customer changed - * his address just before the end of the checkout process - * - * @return bool - * - * @todo Move to main module class - */ - private function checkIfPaymentOptionIsAvailable() - { - $modules = Module::getPaymentModules(); - - if (empty($modules)) { - return false; - } - - foreach ($modules as $module) { - if (isset($module['name']) && $this->module->name === $module['name']) { - return true; - } - } - - return false; - } - - /** - * @param Exception $exception - */ private function handleException(Exception $exception) { $exceptionMessageForCustomer = $this->module->l('Error processing payment, you could have been charged. Please check your order history in your account to check the status of the order or please contact our customer service to know more.'); @@ -317,6 +297,7 @@ private function handleException(Exception $exception) } } elseif ('PrestaShop\Module\PrestashopCheckout\Exception\PayPalException' === $exceptionClass) { switch ($exception->getCode()) { + case PayPalException::CARD_BRAND_NOT_SUPPORTED: case PayPalException::CARD_TYPE_NOT_SUPPORTED: $exceptionMessageForCustomer = $this->module->l('Processing of this card type is not supported. Use another card type.'); $notifyCustomerService = false; @@ -333,6 +314,7 @@ private function handleException(Exception $exception) $exceptionMessageForCustomer = $this->module->l('Your card cannot be used to pay in our country, please try another payment method.'); $notifyCustomerService = false; break; + case PayPalException::COMPLIANCE_VIOLATION: case PayPalException::INSTRUMENT_DECLINED: $exceptionMessageForCustomer = $this->module->l('This payment method declined transaction, please try another.'); $notifyCustomerService = false; @@ -374,12 +356,21 @@ private function handleException(Exception $exception) $notifyCustomerService = false; break; case PayPalException::ORDER_ALREADY_CAPTURED: - $exceptionMessageForCustomer = $this->module->l('Order cannot be saved'); + $exceptionMessageForCustomer = $this->module->l('Order is already captured.'); break; case PayPalException::PAYMENT_DENIED: $exceptionMessageForCustomer = $this->module->l('This payment method has been refused by the payment platform, please use another payment method.'); $notifyCustomerService = false; break; + case PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING: + case PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING: + $exceptionMessageForCustomer = $this->module->l('Card payment cannot be processed at the moment, please use another payment method.'); + $notifyCustomerService = false; + break; + case PayPalException::RESOURCE_NOT_FOUND: + $exceptionMessageForCustomer = $this->module->l('Transaction expired, please try again.'); + $notifyCustomerService = false; + break; } } @@ -405,17 +396,6 @@ private function handleException(Exception $exception) $this->sendBadRequestError($exceptionMessageForCustomer, $exception); } - $psCheckoutCartCollection = new PrestaShopCollection('PsCheckoutCart'); - $psCheckoutCartCollection->where('paypal_order', '=', (int) $paypalOrder); - - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartCollection->getFirst(); - - // Preserve current cart from customer changes to allow merchant to see whats wrong - if (false !== $psCheckoutCart && false === (bool) Order::getOrderByCartId($psCheckoutCart->id_cart)) { - $this->generateNewCart(); - } - $this->exitWithResponse([ 'status' => false, 'httpCode' => 500, @@ -429,18 +409,23 @@ private function handleException(Exception $exception) ]); } - /** - * @param Exception $exception - * - * @throws PrestaShopDatabaseException - * @throws PrestaShopException - * - * @todo To be refactored with Service Container in v2.0.0 - */ private function notifyCustomerService(Exception $exception) { - $paypalOrderId = $this->paypalOrderId; - $contacts = Contact::getContacts((int) $this->context->language->id); + /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ + $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->paypalOrderId); + + if (!Validate::isLoadedObject($psCheckoutCart)) { + return null; + } + + $cart = new Cart($psCheckoutCart->getIdCart()); + + if (!Validate::isLoadedObject($cart)) { + return null; + } + + $contacts = Contact::getContacts((int) $cart->id_lang); if (empty($contacts)) { return; @@ -451,11 +436,11 @@ private function notifyCustomerService(Exception $exception) Tools::encrypt(implode( '|', [ - (int) $this->context->customer->id, - (int) $this->context->shop->id, - (int) $this->context->language->id, + (int) $cart->id_customer, + (int) $cart->id_shop, + (int) $cart->id_lang, (int) $exception->getCode(), - $paypalOrderId, + $this->paypalOrderId, get_class($exception), ] )), @@ -466,8 +451,8 @@ private function notifyCustomerService(Exception $exception) $isThreadAlreadyCreated = (bool) Db::getInstance()->getValue(' SELECT 1 FROM ' . _DB_PREFIX_ . 'customer_thread - WHERE id_customer = ' . (int) $this->context->customer->id . ' - AND id_shop = ' . (int) $this->context->shop->id . ' + WHERE id_customer = ' . (int) $cart->id_customer . ' + AND id_shop = ' . (int) $cart->id_shop . ' AND status = "open" AND token = "' . pSQL($token) . '" '); @@ -479,22 +464,24 @@ private function notifyCustomerService(Exception $exception) $message = $this->module->l('This message is sent automatically by module PrestaShop Checkout') . PHP_EOL . PHP_EOL; $message .= $this->module->l('A customer encountered a processing payment error :') . PHP_EOL; - $message .= $this->module->l('Customer identifier:') . ' ' . (int) $this->context->customer->id . PHP_EOL; - $message .= $this->module->l('Cart identifier:') . ' ' . (int) $this->context->cart->id . PHP_EOL; - $message .= $this->module->l('PayPal order identifier:') . ' ' . Tools::safeOutput($paypalOrderId) . PHP_EOL; + $message .= $this->module->l('Customer identifier:') . ' ' . (int) $cart->id_customer . PHP_EOL; + $message .= $this->module->l('Cart identifier:') . ' ' . (int) $cart->id . PHP_EOL; + $message .= $this->module->l('PayPal order identifier:') . ' ' . Tools::safeOutput($this->paypalOrderId) . PHP_EOL; $message .= $this->module->l('Exception identifier:') . ' ' . (int) $exception->getCode() . PHP_EOL; $message .= $this->module->l('Exception detail:') . ' ' . Tools::safeOutput($exception->getMessage()) . ($exception->getPrevious() !== null ? ': ' . Tools::safeOutput($exception->getPrevious()->getMessage()) : '') . PHP_EOL . PHP_EOL; $message .= $this->module->l('If you need assistance, please contact our Support Team on PrestaShop Checkout configuration page on Help subtab.') . PHP_EOL; + $customer = new Customer((int) $cart->id_customer); + $customerThread = new CustomerThread(); - $customerThread->id_customer = (int) $this->context->customer->id; - $customerThread->id_shop = (int) $this->context->shop->id; + $customerThread->id_customer = (int) $cart->id_customer; + $customerThread->id_shop = (int) $cart->id_shop; $customerThread->id_order = (int) $this->module->currentOrder; - $customerThread->id_lang = (int) $this->context->language->id; + $customerThread->id_lang = (int) $cart->id_lang; $customerThread->id_contact = (int) $contacts[0]['id_contact']; // Should be configurable - $customerThread->email = $this->context->customer->email; + $customerThread->email = $customer->email; $customerThread->status = 'open'; $customerThread->token = $token; $customerThread->add(); @@ -508,16 +495,4 @@ private function notifyCustomerService(Exception $exception) $customerMessage->read = false; $customerMessage->add(); } - - /** - * @see FrontController::init() - */ - private function generateNewCart() - { - $cart = clone $this->context->cart; - $cart->id = 0; - $cart->add(); - - $this->context->cart = $cart; - } } diff --git a/src/Api/Payment/Order.php b/src/Api/Payment/Order.php index 5ed99feab..e76528aa0 100644 --- a/src/Api/Payment/Order.php +++ b/src/Api/Payment/Order.php @@ -56,29 +56,13 @@ public function capture($orderId, $merchantId, $fundingSource) { $this->setRoute('/payments/order/capture'); - $response = $this->post([ + return $this->post([ 'mode' => $fundingSource, 'orderId' => (string) $orderId, 'payee' => [ 'merchant_id' => $merchantId, ], ]); - -// /** @var \Ps_checkout $module */ -// $module = \Module::getInstanceByName('ps_checkout'); -// -// /** @var \Symfony\Component\Cache\Simple\FilesystemCache $captureCache */ -// $captureCache = $module->getService('ps_checkout.cache.paypal.capture'); -// -// $responseBody = isset($response['body']) ? $response['body'] : null; -// -// if ($responseBody) { -// $capture = $responseBody['purchase_units'][0]['payments']['captures'][0]; -// -// $captureCache->set($capture['id'], $capture); -// } - - return $response; } /** diff --git a/src/PayPal/Order/Cache/CacheSettings.php b/src/Cart/Cache/CacheSettings.php similarity index 93% rename from src/PayPal/Order/Cache/CacheSettings.php rename to src/Cart/Cache/CacheSettings.php index 904da64e5..a45000401 100644 --- a/src/PayPal/Order/Cache/CacheSettings.php +++ b/src/Cart/Cache/CacheSettings.php @@ -18,7 +18,7 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache; +namespace PrestaShop\Module\PrestashopCheckout\Cart\Cache; class CacheSettings { diff --git a/src/Order/State/Query/GetOrderStateConfigurationQuery.php b/src/Cart/Exception/CartNotFoundException.php similarity index 88% rename from src/Order/State/Query/GetOrderStateConfigurationQuery.php rename to src/Cart/Exception/CartNotFoundException.php index 340c717e8..6d6eadd18 100644 --- a/src/Order/State/Query/GetOrderStateConfigurationQuery.php +++ b/src/Cart/Exception/CartNotFoundException.php @@ -18,8 +18,8 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\Order\State\Query; +namespace PrestaShop\Module\PrestashopCheckout\Cart\Exception; -class GetOrderStateConfigurationQuery +class CartNotFoundException extends CartException { } diff --git a/src/Session/Command/UpdatePaymentMethodSelectedCommand.php b/src/Checkout/Command/UpdatePaymentMethodSelectedCommand.php similarity index 97% rename from src/Session/Command/UpdatePaymentMethodSelectedCommand.php rename to src/Checkout/Command/UpdatePaymentMethodSelectedCommand.php index c0a477c4e..4a83afebf 100644 --- a/src/Session/Command/UpdatePaymentMethodSelectedCommand.php +++ b/src/Checkout/Command/UpdatePaymentMethodSelectedCommand.php @@ -18,7 +18,7 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\Session\Command; +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Command; use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; diff --git a/src/Session/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php b/src/Checkout/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php similarity index 61% rename from src/Session/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php rename to src/Checkout/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php index 25ffe16af..09eb8b08f 100644 --- a/src/Session/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php +++ b/src/Checkout/CommandHandler/UpdatePaymentMethodSelectedCommandHandler.php @@ -18,40 +18,32 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\Session\CommandHandler; +namespace PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler; use Exception; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Exception\PsCheckoutSessionException; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use PrestaShop\Module\PrestashopCheckout\Session\Command\UpdatePaymentMethodSelectedCommand; -use PrestaShop\Module\PrestashopCheckout\Session\Event\PsCheckoutSessionUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\Session\Exception\PsCheckoutSessionException; use PsCheckoutCart; class UpdatePaymentMethodSelectedCommandHandler { - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - /** * @var PsCheckoutCartRepository */ private $psCheckoutCartRepository; - /** - * @param EventDispatcherInterface $eventDispatcher - * @param PsCheckoutCartRepository $psCheckoutCartRepository - */ - public function __construct( - EventDispatcherInterface $eventDispatcher, - PsCheckoutCartRepository $psCheckoutCartRepository - ) { - $this->eventDispatcher = $eventDispatcher; + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { $this->psCheckoutCartRepository = $psCheckoutCartRepository; } + /** + * @param UpdatePaymentMethodSelectedCommand $command + * + * @throws PsCheckoutSessionException + */ public function handle(UpdatePaymentMethodSelectedCommand $command) { try { @@ -59,7 +51,7 @@ public function handle(UpdatePaymentMethodSelectedCommand $command) $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId($command->getCartId()->getValue()); if (false === $psCheckoutCart) { - throw new PsCheckoutSessionException(sprintf('Unable to retrieve PrestaShop Checkout session #%s', $command->getCartId()->getValue()), PsCheckoutSessionException::UPDATE_FAILED); + throw new CartNotFoundException(sprintf('Unable to retrieve PrestaShop Cart #%s', var_export($command->getCartId()->getValue(), true))); } $psCheckoutCart->id_cart = $command->getCartId()->getValue(); @@ -69,11 +61,7 @@ public function handle(UpdatePaymentMethodSelectedCommand $command) $psCheckoutCart->isExpressCheckout = $command->isExpressCheckout(); $this->psCheckoutCartRepository->save($psCheckoutCart); } catch (Exception $exception) { - throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', $command->getCartId()->getValue()), PsCheckoutSessionException::UPDATE_FAILED, $exception); + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($command->getCartId()->getValue(), true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); } - - $this->eventDispatcher->dispatch( - new PsCheckoutSessionUpdatedEvent($command->getCartId()->getValue()) - ); } } diff --git a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php index 9eab082ae..a316b8762 100644 --- a/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php +++ b/src/Checkout/EventSubscriber/CheckoutEventSubscriber.php @@ -1,5 +1,4 @@ module->getService('ps_checkout.bus.command')->handle(new GetPayPalOrderForCheckoutCompletedQuery( - $event->getPayPalOrderId()->getValue() - )); + try { + /** @var GetPayPalOrderForCheckoutCompletedQueryResult $getPayPalOrderForCheckoutCompletedQueryResult */ + $getPayPalOrderForCheckoutCompletedQueryResult = $this->module->getService('ps_checkout.bus.command')->handle(new GetPayPalOrderForCheckoutCompletedQuery( + $event->getPayPalOrderId()->getValue() + )); + } catch (HttpTimeoutException $exception) { + return $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + } $this->checkoutChecker->continueWithAuthorization($event->getCartId()->getValue(), $getPayPalOrderForCheckoutCompletedQueryResult->getPayPalOrder()); - $this->commandBus->handle( - new CapturePayPalOrderCommand( - $event->getPayPalOrderId()->getValue(), - $event->getFundingSource() - ) - ); + try { + $this->commandBus->handle( + new CapturePayPalOrderCommand( + $event->getPayPalOrderId()->getValue(), + $event->getFundingSource() + ) + ); + } catch (PayPalException $exception) { + if ($exception->getCode() === PayPalException::ORDER_NOT_APPROVED) { + return $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + } elseif ($exception->getCode() === PayPalException::RESOURCE_NOT_FOUND) { + /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ + $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); + + if (Validate::isLoadedObject($psCheckoutCart)) { + $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; + $psCheckoutCartRepository->save($psCheckoutCart); + } + + throw $exception; + } elseif ($exception->getCode() === PayPalException::ORDER_ALREADY_CAPTURED) { + return; + } else { + throw $exception; + } + } catch (HttpTimeoutException $exception) { + return $this->commandBus->handle(new CreateOrderCommand($event->getPayPalOrderId()->getValue())); + } } } diff --git a/src/Session/Exception/PsCheckoutSessionException.php b/src/Checkout/Exception/PsCheckoutSessionException.php similarity index 93% rename from src/Session/Exception/PsCheckoutSessionException.php rename to src/Checkout/Exception/PsCheckoutSessionException.php index cb932dc70..341846d01 100644 --- a/src/Session/Exception/PsCheckoutSessionException.php +++ b/src/Checkout/Exception/PsCheckoutSessionException.php @@ -18,7 +18,7 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\Session\Exception; +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Exception; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; diff --git a/src/Dispatcher/OrderDispatcher.php b/src/Dispatcher/OrderDispatcher.php index 0601dca9b..69f88d8fa 100644 --- a/src/Dispatcher/OrderDispatcher.php +++ b/src/Dispatcher/OrderDispatcher.php @@ -24,10 +24,15 @@ use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovalReversedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderEventDispatcher; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureEventDispatcher; use Ps_checkout; use Psr\Log\LoggerInterface; @@ -67,30 +72,31 @@ public function dispatchEventType($payload) /** @var LoggerInterface $logger */ $logger = $module->getService('ps_checkout.logger'); - /** @var PayPalCaptureEventDispatcher $paypalCaptureEventDispatcher */ - $paypalCaptureEventDispatcher = $module->getService('ps_checkout.paypal.capture.dispatcher'); - - /** @var PayPalOrderEventDispatcher $paypalOrderEventDispatcher */ - $paypalOrderEventDispatcher = $module->getService('ps_checkout.paypal.order.dispatcher'); - switch ($payload['eventType']) { case static::PS_CHECKOUT_PAYMENT_COMPLETED: + $eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + break; case static::PS_CHECKOUT_PAYMENT_PENDING: + $eventDispatcher->dispatch(new PayPalCapturePendingEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + break; case static::PS_CHECKOUT_PAYMENT_DENIED: + $eventDispatcher->dispatch(new PayPalCaptureDeclinedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + break; case static::PS_CHECKOUT_PAYMENT_REFUNDED: + $eventDispatcher->dispatch(new PayPalCaptureRefundedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + break; case static::PS_CHECKOUT_PAYMENT_REVERSED: - $paypalCaptureEventDispatcher->dispatch($payload['resource']['supplementary_data']['related_ids']['order_id'], $payload['resource']); - - return true; + $eventDispatcher->dispatch(new PayPalCaptureReversedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + break; case static::PS_CHECKOUT_ORDER_APPROVED: + $eventDispatcher->dispatch(new PayPalOrderApprovedEvent($payload['orderId'], $payload['resource'])); + break; case static::PS_CHECKOUT_ORDER_COMPLETED: - $paypalOrderEventDispatcher->dispatch($payload['resource']); - - return true; + $eventDispatcher->dispatch(new PayPalOrderCompletedEvent($payload['orderId'], $payload['resource'])); + break; case static::PS_CHECKOUT_ORDER_APPROVAL_REVERSED: $eventDispatcher->dispatch(new PayPalOrderApprovalReversedEvent($payload['orderId'], $payload['resource'])); - - return true; + break; default: $logger->warning( 'Unknown webhook, cannot be processed.', @@ -98,8 +104,8 @@ public function dispatchEventType($payload) 'payload' => $payload, ] ); - - return true; } + + return true; } } diff --git a/src/Exception/PayPalException.php b/src/Exception/PayPalException.php index 4b8aaed6f..c95725238 100644 --- a/src/Exception/PayPalException.php +++ b/src/Exception/PayPalException.php @@ -151,4 +151,6 @@ class PayPalException extends PsCheckoutException const CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE = 126; const NO_EXTERNAL_FUNDING_DETAILS_FOUND = 127; const PAYMENT_DENIED = 128; + const CARD_BRAND_NOT_SUPPORTED = 129; + const RESOURCE_NOT_FOUND = 130; } diff --git a/src/Order/Command/AddOrderPaymentCommand.php b/src/Order/Command/AddOrderPaymentCommand.php index 96ebc5013..76dc900cd 100644 --- a/src/Order/Command/AddOrderPaymentCommand.php +++ b/src/Order/Command/AddOrderPaymentCommand.php @@ -21,6 +21,8 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Command; use DateTimeImmutable; +use Exception; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; class AddOrderPaymentCommand @@ -62,6 +64,9 @@ class AddOrderPaymentCommand * @param string $paymentAmount * @param int $paymentCurrencyId * @param string|null $transactionId + * + * @throws OrderException + * @throws Exception */ public function __construct( $orderId, diff --git a/src/Order/Command/CreateOrderCommand.php b/src/Order/Command/CreateOrderCommand.php index e1b35f5fc..4f72f2d1f 100644 --- a/src/Order/Command/CreateOrderCommand.php +++ b/src/Order/Command/CreateOrderCommand.php @@ -20,103 +20,46 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Command; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; class CreateOrderCommand { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @var string + * @var array|null */ - private $paymentModuleName; + private $capturePayPal; /** - * @var int + * @param string $orderPayPalId + * @param array|null $capturePayPal + * + * @throws PayPalOrderException */ - private $orderStateId; - - /** - * @var string - */ - private $transactionId; - - /** - * @var string - */ - private $paymentMethod; - - /** - * @var string - */ - private $paidAmount; - - /** - * @param int $cartId - * @param string $paymentModuleName - * @param int $orderStateId - * @param string $paymentMethod - * @param string $transactionId - * @param string $paidAmount - */ - public function __construct($cartId, $paymentModuleName, $orderStateId, $paymentMethod, $transactionId, $paidAmount) - { - $this->cartId = new CartId($cartId); - $this->paymentModuleName = $paymentModuleName; - $this->orderStateId = $orderStateId; - $this->transactionId = $transactionId; - $this->paymentMethod = $paymentMethod; - $this->paidAmount = $paidAmount; - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } - - /** - * @return string - */ - public function getPaymentModuleName() - { - return $this->paymentModuleName; - } - - /** - * @return int - */ - public function getOrderStateId() - { - return $this->orderStateId; - } - - /** - * @return string - */ - public function getPaymentMethod() + public function __construct($orderPayPalId, $capturePayPal = null) { - return $this->paymentMethod; + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->capturePayPal = $capturePayPal; } /** - * @return string + * @return PayPalOrderId */ - public function getTransactionId() + public function getOrderPayPalId() { - return $this->transactionId; + return $this->orderPayPalId; } /** - * @return string + * @return array|null */ - public function getPaidAmount() + public function getCapturePayPal() { - return $this->paidAmount; + return $this->capturePayPal; } } diff --git a/src/Order/CommandHandler/AddOrderPaymentCommandHandler.php b/src/Order/CommandHandler/AddOrderPaymentCommandHandler.php index 00f34bb6c..432b31b9c 100644 --- a/src/Order/CommandHandler/AddOrderPaymentCommandHandler.php +++ b/src/Order/CommandHandler/AddOrderPaymentCommandHandler.php @@ -22,13 +22,17 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\CommandHandler; use Currency; +use DateTimeZone; use OrderInvoice; use OrderPayment; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider; use PrestaShop\Module\PrestashopCheckout\Order\AbstractOrderHandler; use PrestaShop\Module\PrestashopCheckout\Order\Command\AddOrderPaymentCommand; use PrestaShop\Module\PrestashopCheckout\Order\Event\OrderPaymentCreatedEvent; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\Payment\Exception\OrderPaymentException; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use Validate; class AddOrderPaymentCommandHandler extends AbstractOrderHandler @@ -37,19 +41,30 @@ class AddOrderPaymentCommandHandler extends AbstractOrderHandler * @var EventDispatcherInterface */ private $eventDispatcher; - /** - * @param EventDispatcherInterface $eventDispatcher + * @var FundingSourceTranslationProvider */ - public function __construct(EventDispatcherInterface $eventDispatcher) - { + private $fundingSourceTranslationProvider; + /** + * @var PayPalConfiguration + */ + private $configuration; + + public function __construct( + EventDispatcherInterface $eventDispatcher, + FundingSourceTranslationProvider $fundingSourceTranslationProvider, + PayPalConfiguration $configuration + ) { $this->eventDispatcher = $eventDispatcher; + $this->fundingSourceTranslationProvider = $fundingSourceTranslationProvider; + $this->configuration = $configuration; } /** * @param AddOrderPaymentCommand $command * * @throws OrderException + * @throws OrderPaymentException */ public function handle(AddOrderPaymentCommand $command) { @@ -65,22 +80,15 @@ public function handle(AddOrderPaymentCommand $command) $orderInvoice = $orderHasInvoice ? $order->getNotPaidInvoicesCollection()->getFirst() : null; if ($orderHasInvoice && !Validate::isLoadedObject($orderInvoice)) { - $module = \Module::getInstanceByName('ps_checkout'); - $module->getLogger()->alert( - 'Order is already paid', - [ - 'id_order' => $command->getOrderId()->getValue(), - ] - ); $orderInvoice = null; } $paymentAdded = $order->addOrderPayment( $command->getPaymentAmount(), - $command->getPaymentMethod(), + $this->fundingSourceTranslationProvider->getPaymentMethodName($command->getPaymentMethod()), $command->getPaymentTransactionId(), $currency, - $command->getPaymentDate()->format('Y-m-d H:i:s'), + $command->getPaymentDate()->setTimezone(new DateTimeZone($this->configuration->getTimeZone()))->format('Y-m-d H:i:s'), $orderInvoice ); diff --git a/src/Order/CommandHandler/CreateOrderCommandHandler.php b/src/Order/CommandHandler/CreateOrderCommandHandler.php index dec55e304..23a826f56 100644 --- a/src/Order/CommandHandler/CreateOrderCommandHandler.php +++ b/src/Order/CommandHandler/CreateOrderCommandHandler.php @@ -21,18 +21,28 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\CommandHandler; use Cart; +use Currency; use Exception; -use Module; use Order; -use PaymentModule; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; use PrestaShop\Module\PrestashopCheckout\Context\ContextStateManager; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider; use PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand; use PrestaShop\Module\PrestashopCheckout\Order\Event\OrderCreatedEvent; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; +use Ps_checkout; +use PsCheckoutCart; +use Validate; class CreateOrderCommandHandler extends AbstractOrderCommandHandler { @@ -47,50 +57,128 @@ class CreateOrderCommandHandler extends AbstractOrderCommandHandler private $contextStateManager; /** - * @param ContextStateManager $contextStateManager + * @var PsCheckoutCartRepository */ - public function __construct(ContextStateManager $contextStateManager, EventDispatcherInterface $eventDispatcher) - { + private $psCheckoutCartRepository; + + /** + * @var OrderStateMapper + */ + private $psOrderStateMapper; + + /** + * @var Ps_checkout + */ + private $module; + + /** + * @var CheckOrderAmount + */ + private $checkOrderAmount; + + public function __construct( + ContextStateManager $contextStateManager, + EventDispatcherInterface $eventDispatcher, + PsCheckoutCartRepository $psCheckoutCartRepository, + OrderStateMapper $psOrderStateMapper, + Ps_checkout $module, + CheckOrderAmount $checkOrderAmount + ) { $this->contextStateManager = $contextStateManager; $this->eventDispatcher = $eventDispatcher; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + $this->psOrderStateMapper = $psOrderStateMapper; + $this->module = $module; + $this->checkOrderAmount = $checkOrderAmount; } /** * @param CreateOrderCommand $command * + * @return void + * + * @throws CartException * @throws OrderException + * @throws OrderNotFoundException * @throws PrestaShopDatabaseException * @throws PrestaShopException + * @throws PsCheckoutException */ public function handle(CreateOrderCommand $command) { - /** @var PaymentModule|false $paymentModule */ - $paymentModule = Module::getInstanceByName($command->getPaymentModuleName()); + /** @var PsCheckoutCart $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($command->getOrderPayPalId()->getValue()); + + $cart = new Cart($psCheckoutCart->getIdCart()); - if (false === $paymentModule) { - throw new OrderException(sprintf('Unable to get "%s" module instance.', $command->getPaymentModuleName()), OrderException::MODULE_INSTANCE_NOT_FOUND); + if (!Validate::isLoadedObject($cart)) { + throw new PsCheckoutException('Cart not found', PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + } + + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', (int) $cart->id); + + if ($orders->count()) { + return; } - $cart = new Cart($command->getCartId()->getValue()); + $fundingSource = $psCheckoutCart->getPaypalFundingSource(); + $transactionId = $orderStateId = $paidAmount = ''; + $capture = $command->getCapturePayPal(); + $currencyId = (int) $cart->id_currency; + + if ($capture) { + $transactionId = $capture['id']; + $paidAmount = $capture['status'] === 'COMPLETED' ? $capture['amount']['value'] : ''; + $currencyId = Currency::getIdByIsoCode($capture['amount']['currency_code'], (int) $cart->id_shop); + } + + if ($paidAmount) { + switch ($this->checkOrderAmount->checkAmount((string) $paidAmount, (string) $cart->getOrderTotal(true, \Cart::BOTH))) { + case CheckOrderAmount::ORDER_NOT_FULL_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID); + break; + case CheckOrderAmount::ORDER_FULL_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); + break; + case CheckOrderAmount::ORDER_TO_MUCH_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); + } + } else { + switch ($fundingSource) { + case 'card': + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); + break; + case 'paypal': + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); + break; + default: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); + } + } + + /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ + $fundingSourceTranslationProvider = $this->module->getService('ps_checkout.funding_source.translation'); + $this->setCartContext($this->contextStateManager, $cart); $extraVars = []; // Transaction identifier is needed only when an OrderPayment will be created // It requires a positive paid amount and an OrderState that's consider the associated order as validated. - if ($command->getPaidAmount() && $command->getTransactionId()) { - $extraVars['transaction_id'] = $command->getTransactionId(); + if ($paidAmount && $transactionId) { + $extraVars['transaction_id'] = $transactionId; } try { - $paymentModule->validateOrder( + $this->module->validateOrder( (int) $cart->id, - $command->getOrderStateId(), - $command->getPaidAmount(), - $command->getPaymentMethod(), + $orderStateId, + $paidAmount, + $fundingSourceTranslationProvider->getPaymentMethodName($fundingSource), null, $extraVars, - null, + $currencyId, false, $cart->secure_key ); @@ -102,29 +190,15 @@ public function handle(CreateOrderCommand $command) throw new OrderNotFoundException(sprintf('Failed to create order from Cart #%s.', var_export($cart->id, true)), OrderNotFoundException::NOT_FOUND); } - // It happens this returns null in case of override or weird modules - if ($paymentModule->currentOrder) { - $this->eventDispatcher->dispatch(new OrderCreatedEvent((int) $paymentModule->currentOrder, (int) $cart->id)); + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', (int) $cart->id); - return; + if (!$orders->count()) { + throw new OrderNotFoundException(sprintf('Unable to retrieve order identifier from Cart #%s.', var_export($cart->id, true)), OrderNotFoundException::NOT_FOUND); } - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $this->eventDispatcher->dispatch(new OrderCreatedEvent((int) Order::getIdByCartId($cart->id), (int) $cart->id)); - - return; + foreach ($orders as $order) { + $this->eventDispatcher->dispatch(new OrderCreatedEvent((int) $order->id, (int) $cart->id)); } - - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $this->eventDispatcher->dispatch(new OrderCreatedEvent((int) Order::getOrderByCartId($cart->id), (int) $cart->id)); - - return; - } - - throw new OrderNotFoundException(sprintf('Unable to retrieve order identifier from Cart #%s.', var_export($cart->id, true)), OrderNotFoundException::NOT_FOUND); } } diff --git a/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php b/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php index abf4cc6e2..741d9b6bd 100644 --- a/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php +++ b/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php @@ -27,7 +27,6 @@ use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; use PrestaShop\Module\PrestashopCheckout\Order\Event\OrderStatusUpdatedEvent; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\Order\State\Service\CheckOrderState; use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; class UpdateOrderStatusCommandHandler extends AbstractOrderCommandHandler @@ -37,18 +36,9 @@ class UpdateOrderStatusCommandHandler extends AbstractOrderCommandHandler */ private $eventDispatcher; - /** - * @var CheckOrderState - */ - private $checkOrderState; - - /** - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct(EventDispatcherInterface $eventDispatcher, CheckOrderState $checkOrderState) + public function __construct(EventDispatcherInterface $eventDispatcher) { $this->eventDispatcher = $eventDispatcher; - $this->checkOrderState = $checkOrderState; } /** @@ -61,27 +51,25 @@ public function __construct(EventDispatcherInterface $eventDispatcher, CheckOrde public function handle(UpdateOrderStatusCommand $command) { $order = $this->getOrder($command->getOrderId()); + $orderCurrentState = (int) $order->getCurrentState(); $orderState = $this->getOrderStateObject($command->getNewOrderStatusId()); + $orderStateId = (int) $orderState->id; - if ($order->getCurrentState() == $orderState->id) { - throw new OrderException(sprintf('The order #%d has already been assigned to OrderState #%d', $command->getOrderId()->getValue(), $orderState->id), OrderException::ORDER_HAS_ALREADY_THIS_STATUS); - } - - if (!$this->checkOrderState->isOrderStateTransitionAvailable($order->getCurrentState(), $orderState->id)) { - throw new OrderException(sprintf('Transition is not valid for order #%d Current order state %d, new order state %d', $command->getOrderId()->getValue(), $order->getCurrentState(), $orderState->id), OrderException::TRANSITION_NOT_ALLOWED); + if ($orderCurrentState === $orderStateId) { + throw new OrderException(sprintf('The order #%d has already been assigned to OrderState #%d', $command->getOrderId()->getValue(), $orderStateId), OrderException::ORDER_HAS_ALREADY_THIS_STATUS); } // Create new OrderHistory $history = new OrderHistory(); $history->id_order = $order->id; - $useExistingPayments = false; + $useExistingPayments = !$order->hasInvoice(); if (!$order->hasInvoice()) { $useExistingPayments = true; } try { - $history->changeIdOrderState((int) $orderState->id, $order, $useExistingPayments); + $history->changeIdOrderState($orderStateId, $order, $useExistingPayments); // Save all changes $historyAdded = $history->addWithemail(true); } catch (Exception $exception) { @@ -92,7 +80,7 @@ public function handle(UpdateOrderStatusCommand $command) throw new OrderException(sprintf('Failed to update status or sent email when changing OrderState #%d of Order #%d.', $command->getNewOrderStatusId()->getValue(), $command->getOrderId()->getValue()), OrderException::FAILED_UPDATE_ORDER_STATUS); } - $this->eventDispatcher->dispatch(new OrderStatusUpdatedEvent((int) $orderState->id)); + $this->eventDispatcher->dispatch(new OrderStatusUpdatedEvent($orderStateId)); } /** diff --git a/src/Order/EventSubscriber/OrderEventSubscriber.php b/src/Order/EventSubscriber/OrderEventSubscriber.php index d18356cfe..2d4f2f4e3 100644 --- a/src/Order/EventSubscriber/OrderEventSubscriber.php +++ b/src/Order/EventSubscriber/OrderEventSubscriber.php @@ -27,6 +27,7 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopException; use Ps_checkout; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -69,7 +70,7 @@ public static function getSubscribedEvents() * * @return void * - * @throws \PrestaShopException + * @throws PrestaShopException * @throws OrderException * @throws PayPalOrderException * @throws OrderStateException diff --git a/src/Order/Payment/Query/GetOrderPaymentQuery.php b/src/Order/Payment/Query/GetOrderPaymentQuery.php deleted file mode 100644 index a9028e7d7..000000000 --- a/src/Order/Payment/Query/GetOrderPaymentQuery.php +++ /dev/null @@ -1,62 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\Payment\Query; - -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\ValueObject\PayPalCaptureId; - -class GetOrderPaymentQuery -{ - /** - * @var OrderId - */ - private $orderId; - - /** - * @var PayPalCaptureId - */ - private $transactionId; - - /** - * @param string $orderId - * @param string $transactionId - * - * @throws PayPalCaptureException - * @throws OrderException - */ - public function __construct($orderId, $transactionId) - { - $this->orderId = new OrderId($orderId); - $this->transactionId = new PayPalCaptureId($transactionId); - } - - public function getOrderId() - { - return $this->orderId; - } - - public function getTransactionId() - { - return $this->transactionId; - } -} diff --git a/src/Order/Payment/Query/GetOrderPaymentQueryResult.php b/src/Order/Payment/Query/GetOrderPaymentQueryResult.php deleted file mode 100644 index 5bb488cc4..000000000 --- a/src/Order/Payment/Query/GetOrderPaymentQueryResult.php +++ /dev/null @@ -1,95 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\Payment\Query; - -class GetOrderPaymentQueryResult -{ - /** @var string */ - private $transactionId; - - /** @var string */ - private $orderReference; - - /** @var string */ - private $amount; - - /** @var string */ - private $paymentMethod; - - /** @var string */ - private $date; - - /** - * @param string $transactionId - * @param string $orderReference - * @param string $amount - * @param string $paymentMethod - * @param string $date - */ - public function __construct($transactionId, $orderReference, $amount, $paymentMethod, $date) - { - $this->transactionId = $transactionId; - $this->orderReference = $orderReference; - $this->amount = $amount; - $this->paymentMethod = $paymentMethod; - $this->date = $date; - } - - /** - * @return string - */ - public function getTransactionId() - { - return $this->transactionId; - } - - /** - * @return string - */ - public function getOrderReference() - { - return $this->orderReference; - } - - /** - * @return string - */ - public function getAmount() - { - return $this->amount; - } - - /** - * @return string - */ - public function getPaymentMethod() - { - return $this->paymentMethod; - } - - /** - * @return string - */ - public function getDate() - { - return $this->date; - } -} diff --git a/src/Order/Payment/QueryHandler/GetOrderPaymentQueryHandler.php b/src/Order/Payment/QueryHandler/GetOrderPaymentQueryHandler.php deleted file mode 100644 index 079929f9f..000000000 --- a/src/Order/Payment/QueryHandler/GetOrderPaymentQueryHandler.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\Payment\QueryHandler; - -use Order; -use OrderPayment; -use PrestaShop\Module\PrestashopCheckout\Order\Payment\Exception\OrderPaymentException; -use PrestaShop\Module\PrestashopCheckout\Order\Payment\Query\GetOrderPaymentQuery; -use PrestaShop\Module\PrestashopCheckout\Order\Payment\Query\GetOrderPaymentQueryResult; -use PrestaShopException; - -class GetOrderPaymentQueryHandler -{ - /** - * @param GetOrderPaymentQuery $query - * - * @return GetOrderPaymentQueryResult - * - * @throws PrestaShopException - * @throws OrderPaymentException - */ - public function handle(GetOrderPaymentQuery $query) - { - $order = new Order($query->getOrderId()->getValue()); - /** @var OrderPayment[] $orderPayments */ - $orderPayments = $order->getOrderPaymentCollection(); - - if (empty($orderPayments)) { - throw new OrderPaymentException('No PrestaShop OrderPayment associated to this PayPal capture id at this time.', OrderPaymentException::INVALID_ID); - } - - foreach ($orderPayments as $orderPayment) { - if ($orderPayment->transaction_id === $query->getTransactionId()->getValue()) { - return new GetOrderPaymentQueryResult( - $orderPayment->transaction_id, - $orderPayment->order_reference, - $orderPayment->amount, - $orderPayment->payment_method, - $orderPayment->date_add - ); - } - } - - throw new OrderPaymentException('No PrestaShop OrderPayment associated to this PayPal capture id at this time.', OrderPaymentException::INVALID_ID); - } -} diff --git a/src/Order/Query/GetOrderForPaymentCompletedQuery.php b/src/Order/Query/GetOrderForPaymentCompletedQuery.php index f58727238..519bbd4ab 100644 --- a/src/Order/Query/GetOrderForPaymentCompletedQuery.php +++ b/src/Order/Query/GetOrderForPaymentCompletedQuery.php @@ -20,31 +20,46 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\ValueObject\PayPalCaptureId; class GetOrderForPaymentCompletedQuery { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @param int $cartId + * @var PayPalCaptureId + */ + private $capturePayPalId; + + /** + * @param string $orderPayPalId + * @param string $capturePayPalId * - * @throws CartException + * @throws PayPalOrderException + * @throws PayPalCaptureException */ - public function __construct($cartId) + public function __construct($orderPayPalId, $capturePayPalId) { - $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->capturePayPalId = new PayPalCaptureId($capturePayPalId); } /** - * @return CartId + * @return PayPalOrderId */ - public function getCartId() + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + public function getCapturePayPalId() { - return $this->cartId; + return $this->capturePayPalId; } } diff --git a/src/Order/Query/GetOrderForPaymentCompletedQueryResult.php b/src/Order/Query/GetOrderForPaymentCompletedQueryResult.php index 12122a564..166a7889a 100644 --- a/src/Order/Query/GetOrderForPaymentCompletedQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentCompletedQueryResult.php @@ -20,17 +20,29 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + class GetOrderForPaymentCompletedQueryResult { /** - * @var int + * @var OrderId */ - private $id; + private $orderId; /** - * @var int + * @var CartId + */ + private $cartId; + + /** + * @var OrderStateId */ - private $currentState; + private $currentStateId; /** * @var bool @@ -53,43 +65,74 @@ class GetOrderForPaymentCompletedQueryResult private $currencyId; /** - * @param int $id - * @param int $currentState + * @var string + */ + private $paymentMethod; + + /** + * @var int|null + */ + private $orderPaymentId; + + /** + * @param int $orderId + * @param int $cartId + * @param int $currentStateId * @param bool $hasBeenPaid * @param string $totalAmount * @param string $totalAmountPaid * @param int $currencyId + * @param string $paymentMethod + * @param int|null $orderPaymentId + * + * @throws OrderException + * @throws CartException + * @throws OrderStateException */ public function __construct( - $id, - $currentState, + $orderId, + $cartId, + $currentStateId, $hasBeenPaid, $totalAmount, $totalAmountPaid, - $currencyId + $currencyId, + $paymentMethod, + $orderPaymentId = null ) { - $this->id = $id; - $this->currentState = $currentState; + $this->orderId = new OrderId($orderId); + $this->cartId = new CartId($cartId); + $this->currentStateId = new OrderStateId($currentStateId); $this->hasBeenPaid = $hasBeenPaid; $this->totalAmount = $totalAmount; $this->totalAmountPaid = $totalAmountPaid; $this->currencyId = $currencyId; + $this->paymentMethod = $paymentMethod; + $this->orderPaymentId = $orderPaymentId; } /** - * @return int + * @return OrderId */ - public function getId() + public function getOrderId() { - return $this->id; + return $this->orderId; } /** - * @return int + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return OrderStateId */ public function getCurrentStateId() { - return $this->currentState; + return $this->currentStateId; } /** @@ -123,4 +166,20 @@ public function getCurrencyId() { return $this->currencyId; } + + /** + * @return string + */ + public function getPaymentMethod() + { + return $this->paymentMethod; + } + + /** + * @return int|null + */ + public function getOrderPaymentId() + { + return $this->orderPaymentId; + } } diff --git a/src/Order/Query/GetOrderForPaymentDeniedQuery.php b/src/Order/Query/GetOrderForPaymentDeniedQuery.php index edfc09c4c..f4b00b56a 100644 --- a/src/Order/Query/GetOrderForPaymentDeniedQuery.php +++ b/src/Order/Query/GetOrderForPaymentDeniedQuery.php @@ -20,31 +20,31 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; class GetOrderForPaymentDeniedQuery { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @param int $cartId + * @param string $orderPayPalId * - * @throws CartException + * @throws PayPalOrderException */ - public function __construct($cartId) + public function __construct($orderPayPalId) { - $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** - * @return CartId + * @return PayPalOrderId */ - public function getCartId() + public function getOrderPayPalId() { - return $this->cartId; + return $this->orderPayPalId; } } diff --git a/src/Order/Query/GetOrderForPaymentDeniedQueryResult.php b/src/Order/Query/GetOrderForPaymentDeniedQueryResult.php index 932c27e16..76a343cc3 100644 --- a/src/Order/Query/GetOrderForPaymentDeniedQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentDeniedQueryResult.php @@ -20,17 +20,22 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + class GetOrderForPaymentDeniedQueryResult { /** - * @var int + * @var OrderId */ - private $id; + private $orderId; /** - * @var int + * @var OrderStateId */ - private $currentState; + private $currentStateId; /** * @var bool @@ -38,34 +43,37 @@ class GetOrderForPaymentDeniedQueryResult private $hasBeenError; /** - * @param int $id + * @param int $orderId * @param int $currentState * @param bool $hasBeenError + * + * @throws OrderException + * @throws OrderStateException */ public function __construct( - $id, + $orderId, $currentState, $hasBeenError ) { - $this->id = $id; - $this->currentState = $currentState; + $this->orderId = new OrderId($orderId); + $this->currentStateId = new OrderStateId($currentState); $this->hasBeenError = $hasBeenError; } /** - * @return int + * @return OrderId */ - public function getId() + public function getOrderId() { - return $this->id; + return $this->orderId; } /** - * @return int + * @return OrderStateId */ public function getCurrentStateId() { - return $this->currentState; + return $this->currentStateId; } /** diff --git a/src/Order/Query/GetOrderForPaymentPendingQuery.php b/src/Order/Query/GetOrderForPaymentPendingQuery.php index 6d9c54e34..a79c09a55 100644 --- a/src/Order/Query/GetOrderForPaymentPendingQuery.php +++ b/src/Order/Query/GetOrderForPaymentPendingQuery.php @@ -20,31 +20,31 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; class GetOrderForPaymentPendingQuery { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @param int $cartId + * @param string $orderPayPalId * - * @throws CartException + * @throws PayPalOrderException */ - public function __construct($cartId) + public function __construct($orderPayPalId) { - $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** - * @return CartId + * @return PayPalOrderId */ - public function getCartId() + public function getOrderPayPalId() { - return $this->cartId; + return $this->orderPayPalId; } } diff --git a/src/Order/Query/GetOrderForPaymentPendingQueryResult.php b/src/Order/Query/GetOrderForPaymentPendingQueryResult.php index d9f80c0d4..f50c016a9 100644 --- a/src/Order/Query/GetOrderForPaymentPendingQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentPendingQueryResult.php @@ -21,17 +21,22 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + class GetOrderForPaymentPendingQueryResult { /** - * @var int + * @var OrderId */ - private $id; + private $orderId; /** - * @var int + * @var OrderStateId */ - private $currentState; + private $currentStateId; /** * @var bool @@ -39,34 +44,45 @@ class GetOrderForPaymentPendingQueryResult private $isInPending; /** - * @param int $id - * @param int $currentState + * @var string + */ + private $paymentMethod; + + /** + * @param int $orderId + * @param int $currentStateId * @param bool $isInPending + * @param string $paymentMethod + * + * @throws OrderException + * @throws OrderStateException */ public function __construct( - $id, - $currentState, - $isInPending + $orderId, + $currentStateId, + $isInPending, + $paymentMethod ) { - $this->id = $id; - $this->currentState = $currentState; + $this->orderId = new OrderId($orderId); + $this->currentStateId = new OrderStateId($currentStateId); $this->isInPending = $isInPending; + $this->paymentMethod = $paymentMethod; } /** - * @return int + * @return OrderId */ - public function getId() + public function getOrderId() { - return $this->id; + return $this->orderId; } /** - * @return int + * @return OrderStateId */ public function getCurrentStateId() { - return $this->currentState; + return $this->currentStateId; } /** @@ -76,4 +92,12 @@ public function isInPending() { return $this->isInPending; } + + /** + * @return string + */ + public function getPaymentMethod() + { + return $this->paymentMethod; + } } diff --git a/src/Order/Query/GetOrderForPaymentRefundedQuery.php b/src/Order/Query/GetOrderForPaymentRefundedQuery.php index c2e7d4aec..ea71b8d15 100644 --- a/src/Order/Query/GetOrderForPaymentRefundedQuery.php +++ b/src/Order/Query/GetOrderForPaymentRefundedQuery.php @@ -20,31 +20,31 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; class GetOrderForPaymentRefundedQuery { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @param int $cartId + * @param string $orderPayPalId * - * @throws CartException + * @throws PayPalOrderException */ - public function __construct($cartId) + public function __construct($orderPayPalId) { - $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** - * @return CartId + * @return PayPalOrderId */ - public function getCartId() + public function getOrderPayPalId() { - return $this->cartId; + return $this->orderPayPalId; } } diff --git a/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php b/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php index 16a5cd357..b3cff68f1 100644 --- a/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentRefundedQueryResult.php @@ -20,17 +20,22 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + class GetOrderForPaymentRefundedQueryResult { /** - * @var int + * @var OrderId */ - private $id; + private $orderId; /** - * @var int + * @var OrderStateId */ - private $currentState; + private $currentStateId; /** * @var bool @@ -58,25 +63,28 @@ class GetOrderForPaymentRefundedQueryResult private $currencyId; /** - * @param int $id - * @param int $currentState + * @param int $orderId + * @param int $currentStateId * @param bool $hasBeenPaid * @param bool $hasBeenTotallyRefund * @param string $totalAmount * @param string $totalRefund * @param int $currencyId + * + * @throws OrderException + * @throws OrderStateException */ public function __construct( - $id, - $currentState, + $orderId, + $currentStateId, $hasBeenPaid, $hasBeenTotallyRefund, $totalAmount, $totalRefund, $currencyId ) { - $this->id = $id; - $this->currentState = $currentState; + $this->orderId = new OrderId($orderId); + $this->currentStateId = new OrderStateId($currentStateId); $this->hasBeenPaid = $hasBeenPaid; $this->hasBeenTotallyRefund = $hasBeenTotallyRefund; $this->totalAmount = $totalAmount; @@ -85,19 +93,19 @@ public function __construct( } /** - * @return int + * @return OrderId */ - public function getId() + public function getOrderId() { - return $this->id; + return $this->orderId; } /** - * @return int + * @return OrderStateId */ public function getCurrentStateId() { - return $this->currentState; + return $this->currentStateId; } /** diff --git a/src/Order/Query/GetOrderForPaymentReversedQuery.php b/src/Order/Query/GetOrderForPaymentReversedQuery.php index 838ffadd7..2e52e4374 100644 --- a/src/Order/Query/GetOrderForPaymentReversedQuery.php +++ b/src/Order/Query/GetOrderForPaymentReversedQuery.php @@ -20,31 +20,46 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\ValueObject\PayPalCaptureId; class GetOrderForPaymentReversedQuery { /** - * @var CartId + * @var PayPalOrderId */ - private $cartId; + private $orderPayPalId; /** - * @param int $cartId + * @var PayPalCaptureId + */ + private $capturePayPalId; + + /** + * @param string $orderPayPalId + * @param string $capturePayPalId * - * @throws CartException + * @throws PayPalOrderException + * @throws PayPalCaptureException */ - public function __construct($cartId) + public function __construct($orderPayPalId, $capturePayPalId) { - $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->capturePayPalId = new PayPalCaptureId($capturePayPalId); } /** - * @return CartId + * @return PayPalOrderId */ - public function getCartId() + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + public function getCapturePayPalId() { - return $this->cartId; + return $this->capturePayPalId; } } diff --git a/src/Order/Query/GetOrderForPaymentReversedQueryResult.php b/src/Order/Query/GetOrderForPaymentReversedQueryResult.php index e5f00ab65..874edfdf1 100644 --- a/src/Order/Query/GetOrderForPaymentReversedQueryResult.php +++ b/src/Order/Query/GetOrderForPaymentReversedQueryResult.php @@ -20,17 +20,22 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\Query; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + class GetOrderForPaymentReversedQueryResult { /** - * @var int + * @var OrderId */ - private $id; + private $orderId; /** - * @var int + * @var OrderStateId */ - private $currentState; + private $currentStateId; /** * @var bool @@ -43,37 +48,40 @@ class GetOrderForPaymentReversedQueryResult private $hasBeenTotallyRefund; /** - * @param int $id - * @param int $currentState + * @param int $orderId + * @param int $currentStateId * @param bool $hasBeenPaid * @param bool $hasBeenTotallyRefund + * + * @throws OrderException + * @throws OrderStateException */ public function __construct( - $id, - $currentState, + $orderId, + $currentStateId, $hasBeenPaid, $hasBeenTotallyRefund ) { - $this->id = $id; - $this->currentState = $currentState; + $this->orderId = new OrderId($orderId); + $this->currentStateId = new OrderStateId($currentStateId); $this->hasBeenPaid = $hasBeenPaid; $this->hasBeenTotallyRefund = $hasBeenTotallyRefund; } /** - * @return int + * @return OrderId */ - public function getId() + public function getOrderId() { - return $this->id; + return $this->orderId; } /** - * @return int + * @return OrderStateId */ public function getCurrentStateId() { - return $this->currentState; + return $this->currentStateId; } /** diff --git a/src/Order/Query/GetOrderQuery.php b/src/Order/Query/GetOrderQuery.php deleted file mode 100644 index 8736acdf5..000000000 --- a/src/Order/Query/GetOrderQuery.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\Query; - -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; - -class GetOrderQuery -{ - /** - * @var CartId - */ - private $cartId; - - /** - * @param int $cartId - * - * @throws CartException - */ - public function __construct($cartId) - { - $this->cartId = new CartId($cartId); - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } -} diff --git a/src/Order/Query/GetOrderQueryResult.php b/src/Order/Query/GetOrderQueryResult.php deleted file mode 100644 index cb69fa5c9..000000000 --- a/src/Order/Query/GetOrderQueryResult.php +++ /dev/null @@ -1,205 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\Query; - -class GetOrderQueryResult -{ - /** - * @var int - */ - private $id; - - /** - * @var int - */ - private $currentState; - - /** - * @var bool - */ - private $hasBeenPaid; - - /** - * @var bool - */ - private $hasBeenShipped; - - /** - * @var bool - */ - private $hasBeenDelivered; - - /** - * @var bool - */ - private $hasBeenTotallyRefund; - - /** - * @var bool - */ - private $isInPreparation; - - /** - * @var bool - */ - private $isInPending; - - /** - * @var string - */ - private $totalAmount; - - /** - * @var string - */ - private $totalAmountPaid; - - /** - * @var int - */ - private $currencyId; - - /** - * @param int $id - * @param int $currentState - * @param bool $hasBeenPaid - * @param bool $hasBeenShipped - * @param bool $hasBeenDelivered - * @param bool $hasBeenTotallyRefund - * @param bool $isInPreparation - * @param bool $isInPending - * @param string $totalAmount - * @param string $totalAmountPaid - */ - public function __construct( - $id, - $currentState, - $hasBeenPaid, - $hasBeenShipped, - $hasBeenDelivered, - $hasBeenTotallyRefund, - $isInPreparation, - $isInPending, - $totalAmount, - $totalAmountPaid, - $currencyId - ) { - $this->id = $id; - $this->currentState = $currentState; - $this->hasBeenPaid = $hasBeenPaid; - $this->hasBeenShipped = $hasBeenShipped; - $this->hasBeenDelivered = $hasBeenDelivered; - $this->hasBeenTotallyRefund = $hasBeenTotallyRefund; - $this->isInPreparation = $isInPreparation; - $this->isInPending = $isInPending; - $this->totalAmount = $totalAmount; - $this->totalAmountPaid = $totalAmountPaid; - $this->currencyId = $currencyId; - } - - /** - * @return int - */ - public function getId() - { - return $this->id; - } - - /** - * @return int - */ - public function getCurrentStateId() - { - return $this->currentState; - } - - /** - * @return bool - */ - public function hasBeenPaid() - { - return $this->hasBeenPaid; - } - - /** - * @return bool - */ - public function hasBeenShipped() - { - return $this->hasBeenShipped; - } - - /** - * @return bool - */ - public function hasBeenDelivered() - { - return $this->hasBeenDelivered; - } - - /** - * @return bool - */ - public function hasBeenTotallyRefund() - { - return $this->hasBeenTotallyRefund; - } - - /** - * @return bool - */ - public function isInPreparation() - { - return $this->isInPreparation; - } - - /** - * @return string - */ - public function getTotalAmount() - { - return $this->totalAmount; - } - - /** - * @return string - */ - public function getTotalAmountPaid() - { - return $this->totalAmountPaid; - } - - /** - * @return int - */ - public function getCurrencyId() - { - return $this->currencyId; - } - - /** - * @return bool - */ - public function isInPending() - { - return $this->isInPending; - } -} diff --git a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php index 8ce32c195..b394f2f28 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php @@ -21,28 +21,29 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; +use Order; +use OrderPayment; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQueryResult; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; -use Psr\SimpleCache\CacheInterface; +use Validate; class GetOrderForPaymentCompletedQueryHandler { /** - * @var CacheInterface + * @var PsCheckoutCartRepository */ - private $orderPrestaShopcache; + private $psCheckoutCartRepository; - /** - * @param CacheInterface $orderPrestaShopcache - */ - public function __construct(CacheInterface $orderPrestaShopcache) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->orderPrestaShopcache = $orderPrestaShopcache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } /** @@ -56,42 +57,48 @@ public function __construct(CacheInterface $orderPrestaShopcache) */ public function handle(GetOrderForPaymentCompletedQuery $query) { - /** @var GetOrderForPaymentCompletedQueryResult $result */ - $result = $this->orderPrestaShopcache->get(CacheSettings::CART_ID . $query->getCartId()->getValue()); - if (!empty($result) && $result instanceof GetOrderForPaymentCompletedQueryResult) { - return $result; + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); + + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); } - $orderId = null; - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); - } elseif (method_exists(\Order::class, 'getOrderByCartId')) { // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); + + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + /** @var Order $order */ + $order = $orders->getFirst(); + + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $order = new \Order($orderId); + /** @var OrderPayment[] $orderPayments */ + $orderPayments = $order->getOrderPaymentCollection(); + $orderPaymentId = null; - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!empty($orderPayments)) { + foreach ($orderPayments as $orderPayment) { + if ($orderPayment->transaction_id === $query->getCapturePayPalId()->getValue()) { + $orderPaymentId = (int) $orderPayment->id; + } + } } - $result = new GetOrderForPaymentCompletedQueryResult( + + return new GetOrderForPaymentCompletedQueryResult( (int) $order->id, + (int) $order->id_cart, (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), (string) $order->getTotalProductsWithTaxes(), (string) $order->getTotalPaid(), - (int) $order->id_currency + (int) $order->id_currency, + $psCheckoutCart->getPaypalFundingSource(), + $orderPaymentId ); - - $this->orderPrestaShopcache->set(CacheSettings::CART_ID . $query->getCartId()->getValue(), $result); - - return $result; } } diff --git a/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php index f81f1399f..ea66ca0d7 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php @@ -21,29 +21,31 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; +use Configuration; +use Order; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; -use Psr\SimpleCache\CacheInterface; +use PsCheckoutCart; +use Validate; class GetOrderForPaymentDeniedQueryHandler { /** - * @var CacheInterface + * @var PsCheckoutCartRepository */ - private $orderPrestaShopCache; + private $psCheckoutCartRepository; - /** - * @param CacheInterface $orderPrestaShopCache - */ - public function __construct(CacheInterface $orderPrestaShopCache) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->orderPrestaShopCache = $orderPrestaShopCache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } /** @@ -57,55 +59,42 @@ public function __construct(CacheInterface $orderPrestaShopCache) */ public function handle(GetOrderForPaymentDeniedQuery $query) { - /** @var GetOrderForPaymentDeniedQueryResult $result */ - $result = $this->orderPrestaShopCache->get(CacheSettings::CART_ID . $query->getCartId()->getValue()); - if (!empty($result) && $result instanceof GetOrderForPaymentDeniedQueryResult) { - return $result; - } - - $orderId = null; + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); } - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(\Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); - } + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $order = new \Order($orderId); + /** @var Order $order */ + $order = $orders->getFirst(); - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $result = new GetOrderForPaymentDeniedQueryResult( + return new GetOrderForPaymentDeniedQueryResult( (int) $order->id, (int) $order->getCurrentState(), $this->hasBeenError($order) ); - - $this->orderPrestaShopCache->set(CacheSettings::CART_ID . $query->getCartId()->getValue(), $result); - - return $result; } /** - * @param \Order $order + * @param Order $order * * @return bool */ - private function hasBeenError(\Order $order) + private function hasBeenError(Order $order) { - return $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::PAYMENT_ERROR)) - || $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED)); + return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PAYMENT_ERROR))) + || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED))); } } diff --git a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php index 91916d790..89875d65d 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php @@ -21,29 +21,31 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; +use Configuration; +use Order; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; -use Psr\SimpleCache\CacheInterface; +use PsCheckoutCart; +use Validate; class GetOrderForPaymentPendingQueryHandler { /** - * @var CacheInterface + * @var PsCheckoutCartRepository */ - private $orderPrestaShopCache; + private $psCheckoutCartRepository; - /** - * @param CacheInterface $orderPrestaShopCache - */ - public function __construct(CacheInterface $orderPrestaShopCache) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->orderPrestaShopCache = $orderPrestaShopCache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } /** @@ -57,56 +59,44 @@ public function __construct(CacheInterface $orderPrestaShopCache) */ public function handle(GetOrderForPaymentPendingQuery $query) { - /** @var GetOrderForPaymentPendingQueryResult $result */ - $result = $this->orderPrestaShopCache->get(CacheSettings::CART_ID . $query->getCartId()->getValue()); - if (!empty($result) && $result instanceof GetOrderForPaymentPendingQueryResult) { - return $result; - } - - $orderId = null; + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); } - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(\Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); - } + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $order = new \Order($orderId); + /** @var Order $order */ + $order = $orders->getFirst(); - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $result = new GetOrderForPaymentPendingQueryResult( + return new GetOrderForPaymentPendingQueryResult( (int) $order->id, (int) $order->getCurrentState(), - $this->isInPending($order) + $this->isInPending($order), + $psCheckoutCart->getPaypalFundingSource() ); - - $this->orderPrestaShopCache->set(CacheSettings::CART_ID . $query->getCartId()->getValue(), $result); - - return $result; } /** - * @param \Order $order + * @param Order $order * * @return bool */ - private function isInPending(\Order $order) + private function isInPending(Order $order) { - return $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT)) - || $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT)) - || $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT)); + return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT))) + || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT))) + || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT))); } } diff --git a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php index 5117d44e8..b92650523 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php @@ -21,28 +21,30 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; +use Order; +use OrderSlipCore; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; -use Psr\SimpleCache\CacheInterface; +use PsCheckoutCart; +use Validate; class GetOrderForPaymentRefundedQueryHandler { /** - * @var CacheInterface + * @var PsCheckoutCartRepository */ - private $orderPrestaShopCache; + private $psCheckoutCartRepository; - /** - * @param CacheInterface $orderPrestaShopCache - */ - public function __construct(CacheInterface $orderPrestaShopCache) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->orderPrestaShopCache = $orderPrestaShopCache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } /** @@ -56,39 +58,30 @@ public function __construct(CacheInterface $orderPrestaShopCache) */ public function handle(GetOrderForPaymentRefundedQuery $query) { - /** @var GetOrderForPaymentRefundedQueryResult $result */ - $result = $this->orderPrestaShopCache->get(CacheSettings::CART_ID . $query->getCartId()->getValue()); - if (!empty($result) && $result instanceof GetOrderForPaymentRefundedQueryResult) { - return $result; - } - - $orderId = null; + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); } - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(\Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); - } + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $order = new \Order($orderId); + /** @var Order $order */ + $order = $orders->getFirst(); - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } $totalRefund = $this->getTotalRefund($order); - $result = new GetOrderForPaymentRefundedQueryResult( + return new GetOrderForPaymentRefundedQueryResult( (int) $order->id, (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), @@ -97,10 +90,6 @@ public function handle(GetOrderForPaymentRefundedQuery $query) (string) $totalRefund, (int) $order->id_currency ); - - $this->orderPrestaShopCache->set(CacheSettings::CART_ID . $query->getCartId()->getValue(), $result); - - return $result; } private function hasBeenTotallyRefunded($refundAmount, $order) @@ -108,12 +97,13 @@ private function hasBeenTotallyRefunded($refundAmount, $order) return $refundAmount >= $order->total_paid; } - private function getTotalRefund(\Order $order) + private function getTotalRefund(Order $order) { $orderSlips = $order->getOrderSlipsCollection(); $refundAmount = 0; - /** @var \OrderSlipCore $orderSlip */ + foreach ($orderSlips as $orderSlip) { + /* @var OrderSlipCore $orderSlip */ $refundAmount += $orderSlip->amount + $orderSlip->shipping_cost_amount; } diff --git a/src/Order/QueryHandler/GetOrderForPaymentReversedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentReversedQueryHandler.php index 53f11cfc2..7edb911cd 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentReversedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentReversedQueryHandler.php @@ -21,29 +21,28 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; +use Order; +use OrderSlip; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQueryResult; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; use PrestaShopDatabaseException; use PrestaShopException; -use Psr\SimpleCache\CacheInterface; class GetOrderForPaymentReversedQueryHandler { /** - * @var CacheInterface + * @var PsCheckoutCartRepository */ - private $orderPrestaShopCache; + private $psCheckoutCartRepository; - /** - * @param CacheInterface $orderPrestaShopCache - */ - public function __construct(CacheInterface $orderPrestaShopCache) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->orderPrestaShopCache = $orderPrestaShopCache; + $this->psCheckoutCartRepository = $psCheckoutCartRepository; } /** @@ -57,55 +56,45 @@ public function __construct(CacheInterface $orderPrestaShopCache) */ public function handle(GetOrderForPaymentReversedQuery $query) { - /** @var GetOrderForPaymentPendingQueryResult $result */ - $result = $this->orderPrestaShopCache->get(CacheSettings::CART_ID . $query->getCartId()->getValue()); - if (!empty($result) && $result instanceof GetOrderForPaymentPendingQueryResult) { - return $result; - } - - $orderId = null; + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); } - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(\Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); - } + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $order = new \Order($orderId); + /** @var Order $order */ + $order = $orders->getFirst(); - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $result = new GetOrderForPaymentReversedQueryResult( + return new GetOrderForPaymentReversedQueryResult( (int) $order->id, (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), $this->hasBeenTotallyRefunded($order) ); - - $this->orderPrestaShopCache->set(CacheSettings::CART_ID . $query->getCartId()->getValue(), $result); - - return $result; } - private function hasBeenTotallyRefunded(\Order $order) + private function hasBeenTotallyRefunded(Order $order) { $orderSlips = $order->getOrderSlipsCollection(); $refundAmount = 0; - /** @var \OrderSlipCore $orderSlip */ - foreach ($orderSlips as $orderSlip) { - $refundAmount += $orderSlip->amount + $orderSlip->shipping_cost_amount; + + if (!empty($orderSlips)) { + foreach ($orderSlips as $orderSlip) { + /* @var OrderSlip $orderSlip */ + $refundAmount += $orderSlip->amount + $orderSlip->shipping_cost_amount; + } } return $refundAmount >= $order->total_paid; diff --git a/src/Order/QueryHandler/GetOrderQueryHandler.php b/src/Order/QueryHandler/GetOrderQueryHandler.php deleted file mode 100644 index 8ef30c3c9..000000000 --- a/src/Order/QueryHandler/GetOrderQueryHandler.php +++ /dev/null @@ -1,104 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; - -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderQuery; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderQueryResult; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; - -class GetOrderQueryHandler -{ - /** - * @param GetOrderQuery $query - * - * @return GetOrderQueryResult - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - * @throws \PrestaShopException - */ - public function handle(GetOrderQuery $query) - { - $orderId = null; - - // Order::getIdByCartId() is available since PrestaShop 1.7.1.0 - if (method_exists(\Order::class, 'getIdByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getIdByCartId($query->getCartId()->getValue()); - } - - // Order::getIdByCartId() is available before PrestaShop 1.7.1.0, removed since PrestaShop 8.0.0 - if (method_exists(\Order::class, 'getOrderByCartId')) { - // @phpstan-ignore-next-line - $orderId = (int) \Order::getOrderByCartId($query->getCartId()->getValue()); - } - - if (!$orderId) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); - } - - $order = new \Order($orderId); - - if (!\Validate::isLoadedObject($order)) { - throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.', OrderNotFoundException::NOT_FOUND); - } - - return new GetOrderQueryResult( - (int) $order->id, - (int) $order->getCurrentState(), - (bool) $order->hasBeenPaid(), - (bool) $order->hasBeenShipped(), - (bool) $order->hasBeenDelivered(), - $this->hasBeenTotallyRefunded($order), - (bool) $order->isInPreparation(), - $this->isInPending($order), - (string) $order->getTotalProductsWithTaxes(), /* @phpstan-ignore-line */ - (string) $order->getTotalPaid(), - (int) $order->id_currency - ); - } - - /** - * @param \Order $order - * - * @return bool - */ - private function isInPending(\Order $order) - { - return $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT)) - || $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT)) - || $order->getHistory($order->id_lang, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT)); - } - - private function hasBeenTotallyRefunded(\Order $order) - { - $orderSlips = $order->getOrderSlipsCollection(); - $refundAmount = 0; - /** @var \OrderSlipCore $orderSlip */ - foreach ($orderSlips as $orderSlip) { - $refundAmount += $orderSlip->amount + $orderSlip->shipping_cost_amount; - } - - return $refundAmount >= $order->total_paid; - } -} diff --git a/src/Order/Service/CheckOrderAmount.php b/src/Order/Service/CheckOrderAmount.php index de6b6db98..91cf46ec9 100644 --- a/src/Order/Service/CheckOrderAmount.php +++ b/src/Order/Service/CheckOrderAmount.php @@ -45,17 +45,17 @@ class CheckOrderAmount public function checkAmount($totalAmount, $totalAmountPaid) { if (!is_string($totalAmount)) { - throw new OrderException(sprintf('Type of totalAmount (%s) is not string', gettype($totalAmount)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); + throw new OrderException(sprintf('Type of totalAmount (%s) is not string', var_export($totalAmount, true)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); } if (!is_numeric($totalAmount)) { - throw new OrderException(sprintf('Type of totalAmount (%s) is not numeric', $totalAmount), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); + throw new OrderException(sprintf('Type of totalAmount (%s) is not numeric', var_export($totalAmount, true)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); } if (!is_string($totalAmountPaid)) { - throw new OrderException(sprintf('Type of totalAmountPaid (%s) is not string', gettype($totalAmountPaid)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); + throw new OrderException(sprintf('Type of totalAmountPaid (%s) is not string', var_export($totalAmountPaid, true)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); } if (!is_numeric($totalAmountPaid)) { - throw new OrderException(sprintf('Type of totalAmountPaid (%s) is not numeric', $totalAmountPaid), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); + throw new OrderException(sprintf('Type of totalAmountPaid (%s) is not numeric', var_export($totalAmountPaid, true)), OrderException::ORDER_CHECK_AMOUNT_BAD_PARAMETER); } if ((float) $totalAmount == (float) $totalAmountPaid) { diff --git a/src/Order/State/Query/GetOrderStateConfigurationQueryResult.php b/src/Order/State/Query/GetOrderStateConfigurationQueryResult.php deleted file mode 100644 index 09ebcd63e..000000000 --- a/src/Order/State/Query/GetOrderStateConfigurationQueryResult.php +++ /dev/null @@ -1,258 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\Query; - -use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateConfiguration; - -class GetOrderStateConfigurationQueryResult -{ - /** - * @var OrderStateConfiguration - */ - private $canceledState; - - /** - * @var OrderStateConfiguration - */ - private $paymentErrorState; - - /** - * @var OrderStateConfiguration - */ - private $outOfStockUnpaidState; - - /** - * @var OrderStateConfiguration - */ - private $outOfStockPaidState; - - /** - * @var OrderStateConfiguration - */ - private $paymentAcceptedState; - - /** - * @var OrderStateConfiguration - */ - private $refundedState; - - /** - * @var OrderStateConfiguration - */ - private $authorizedState; - - /** - * @var OrderStateConfiguration - */ - private $partiallyPaidState; - - /** - * @var OrderStateConfiguration - */ - private $partiallyRefundedState; - - /** - * @var OrderStateConfiguration - */ - private $waitingCaptureState; - - /** - * @var OrderStateConfiguration - */ - private $waitingPaymentCardState; - - /** - * @var OrderStateConfiguration - */ - private $waitingPaymentPayPalState; - - /** - * @var OrderStateConfiguration - */ - private $waitingPaymentLocalState; - - /** - * @param OrderStateConfiguration $canceledState - * @param OrderStateConfiguration $paymentErrorState - * @param OrderStateConfiguration $outOfStockUnpaidState - * @param OrderStateConfiguration $outOfStockPaidState - * @param OrderStateConfiguration $paymentAcceptedState - * @param OrderStateConfiguration $refundedState - * @param OrderStateConfiguration $authorizedState - * @param OrderStateConfiguration $partiallyPaidState - * @param OrderStateConfiguration $partiallyRefundedState - * @param OrderStateConfiguration $waitingCaptureState - * @param OrderStateConfiguration $waitingPaymentCardState - * @param OrderStateConfiguration $waitingPaymentPayPalState - * @param OrderStateConfiguration $waitingPaymentLocalState - */ - public function __construct( - $canceledState, - $paymentErrorState, - $outOfStockUnpaidState, - $outOfStockPaidState, - $paymentAcceptedState, - $refundedState, - $authorizedState, - $partiallyPaidState, - $partiallyRefundedState, - $waitingCaptureState, - $waitingPaymentCardState, - $waitingPaymentPayPalState, - $waitingPaymentLocalState - ) { - $this->canceledState = $canceledState; - $this->paymentErrorState = $paymentErrorState; - $this->outOfStockUnpaidState = $outOfStockUnpaidState; - $this->outOfStockPaidState = $outOfStockPaidState; - $this->paymentAcceptedState = $paymentAcceptedState; - $this->refundedState = $refundedState; - $this->authorizedState = $authorizedState; - $this->partiallyPaidState = $partiallyPaidState; - $this->partiallyRefundedState = $partiallyRefundedState; - $this->waitingCaptureState = $waitingCaptureState; - $this->waitingPaymentCardState = $waitingPaymentCardState; - $this->waitingPaymentPayPalState = $waitingPaymentPayPalState; - $this->waitingPaymentLocalState = $waitingPaymentLocalState; - } - - /** - * @return OrderStateConfiguration - */ - public function getCanceledState() - { - return $this->canceledState; - } - - /** - * @return OrderStateConfiguration - */ - public function getPaymentErrorState() - { - return $this->paymentErrorState; - } - - /** - * @return OrderStateConfiguration - */ - public function getOutOfStockUnpaidState() - { - return $this->outOfStockUnpaidState; - } - - /** - * @return OrderStateConfiguration - */ - public function getOutOfStockPaidState() - { - return $this->outOfStockPaidState; - } - - /** - * @return OrderStateConfiguration - */ - public function getPaymentAcceptedState() - { - return $this->paymentAcceptedState; - } - - /** - * @return OrderStateConfiguration - */ - public function getRefundedState() - { - return $this->refundedState; - } - - /** - * @return OrderStateConfiguration - */ - public function getAuthorizedState() - { - return $this->authorizedState; - } - - /** - * @return OrderStateConfiguration - */ - public function getPartiallyPaidState() - { - return $this->partiallyPaidState; - } - - /** - * @return OrderStateConfiguration - */ - public function getPartiallyRefundedState() - { - return $this->partiallyRefundedState; - } - - /** - * @return OrderStateConfiguration - */ - public function getWaitingCaptureState() - { - return $this->waitingCaptureState; - } - - /** - * @return OrderStateConfiguration - */ - public function getWaitingPaymentCardState() - { - return $this->waitingPaymentCardState; - } - - /** - * @return OrderStateConfiguration - */ - public function getWaitingPaymentPayPalState() - { - return $this->waitingPaymentPayPalState; - } - - /** - * @return OrderStateConfiguration - */ - public function getWaitingPaymentLocalState() - { - return $this->waitingPaymentLocalState; - } - - /** - * @todo Remove this - * - * @param $orderStateName - * - * @return int|false - */ - public function getIdByKey($orderStateName) - { - foreach ($this as $orderState) { - if ($orderState->getOrderStateConfigurationName() == $orderStateName) { - return $orderState->getOrderStateId(); - } - } - - return false; - } -} diff --git a/src/Order/State/Query/GetOrderStateQuery.php b/src/Order/State/Query/GetOrderStateQuery.php deleted file mode 100644 index e779ee488..000000000 --- a/src/Order/State/Query/GetOrderStateQuery.php +++ /dev/null @@ -1,46 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\Query; - -class GetOrderStateQuery -{ - /** - * @var string Const in OrderStateConfiguration - */ - private $orderState; - - /** - * @param string $orderState - */ - public function __construct($orderState) - { - $this->orderState = $orderState; - } - - /** - * @return string - */ - public function getOrderState() - { - return $this->orderState; - } -} diff --git a/src/Order/State/Query/GetOrderStateQueryResult.php b/src/Order/State/Query/GetOrderStateQueryResult.php deleted file mode 100644 index 46434ee84..000000000 --- a/src/Order/State/Query/GetOrderStateQueryResult.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\Query; - -use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; -use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; - -class GetOrderStateQueryResult -{ - /** - * @var OrderStateId - */ - private $orderStateId; - - /** - * @param int $orderStateId - * - * @throws OrderStateException - */ - public function __construct($orderStateId) - { - $this->orderStateId = new OrderStateId($orderStateId); - } - - /** - * @return OrderStateId - */ - public function getOrderStateId() - { - return $this->orderStateId; - } -} diff --git a/src/Order/State/QueryHandler/GetOrderStateConfigurationQueryHandler.php b/src/Order/State/QueryHandler/GetOrderStateConfigurationQueryHandler.php deleted file mode 100644 index 33e31e487..000000000 --- a/src/Order/State/QueryHandler/GetOrderStateConfigurationQueryHandler.php +++ /dev/null @@ -1,56 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\QueryHandler; - -use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\Order\State\Query\GetOrderStateConfigurationQuery; -use PrestaShop\Module\PrestashopCheckout\Order\State\Query\GetOrderStateConfigurationQueryResult; -use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateConfiguration; - -class GetOrderStateConfigurationQueryHandler -{ - /** - * @param GetOrderStateConfigurationQuery $query - * - * @return GetOrderStateConfigurationQueryResult - * - * @throws OrderStateException - */ - public function handle(GetOrderStateConfigurationQuery $query) - { - return new GetOrderStateConfigurationQueryResult( - new OrderStateConfiguration(OrderStateConfigurationKeys::CANCELED, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED)), - new OrderStateConfiguration(OrderStateConfigurationKeys::PAYMENT_ERROR, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::PAYMENT_ERROR)), - new OrderStateConfiguration(OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID)), - new OrderStateConfiguration(OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::OUT_OF_STOCK_PAID)), - new OrderStateConfiguration(OrderStateConfigurationKeys::PAYMENT_ACCEPTED, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::PAYMENT_ACCEPTED)), - new OrderStateConfiguration(OrderStateConfigurationKeys::REFUNDED, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::REFUNDED)), - new OrderStateConfiguration(OrderStateConfigurationKeys::AUTHORIZED, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::AUTHORIZED)), - new OrderStateConfiguration(OrderStateConfigurationKeys::PARTIALLY_PAID, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::PARTIALLY_PAID)), - new OrderStateConfiguration(OrderStateConfigurationKeys::PARTIALLY_REFUNDED, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::PARTIALLY_REFUNDED)), - new OrderStateConfiguration(OrderStateConfigurationKeys::WAITING_CAPTURE, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CAPTURE)), - new OrderStateConfiguration(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT)), - new OrderStateConfiguration(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT)), - new OrderStateConfiguration(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, (int) \Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT)) - ); - } -} diff --git a/src/Order/State/QueryHandler/GetOrderStateQueryHandler.php b/src/Order/State/QueryHandler/GetOrderStateQueryHandler.php deleted file mode 100644 index 9a598c468..000000000 --- a/src/Order/State/QueryHandler/GetOrderStateQueryHandler.php +++ /dev/null @@ -1,33 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\QueryHandler; - -use PrestaShop\Module\PrestashopCheckout\Order\State\Query\GetOrderStateQuery; -use PrestaShop\Module\PrestashopCheckout\Order\State\Query\GetOrderStateQueryResult; - -class GetOrderStateQueryHandler -{ - public function handle(GetOrderStateQuery $query) - { - return new GetOrderStateQueryResult((int) \Configuration::getGlobalValue($query->getOrderState())); - } -} diff --git a/src/Order/State/Service/CheckOrderState.php b/src/Order/State/Service/CheckOrderState.php deleted file mode 100644 index e5baaacc7..000000000 --- a/src/Order/State/Service/CheckOrderState.php +++ /dev/null @@ -1,109 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\Service; - -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; - -class CheckOrderState -{ - const TRANSITION_ALLOWED = [ - OrderStateConfigurationKeys::PAYMENT_ACCEPTED => [ - OrderStateConfigurationKeys::PARTIALLY_REFUNDED, - OrderStateConfigurationKeys::REFUNDED, - ], - OrderStateConfigurationKeys::WAITING_CAPTURE => [ - OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, - OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, - OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, - ], - OrderStateConfigurationKeys::PARTIALLY_REFUNDED => [ - OrderStateConfigurationKeys::REFUNDED, - ], - OrderStateConfigurationKeys::REFUNDED => [], - OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT => [ - OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - OrderStateConfigurationKeys::PARTIALLY_PAID, - OrderStateConfigurationKeys::CANCELED, - OrderStateConfigurationKeys::PAYMENT_ERROR, - ], - OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT => [ - OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - OrderStateConfigurationKeys::PARTIALLY_PAID, - OrderStateConfigurationKeys::CANCELED, - OrderStateConfigurationKeys::PAYMENT_ERROR, - ], - OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT => [ - OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - OrderStateConfigurationKeys::PARTIALLY_PAID, - OrderStateConfigurationKeys::CANCELED, - OrderStateConfigurationKeys::PAYMENT_ERROR, - ], - OrderStateConfigurationKeys::AUTHORIZED => [ - OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, - OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, - OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, - OrderStateConfigurationKeys::PARTIALLY_REFUNDED, - ], - OrderStateConfigurationKeys::PARTIALLY_PAID => [ - OrderStateConfigurationKeys::PAYMENT_ERROR, - OrderStateConfigurationKeys::PAYMENT_ACCEPTED, - ], - OrderStateConfigurationKeys::OUT_OF_STOCK_PAID => [], - OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID => [], - OrderStateConfigurationKeys::PAYMENT_ERROR => [], - OrderStateConfigurationKeys::CANCELED => [], - ]; - - /** - * @param string $currentOrderState - * @param string $newOrderState - * - * @return bool - */ - public function isCurrentOrderState($currentOrderState, $newOrderState) - { - return $currentOrderState === $newOrderState; - } - - /** - * @param string $currentOrderStateId - * @param string $newOrderStateId - * - * @return bool - * - * @throws OrderException - */ - public function isOrderStateTransitionAvailable($currentOrderStateId, $newOrderStateId) - { - if (!is_string($currentOrderStateId)) { - throw new OrderException(sprintf('Type of currentOrderStateId (%s) is not string', gettype($currentOrderStateId)), OrderException::STATUS_CHECK_AVAILABLE_BAD_PARAMETER); - } - if (!is_string($newOrderStateId)) { - throw new OrderException(sprintf('Type of newOrderStateId (%s) is not string', gettype($newOrderStateId)), OrderException::STATUS_CHECK_AVAILABLE_BAD_PARAMETER); - } - if (!key_exists($currentOrderStateId, self::TRANSITION_ALLOWED)) { - throw new OrderException(sprintf('The currentOrderStateId doesn\'t exist (%s)', $currentOrderStateId), OrderException::STATUS_CHECK_AVAILABLE_BAD_PARAMETER); - } - - return in_array($newOrderStateId, self::TRANSITION_ALLOWED[$currentOrderStateId]); - } -} diff --git a/src/Order/State/Service/OrderStateMapper.php b/src/Order/State/Service/OrderStateMapper.php new file mode 100644 index 000000000..c48bfc062 --- /dev/null +++ b/src/Order/State/Service/OrderStateMapper.php @@ -0,0 +1,98 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Order\State\Service; + +use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; + +class OrderStateMapper +{ + /** + * @var PrestaShopConfiguration + */ + private $configuration; + + /** + * @var array + */ + private $orderStateMapping; + + /** + * @param PrestaShopConfiguration $configuration + */ + public function __construct(PrestaShopConfiguration $configuration) + { + $this->configuration = $configuration; + $this->orderStateMapping = [ + OrderStateConfigurationKeys::CANCELED => (int) $this->configuration->get(OrderStateConfigurationKeys::CANCELED, ['global' => true]), + OrderStateConfigurationKeys::PAYMENT_ERROR => (int) $this->configuration->get(OrderStateConfigurationKeys::PAYMENT_ERROR, ['global' => true]), + OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID => (int) $this->configuration->get(OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, ['global' => true]), + OrderStateConfigurationKeys::OUT_OF_STOCK_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, ['global' => true]), + OrderStateConfigurationKeys::PAYMENT_ACCEPTED => (int) $this->configuration->get(OrderStateConfigurationKeys::PAYMENT_ACCEPTED, ['global' => true]), + OrderStateConfigurationKeys::REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::REFUNDED, ['global' => true]), + OrderStateConfigurationKeys::AUTHORIZED => (int) $this->configuration->get(OrderStateConfigurationKeys::AUTHORIZED, ['global' => true]), + OrderStateConfigurationKeys::PARTIALLY_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::PARTIALLY_PAID, ['global' => true]), + OrderStateConfigurationKeys::PARTIALLY_REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::PARTIALLY_REFUNDED, ['global' => true]), + OrderStateConfigurationKeys::WAITING_CAPTURE => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_CAPTURE, ['global' => true]), + OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, ['global' => true]), + ]; + } + + /** + * @param string $key + * + * @return int + */ + public function getIdByKey($key) + { + if (isset($this->orderStateMapping[$key]) && $this->orderStateMapping[$key]) { + return $this->orderStateMapping[$key]; + } + + throw new \InvalidArgumentException(sprintf('Order state key "%s" is not mapped', var_export($key, true))); + } + + /** + * @return array + */ + public function getOrderStateMapping() + { + return $this->orderStateMapping; + } + + /** + * @param int $orderCurrentState + * + * @return string + */ + public function getKeyById($orderCurrentState) + { + $orderStateMapping = array_flip($this->orderStateMapping); + + if (isset($orderStateMapping[$orderCurrentState]) && $orderStateMapping[$orderCurrentState]) { + return $orderStateMapping[$orderCurrentState]; + } + + throw new \InvalidArgumentException(sprintf('Order state id "%s" is not mapped', var_export($orderCurrentState, true))); + } +} diff --git a/src/Order/State/ValueObject/OrderStateConfiguration.php b/src/Order/State/ValueObject/OrderStateConfiguration.php deleted file mode 100644 index 47081c547..000000000 --- a/src/Order/State/ValueObject/OrderStateConfiguration.php +++ /dev/null @@ -1,64 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject; - -use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; - -class OrderStateConfiguration -{ - /** - * @var OrderStateId - */ - private $orderStateId; - - /** - * @var string - */ - private $orderStateConfigurationName; - - /** - * @param string $orderStateConfigurationName - * @param int $orderStateId - * - * @throws OrderStateException - */ - public function __construct($orderStateConfigurationName, $orderStateId) - { - $this->orderStateConfigurationName = $orderStateConfigurationName; - $this->orderStateId = new OrderStateId($orderStateId); - } - - /** - * @return int - */ - public function getOrderStateId() - { - return $this->orderStateId->getValue(); - } - - /** - * @return string - */ - public function getOrderStateConfigurationName() - { - return $this->orderStateConfigurationName; - } -} diff --git a/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php b/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php index dad6d24e4..a0b5c15d4 100644 --- a/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php +++ b/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php @@ -20,16 +20,7 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\EventSubscriber; -use DateTime; -use Exception; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; -use PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Event\PayPalClientTokenUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use PrestaShop\Module\PrestashopCheckout\Session\Command\UpdatePsCheckoutSessionCommand; -use PrestaShop\Module\PrestashopCheckout\Session\Exception\PsCheckoutSessionException; -use PrestaShopException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; class PayPalIdentityEventSubscriber implements EventSubscriberInterface @@ -53,38 +44,7 @@ public function __construct(CommandBusInterface $commandBus) public static function getSubscribedEvents() { return [ - PayPalClientTokenUpdatedEvent::class => 'updatePsCheckoutSession', + //PayPalClientTokenUpdatedEvent::class => '', ]; } - - /** - * @param PayPalClientTokenUpdatedEvent $event - * - * @return void - * - * @throws PrestaShopException - * @throws CartException - * @throws PayPalOrderException - * @throws PsCheckoutSessionException - * @throws Exception - */ - public function updatePsCheckoutSession(PayPalClientTokenUpdatedEvent $event) - { - $psCheckoutCartRepository = new PsCheckoutCartRepository(); - $psCheckoutCart = $psCheckoutCartRepository->findOneByCartId($event->getCartId()->getValue()); - $this->commandBus->handle( - new UpdatePsCheckoutSessionCommand( - $psCheckoutCart->getPaypalOrderId(), - $event->getCartId()->getValue(), - $psCheckoutCart->getPaypalFundingSource(), - $psCheckoutCart->getPaypalIntent(), - $psCheckoutCart->getPaypalStatus(), - $event->getToken(), - (new DateTime())->setTimestamp($event->getCreatedAt())->modify("+{$event->getExpireIn()} seconds")->format('Y-m-d H:i:s'), - $psCheckoutCart->paypal_authorization_expire, - $psCheckoutCart->isHostedFields(), - $psCheckoutCart->isExpressCheckout() - ) - ); - } } diff --git a/src/PayPal/Order/Command/PrunePayPalOrderCacheCommand.php b/src/PayPal/Order/Command/PrunePayPalOrderCacheCommand.php deleted file mode 100644 index 7700b47ba..000000000 --- a/src/PayPal/Order/Command/PrunePayPalOrderCacheCommand.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; - -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; - -class PrunePayPalOrderCacheCommand -{ - /** - * @var PayPalOrderId - */ - private $orderId; - - /** - * @param string $orderId - * - * @throws PayPalOrderException - */ - public function __construct($orderId) - { - $this->orderId = new PayPalOrderId($orderId); - } - - /** - * @return PayPalOrderId - */ - public function getOrderId() - { - return $this->orderId; - } -} diff --git a/src/PayPal/Order/Command/RemovePayPalOrderCacheCommand.php b/src/PayPal/Order/Command/RemovePayPalOrderCacheCommand.php deleted file mode 100644 index 9ca63eff9..000000000 --- a/src/PayPal/Order/Command/RemovePayPalOrderCacheCommand.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; - -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; - -class RemovePayPalOrderCacheCommand -{ - // todo change variable name to paypalOrderId - /** - * @var PayPalOrderId - */ - private $orderId; - - /** - * @param string $orderId - * - * @throws PayPalOrderException - */ - public function __construct($orderId) - { - $this->orderId = new PayPalOrderId($orderId); - } - - /** - * @return PayPalOrderId - */ - public function getOrderId() - { - return $this->orderId; - } -} diff --git a/src/PayPal/Order/Command/SavePayPalOrderCommand.php b/src/PayPal/Order/Command/SavePayPalOrderCommand.php index 2024bb55f..4533468e8 100644 --- a/src/PayPal/Order/Command/SavePayPalOrderCommand.php +++ b/src/PayPal/Order/Command/SavePayPalOrderCommand.php @@ -44,11 +44,11 @@ class SavePayPalOrderCommand /** * @param string $orderPayPalId * @param string $orderPayPalStatus - * @param string $orderPayPal + * @param array $orderPayPal * * @throws PayPalOrderException */ - public function __construct($orderPayPalId, $orderPayPalStatus, $orderPayPal) + public function __construct($orderPayPalId, $orderPayPalStatus, array $orderPayPal) { $this->orderPayPalId = new PayPalOrderId($orderPayPalId); $this->orderPayPalStatus = $orderPayPalStatus; diff --git a/src/PayPal/Order/Command/UpdatePayPalOrderCacheCommand.php b/src/PayPal/Order/Command/UpdatePayPalOrderCacheCommand.php deleted file mode 100644 index 55bdab921..000000000 --- a/src/PayPal/Order/Command/UpdatePayPalOrderCacheCommand.php +++ /dev/null @@ -1,66 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; - -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; - -class UpdatePayPalOrderCacheCommand -{ - /** - * @var PayPalOrderId - */ - private $orderPayPalId; - - /** - * @var array - */ - private $orderPayPal; - - /** - * @param string $orderPayPalId - * @param array $orderPayPal - * - * @throws PayPalOrderException - */ - public function __construct($orderPayPalId, array $orderPayPal) - { - $this->orderPayPalId = new PayPalOrderId($orderPayPalId); - $this->orderPayPal = $orderPayPal; - } - - /** - * @return PayPalOrderId - */ - public function getOrderId() - { - return $this->orderPayPalId; - } - - /** - * @return array - */ - public function getOrderPayPal() - { - return $this->orderPayPal; - } -} diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index b5bec5d1b..07be2921d 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -23,89 +23,86 @@ use Configuration; use Context; -use Exception; use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderEventDispatcher; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureEventDispatcher; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; use PrestaShop\Module\PrestashopCheckout\PayPalError; use PrestaShop\Module\PrestashopCheckout\PayPalProcessorResponse; class CapturePayPalOrderCommandHandler { /** - * @var PayPalOrderEventDispatcher + * @var EventDispatcherInterface */ - private $paypalOrderEventDispatcher; + private $eventDispatcher; - /** - * @var PayPalCaptureEventDispatcher - */ - private $paypalCaptureEventDispatcher; - - /** - * @param PayPalOrderEventDispatcher $paypalOrderEventDispatcher - * @param PayPalCaptureEventDispatcher $paypalCaptureEventDispatcher - */ - public function __construct(PayPalOrderEventDispatcher $paypalOrderEventDispatcher, PayPalCaptureEventDispatcher $paypalCaptureEventDispatcher) + public function __construct(EventDispatcherInterface $eventDispatcher) { - $this->paypalOrderEventDispatcher = $paypalOrderEventDispatcher; - $this->paypalCaptureEventDispatcher = $paypalCaptureEventDispatcher; + $this->eventDispatcher = $eventDispatcher; } public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { - try { - $context = Context::getContext(); - $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); - $apiOrder = new Order($context->link); - $response = $apiOrder->capture( - $capturePayPalOrderCommand->getOrderId()->getValue(), - $merchantId, - $capturePayPalOrderCommand->getFundingSource() - ); + $context = Context::getContext(); + $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); + $apiOrder = new Order($context->link); + $response = $apiOrder->capture( + $capturePayPalOrderCommand->getOrderId()->getValue(), + $merchantId, + $capturePayPalOrderCommand->getFundingSource() + ); - if (false === $response['status']) { - if (false === empty($response['body']['message'])) { - (new PayPalError($response['body']['message']))->throwException(); - } + if (false === $response['status']) { + if (isset($response['body']['details'][0]['issue'])) { + (new PayPalError($response['body']['details'][0]['issue']))->throwException(); + } - if (false === empty($response['exceptionMessage']) && false === empty($response['exceptionCode'])) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } + if (isset($response['body']['name'])) { + (new PayPalError($response['body']['name']))->throwException(); + } - throw new PsCheckoutException(isset($response['body']['error']) ? $response['body']['error'] : 'Unknown error', PsCheckoutException::UNKNOWN); + if (false === empty($response['exceptionMessage']) && false === empty($response['exceptionCode'])) { + throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); } - $capturePayPal = $response['body']['purchase_units'][0]['payments']['captures'][0]; + throw new PsCheckoutException(isset($response['body']['error']) ? $response['body']['error'] : 'Unknown error', PsCheckoutException::UNKNOWN); + } - if (false === empty($capturePayPal)) { - $captureId = $capturePayPal['id']; - $captureStatus = $capturePayPal['status']; + $orderPayPal = $response['body']; + $capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0]; - if ( - 'DECLINED' === $captureStatus - && false === empty($response['body']['payment_source']) - && false === empty($response['body']['payment_source'][0]['card']) - && false === empty($capturePayPal['processor_response']) - ) { - $payPalProcessorResponse = new PayPalProcessorResponse( - isset($response['body']['payment_source'][0]['card']['brand']) ? $response['body']['payment_source'][0]['card']['brand'] : null, - isset($response['body']['payment_source'][0]['card']['type']) ? $response['body']['payment_source'][0]['card']['type'] : null, - isset($capturePayPal['processor_response']['avs_code']) ? $capturePayPal['processor_response']['avs_code'] : null, - isset($capturePayPal['processor_response']['cvv_code']) ? $capturePayPal['processor_response']['cvv_code'] : null, - isset($capturePayPal['processor_response']['response_code']) ? $capturePayPal['processor_response']['response_code'] : null - ); - $payPalProcessorResponse->throwException(); - } - } + if ( + 'DECLINED' === $capturePayPal['status'] + && false === empty($response['body']['payment_source']) + && false === empty($response['body']['payment_source'][0]['card']) + && false === empty($capturePayPal['processor_response']) + ) { + $payPalProcessorResponse = new PayPalProcessorResponse( + isset($response['body']['payment_source'][0]['card']['brand']) ? $response['body']['payment_source'][0]['card']['brand'] : null, + isset($response['body']['payment_source'][0]['card']['type']) ? $response['body']['payment_source'][0]['card']['type'] : null, + isset($capturePayPal['processor_response']['avs_code']) ? $capturePayPal['processor_response']['avs_code'] : null, + isset($capturePayPal['processor_response']['cvv_code']) ? $capturePayPal['processor_response']['cvv_code'] : null, + isset($capturePayPal['processor_response']['response_code']) ? $capturePayPal['processor_response']['response_code'] : null + ); + $payPalProcessorResponse->throwException(); + } + + if ($orderPayPal['status'] === PayPalOrderStatus::COMPLETED) { + $this->eventDispatcher->dispatch(new PayPalOrderCompletedEvent($orderPayPal['id'], $orderPayPal)); + } + + if ($capturePayPal['status'] === PayPalCaptureStatus::PENDING) { + $this->eventDispatcher->dispatch(new PayPalCapturePendingEvent($capturePayPal['id'], $orderPayPal['id'], $capturePayPal)); + } - $this->paypalOrderEventDispatcher->dispatch($response['body']); - $this->paypalCaptureEventDispatcher->dispatch($response['body']['id'], $capturePayPal); - } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to capture PayPal Order %s', $capturePayPalOrderCommand->getOrderId()->getValue()), PayPalOrderException::CANNOT_CAPTURE_ORDER, $exception); + if ($capturePayPal['status'] === PayPalCaptureStatus::COMPLETED) { + $this->eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($capturePayPal['id'], $orderPayPal['id'], $capturePayPal)); } } } diff --git a/src/PayPal/Order/CommandHandler/PrunePayPalOrderCacheCommandHandler.php b/src/PayPal/Order/CommandHandler/PrunePayPalOrderCacheCommandHandler.php deleted file mode 100644 index 55098fcf1..000000000 --- a/src/PayPal/Order/CommandHandler/PrunePayPalOrderCacheCommandHandler.php +++ /dev/null @@ -1,74 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; - -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\PrunePayPalOrderCacheCommand; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCacheUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use Psr\SimpleCache\CacheException; -use Psr\SimpleCache\CacheInterface; - -class PrunePayPalOrderCacheCommandHandler -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @var CacheInterface - */ - private $orderPayPalCache; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CacheInterface $orderPayPalCache - */ - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) - { - $this->eventDispatcher = $eventDispatcher; - $this->orderPayPalCache = $orderPayPalCache; - } - - /** - * @param PrunePayPalOrderCacheCommand $prunePayPalOrderCacheCommand - * - * @return void - * - * @throws PayPalOrderException - */ - public function handle(PrunePayPalOrderCacheCommand $prunePayPalOrderCacheCommand) - { - try { - // Cache used provide pruning (deletion) of all expired cache items to reduce cache size - if (method_exists($this->orderPayPalCache, 'prune')) { - $this->orderPayPalCache->prune(); - } - } catch (CacheException $exception) { - throw new PayPalOrderException('Unable to prune PayPal Order Cache', PayPalOrderException::CACHE_EXCEPTION, $exception); - } - - $this->eventDispatcher->dispatch( - new PayPalOrderCacheUpdatedEvent($prunePayPalOrderCacheCommand->getOrderId()->getValue()) - ); - } -} diff --git a/src/PayPal/Order/CommandHandler/RemovePayPalOrderCacheCommandHandler.php b/src/PayPal/Order/CommandHandler/RemovePayPalOrderCacheCommandHandler.php deleted file mode 100644 index e830b9bc2..000000000 --- a/src/PayPal/Order/CommandHandler/RemovePayPalOrderCacheCommandHandler.php +++ /dev/null @@ -1,75 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; - -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\RemovePayPalOrderCacheCommand; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCacheUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use Psr\SimpleCache\CacheException; -use Psr\SimpleCache\CacheInterface; - -class RemovePayPalOrderCacheCommandHandler -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @var CacheInterface - */ - private $orderPayPalCache; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CacheInterface $orderPayPalCache - */ - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) - { - $this->eventDispatcher = $eventDispatcher; - $this->orderPayPalCache = $orderPayPalCache; - } - - /** - * @param RemovePayPalOrderCacheCommand $removePayPalOrderCacheCommand - * - * @return void - * - * @throws PayPalOrderException - */ - public function handle(RemovePayPalOrderCacheCommand $removePayPalOrderCacheCommand) - { - try { - if ($this->orderPayPalCache->has(CacheSettings::PAYPAL_ORDER_ID . $removePayPalOrderCacheCommand->getOrderId()->getValue())) { - $this->orderPayPalCache->delete(CacheSettings::PAYPAL_ORDER_ID . $removePayPalOrderCacheCommand->getOrderId()->getValue()); - } - } catch (CacheException $exception) { - throw new PayPalOrderException('Unable to remove item from PayPal Order Cache', PayPalOrderException::CACHE_EXCEPTION, $exception); - } - - $this->eventDispatcher->dispatch( - new PayPalOrderCacheUpdatedEvent($removePayPalOrderCacheCommand->getOrderId()->getValue()) - ); - } -} diff --git a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php index 7198f6811..f083a905f 100644 --- a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php @@ -22,43 +22,34 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; use Exception; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; class SavePayPalOrderCommandHandler { - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - /** * @var PsCheckoutCartRepository */ private $psCheckoutCartRepository; - /** - * @param EventDispatcherInterface $eventDispatcher - * @param PsCheckoutCartRepository $psCheckoutCartRepository - */ - public function __construct(EventDispatcherInterface $eventDispatcher, PsCheckoutCartRepository $psCheckoutCartRepository) + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) { - $this->eventDispatcher = $eventDispatcher; $this->psCheckoutCartRepository = $psCheckoutCartRepository; } public function handle(SavePayPalOrderCommand $savePayPalOrderCommand) { try { - /** @var \PsCheckoutCart|false $psCheckoutCart */ + /** @var PsCheckoutCart|false $psCheckoutCart */ $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($savePayPalOrderCommand->getOrderPayPalId()->getValue()); $psCheckoutCart->paypal_order = $savePayPalOrderCommand->getOrderPayPalId()->getValue(); $psCheckoutCart->paypal_status = $savePayPalOrderCommand->getOrderPaypalStatus(); + $psCheckoutCart->paypal_token = null; + $psCheckoutCart->paypal_token_expire = null; $this->psCheckoutCartRepository->save($psCheckoutCart); - // Update an Aggregate or dispatch an Event with $transactionIdentifier } catch (Exception $exception) { throw new PayPalOrderException(sprintf('Unable to retrieve PrestaShop cart #%d', $savePayPalOrderCommand->getOrderPayPalId()->getValue()), PayPalOrderException::SESSION_EXCEPTION, $exception); } diff --git a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php deleted file mode 100644 index d8de13a8c..000000000 --- a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCacheCommandHandler.php +++ /dev/null @@ -1,47 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; - -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCacheCommand; -use Psr\SimpleCache\CacheInterface; - -class UpdatePayPalOrderCacheCommandHandler -{ - /** - * @var CacheInterface - */ - private $paypalOrderCache; - - /** - * @param CacheInterface $paypalOrderCache - */ - public function __construct($paypalOrderCache) - { - $this->paypalOrderCache = $paypalOrderCache; - } - - public function handle(UpdatePayPalOrderCacheCommand $updatePayPalOrderCacheCommand) - { - $orderPayPal = $updatePayPalOrderCacheCommand->getOrderPayPal(); - $this->paypalOrderCache->set($orderPayPal['id'], $orderPayPal); - } -} diff --git a/src/PayPal/Order/Comparator/PayPalOrderComparator.php b/src/PayPal/Order/Comparator/PayPalOrderComparator.php deleted file mode 100644 index 2cc73bd5f..000000000 --- a/src/PayPal/Order/Comparator/PayPalOrderComparator.php +++ /dev/null @@ -1,116 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Comparator; - -use Psr\SimpleCache\CacheInterface; -use Psr\SimpleCache\InvalidArgumentException; - -class PayPalOrderComparator -{ - /** - * @var CacheInterface - */ - private $orderPayPalCache; - - /** - * @var array - */ - private $newOrderPayPal; - - /** - * @var array - */ - private $currentOrderPayPal; - - /** - * @param CacheInterface $orderPayPalCache - */ - public function __construct($orderPayPalCache) - { - $this->orderPayPalCache = $orderPayPalCache; - } - - /** - * @throws InvalidArgumentException - */ - public function compare($newOrderPayPal) - { - $this->newOrderPayPal = $newOrderPayPal; - $this->currentOrderPayPal = $this->orderPayPalCache->get($this->newOrderPayPal['id']); - - if (empty($this->currentOrderPayPal)) { - return false; - } - - return $this->checkOrderId() && $this->checkOrderStatus() && $this->checkUpdateTime(); - } - - /** - * @return bool - */ - private function checkOrderId() - { - return $this->newOrderPayPal['id'] === $this->currentOrderPayPal['id']; - } - - /** - * @return bool - */ - private function checkOrderStatus() - { - return $this->newOrderPayPal['status'] === $this->currentOrderPayPal['status']; - } - - /** - * True if orderPayPal & orderPayPalCache are similar, false if there's a change - * - * @return bool - */ - private function checkUpdateTime() - { - if ($this->newOrderPayPal['status'] !== 'COMPLETED') { - // We only have update_time in some COMPLETED requests, which mean we can't compare the update_time - - return true; - } - - if (!isset($this->newOrderPayPal['update_time']) && isset($this->currentOrderPayPal['update_time'])) { - // We can have an outdated webhook coming through, we keep the recent one - - return true; - } - - if (isset($this->newOrderPayPal['update_time']) && !isset($this->currentOrderPayPal['update_time'])) { - // We don't have an update_time in cache but we have an update_time in the new orderPayPal, which means it's recent - - return false; - } - - if (date_create_from_format('Y-m-d\TH:i:s\Z', $this->newOrderPayPal['update_time']) > date_create_from_format('Y-m-d\TH:i:s\Z', $this->currentOrderPayPal['update_time'])) { - // The update_time in the new orderPayPal is more recent than the cache one - - return false; - } - - return true; - } -} diff --git a/src/PayPal/Order/Event/PayPalOrderCacheUpdatedEvent.php b/src/PayPal/Order/Event/PayPalOrderCacheUpdatedEvent.php deleted file mode 100644 index 0fc2b56f7..000000000 --- a/src/PayPal/Order/Event/PayPalOrderCacheUpdatedEvent.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; - -use PrestaShop\Module\PrestashopCheckout\Event\Event; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; - -class PayPalOrderCacheUpdatedEvent extends Event -{ - /** - * @var PayPalOrderId - */ - private $orderPayPalId; - - /** - * @param string $orderPayPalId - * - * @throws PayPalOrderException - */ - public function __construct($orderPayPalId) - { - $this->orderPayPalId = new PayPalOrderId($orderPayPalId); - } - - /** - * @return PayPalOrderId - */ - public function getOrderPayPalId() - { - return $this->orderPayPalId; - } -} diff --git a/src/PayPal/Order/Event/PayPalOrderFetchedEvent.php b/src/PayPal/Order/Event/PayPalOrderFetchedEvent.php deleted file mode 100644 index 53670b737..000000000 --- a/src/PayPal/Order/Event/PayPalOrderFetchedEvent.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; - -class PayPalOrderFetchedEvent extends PayPalOrderEvent -{ -} diff --git a/src/PayPal/Order/Event/PayPalOrderNotApprovedEvent.php b/src/PayPal/Order/Event/PayPalOrderNotApprovedEvent.php deleted file mode 100644 index d6a2ca10b..000000000 --- a/src/PayPal/Order/Event/PayPalOrderNotApprovedEvent.php +++ /dev/null @@ -1,25 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; - -class PayPalOrderNotApprovedEvent extends PayPalOrderEvent -{ -} diff --git a/src/PayPal/Order/Event/PayPalOrderSavedEvent.php b/src/PayPal/Order/Event/PayPalOrderSavedEvent.php deleted file mode 100644 index be027aacb..000000000 --- a/src/PayPal/Order/Event/PayPalOrderSavedEvent.php +++ /dev/null @@ -1,52 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; - -class PayPalOrderSavedEvent extends PayPalOrderEvent -{ - /** - * @var string - */ - private $dateUpd; - - /** - * @param string $orderPayPalId - * @param array $orderPayPal - * @param string $dateUpd - * - * @throws PayPalCaptureException - * @throws PayPalOrderException - */ - public function __construct($orderPayPalId, $orderPayPal, $dateUpd) - { - parent::__construct($orderPayPalId, $orderPayPal); - $this->dateUpd = $dateUpd; - } - - /** - * @return string - */ - public function getDateUpd() - { - return $this->dateUpd; - } -} diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index 21aac3f5c..7313698e7 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -22,9 +22,9 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\EventSubscriber; use Exception; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; use PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CheckTransitionPayPalOrderStatusService; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovalReversedEvent; @@ -32,13 +32,11 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderNotApprovedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; use PrestaShopException; use Ps_checkout; -use PsCheckoutCart; -use Psr\Log\LoggerInterface; use Psr\SimpleCache\CacheInterface; use Psr\SimpleCache\InvalidArgumentException; use Symfony\Component\EventDispatcher\EventSubscriberInterface; @@ -50,11 +48,6 @@ class PayPalOrderEventSubscriber implements EventSubscriberInterface */ private $module; - /** - * @var LoggerInterface - */ - private $logger; - /** * @var PsCheckoutCartRepository */ @@ -71,65 +64,51 @@ class PayPalOrderEventSubscriber implements EventSubscriberInterface private $checkoutChecker; /** - * @param Ps_checkout $module - * @param LoggerInterface $logger - * @param PsCheckoutCartRepository $psCheckoutCartRepository - * @param CacheInterface $orderPayPalCache + * @var CheckTransitionPayPalOrderStatusService */ + private $checkTransitionPayPalOrderStatusService; + public function __construct( Ps_checkout $module, - LoggerInterface $logger, PsCheckoutCartRepository $psCheckoutCartRepository, - CacheInterface $orderPayPalCache + CacheInterface $orderPayPalCache, + CheckoutChecker $checkoutChecker, + CheckTransitionPayPalOrderStatusService $checkTransitionPayPalOrderStatusService ) { $this->module = $module; - $this->logger = $logger; $this->psCheckoutCartRepository = $psCheckoutCartRepository; $this->orderPayPalCache = $orderPayPalCache; - $this->checkoutChecker = $this->module->getService('ps_checkout.checkout.checker'); + $this->checkoutChecker = $checkoutChecker; + $this->checkTransitionPayPalOrderStatusService = $checkTransitionPayPalOrderStatusService; } /** - * @return array[] + * {@inheritdoc} */ public static function getSubscribedEvents() { return [ PayPalOrderCreatedEvent::class => [ - ['savePayPalOrder'], + ['saveCreatedPayPalOrder'], ['updateCache'], ], PayPalOrderApprovedEvent::class => [ - ['savePayPalOrder'], + ['saveApprovedPayPalOrder'], ['updateCache'], ['capturePayPalOrder'], ], - PayPalOrderNotApprovedEvent::class => [ - ['savePayPalOrder'], - ['updateCache'], - ], PayPalOrderCompletedEvent::class => [ - ['savePayPalOrder'], + ['saveCompletedPayPalOrder'], ['updateCache'], ], PayPalOrderApprovalReversedEvent::class => [ - ['savePayPalOrder'], + ['saveApprovalReversedPayPalOrder'], ['updateCache'], ], ]; } - /** - * @param $event - * - * @return void - * - * @throws PayPalOrderException - * @throws PsCheckoutException - * @throws PrestaShopException - * @throws CartException - */ - public function savePayPalOrder($event) + public function saveCreatedPayPalOrder(PayPalOrderCreatedEvent $event) { $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); @@ -137,45 +116,77 @@ public function savePayPalOrder($event) throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } - switch (get_class($event)) { - case PayPalOrderCreatedEvent::class: - $orderStatus = PsCheckoutCart::STATUS_CREATED; - break; - case PayPalOrderApprovedEvent::class: - $orderStatus = PsCheckoutCart::STATUS_APPROVED; - break; - case PayPalOrderCompletedEvent::class: - $orderStatus = PsCheckoutCart::STATUS_COMPLETED; - break; - case PayPalOrderApprovalReversedEvent::class: - $orderStatus = PsCheckoutCart::STATUS_APPROVAL_REVERSED; - break; - case PayPalOrderNotApprovedEvent::class: - $orderStatus = PsCheckoutCart::STATUS_PAYER_ACTION_REQUIRED; - break; - default: - $orderStatus = PsCheckoutCart::STATUS_PENDING_APPROVAL; + if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::CREATED)) { + return; + } + + $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $event->getOrderPayPalId()->getValue(), + PayPalOrderStatus::CREATED, + $event->getOrderPayPal() + )); + } + + public function saveApprovedPayPalOrder(PayPalOrderApprovedEvent $event) + { + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } - // COMPLETED is a final status, always ensure we don't update to previous status due to outdated webhook for example - if ($psCheckoutCart->getPaypalStatus() === PsCheckoutCart::STATUS_COMPLETED) { + if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::APPROVED)) { return; } - if ($psCheckoutCart->getPaypalStatus() !== $orderStatus) { - $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( - $event->getOrderPayPalId()->getValue(), - $orderStatus, - $event->getOrderPayPal() - )); + $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $event->getOrderPayPalId()->getValue(), + PayPalOrderStatus::APPROVED, + $event->getOrderPayPal() + )); + } + + public function saveCompletedPayPalOrder(PayPalOrderCompletedEvent $event) + { + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + } + + if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::COMPLETED)) { + return; + } + + $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $event->getOrderPayPalId()->getValue(), + PayPalOrderStatus::COMPLETED, + $event->getOrderPayPal() + )); + } + + public function saveApprovalReversedPayPalOrder(PayPalOrderApprovalReversedEvent $event) + { + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + } + + if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::REVERSED)) { + return; } + + $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $event->getOrderPayPalId()->getValue(), + PayPalOrderStatus::REVERSED, + $event->getOrderPayPal() + )); } /** * @param PayPalOrderApprovedEvent $event * - * @return void - * * @throws PsCheckoutException * @throws PrestaShopException * @throws PayPalOrderException @@ -189,10 +200,15 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); } + // TODO: Remove this condition when a flag will be added on checkout completed if ($psCheckoutCart->isExpressCheckout()) { return; } + if ($psCheckoutCart->getPaypalStatus() === PayPalOrderStatus::COMPLETED) { + return; + } + $this->checkoutChecker->continueWithAuthorization($psCheckoutCart->getIdCart(), $event->getOrderPayPal()); $this->module->getService('ps_checkout.bus.command')->handle( @@ -206,12 +222,17 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) /** * @param PayPalOrderEvent $event * - * @return void - * * @throws InvalidArgumentException */ public function updateCache(PayPalOrderEvent $event) { - $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $event->getOrderPayPal()); + $currentOrderPayPal = $this->orderPayPalCache->get($event->getOrderPayPalId()->getValue()); + $newOrderPayPal = $event->getOrderPayPal(); + + if ($currentOrderPayPal && !$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($currentOrderPayPal['status'], $newOrderPayPal['status'])) { + return; + } + + $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $newOrderPayPal); } } diff --git a/src/PayPal/Order/PayPalOrderEventDispatcher.php b/src/PayPal/Order/PayPalOrderEventDispatcher.php deleted file mode 100644 index 34138b38c..000000000 --- a/src/PayPal/Order/PayPalOrderEventDispatcher.php +++ /dev/null @@ -1,163 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order; - -use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Comparator\PayPalOrderComparator; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderSavedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQueryResult; -use Psr\SimpleCache\CacheInterface; -use Psr\SimpleCache\InvalidArgumentException; - -class PayPalOrderEventDispatcher -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @var CommandBusInterface - */ - private $commandBus; - - /** - * @var CheckTransitionPayPalOrderStatusService - */ - private $checkTransitionPayPalOrderStatusService; - - /** - * @var PayPalOrderComparator - */ - private $paypalOrderComparator; - - /** - * @var CacheInterface - */ - private $orderPayPalCache; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CommandBusInterface $commandBus - * @param CheckTransitionPayPalOrderStatusService $checkTransitionPayPalOrderStatusService - * @param PayPalOrderComparator $paypalOrderComparator - * @param CacheInterface $orderPayPalCache - */ - public function __construct( - EventDispatcherInterface $eventDispatcher, - CommandBusInterface $commandBus, - CheckTransitionPayPalOrderStatusService $checkTransitionPayPalOrderStatusService, - PayPalOrderComparator $paypalOrderComparator, - CacheInterface $orderPayPalCache - ) { - $this->eventDispatcher = $eventDispatcher; - $this->commandBus = $commandBus; - $this->checkTransitionPayPalOrderStatusService = $checkTransitionPayPalOrderStatusService; - $this->paypalOrderComparator = $paypalOrderComparator; - $this->orderPayPalCache = $orderPayPalCache; - } - - /** - * @param array $orderPayPal - * - * @return void - * - * @throws Exception\PayPalOrderException - * @throws OrderException - * @throws InvalidArgumentException - */ - public function dispatch(array $orderPayPal) - { - $module = \Module::getInstanceByName('ps_checkout'); - - $cacheOrderPayPal = $this->orderPayPalCache->get($orderPayPal['id']); - if (empty($cacheOrderPayPal)) { - /** @var GetCurrentPayPalOrderStatusQueryResult $getCurrentPayPalOrderStatusQueryResult */ - $getCurrentPayPalOrderStatusQueryResult = $this->commandBus->handle(new GetCurrentPayPalOrderStatusQuery($orderPayPal['id'])); - $oldStatus = $getCurrentPayPalOrderStatusQueryResult->getStatus(); - } else { - $oldStatus = $cacheOrderPayPal['status']; - } - - if ( - !$this->paypalOrderComparator->compare($orderPayPal) - && $this->checkTransitionPayPalOrderStatusService->checkAvailableStatus( - $oldStatus, - $orderPayPal['status'] - ) - ) { - $module->getLogger()->debug( - 'Need to dispatch PayPalOrderEvent', - [ - 'PayPalOrderId' => $orderPayPal['id'], - 'CurrentPayPalOrderStatus' => $oldStatus, - 'NewPayPalOrderStatus' => $orderPayPal['status'], - ] - ); - $this->dispatchAfterCheck($orderPayPal); - } else { - $module->getLogger()->debug( - 'No need to dispatch PayPalOrderEvent', - [ - 'PayPalOrderId' => $orderPayPal['id'], - 'CurrentPayPalOrderStatus' => $oldStatus, - 'NewPayPalOrderStatus' => $orderPayPal['status'], - 'CompareOrder' => $this->paypalOrderComparator->compare($orderPayPal), - 'CheckTransitionAvailable' => $this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($oldStatus, $orderPayPal['status']), - 'isCacheFilled' => !empty($cacheOrderPayPal), - 'CacheOrderPayPal' => $cacheOrderPayPal, - ] - ); - } - } - - /** - * @throws Exception\PayPalOrderException - */ - private function dispatchAfterCheck(array $orderPayPal) - { - switch ($orderPayPal['status']) { - case PayPalOrderStatus::APPROVED: - $this->eventDispatcher->dispatch(new PayPalOrderApprovedEvent($orderPayPal['id'], $orderPayPal)); - break; - case PayPalOrderStatus::COMPLETED: - $this->eventDispatcher->dispatch(new PayPalOrderCompletedEvent($orderPayPal['id'], $orderPayPal)); - break; - case PayPalOrderStatus::CREATED: - $this->eventDispatcher->dispatch(new PayPalOrderCreatedEvent($orderPayPal['id'], $orderPayPal)); - break; - case PayPalOrderStatus::SAVED: - $this->eventDispatcher->dispatch(new PayPalOrderSavedEvent($orderPayPal['id'], $orderPayPal, $orderPayPal['update_time'])); - break; - case PayPalOrderStatus::PAYER_ACTION_REQUIRED: - case PayPalOrderStatus::PENDING_APPROVAL: - case PayPalOrderStatus::VOIDED: - break; - } - } -} diff --git a/src/PayPal/Order/PayPalOrderStatus.php b/src/PayPal/Order/PayPalOrderStatus.php index db0143619..94140a557 100644 --- a/src/PayPal/Order/PayPalOrderStatus.php +++ b/src/PayPal/Order/PayPalOrderStatus.php @@ -29,38 +29,42 @@ class PayPalOrderStatus const PAYER_ACTION_REQUIRED = 'PAYER_ACTION_REQUIRED'; const VOIDED = 'VOIDED'; const COMPLETED = 'COMPLETED'; + const CANCELED = 'CANCELED'; + const REVERSED = 'REVERSED'; const TRANSITION_AVAILABLE = [ self::CREATED => [ - self::CREATED, self::APPROVED, self::PENDING_APPROVAL, self::SAVED, self::PAYER_ACTION_REQUIRED, self::VOIDED, self::COMPLETED, + self::CANCELED, ], self::SAVED => [ - self::SAVED, self::VOIDED, ], self::APPROVED => [ - self::APPROVED, self::PAYER_ACTION_REQUIRED, self::COMPLETED, + self::REVERSED, + self::CANCELED, ], self::PENDING_APPROVAL => [ - self::PENDING_APPROVAL, self::PAYER_ACTION_REQUIRED, self::APPROVED, + self::CANCELED, ], self::PAYER_ACTION_REQUIRED => [ - self::PAYER_ACTION_REQUIRED, self::PENDING_APPROVAL, self::APPROVED, self::COMPLETED, + self::CANCELED, ], - self::VOIDED => [self::VOIDED], - self::COMPLETED => [self::COMPLETED], + self::VOIDED => [], + self::COMPLETED => [], + self::CANCELED => [], + self::REVERSED => [], ]; } diff --git a/src/PayPal/Order/PayPalOrderSummaryView.php b/src/PayPal/Order/PayPalOrderSummaryView.php index d0fd08c4e..d62178c94 100644 --- a/src/PayPal/Order/PayPalOrderSummaryView.php +++ b/src/PayPal/Order/PayPalOrderSummaryView.php @@ -88,7 +88,7 @@ public function __construct( */ public function getTemplateVars() { - $orderStatus = $this->orderPayPalDataProvider->getOrderStatus(); + $orderStatus = $this->orderPayPalDataProvider->getOrderStatus() ? $this->orderPayPalDataProvider->getOrderStatus() : $this->checkoutDataProvider->getPaypalOrderStatus(); $orderTransactionStatus = $this->orderPayPalDataProvider->getTransactionStatus(); $fundingSource = $this->checkoutDataProvider->getFundingSourceName(); diff --git a/src/PayPal/Order/PayPalOrderSummaryViewBuilder.php b/src/PayPal/Order/PayPalOrderSummaryViewBuilder.php index e9f88fb4a..faad6437e 100644 --- a/src/PayPal/Order/PayPalOrderSummaryViewBuilder.php +++ b/src/PayPal/Order/PayPalOrderSummaryViewBuilder.php @@ -97,10 +97,10 @@ public function build(Order $order) throw new PsCheckoutException('Unable to retrieve cart data'); } - $orderPayPal = $this->orderPayPalProvider->getById($psCheckoutCart->paypal_order); - - if (!$orderPayPal) { - throw new PsCheckoutException('Unable to retrieve PayPal order data'); + try { + $orderPayPal = $this->orderPayPalProvider->getById($psCheckoutCart->paypal_order); + } catch (Exception $exception) { + $orderPayPal = []; } $orderPayPalDataProvider = new PaypalOrderDataProvider($orderPayPal); diff --git a/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQuery.php b/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQuery.php index 74fe01dca..f9216815c 100644 --- a/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQuery.php +++ b/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQuery.php @@ -28,23 +28,23 @@ class GetCurrentPayPalOrderStatusQuery /** * @var PayPalOrderId */ - private $paypalOrderId; + private $orderPayPalId; /** - * @param string $orderId + * @param string $orderPayPalId * * @throws PayPalOrderException */ - public function __construct($paypalOrderId) + public function __construct($orderPayPalId) { - $this->paypalOrderId = new PayPalOrderId($paypalOrderId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** * @return PayPalOrderId */ - public function getPayPalOrderId() + public function getOrderPayPalId() { - return $this->paypalOrderId; + return $this->orderPayPalId; } } diff --git a/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQueryResult.php b/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQueryResult.php index 81c59d487..5d60ebe93 100644 --- a/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQueryResult.php +++ b/src/PayPal/Order/Query/GetCurrentPayPalOrderStatusQueryResult.php @@ -28,31 +28,31 @@ class GetCurrentPayPalOrderStatusQueryResult /** * @var PayPalOrderId */ - private $payPalOrderId; + private $orderPayPalId; /** * @var string */ - private $payPalOrderStatus; + private $orderPayPalStatus; /** - * @param string $payPalOrderId - * @param string $payPalOrderStatus + * @param string $orderPayPalId + * @param string $orderPayPalStatus * * @throws PayPalOrderException */ - public function __construct($payPalOrderId, $payPalOrderStatus) + public function __construct($orderPayPalId, $orderPayPalStatus) { - $this->payPalOrderId = new PayPalOrderId($payPalOrderId); - $this->payPalOrderStatus = $payPalOrderStatus; + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->orderPayPalStatus = $orderPayPalStatus; } /** * @return PayPalOrderId */ - public function getPayPalOrderId() + public function getOrderPayPalId() { - return $this->payPalOrderId; + return $this->orderPayPalId; } /** @@ -60,6 +60,6 @@ public function getPayPalOrderId() */ public function getStatus() { - return $this->payPalOrderStatus; + return $this->orderPayPalStatus; } } diff --git a/src/PayPal/Order/Query/GetPayPalOrderForCheckoutCompletedQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForCheckoutCompletedQuery.php index c9bf70722..fe817bf4a 100644 --- a/src/PayPal/Order/Query/GetPayPalOrderForCheckoutCompletedQuery.php +++ b/src/PayPal/Order/Query/GetPayPalOrderForCheckoutCompletedQuery.php @@ -28,23 +28,23 @@ class GetPayPalOrderForCheckoutCompletedQuery /** * @var PayPalOrderId */ - private $orderId; + private $orderPayPalId; /** - * @param string $orderId + * @param string $orderPayPalId * * @throws PayPalOrderException */ - public function __construct($orderId) + public function __construct($orderPayPalId) { - $this->orderId = new PayPalOrderId($orderId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** * @return PayPalOrderId */ - public function getOrderId() + public function getOrderPayPalId() { - return $this->orderId; + return $this->orderPayPalId; } } diff --git a/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQuery.php index 85a194ee3..1cbc5db82 100644 --- a/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQuery.php +++ b/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQuery.php @@ -29,23 +29,23 @@ class GetPayPalOrderForOrderConfirmationQuery /** * @var PayPalOrderId */ - private $orderId; + private $orderPayPalId; /** - * @param string $orderId + * @param string $orderPayPalId * * @throws PayPalOrderException */ - public function __construct($orderId) + public function __construct($orderPayPalId) { - $this->orderId = new PayPalOrderId($orderId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); } /** * @return PayPalOrderId */ - public function getOrderId() + public function getOrderPayPalId() { - return $this->orderId; + return $this->orderPayPalId; } } diff --git a/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQueryResult.php b/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQueryResult.php index 9c4f14900..f633a5f37 100644 --- a/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQueryResult.php +++ b/src/PayPal/Order/Query/GetPayPalOrderForOrderConfirmationQueryResult.php @@ -26,21 +26,21 @@ class GetPayPalOrderForOrderConfirmationQueryResult /** * @var array */ - private $order; + private $orderPayPal; /** - * @param array $order + * @param array $orderPayPal */ - public function __construct(array $order) + public function __construct(array $orderPayPal) { - $this->order = $order; + $this->orderPayPal = $orderPayPal; } /** * @return array */ - public function getOrder() + public function getOrderPayPal() { - return $this->order; + return $this->orderPayPal; } } diff --git a/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php b/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php index add46d5dc..9d67e52ee 100644 --- a/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetCurrentPayPalOrderStatusQueryHandler.php @@ -52,7 +52,7 @@ public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) public function handle(GetCurrentPayPalOrderStatusQuery $getPayPalOrderQuery) { try { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($getPayPalOrderQuery->getPayPalOrderId()->getValue()); + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($getPayPalOrderQuery->getOrderPayPalId()->getValue()); } catch (Exception $exception) { throw new PayPalOrderException('Cannot retrieve cart', PayPalOrderException::PRESTASHOP_CART_NOT_FOUND, $exception); } diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index 02985b1f5..686b74eb2 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -22,12 +22,8 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; use Exception; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderFetchedEvent; +use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderEventDispatcher; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQueryResult; use PrestaShop\Module\PrestashopCheckout\PaypalOrder; @@ -39,31 +35,14 @@ */ class GetPayPalOrderForCheckoutCompletedQueryHandler { - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - /** * @var CacheInterface */ private $orderPayPalCache; - /** - * @var PayPalOrderEventDispatcher - */ - private $paypalOrderEventDispatcher; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CacheInterface $orderPayPalCache - * @param PayPalOrderEventDispatcher $paypalOrderEventDispatcher - */ - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache, PayPalOrderEventDispatcher $paypalOrderEventDispatcher) + public function __construct(CacheInterface $orderPayPalCache) { - $this->eventDispatcher = $eventDispatcher; $this->orderPayPalCache = $orderPayPalCache; - $this->paypalOrderEventDispatcher = $paypalOrderEventDispatcher; } /** @@ -72,32 +51,29 @@ public function __construct(EventDispatcherInterface $eventDispatcher, CacheInte * @return GetPayPalOrderForCheckoutCompletedQueryResult * * @throws PayPalOrderException - * @throws OrderException * @throws InvalidArgumentException */ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQuery) { /** @var array{id: string, status: string} $order */ - $order = $this->orderPayPalCache->get(CacheSettings::PAYPAL_ORDER_ID . $getPayPalOrderQuery->getOrderId()->getValue()); + $order = $this->orderPayPalCache->get($getPayPalOrderQuery->getOrderPayPalId()->getValue()); if (!empty($order) && $order['status'] === 'APPROVED') { return new GetPayPalOrderForCheckoutCompletedQueryResult($order); } try { - $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderId()->getValue()); + $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + } catch (HttpTimeoutException $exception) { + throw $exception; } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $getPayPalOrderQuery->getOrderPayPalId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); } if (!$orderPayPal->isLoaded()) { - throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $getPayPalOrderQuery->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); + throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $getPayPalOrderQuery->getOrderPayPalId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); } - $this->eventDispatcher->dispatch( - new PayPalOrderFetchedEvent($getPayPalOrderQuery->getOrderId()->getValue(), $orderPayPal->getOrder()) - ); - return new GetPayPalOrderForCheckoutCompletedQueryResult($orderPayPal->getOrder()); } } diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php index 1b5bc9c1b..b1cf85205 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForOrderConfirmationQueryHandler.php @@ -21,9 +21,6 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderFetchedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQueryResult; @@ -33,23 +30,13 @@ class GetPayPalOrderForOrderConfirmationQueryHandler { - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - /** * @var CacheInterface */ private $orderPayPalCache; - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CacheInterface $orderPayPalCache - */ - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) + public function __construct(CacheInterface $orderPayPalCache) { - $this->eventDispatcher = $eventDispatcher; $this->orderPayPalCache = $orderPayPalCache; } @@ -64,26 +51,22 @@ public function __construct(EventDispatcherInterface $eventDispatcher, CacheInte public function handle(GetPayPalOrderForOrderConfirmationQuery $query) { /** @var array{id: string, status: string} $order */ - $order = $this->orderPayPalCache->get(CacheSettings::PAYPAL_ORDER_ID . $query->getOrderId()->getValue()); + $order = $this->orderPayPalCache->get($query->getOrderPayPalId()->getValue()); if (!empty($order) && ($order['status'] === 'PENDING' || $order['status'] === 'COMPLETED')) { return new GetPayPalOrderForOrderConfirmationQueryResult($order); } try { - $orderPayPal = new PaypalOrder($query->getOrderId()->getValue()); + $orderPayPal = new PaypalOrder($query->getOrderPayPalId()->getValue()); } catch (\Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $query->getOrderId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); + throw new PayPalOrderException(sprintf('Unable to retrieve PayPal Order %s', $query->getOrderPayPalId()->getValue()), PayPalOrderException::CANNOT_RETRIEVE_ORDER, $exception); } if (!$orderPayPal->isLoaded()) { - throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $query->getOrderId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); + throw new PayPalOrderException(sprintf('No data for PayPal Order %s', $query->getOrderPayPalId()->getValue()), PayPalOrderException::EMPTY_ORDER_DATA); } - $this->eventDispatcher->dispatch( - new PayPalOrderFetchedEvent($query->getOrderId()->getValue(), $orderPayPal->getOrder()) - ); - return new GetPayPalOrderForOrderConfirmationQueryResult($orderPayPal->getOrder()); } } diff --git a/src/PayPal/PayPalClientTokenProvider.php b/src/PayPal/PayPalClientTokenProvider.php index 1b22efbb4..9d8119794 100644 --- a/src/PayPal/PayPalClientTokenProvider.php +++ b/src/PayPal/PayPalClientTokenProvider.php @@ -22,22 +22,61 @@ use Configuration; use Context; +use DateTime; use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopDatabaseException; +use PrestaShopException; +use PsCheckoutCart; +use Validate; class PayPalClientTokenProvider { + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + /** * @return string * * @throws PsCheckoutException + * @throws PrestaShopDatabaseException + * @throws PrestaShopException */ public function getPayPalClientToken() { $context = Context::getContext(); + + if (!Validate::isLoadedObject($context->cart)) { + return ''; + } + + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId((int) $context->cart->id); + + if ($psCheckoutCart && !$psCheckoutCart->isPaypalClientTokenExpired()) { + return $psCheckoutCart->getPaypalClientToken(); + } + $apiOrder = new Order($context->link); $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); + $clientToken = $apiOrder->generateClientToken($merchantId); + + if (!$psCheckoutCart) { + $psCheckoutCart = new PsCheckoutCart(); + $psCheckoutCart->id_cart = (int) $context->cart->id; + } + + $psCheckoutCart->paypal_token = $clientToken; + $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); + $this->psCheckoutCartRepository->save($psCheckoutCart); - return $apiOrder->generateClientToken($merchantId); + return $clientToken; } } diff --git a/src/PayPal/PayPalConfiguration.php b/src/PayPal/PayPalConfiguration.php index 164cbe65e..974b157aa 100644 --- a/src/PayPal/PayPalConfiguration.php +++ b/src/PayPal/PayPalConfiguration.php @@ -418,4 +418,14 @@ public function isHostedFieldsEnabled() { return (bool) $this->configuration->get(static::PS_CHECKOUT_CARD_HOSTED_FIELDS_ENABLED); } + + /** + * @return string + */ + public function getTimeZone() + { + return $this->configuration->get('PS_TIMEZONE', [ + 'default' => date_default_timezone_get(), + ]); + } } diff --git a/src/PayPal/Payment/Capture/Comparator/PayPalCaptureComparator.php b/src/PayPal/Payment/Capture/Comparator/PayPalCaptureComparator.php deleted file mode 100644 index baae437cd..000000000 --- a/src/PayPal/Payment/Capture/Comparator/PayPalCaptureComparator.php +++ /dev/null @@ -1,124 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Comparator; - -use Psr\SimpleCache\CacheInterface; -use Psr\SimpleCache\InvalidArgumentException; - -class PayPalCaptureComparator -{ - /** - * @var CacheInterface - */ - private $capturePayPalCache; - - /** - * @var array - */ - private $newCapturePayPal; - - /** - * @var array - */ - private $currentCapturePayPal; - - /** - * @param CacheInterface $capturePayPalCache - */ - public function __construct($capturePayPalCache) - { - $this->capturePayPalCache = $capturePayPalCache; - } - - /** - * @throws InvalidArgumentException - */ - public function compare($newCapturePayPal) - { - $this->newCapturePayPal = $newCapturePayPal; - $this->currentCapturePayPal = $this->capturePayPalCache->get($this->newCapturePayPal['id']); - - if (empty($this->currentCapturePayPal)) { - return false; - } - - return $this->checkCaptureId() && $this->checkCaptureStatus() && $this->checkUpdateTime() && $this->checkAmount(); - } - - /** - * @return bool - */ - private function checkAmount() - { - return $this->newCapturePayPal['amount']['value'] === $this->currentCapturePayPal['amount']['value']; - } - - /** - * @return bool - */ - private function checkCaptureId() - { - return $this->newCapturePayPal['id'] === $this->currentCapturePayPal['id']; - } - - /** - * @return bool - */ - private function checkCaptureStatus() - { - return $this->newCapturePayPal['status'] === $this->currentCapturePayPal['status']; - } - - /** - * True if capturePayPal & capturePayPalCache are similar, false if there's a change - * - * @return bool - */ - private function checkUpdateTime() - { - if ($this->newCapturePayPal['status'] !== 'COMPLETED') { - // We only have update_time in some COMPLETED requests, which mean we can't compare the update_time - - return true; - } - - if (!isset($this->newCapturePayPal['update_time']) && isset($this->currentCapturePayPal['update_time'])) { - // We can have an outdated webhook coming through, we keep the recent one - - return true; - } - - if (isset($this->newCapturePayPal['update_time']) && !isset($this->currentCapturePayPal['update_time'])) { - // We don't have an update_time in cache but we have an update_time in the new orderPayPal, which means it's recent - - return false; - } - - if (date_create_from_format('Y-m-d\TH:i:s\Z', $this->newCapturePayPal['update_time']) > date_create_from_format('Y-m-d\TH:i:s\Z', $this->currentCapturePayPal['update_time'])) { - // The update_time in the new orderPayPal is more recent than the cache one - - return false; - } - - return true; - } -} diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index fe7f55561..f2a98d3ab 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -1,5 +1,4 @@ module = $module; - $this->logger = $logger; - $this->psCheckoutCartRepository = $psCheckoutCartRepository; $this->checkOrderAmount = $checkOrderAmount; - /** @var CommandBusInterface $commandBus */ - $commandBus = $this->module->getService('ps_checkout.bus.command'); - $this->commandBus = $commandBus; + $this->commandBus = $this->module->getService('ps_checkout.bus.command'); $this->capturePayPalCache = $capturePayPalCache; + $this->orderStateMapper = $orderStateMapper; } /** @@ -127,7 +99,7 @@ public static function getSubscribedEvents() { return [ PayPalCaptureCompletedEvent::class => [ - ['createPaidOrder'], + ['createOrder'], ['createOrderPayment'], ['setPaymentCompletedOrderStatus'], ['updateCache'], @@ -137,7 +109,7 @@ public static function getSubscribedEvents() ['updateCache'], ], PayPalCapturePendingEvent::class => [ - ['createPendingOrder'], + ['createOrder'], ['setPaymentPendingOrderStatus'], ['updateCache'], ], @@ -157,142 +129,13 @@ public static function getSubscribedEvents() * * @return void * - * @throws PrestaShopException + * @throws PayPalOrderException */ - public function createPaidOrder(PayPalCaptureCompletedEvent $event) + public function createOrder(PayPalCaptureCompletedEvent $event) { - /** @var PsCheckoutCart $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - - $cart = new \Cart($psCheckoutCart->getIdCart()); - - if (!\Validate::isLoadedObject($cart)) { - throw new PsCheckoutException('Cart not found', PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - - try { - /** @var GetOrderQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderQuery($psCheckoutCart->getIdCart())); - - $this->logger->info( - 'PrestaShop Order for PayPal Order is already created.', - [ - 'id_cart' => $psCheckoutCart->getIdCart(), - 'id_order' => $order->getId(), - 'PayPalOrderId' => $event->getPayPalOrderId()->getValue(), - ] - ); - - return; // If we already have an Order (when going from Pending to Completed), we stop - } catch (PsCheckoutException $exception) { - $this->logger->info( - 'No PrestaShop Order for PayPal Order, create it', - [ - 'id_cart' => $psCheckoutCart->getIdCart(), - 'PayPalOrderId' => $event->getPayPalOrderId()->getValue(), - ] - ); - } - - $capture = $event->getCapture(); - - $transactionId = $orderStateId = $paidAmount = ''; - $fundingSource = $psCheckoutCart->getPaypalFundingSource(); - - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - - if (empty($capture['amount']['value'])) { - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID); - } else { - switch ($this->checkOrderAmount->checkAmount((string) $capture['amount']['value'], (string) $cart->getOrderTotal(true, \Cart::BOTH))) { - case CheckOrderAmount::ORDER_NOT_FULL_PAID: - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID); - break; - case CheckOrderAmount::ORDER_FULL_PAID: - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); - $transactionId = $event->getPayPalCaptureId()->getValue(); - $paidAmount = $capture['amount']['value']; - break; - case CheckOrderAmount::ORDER_TO_MUCH_PAID: - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); - } - } - - /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ - $fundingSourceTranslationProvider = $this->module->getService('ps_checkout.funding_source.translation'); - $this->commandBus->handle(new CreateOrderCommand( - $psCheckoutCart->getIdCart(), - 'ps_checkout', - $orderStateId, - $fundingSourceTranslationProvider->getPaymentMethodName($fundingSource), - $transactionId, - $paidAmount - )); - } - - /** - * @param PayPalCapturePendingEvent $event - * - * @return void - * - * @throws PrestaShopException - */ - public function createPendingOrder(PayPalCapturePendingEvent $event) - { - /** @var PsCheckoutCart $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - - $cart = new \Cart($psCheckoutCart->getIdCart()); - - if (!\Validate::isLoadedObject($cart)) { - throw new PsCheckoutException('Cart not found', PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - - try { - /** @var GetOrderQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderQuery($psCheckoutCart->getIdCart())); - - $this->logger->info( - 'PrestaShop Order for PayPal Order is already created.', - [ - 'id_cart' => $psCheckoutCart->getIdCart(), - 'id_order' => $order->getId(), - 'PayPalOrderId' => $event->getPayPalOrderId()->getValue(), - ] - ); - - return; - } catch (PsCheckoutException $exception) { - } - - $transactionId = $paidAmount = ''; - $fundingSource = $psCheckoutCart->getPaypalFundingSource(); - - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - - switch ($fundingSource) { - case 'card': - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); - break; - case 'paypal': - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); - break; - default: - $orderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); - } - - /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ - $fundingSourceTranslationProvider = $this->module->getService('ps_checkout.funding_source.translation'); - - $this->commandBus->handle(new CreateOrderCommand( - $psCheckoutCart->getIdCart(), - 'ps_checkout', - $orderStateId, - $fundingSourceTranslationProvider->getPaymentMethodName($fundingSource), - $transactionId, - $paidAmount + $event->getPayPalOrderId()->getValue(), + $event->getCapture() )); } @@ -301,218 +144,149 @@ public function createPendingOrder(PayPalCapturePendingEvent $event) * * @return void * - * @throws CartException - * @throws PsCheckoutException - * @throws PrestaShopException + * @throws OrderException + * @throws PayPalOrderException * @throws PayPalCaptureException */ public function createOrderPayment(PayPalCaptureCompletedEvent $event) { - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - - /** @var GetOrderQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderQuery($psCheckoutCart->getIdCart())); - - try { - $this->commandBus->handle(new GetOrderPaymentQuery($order->getId(), $event->getPayPalCaptureId()->getValue())); - - $this->logger->info( - 'Order Payment is already created.', - [ - 'id_order' => $order->getId(), - 'PayPalCaptureId' => $event->getPayPalCaptureId()->getValue(), - ]); + /** @var GetOrderForPaymentCompletedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); - return; // We already have an OrderPayment, there's no need to add another one - } catch (OrderPaymentException $e) { + if ($order->getOrderPaymentId()) { + return; } - /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ - $fundingSourceTranslationProvider = $this->module->getService('ps_checkout.funding_source.translation'); - $capture = $event->getCapture(); - $paymentAmount = ''; - $transactionId = null; - $captureAmount = sprintf('%01.2f', $capture['amount']['value']); - $orderAmount = sprintf('%01.2f', $order->getTotalAmount()); - $paidAmount = sprintf('%01.2f', $order->getTotalAmountPaid()); - - if ($captureAmount + 0.05 < ($orderAmount - $paidAmount) || $captureAmount - 0.05 > ($orderAmount - $paidAmount)) { - $paymentAmount = $capture['amount']['value']; - $transactionId = $event->getPayPalCaptureId()->getValue(); - } - - $createTime = new \DateTime($capture['create_time']); $this->commandBus->handle(new AddOrderPaymentCommand( - $order->getId(), - $createTime->format('Y-m-d H:i:s'), - $fundingSourceTranslationProvider->getPaymentMethodName($psCheckoutCart->paypal_funding), - $paymentAmount, + $order->getOrderId()->getValue(), + $capture['create_time'], + $order->getPaymentMethod(), + $capture['amount']['value'], $order->getCurrencyId(), - $transactionId + $event->getPayPalCaptureId()->getValue() )); } /** - * @throws PrestaShopException + * @param PayPalCaptureCompletedEvent $event + * * @throws OrderException * @throws OrderStateException - * @throws PsCheckoutException - * @throws CartException + * @throws PayPalOrderException + * @throws PayPalCaptureException */ public function setPaymentCompletedOrderStatus(PayPalCaptureCompletedEvent $event) { - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - /** @var GetOrderForPaymentCompletedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($psCheckoutCart->getIdCart())); + $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); if ($order->hasBeenPaid()) { return; } - switch ($this->checkOrderAmount->checkAmount((string) $order->getTotalAmount(), (string) $event->getCapture()['amount']['value'] + $order->getTotalAmountPaid())) { + switch ($this->checkOrderAmount->checkAmount((string) $order->getTotalAmount(), (string) $event->getCapture()['amount']['value'])) { case CheckOrderAmount::ORDER_FULL_PAID: case CheckOrderAmount::ORDER_TO_MUCH_PAID: - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getPaymentAcceptedState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED))); break; case CheckOrderAmount::ORDER_NOT_FULL_PAID: - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getPartiallyPaidState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID))); break; } } /** - * @throws PrestaShopException + * @param PayPalCapturePendingEvent $event + * * @throws OrderException * @throws OrderStateException - * @throws CartException + * @throws PayPalOrderException */ public function setPaymentPendingOrderStatus(PayPalCapturePendingEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - /** @var GetOrderForPaymentPendingQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentPendingQuery($psCheckoutCart->getIdCart())); + $order = $this->commandBus->handle(new GetOrderForPaymentPendingQuery($event->getPayPalOrderId()->getValue())); - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); + if ($order->isInPending()) { + return; + } - switch ($psCheckoutCart->getPaypalFundingSource()) { + switch ($order->getPaymentMethod()) { case 'card': - $newOrderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); + $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); break; case 'paypal': - $newOrderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); + $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); break; default: - $newOrderStateId = $getOrderStateConfiguration->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); - } - - if ($order->isInPending()) { - return; + $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); } - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $newOrderStateId)); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $newOrderStateId)); } + /** + * @param PayPalCaptureDeclinedEvent $event + * + * @throws OrderException + * @throws OrderStateException + * @throws PayPalOrderException + */ public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - - try { - /** @var GetOrderForPaymentDeniedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentDeniedQuery($psCheckoutCart->getIdCart())); - } catch (PsCheckoutException $exception) { - return; - } - - // Si timeout lors de la capture, on a créé une commande PS avec un status pending - // Donc on doit mettre à jour le status de la commande PS en PAYMENT_ERROR + /** @var GetOrderForPaymentDeniedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentDeniedQuery($event->getPayPalOrderId()->getValue())); - // Si OrderStateHistory y a déjà PAYMENT_ERROR if ($order->hasBeenError()) { return; } - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getPaymentErrorState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ERROR))); } /** - * @throws PrestaShopException + * @param PayPalCaptureRefundedEvent $event + * * @throws OrderException * @throws OrderStateException - * @throws PsCheckoutException - * @throws CartException + * @throws PayPalOrderException */ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } /** @var GetOrderForPaymentRefundedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($psCheckoutCart->getIdCart())); + $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); - if (!$order->hasBeenPaid()) { - return; - } - if ($order->hasBeenTotallyRefund()) { + if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { return; } + if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $order->getTotalRefund()) == CheckOrderAmount::ORDER_NOT_FULL_PAID) { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getPartiallyRefundedState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_REFUNDED))); } else { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getRefundedState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::REFUNDED))); } } /** - * @throws PrestaShopException + * @param PayPalCaptureReversedEvent $event + * * @throws OrderException * @throws OrderStateException - * @throws PsCheckoutException - * @throws CartException + * @throws PayPalCaptureException + * @throws PayPalOrderException */ public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getPayPalOrderId()->getValue()); - /** @var GetOrderStateConfigurationQueryResult $getOrderStateConfiguration */ - $getOrderStateConfiguration = $this->commandBus->handle(new GetOrderStateConfigurationQuery()); - - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getPayPalOrderId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - /** @var GetOrderForPaymentReversedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentReversedQuery($psCheckoutCart->getIdCart())); - - if (!$order->hasBeenPaid()) { - return; - } + $order = $this->commandBus->handle(new GetOrderForPaymentReversedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); - if ($order->hasBeenTotallyRefund()) { + if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { return; } - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getId(), $getOrderStateConfiguration->getRefundedState()->getOrderStateId())); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::REFUNDED))); } /** diff --git a/src/PayPal/Payment/Capture/PayPalCaptureEventDispatcher.php b/src/PayPal/Payment/Capture/PayPalCaptureEventDispatcher.php deleted file mode 100644 index 02fb10481..000000000 --- a/src/PayPal/Payment/Capture/PayPalCaptureEventDispatcher.php +++ /dev/null @@ -1,155 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture; - -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Comparator\PayPalCaptureComparator; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; -use Psr\SimpleCache\CacheInterface; -use Psr\SimpleCache\InvalidArgumentException; - -class PayPalCaptureEventDispatcher -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @var CheckTransitionPayPalCaptureStatusService - */ - private $checkTransitionPayPalCaptureStatusService; - - /** - * @var PayPalCaptureComparator - */ - private $paypalCaptureComparator; - - /** - * @var CacheInterface - */ - private $capturePayPalCache; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param CheckTransitionPayPalCaptureStatusService $checkTransitionPayPalCaptureStatusService - * @param PayPalCaptureComparator $paypalCaptureComparator - * @param CacheInterface $capturePayPalCache - */ - public function __construct( - EventDispatcherInterface $eventDispatcher, - CheckTransitionPayPalCaptureStatusService $checkTransitionPayPalCaptureStatusService, - PayPalCaptureComparator $paypalCaptureComparator, - CacheInterface $capturePayPalCache - ) { - $this->eventDispatcher = $eventDispatcher; - $this->checkTransitionPayPalCaptureStatusService = $checkTransitionPayPalCaptureStatusService; - $this->paypalCaptureComparator = $paypalCaptureComparator; - $this->capturePayPalCache = $capturePayPalCache; - } - - /** - * @param string $orderId - * @param array $capture - * - * @return void - * - * @throws InvalidArgumentException - * @throws OrderException - * @throws PayPalCaptureException - * @throws PayPalOrderException - */ - public function dispatch($orderId, array $capture) - { - $module = \Module::getInstanceByName('ps_checkout'); - - $cacheCapturePayPal = $this->capturePayPalCache->get($capture['id']); - - if (empty($cacheCapturePayPal)) { - $this->dispatchAfterCheck($orderId, $capture); - } elseif ( - !$this->paypalCaptureComparator->compare($capture) - && $this->checkTransitionPayPalCaptureStatusService->checkAvailableStatus( - $cacheCapturePayPal['status'], - $capture['status'] - ) - ) { - $module->getLogger()->debug( - 'Need to dispatch PayPalCaptureEvent', - [ - 'PayPalOrderId' => $orderId, - 'PayPalCaptureId' => $capture['id'], - 'CurrentPayPalCaptureStatus' => $cacheCapturePayPal['status'], - 'NewPayPalCaptureStatus' => $capture['status'], - ] - ); - $this->dispatchAfterCheck($orderId, $capture); - } else { - $module->getLogger()->debug( - 'No need to dispatch PayPalCaptureEvent', - [ - 'PayPalOrderId' => $orderId, - 'PayPalCaptureId' => $capture['id'], - 'CurrentPayPalCaptureStatus' => $cacheCapturePayPal['status'], - 'NewPayPalCaptureStatus' => $capture['status'], - ] - ); - } - } - - /** - * @param string $orderId - * @param array $capture - * - * @return void - * - * @throws PayPalCaptureException - * @throws PayPalOrderException - */ - private function dispatchAfterCheck($orderId, array $capture) - { - switch ($capture['status']) { - case PayPalCaptureStatus::COMPLETED: - $this->eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($capture['id'], $orderId, $capture)); - break; - case PayPalCaptureStatus::DECLINED: - $this->eventDispatcher->dispatch(new PayPalCaptureDeclinedEvent($capture['id'], $orderId, $capture)); - break; - case PayPalCaptureStatus::PENDING: - $this->eventDispatcher->dispatch(new PayPalCapturePendingEvent($capture['id'], $orderId, $capture)); - break; - case PayPalCaptureStatus::REFUND: - $this->eventDispatcher->dispatch(new PayPalCaptureRefundedEvent($capture['id'], $orderId, $capture)); - break; - case PayPalCaptureStatus::REVERSED: - $this->eventDispatcher->dispatch(new PayPalCaptureReversedEvent($capture['id'], $orderId, $capture)); - break; - } - } -} diff --git a/src/PayPal/Payment/Capture/PayPalCaptureStatus.php b/src/PayPal/Payment/Capture/PayPalCaptureStatus.php index adf7ff38b..65b2f070a 100644 --- a/src/PayPal/Payment/Capture/PayPalCaptureStatus.php +++ b/src/PayPal/Payment/Capture/PayPalCaptureStatus.php @@ -44,7 +44,6 @@ class PayPalCaptureStatus self::FAILED => [], self::DECLINED => [], self::COMPLETED => [ - self::COMPLETED, self::REFUND, self::PARTIALLY_REFUNDED, self::REVERSED, diff --git a/src/PayPalError.php b/src/PayPalError.php index 4a7763201..8a6d2eea5 100644 --- a/src/PayPalError.php +++ b/src/PayPalError.php @@ -299,6 +299,10 @@ public function throwException() throw new PayPalException('External funding details not found.', PayPalException::NO_EXTERNAL_FUNDING_DETAILS_FOUND); case 'PAYMENT_DENIED': throw new PayPalException('Payment denied.', PayPalException::PAYMENT_DENIED); + case 'CARD_BRAND_NOT_SUPPORTED': + throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED); + case 'RESOURCE_NOT_FOUND': + throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND); default: throw new PayPalException($this->message, PayPalException::UNKNOWN); } diff --git a/src/Repository/PsCheckoutCartRepository.php b/src/Repository/PsCheckoutCartRepository.php index e1aad25db..f85866f7c 100644 --- a/src/Repository/PsCheckoutCartRepository.php +++ b/src/Repository/PsCheckoutCartRepository.php @@ -20,8 +20,8 @@ namespace PrestaShop\Module\PrestashopCheckout\Repository; +use PrestaShop\Module\PrestashopCheckout\Cart\Cache\CacheSettings; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Cache\CacheSettings; use Psr\SimpleCache\CacheInterface; class PsCheckoutCartRepository diff --git a/src/Session/Command/UpdatePsCheckoutSessionCommand.php b/src/Session/Command/UpdatePsCheckoutSessionCommand.php deleted file mode 100644 index 1ec74d6f9..000000000 --- a/src/Session/Command/UpdatePsCheckoutSessionCommand.php +++ /dev/null @@ -1,198 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Session\Command; - -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; - -class UpdatePsCheckoutSessionCommand -{ - /** - * @var PayPalOrderId - */ - private $orderId; - - /** - * @var CartId - */ - private $cartId; - - /** - * @var string - */ - private $fundingSource; - - /** - * @var string - */ - private $paypalIntent; - - /** - * @var string - */ - private $paypalStatus; - - /** - * @var string - */ - private $paypalToken; - - /** - * @var string; - */ - private $paypalTokenExpire; - - /** - * @var string; - */ - private $paypalAuthorizationExpire; - - /** - * @var bool; - */ - private $isExpressCheckout; - - /** - * @var bool; - */ - private $isHostedFields; - - /** - * @param string $orderId - * @param int $id_cart - * @param string $fundingSource - * @param string $paypalIntent - * @param string $paypalStatus - * @param string $paypalToken - * @param string $paypalTokenExpire - * @param string $paypalAuthorizationExpire - * @param bool $isHostedFields - * @param bool $isExpressCheckout - * - * @throws PayPalOrderException - * @throws CartException - */ - public function __construct( - $orderId, - $id_cart, - $fundingSource, - $paypalIntent, - $paypalStatus, - $paypalToken, - $paypalTokenExpire, - $paypalAuthorizationExpire, - $isHostedFields, - $isExpressCheckout - ) { - $this->orderId = new PayPalOrderId($orderId); - $this->cartId = new CartId($id_cart); - $this->fundingSource = $fundingSource; - $this->paypalIntent = $paypalIntent; - $this->paypalStatus = $paypalStatus; - $this->paypalToken = $paypalToken; - $this->paypalTokenExpire = $paypalTokenExpire; - $this->paypalAuthorizationExpire = $paypalAuthorizationExpire; - $this->isHostedFields = $isHostedFields; - $this->isExpressCheckout = $isExpressCheckout; - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } - - /** - * @return string - */ - public function getPaypalIntent() - { - return $this->paypalIntent; - } - - /** - * @return string - */ - public function getPaypalStatus() - { - return $this->paypalStatus; - } - - /** - * @return string - */ - public function getPaypalToken() - { - return $this->paypalToken; - } - - /** - * @return string - */ - public function getPaypalAuthorizationExpire() - { - return $this->paypalAuthorizationExpire; - } - - /** - * @return string - */ - public function getPaypalTokenExpire() - { - return $this->paypalTokenExpire; - } - - /** - * @return bool - */ - public function isExpressCheckout() - { - return $this->isExpressCheckout; - } - - /** - * @return bool - */ - public function isHostedFields() - { - return $this->isHostedFields; - } - - /** - * @return PayPalOrderId - */ - public function getPayPalOrderId() - { - return $this->orderId; - } - - /** - * @return string - */ - public function getFundingSource() - { - return $this->fundingSource; - } -} diff --git a/src/Session/CommandHandler/UpdatePsCheckoutSessionCommandHandler.php b/src/Session/CommandHandler/UpdatePsCheckoutSessionCommandHandler.php deleted file mode 100644 index 536d3cb8f..000000000 --- a/src/Session/CommandHandler/UpdatePsCheckoutSessionCommandHandler.php +++ /dev/null @@ -1,92 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Session\CommandHandler; - -use Exception; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use PrestaShop\Module\PrestashopCheckout\Session\Command\UpdatePsCheckoutSessionCommand; -use PrestaShop\Module\PrestashopCheckout\Session\Event\PsCheckoutSessionUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\Session\Exception\PsCheckoutSessionException; -use PsCheckoutCart; - -class UpdatePsCheckoutSessionCommandHandler -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @var PsCheckoutCartRepository - */ - private $psCheckoutCartRepository; - - /** - * @param EventDispatcherInterface $eventDispatcher - * @param PsCheckoutCartRepository $psCheckoutCartRepository - */ - public function __construct( - EventDispatcherInterface $eventDispatcher, - PsCheckoutCartRepository $psCheckoutCartRepository - ) { - $this->eventDispatcher = $eventDispatcher; - $this->psCheckoutCartRepository = $psCheckoutCartRepository; - } - - /** - * @param UpdatePsCheckoutSessionCommand $updatePsCheckoutSessionCommand - * - * @return void - * - * @throws PayPalOrderException - * @throws CartException - * @throws PsCheckoutSessionException - */ - public function handle(UpdatePsCheckoutSessionCommand $updatePsCheckoutSessionCommand) - { - try { - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId($updatePsCheckoutSessionCommand->getCartId()->getValue()); - - if (false === $psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = $updatePsCheckoutSessionCommand->getCartId()->getValue(); - $psCheckoutCart->paypal_intent = $updatePsCheckoutSessionCommand->getPaypalIntent(); - $psCheckoutCart->paypal_order = $updatePsCheckoutSessionCommand->getPayPalOrderId()->getValue(); - $psCheckoutCart->paypal_status = $updatePsCheckoutSessionCommand->getPaypalStatus(); - $this->psCheckoutCartRepository->save($psCheckoutCart); - } else { - $psCheckoutCart->paypal_order = $updatePsCheckoutSessionCommand->getPayPalOrderId()->getValue(); - $psCheckoutCart->paypal_status = $updatePsCheckoutSessionCommand->getPaypalStatus(); - $this->psCheckoutCartRepository->save($psCheckoutCart); - } - } catch (Exception $exception) { - throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', $updatePsCheckoutSessionCommand->getPayPalOrderId()->getValue()), PsCheckoutSessionException::UPDATE_FAILED, $exception); - } - - $this->eventDispatcher->dispatch( - new PsCheckoutSessionUpdatedEvent($updatePsCheckoutSessionCommand->getCartId()->getValue()) - ); - } -} diff --git a/src/Session/Event/PsCheckoutSessionUpdatedEvent.php b/src/Session/Event/PsCheckoutSessionUpdatedEvent.php deleted file mode 100644 index 73e738734..000000000 --- a/src/Session/Event/PsCheckoutSessionUpdatedEvent.php +++ /dev/null @@ -1,51 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Session\Event; - -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; -use PrestaShop\Module\PrestashopCheckout\Event\Event; - -class PsCheckoutSessionUpdatedEvent extends Event -{ - /** - * @var CartId - */ - private $cartId; - - /** - * @param int $cartId - * - * @throws CartException - */ - public function __construct($cartId) - { - $this->cartId = new CartId($cartId); - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } -} diff --git a/src/Translations/Translations.php b/src/Translations/Translations.php index 7629dc8c4..087708e97 100644 --- a/src/Translations/Translations.php +++ b/src/Translations/Translations.php @@ -523,6 +523,8 @@ public function getTranslations() 'notificationPendingApproval' => $this->module->l('Your payment needs to be approved, please click the button below.', 'translations'), 'notificationPayerActionRequired' => $this->module->l('Your payment needs to be authenticated, please click the button below.', 'translations'), 'fundingSource' => $this->module->l('Funding source', 'translations'), + 'orderIdentifier' => $this->module->l('Order identifier', 'translations'), + 'orderStatus' => $this->module->l('Order status', 'translations'), 'transactionIdentifier' => $this->module->l('Transaction identifier', 'translations'), 'transactionStatus' => $this->module->l('Transaction status', 'translations'), 'amountPaid' => $this->module->l('Amount paid', 'translations'), diff --git a/src/ValidateOrder.php b/src/ValidateOrder.php index 712d91236..bd1a2cbc5 100644 --- a/src/ValidateOrder.php +++ b/src/ValidateOrder.php @@ -201,9 +201,9 @@ public function validateOrder($payload) $payPalProcessorResponse->throwException(); } } - /** @var CacheInterface $paypalOrderCache */ - $paypalOrderCache = $module->getService('ps_checkout.cache.paypal.order'); - $paypalOrderCache->set($response['body']['id'], $response['body']); + /** @var CacheInterface $orderPayPalCache */ + $orderPayPalCache = $module->getService('ps_checkout.cache.paypal.order'); + $orderPayPalCache->set($response['body']['id'], $response['body']); if (false === $psCheckoutCart) { $psCheckoutCart = new \PsCheckoutCart(); diff --git a/tests/Unit/Order/State/Service/CheckOrderStateTest.php b/tests/Unit/Order/State/Service/CheckOrderStateTest.php deleted file mode 100644 index 0cee5273a..000000000 --- a/tests/Unit/Order/State/Service/CheckOrderStateTest.php +++ /dev/null @@ -1,165 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace Tests\Unit\Order\State\Service; - -use PHPUnit\Framework\TestCase; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; -use PrestaShop\Module\PrestashopCheckout\Order\State\Service\CheckOrderState; - -class CheckOrderStateTest extends TestCase -{ - /** - * @dataProvider dataProvider - */ - public function testGetNewOrderState($currentOrderStateId, $newOrderStateId, $expectedResult) - { - $checkOrderState = new CheckOrderState(); - $result = $checkOrderState->isOrderStateTransitionAvailable($currentOrderStateId, $newOrderStateId); - $this->assertEquals($expectedResult, $result); - } - - public function dataProvider() - { - return [ - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::CANCELED, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::PAYMENT_ERROR, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::REFUNDED, true], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, true], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::PAYMENT_ACCEPTED, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::REFUNDED, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, true], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::AUTHORIZED, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, true], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::PAYMENT_ERROR, true], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, true], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::PARTIALLY_PAID, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::REFUNDED, true], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::PARTIALLY_REFUNDED, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::CANCELED, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::PAYMENT_ERROR, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::PARTIALLY_PAID, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::WAITING_CAPTURE, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, true], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::CANCELED, true], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::PAYMENT_ERROR, true], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::PAYMENT_ACCEPTED, true], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::REFUNDED, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::AUTHORIZED, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::PARTIALLY_PAID, true], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::PARTIALLY_REFUNDED, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::WAITING_CAPTURE, false], - [OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, false], - ]; - } -} diff --git a/tests/Unit/PayPal/Order/CheckTransitionPayPalOrderStatusServiceTest.php b/tests/Unit/PayPal/Order/CheckTransitionPayPalOrderStatusServiceTest.php index 4b1ac839f..378f1b97d 100644 --- a/tests/Unit/PayPal/Order/CheckTransitionPayPalOrderStatusServiceTest.php +++ b/tests/Unit/PayPal/Order/CheckTransitionPayPalOrderStatusServiceTest.php @@ -33,8 +33,7 @@ class CheckTransitionPayPalOrderStatusServiceTest extends TestCase public function testCheckAvailableStatus($oldStatus, $newStatus, $expectedResult) { $checkTransition = new CheckTransitionPayPalOrderStatusService(); - $result = $checkTransition->checkAvailableStatus($oldStatus, $newStatus); - $this->assertEquals($expectedResult, $result); + $this->assertEquals($expectedResult, $checkTransition->checkAvailableStatus($oldStatus, $newStatus), sprintf('Transition from %s to %s should be %s', $oldStatus, $newStatus, $expectedResult ? 'allowed' : 'not allowed')); } public function statusProvider() @@ -47,34 +46,39 @@ public function statusProvider() [PayPalOrderStatus::CREATED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, true], [PayPalOrderStatus::CREATED, PayPalOrderStatus::VOIDED, true], [PayPalOrderStatus::CREATED, PayPalOrderStatus::COMPLETED, true], + [PayPalOrderStatus::CREATED, PayPalOrderStatus::CANCELED, true], [PayPalOrderStatus::SAVED, PayPalOrderStatus::CREATED, false], [PayPalOrderStatus::SAVED, PayPalOrderStatus::SAVED, false], [PayPalOrderStatus::SAVED, PayPalOrderStatus::APPROVED, false], [PayPalOrderStatus::SAVED, PayPalOrderStatus::PENDING_APPROVAL, false], [PayPalOrderStatus::SAVED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, false], - [PayPalOrderStatus::SAVED, PayPalOrderStatus::VOIDED, false], + [PayPalOrderStatus::SAVED, PayPalOrderStatus::VOIDED, true], [PayPalOrderStatus::SAVED, PayPalOrderStatus::COMPLETED, false], [PayPalOrderStatus::APPROVED, PayPalOrderStatus::CREATED, false], [PayPalOrderStatus::APPROVED, PayPalOrderStatus::SAVED, false], [PayPalOrderStatus::APPROVED, PayPalOrderStatus::APPROVED, false], [PayPalOrderStatus::APPROVED, PayPalOrderStatus::PENDING_APPROVAL, false], - [PayPalOrderStatus::APPROVED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, false], + [PayPalOrderStatus::APPROVED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, true], [PayPalOrderStatus::APPROVED, PayPalOrderStatus::VOIDED, false], - [PayPalOrderStatus::APPROVED, PayPalOrderStatus::COMPLETED, false], + [PayPalOrderStatus::APPROVED, PayPalOrderStatus::COMPLETED, true], + [PayPalOrderStatus::APPROVED, PayPalOrderStatus::CANCELED, true], + [PayPalOrderStatus::APPROVED, PayPalOrderStatus::REVERSED, true], [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::CREATED, false], - [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::SAVED, true], + [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::SAVED, false], [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::APPROVED, true], [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::PENDING_APPROVAL, false], - [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::PAYER_ACTION_REQUIRED, false], - [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::VOIDED, true], + [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::PAYER_ACTION_REQUIRED, true], + [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::VOIDED, false], [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::COMPLETED, false], + [PayPalOrderStatus::PENDING_APPROVAL, PayPalOrderStatus::CANCELED, true], [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::CREATED, false], - [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::SAVED, true], - [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::APPROVED, false], - [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::PENDING_APPROVAL, false], + [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::SAVED, false], + [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::APPROVED, true], + [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::PENDING_APPROVAL, true], [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, false], - [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::VOIDED, true], + [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::VOIDED, false], [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::COMPLETED, true], + [PayPalOrderStatus::PAYER_ACTION_REQUIRED, PayPalOrderStatus::CANCELED, true], [PayPalOrderStatus::VOIDED, PayPalOrderStatus::CREATED, false], [PayPalOrderStatus::VOIDED, PayPalOrderStatus::SAVED, false], [PayPalOrderStatus::VOIDED, PayPalOrderStatus::APPROVED, false], @@ -88,7 +92,7 @@ public function statusProvider() [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::PENDING_APPROVAL, false], [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::PAYER_ACTION_REQUIRED, false], [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::VOIDED, false], - [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::COMPLETED, true], + [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::COMPLETED, false], ]; } @@ -101,7 +105,7 @@ public function testInvalidValueThrowsException($oldStatus, $newStatus, $expecte $this->expectExceptionCode($expectedException['exception_code']); $this->expectExceptionMessage($expectedException['exception_message']); $checkTransition = new CheckTransitionPayPalOrderStatusService(); - $result = $checkTransition->checkAvailableStatus($oldStatus, $newStatus); + $checkTransition->checkAvailableStatus($oldStatus, $newStatus); } public function invalidStatusProvider() diff --git a/tests/Unit/PayPal/Order/Comparator/PayPalOrderComparatorTest.php b/tests/Unit/PayPal/Order/Comparator/PayPalOrderComparatorTest.php deleted file mode 100644 index 9b21323a6..000000000 --- a/tests/Unit/PayPal/Order/Comparator/PayPalOrderComparatorTest.php +++ /dev/null @@ -1,894 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace Tests\Unit\PayPal\Order\Comparator; - -use PHPUnit\Framework\TestCase; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Comparator\PayPalOrderComparator; -use Symfony\Component\Cache\Simple\ArrayCache; - -class PayPalOrderComparatorTest extends TestCase -{ - public function testCheckPayPalCreatedIsDifferent() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalCreated(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - $secondPayPalOrder = json_decode($this->getSecondOrderPayPalCreated(), true); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondPayPalOrder)); - } - - public function testCheckPayPalCreatedIsSimilar() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalCreated(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstPayPalOrder)); - } - - public function testCheckPayPalPayerActionRequiredIsDifferent() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalPayerActionRequired(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - $secondPayPalOrder = json_decode($this->getSecondOrderPayPalPayerActionRequired(), true); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondPayPalOrder)); - } - - public function testCheckPayPalPayerActionRequiredIsSimilar() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalPayerActionRequired(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstPayPalOrder)); - } - - public function testCheckPayPalApprovedIsDifferent() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalApproved(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - $secondPayPalOrder = json_decode($this->getSecondOrderPayPalApproved(), true); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondPayPalOrder)); - } - - public function testCheckPayPalApprovedIsSimilar() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalApproved(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstPayPalOrder)); - } - - public function testCheckPayPalCompletedIsDifferent() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalCompleted(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - $secondPayPalOrder = json_decode($this->getSecondOrderPayPalCompleted(), true); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondPayPalOrder)); - } - - public function testCheckPayPalCompletedIsSimilar() - { - $firstPayPalOrder = json_decode($this->getFirstOrderPayPalCompleted(), true); - $cache = new ArrayCache(); - $cache->set($firstPayPalOrder['id'], $firstPayPalOrder); - - $comparator = new PayPalOrderComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstPayPalOrder)); - } - - public function getFirstOrderPayPalCreated() - { - return '{ - "id": "03V05332DU5412024", - "intent": "CAPTURE", - "status": "CREATED", - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ] - } - ], - "create_time": "2023-05-23T09:27:26Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - }, - { - "href": "https://www.sandbox.paypal.com/checkoutnow?token=03V05332DU5412024", - "rel": "approve", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "update", - "method": "PATCH" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024/capture", - "rel": "capture", - "method": "POST" - } - ] - }'; - } - - public function getSecondOrderPayPalCreated() - { - return '{ - "id": "747FL082645FO0392", - "intent": "CAPTURE", - "status": "CREATED", - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ] - } - ], - "create_time": "2023-05-23T09:27:26Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - }, - { - "href": "https://www.sandbox.paypal.com/checkoutnow?token=03V05332DU5412024", - "rel": "approve", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "update", - "method": "PATCH" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024/capture", - "rel": "capture", - "method": "POST" - } - ] - }'; - } - - public function getFirstOrderPayPalPayerActionRequired() - { - return '{ - "id": "1B4263080R024183U", - "intent": "CAPTURE", - "status": "PAYER_ACTION_REQUIRED", - "payment_source": { - "paypal": { - "email_address": "john@doe.com", - "name": { - "given_name": "John", - "surname": "Doe" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E", - "display_data": { - "brand_name": "EXAMPLE INC" - } - }, - "soft_descriptor": "JOHNMERCHAN", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ] - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "john@doe.com" - }, - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/1B4263080R024183U", - "rel": "self", - "method": "GET" - }, - { - "href": "https://www.sandbox.paypal.com/checkoutnow?token=1B4263080R024183U", - "rel": "payer-action", - "method": "GET" - } - ] - }'; - } - - public function getSecondOrderPayPalPayerActionRequired() - { - return '{ - "id": "8J7F990284F73L011", - "intent": "CAPTURE", - "status": "PAYER_ACTION_REQUIRED", - "payment_source": { - "paypal": { - "email_address": "john@doe.com", - "name": { - "given_name": "John", - "surname": "Doe" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E", - "display_data": { - "brand_name": "EXAMPLE INC" - } - }, - "soft_descriptor": "JOHNMERCHAN", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ] - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "john@doe.com" - }, - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/1B4263080R024183U", - "rel": "self", - "method": "GET" - }, - { - "href": "https://www.sandbox.paypal.com/checkoutnow?token=1B4263080R024183U", - "rel": "payer-action", - "method": "GET" - } - ] - }'; - } - - public function getFirstOrderPayPalApproved() - { - return '{ - "id": "03V05332DU5412024", - "intent": "CAPTURE", - "status": "APPROVED", - "payment_source": { - "paypal": { - "email_address": "sb-26qa712980505@personal.example.com", - "account_id": "VRT593XYPLRRJ", - "name": { - "given_name": "John", - "surname": "Doe" - }, - "address": { - "country_code": "ES" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "soft_descriptor": "JOHNMERCHAN", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ], - "shipping": { - "name": { - "full_name": "John Doe" - }, - "address": { - "address_line_1": "calle Vilamar� 76993- 17469", - "admin_area_2": "Albacete", - "admin_area_1": "Albacete", - "postal_code": "02001", - "country_code": "ES" - } - } - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "sb-26qa712980505@personal.example.com", - "payer_id": "VRT593XYPLRRJ", - "address": { - "country_code": "ES" - } - }, - "create_time": "2023-05-23T09:27:26Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "update", - "method": "PATCH" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024/capture", - "rel": "capture", - "method": "POST" - } - ] - }'; - } - - public function getSecondOrderPayPalApproved() - { - return '{ - "id": "8U7635BB00L102904", - "intent": "CAPTURE", - "status": "APPROVED", - "payment_source": { - "paypal": { - "email_address": "sb-26qa712980505@personal.example.com", - "account_id": "VRT593XYPLRRJ", - "name": { - "given_name": "John", - "surname": "Doe" - }, - "address": { - "country_code": "ES" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "soft_descriptor": "JOHNMERCHAN", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "quantity": "1", - "description": "Green XL" - } - ], - "shipping": { - "name": { - "full_name": "John Doe" - }, - "address": { - "address_line_1": "calle Vilamar� 76993- 17469", - "admin_area_2": "Albacete", - "admin_area_1": "Albacete", - "postal_code": "02001", - "country_code": "ES" - } - } - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "sb-26qa712980505@personal.example.com", - "payer_id": "VRT593XYPLRRJ", - "address": { - "country_code": "ES" - } - }, - "create_time": "2023-05-23T09:27:26Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "update", - "method": "PATCH" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024/capture", - "rel": "capture", - "method": "POST" - } - ] - }'; - } - - public function getFirstOrderPayPalCompleted() - { - return '{ - "id": "2873R0D373T084752", - "intent": "CAPTURE", - "status": "COMPLETED", - "payment_source": { - "paypal": { - "email_address": "sb-26qa712980505@personal.example.com", - "account_id": "VRT593XYPLRRJ", - "name": { - "given_name": "John", - "surname": "Doe" - }, - "address": { - "country_code": "ES" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - }, - "shipping": { - "currency_code": "USD", - "value": "0.00" - }, - "handling": { - "currency_code": "USD", - "value": "0.00" - }, - "insurance": { - "currency_code": "USD", - "value": "0.00" - }, - "shipping_discount": { - "currency_code": "USD", - "value": "0.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "description": "T-Shirt", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "tax": { - "currency_code": "USD", - "value": "0.00" - }, - "quantity": "1", - "description": "Green XL", - "image_url": "" - } - ], - "shipping": { - "name": { - "full_name": "John Doe" - }, - "address": { - "address_line_1": "calle Vilamar� 76993- 17469", - "admin_area_2": "Albacete", - "admin_area_1": "Albacete", - "postal_code": "02001", - "country_code": "ES" - } - }, - "payments": { - "captures": [ - { - "id": "0C0885549K6295627", - "status": "COMPLETED", - "amount": { - "currency_code": "USD", - "value": "100.00" - }, - "final_capture": true, - "disbursement_mode": "INSTANT", - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "paypal_fee": { - "currency_code": "USD", - "value": "5.48" - }, - "net_amount": { - "currency_code": "USD", - "value": "94.52" - } - }, - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "up", - "method": "GET" - } - ], - "create_time": "2023-05-23T09:29:15Z", - "update_time": "2023-05-23T09:29:15Z" - } - ] - } - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "sb-26qa712980505@personal.example.com", - "payer_id": "VRT593XYPLRRJ", - "address": { - "country_code": "ES" - } - }, - "create_time": "2023-05-23T09:27:26Z", - "update_time": "2023-05-23T09:36:15Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - } - ] - }'; - } - - public function getSecondOrderPayPalCompleted() - { - return '{ - "id": "2873R0D373T084752", - "intent": "CAPTURE", - "status": "COMPLETED", - "payment_source": { - "paypal": { - "email_address": "sb-26qa712980505@personal.example.com", - "account_id": "VRT593XYPLRRJ", - "name": { - "given_name": "John", - "surname": "Doe" - }, - "address": { - "country_code": "ES" - } - } - }, - "purchase_units": [ - { - "reference_id": "default", - "amount": { - "currency_code": "USD", - "value": "100.00", - "breakdown": { - "item_total": { - "currency_code": "USD", - "value": "100.00" - }, - "shipping": { - "currency_code": "USD", - "value": "0.00" - }, - "handling": { - "currency_code": "USD", - "value": "0.00" - }, - "insurance": { - "currency_code": "USD", - "value": "0.00" - }, - "shipping_discount": { - "currency_code": "USD", - "value": "0.00" - } - } - }, - "payee": { - "email_address": "john_merchant@example.com", - "merchant_id": "C7CYMKZDG8D6E" - }, - "description": "T-Shirt", - "items": [ - { - "name": "T-Shirt", - "unit_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "tax": { - "currency_code": "USD", - "value": "0.00" - }, - "quantity": "1", - "description": "Green XL", - "image_url": "" - } - ], - "shipping": { - "name": { - "full_name": "John Doe" - }, - "address": { - "address_line_1": "calle Vilamar� 76993- 17469", - "admin_area_2": "Albacete", - "admin_area_1": "Albacete", - "postal_code": "02001", - "country_code": "ES" - } - }, - "payments": { - "captures": [ - { - "id": "0C0885549K6295627", - "status": "COMPLETED", - "amount": { - "currency_code": "USD", - "value": "100.00" - }, - "final_capture": true, - "disbursement_mode": "INSTANT", - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "paypal_fee": { - "currency_code": "USD", - "value": "5.48" - }, - "net_amount": { - "currency_code": "USD", - "value": "94.52" - } - }, - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "up", - "method": "GET" - } - ], - "create_time": "2023-05-23T09:29:15Z", - "update_time": "2023-05-23T09:29:15Z" - } - ] - } - } - ], - "payer": { - "name": { - "given_name": "John", - "surname": "Doe" - }, - "email_address": "sb-26qa712980505@personal.example.com", - "payer_id": "VRT593XYPLRRJ", - "address": { - "country_code": "ES" - } - }, - "create_time": "2023-05-23T09:27:26Z", - "update_time": "2023-05-23T09:29:15Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "self", - "method": "GET" - } - ] - }'; - } -} diff --git a/tests/Unit/PayPal/Payment/Authorization/CheckTransitionPayPalAuthorizationStatusServiceTest.php b/tests/Unit/PayPal/Payment/Authorization/CheckTransitionPayPalAuthorizationStatusServiceTest.php index e1d20279e..a5492cdff 100644 --- a/tests/Unit/PayPal/Payment/Authorization/CheckTransitionPayPalAuthorizationStatusServiceTest.php +++ b/tests/Unit/PayPal/Payment/Authorization/CheckTransitionPayPalAuthorizationStatusServiceTest.php @@ -33,8 +33,7 @@ class CheckTransitionPayPalAuthorizationStatusServiceTest extends TestCase public function testCheckAvailableStatus($oldStatus, $newStatus, $expectedResult) { $checkTransition = new CheckTransitionPayPalAuthorizationStatusService(); - $result = $checkTransition->checkAvailableStatus($oldStatus, $newStatus); - $this->assertEquals($expectedResult, $result); + $this->assertEquals($expectedResult, $checkTransition->checkAvailableStatus($oldStatus, $newStatus), sprintf('Transition from %s to %s should be %s', $oldStatus, $newStatus, $expectedResult ? 'allowed' : 'not allowed')); } public function statusProvider() diff --git a/tests/Unit/PayPal/Payment/Capture/CheckTransitionPayPalCaptureStatusServiceTest.php b/tests/Unit/PayPal/Payment/Capture/CheckTransitionPayPalCaptureStatusServiceTest.php index 651bf5975..87b06de8f 100644 --- a/tests/Unit/PayPal/Payment/Capture/CheckTransitionPayPalCaptureStatusServiceTest.php +++ b/tests/Unit/PayPal/Payment/Capture/CheckTransitionPayPalCaptureStatusServiceTest.php @@ -33,8 +33,7 @@ class CheckTransitionPayPalCaptureStatusServiceTest extends TestCase public function testCheckAvailableStatus($oldStatus, $newStatus, $expectedResult) { $checkTransition = new CheckTransitionPayPalCaptureStatusService(); - $result = $checkTransition->checkAvailableStatus($oldStatus, $newStatus); - $this->assertEquals($expectedResult, $result); + $this->assertEquals($expectedResult, $checkTransition->checkAvailableStatus($oldStatus, $newStatus), sprintf('Transition from %s to %s should be %s', $oldStatus, $newStatus, $expectedResult ? 'allowed' : 'not allowed')); } public function statusProvider() diff --git a/tests/Unit/PayPal/Payment/Capture/Comparator/PayPalCaptureComparatorTest.php b/tests/Unit/PayPal/Payment/Capture/Comparator/PayPalCaptureComparatorTest.php deleted file mode 100644 index e2eec3f96..000000000 --- a/tests/Unit/PayPal/Payment/Capture/Comparator/PayPalCaptureComparatorTest.php +++ /dev/null @@ -1,393 +0,0 @@ -getFirstCaptureCompleted(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - $secondCaptureOrder = json_decode($this->getSecondCaptureCompleted(), true); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondCaptureOrder)); - } - - public function testCheckCaptureCompletedIsSimilar() - { - $firstCaptureOrder = json_decode($this->getFirstCaptureCompleted(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstCaptureOrder)); - } - - public function testCheckCapturePendingIsDifferent() - { - $firstCaptureOrder = json_decode($this->getFirstCapturePending(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - $secondCaptureOrder = json_decode($this->getSecondCapturePending(), true); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondCaptureOrder)); - } - - public function testCheckCapturePendingIsSimilar() - { - $firstCaptureOrder = json_decode($this->getFirstCapturePending(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstCaptureOrder)); - } - - public function testCheckCaptureDeniedIsDifferent() - { - $firstCaptureOrder = json_decode($this->getFirstCaptureDenied(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - $secondCaptureOrder = json_decode($this->getSecondCaptureDenied(), true); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(false, $comparator->compare($secondCaptureOrder)); - } - - public function testCheckCaptureDeniedIsSimilar() - { - $firstCaptureOrder = json_decode($this->getFirstCaptureDenied(), true); - $cache = new ArrayCache(); - $cache->set($firstCaptureOrder['id'], $firstCaptureOrder); - - $comparator = new PayPalCaptureComparator($cache); - - $this->assertEquals(true, $comparator->compare($firstCaptureOrder)); - } - - public function getFirstCaptureCompleted() - { - return '{ - "id": "0C0885549K6295627", - "amount": { - "currency_code": "USD", - "value": "100.00" - }, - "final_capture": true, - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "disbursement_mode": "INSTANT", - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "USD", - "value": "100.00" - }, - "paypal_fee": { - "currency_code": "USD", - "value": "5.48" - }, - "net_amount": { - "currency_code": "USD", - "value": "94.52" - } - }, - "status": "COMPLETED", - "supplementary_data": { - "related_ids": { - "order_id": "03V05332DU5412024" - } - }, - "create_time": "2023-05-23T09:29:15Z", - "update_time": "2023-05-23T09:29:15Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "up", - "method": "GET" - } - ] - }'; - } - - public function getSecondCaptureCompleted() - { - return '{ - "id": "86LF9BA1ATA95FF23", - "amount": { - "currency_code": "USD", - "value": "88.00" - }, - "final_capture": true, - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "disbursement_mode": "INSTANT", - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "USD", - "value": "88.00" - }, - "paypal_fee": { - "currency_code": "USD", - "value": "5.48" - }, - "net_amount": { - "currency_code": "USD", - "value": "83.52" - } - }, - "status": "COMPLETED", - "supplementary_data": { - "related_ids": { - "order_id": "03V05332DU5412024" - } - }, - "create_time": "2023-05-23T09:29:15Z", - "update_time": "2023-05-23T09:29:15Z", - "links": [ - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.sandbox.paypal.com/v2/payments/captures/0C0885549K6295627/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.sandbox.paypal.com/v2/checkout/orders/03V05332DU5412024", - "rel": "up", - "method": "GET" - } - ] - }'; - } - - public function getFirstCapturePending() - { - return '{ - "id": "8R008448MC910842W", - "disbursement_mode": "INSTANT", - "amount": { - "value": "60.00", - "currency_code": "USD" - }, - "seller_protection": { - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ], - "status": "ELIGIBLE" - }, - "supplementary_data": { - "related_ids": { - "order_id": "8YS138211R2394904" - } - }, - "update_time": "2023-04-28T14:30:40Z", - "create_time": "2023-04-28T14:30:40Z", - "final_capture": false, - "invoice_id": "1682692239", - "links": [ - { - "method": "GET", - "rel": "self", - "href": "https://api.sandbox.paypal.com/v2/payments/captures/8R008448MC910842W" - }, - { - "method": "POST", - "rel": "refund", - "href": "https://api.sandbox.paypal.com/v2/payments/captures/8R008448MC910842W/refund" - }, - { - "method": "GET", - "rel": "up", - "href": "https://api.sandbox.paypal.com/v2/payments/authorizations/1X092516FU319661J" - } - ], - "status_details": { - "reason": "RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION" - }, - "status": "PENDING" - }'; - } - - public function getSecondCapturePending() - { - return '{ - "id": "9IL74V1TT0R172947", - "disbursement_mode": "INSTANT", - "amount": { - "value": "60.00", - "currency_code": "USD" - }, - "seller_protection": { - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ], - "status": "ELIGIBLE" - }, - "supplementary_data": { - "related_ids": { - "order_id": "8YS138211R2394904" - } - }, - "update_time": "2023-04-28T14:30:40Z", - "create_time": "2023-04-28T14:30:40Z", - "final_capture": false, - "invoice_id": "1682692239", - "links": [ - { - "method": "GET", - "rel": "self", - "href": "https://api.sandbox.paypal.com/v2/payments/captures/8R008448MC910842W" - }, - { - "method": "POST", - "rel": "refund", - "href": "https://api.sandbox.paypal.com/v2/payments/captures/8R008448MC910842W/refund" - }, - { - "method": "GET", - "rel": "up", - "href": "https://api.sandbox.paypal.com/v2/payments/authorizations/1X092516FU319661J" - } - ], - "status_details": { - "reason": "RECEIVING_PREFERENCE_MANDATES_MANUAL_ACTION" - }, - "status": "PENDING" - }'; - } - - public function getFirstCaptureDenied() - { - return '{ - "id": "7NW873794T343360M", - "amount": { - "currency_code": "AUD", - "value": "2.51" - }, - "final_capture": true, - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "AUD", - "value": "2.51" - }, - "net_amount": { - "currency_code": "AUD", - "value": "2.51" - } - }, - "status": "DECLINED", - "create_time": "2019-02-14T22:18:14Z", - "update_time": "2019-02-14T22:20:01Z", - "links": [ - { - "href": "https://api.paypal.com/v2/payments/captures/7NW873794T343360M", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.paypal.com/v2/payments/captures/7NW873794T343360M/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.paypal.com/v2/payments/authorizations/2W543679LP5841156", - "rel": "up", - "method": "GET" - } - ] - }'; - } - - public function getSecondCaptureDenied() - { - return '{ - "id": "8J7479BF00293401L", - "amount": { - "currency_code": "AUD", - "value": "28.99" - }, - "final_capture": true, - "seller_protection": { - "status": "ELIGIBLE", - "dispute_categories": [ - "ITEM_NOT_RECEIVED", - "UNAUTHORIZED_TRANSACTION" - ] - }, - "seller_receivable_breakdown": { - "gross_amount": { - "currency_code": "AUD", - "value": "28.99" - }, - "net_amount": { - "currency_code": "AUD", - "value": "28.99" - } - }, - "status": "DECLINED", - "create_time": "2019-02-14T22:18:14Z", - "update_time": "2019-02-14T22:20:01Z", - "links": [ - { - "href": "https://api.paypal.com/v2/payments/captures/7NW873794T343360M", - "rel": "self", - "method": "GET" - }, - { - "href": "https://api.paypal.com/v2/payments/captures/7NW873794T343360M/refund", - "rel": "refund", - "method": "POST" - }, - { - "href": "https://api.paypal.com/v2/payments/authorizations/2W543679LP5841156", - "rel": "up", - "method": "GET" - } - ] - }'; - } -} diff --git a/tests/Unit/PayPal/Payment/Refund/CheckTransitionPayPalRefundStatusServiceTest.php b/tests/Unit/PayPal/Payment/Refund/CheckTransitionPayPalRefundStatusServiceTest.php index 98de6b6cb..61bad4382 100644 --- a/tests/Unit/PayPal/Payment/Refund/CheckTransitionPayPalRefundStatusServiceTest.php +++ b/tests/Unit/PayPal/Payment/Refund/CheckTransitionPayPalRefundStatusServiceTest.php @@ -33,8 +33,7 @@ class CheckTransitionPayPalRefundStatusServiceTest extends TestCase public function testCheckAvailableStatus($oldStatus, $newStatus, $expectedResult) { $checkTransition = new CheckTransitionPayPalRefundStatusService(); - $result = $checkTransition->checkAvailableStatus($oldStatus, $newStatus); - $this->assertEquals($expectedResult, $result); + $this->assertEquals($expectedResult, $checkTransition->checkAvailableStatus($oldStatus, $newStatus), sprintf('Transition from %s to %s should be %s', $oldStatus, $newStatus, $expectedResult ? 'allowed' : 'not allowed')); } public function statusProvider() diff --git a/views/templates/admin/ajaxPayPalOrder.tpl b/views/templates/admin/ajaxPayPalOrder.tpl index 200e37209..2b446fcfe 100644 --- a/views/templates/admin/ajaxPayPalOrder.tpl +++ b/views/templates/admin/ajaxPayPalOrder.tpl @@ -20,16 +20,26 @@ {if !$orderPayPal}
- -
-

{l s='The PayPal account that was used to create this order is no longer linked to the PrestaShop Checkout module.' mod='ps_checkout'}

-

{l s='In order to see this information, please reconnect the correct PayPal account.' mod='ps_checkout'}

-
- + {if $psCheckoutCart->getPaypalStatus() === 'CANCELED'} + + {elseif $psCheckoutCart->getPaypalStatus() === 'REVERSED'} + + {else} + +
+

{l s='The PayPal account that was used to create this order is no longer linked to the PrestaShop Checkout module.' mod='ps_checkout'}

+

{l s='In order to see this information, please reconnect the correct PayPal account.' mod='ps_checkout'}

+
+ + {/if}
{/if} diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl index 05abf9cee..698ff896d 100644 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -20,16 +20,26 @@ {if !$orderPayPal}
- -
-

{l s='The PayPal account that was used to create this order is no longer linked to the PrestaShop Checkout module.' mod='ps_checkout'}

-

{l s='In order to see this information, please reconnect the correct PayPal account.' mod='ps_checkout'}

-
- + {if $psCheckoutCart->getPaypalStatus() === 'CANCELED'} + + {elseif $psCheckoutCart->getPaypalStatus() === 'REVERSED'} + + {else} + +
+

{l s='The PayPal account that was used to create this order is no longer linked to the PrestaShop Checkout module.' mod='ps_checkout'}

+

{l s='In order to see this information, please reconnect the correct PayPal account.' mod='ps_checkout'}

+
+ + {/if}
{/if} diff --git a/views/templates/hook/displayOrderConfirmation.tpl b/views/templates/hook/displayOrderConfirmation.tpl index c6b3da041..a51780849 100644 --- a/views/templates/hook/displayOrderConfirmation.tpl +++ b/views/templates/hook/displayOrderConfirmation.tpl @@ -24,7 +24,7 @@ {/if} - {if $orderPayPalTransactionStatus === 'PENDING'} + {if $orderPayPalTransactionStatus === 'PENDING' || $orderPayPalStatus === 'APPROVED' || $orderPayPalStatus === 'CREATED'}
{l s='Your order is waiting for payment confirmation. You will receive an email when your payment has been validated. You can also check the order status in your order history in your account.' mod='ps_checkout'}
diff --git a/views/templates/hook/displayOrderDetail.tpl b/views/templates/hook/displayOrderDetail.tpl index 763a13a2a..e4d9258ec 100644 --- a/views/templates/hook/displayOrderDetail.tpl +++ b/views/templates/hook/displayOrderDetail.tpl @@ -51,6 +51,11 @@
{$orderPayPalTransactionStatusTranslated|escape:'html':'UTF-8'}
{$translations.amountPaid|escape:'html':'UTF-8'}
{$orderPayPalTransactionAmount|escape:'html':'UTF-8'}
+ {else} +
{$translations.orderIdentifier|escape:'html':'UTF-8'}
+
{$orderPayPalId|escape:'html':'UTF-8'}
+
{$translations.orderStatus|escape:'html':'UTF-8'}
+
{$orderPayPalStatus|escape:'html':'UTF-8'}
{/if} diff --git a/views/templates/hook/displayPaymentReturn.tpl b/views/templates/hook/displayPaymentReturn.tpl index 20ffd4009..182d1e906 100644 --- a/views/templates/hook/displayPaymentReturn.tpl +++ b/views/templates/hook/displayPaymentReturn.tpl @@ -37,6 +37,11 @@
{$orderPayPalTransactionStatusTranslated|escape:'html':'UTF-8'}
{$translations.amountPaid|escape:'html':'UTF-8'}
{$orderPayPalTransactionAmount|escape:'html':'UTF-8'}
+ {else} +
{$translations.orderIdentifier|escape:'html':'UTF-8'}
+
{$orderPayPalId|escape:'html':'UTF-8'}
+
{$translations.orderStatus|escape:'html':'UTF-8'}
+
{$orderPayPalStatus|escape:'html':'UTF-8'}
{/if} From dea9d1fe0e0abd3aa96675fc74adc1dc0a4e9880 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Jun 2023 12:10:16 +0200 Subject: [PATCH 011/343] Changes on upgrade script --- upgrade/upgrade-8.3.3.0.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index 337d12132..8c1b3e77b 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -38,6 +38,26 @@ function upgrade_module_8_3_3_0($module) $module->registerHook('displayPaymentReturn'); $module->registerHook('displayOrderDetail'); + $module->registerHook('displayHeader'); + + try { + $db = Db::getInstance(); + $db->delete( + 'pscheckout_cart', + 'paypal_order IS NULL' + ); + $db->update( + 'pscheckout_cart', + [ + 'paypal_token' => null, + 'paypal_token_expire' => null, + ], + 'paypal_token IS NOT NULL', + 0, + true + ); + } catch (Exception $e) { + } // Restore initial PrestaShop shop context if (Shop::CONTEXT_SHOP === $savedShopContext) { From 29712c37d08110eed93eeb0e968c6dc83fab9375 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Jun 2023 16:07:09 +0200 Subject: [PATCH 012/343] Fix error on create PENDING order --- .../Capture/EventSubscriber/PayPalCaptureEventSubscriber.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index f2a98d3ab..aacd69996 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -125,13 +125,13 @@ public static function getSubscribedEvents() } /** - * @param PayPalCaptureCompletedEvent $event + * @param PayPalCaptureEvent $event * * @return void * * @throws PayPalOrderException */ - public function createOrder(PayPalCaptureCompletedEvent $event) + public function createOrder(PayPalCaptureEvent $event) { $this->commandBus->handle(new CreateOrderCommand( $event->getPayPalOrderId()->getValue(), From 68dbb3eb837ce8247d04ca4e2d10833cfff73739 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Jun 2023 17:37:36 +0200 Subject: [PATCH 013/343] Fix QA feedbacks --- src/Dispatcher/OrderDispatcher.php | 12 ++++++------ .../EventSubscriber/PayPalOrderEventSubscriber.php | 12 +++++++++++- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Dispatcher/OrderDispatcher.php b/src/Dispatcher/OrderDispatcher.php index 69f88d8fa..039ec85c9 100644 --- a/src/Dispatcher/OrderDispatcher.php +++ b/src/Dispatcher/OrderDispatcher.php @@ -46,7 +46,7 @@ class OrderDispatcher implements Dispatcher const PS_CHECKOUT_PAYMENT_DENIED = 'PaymentCaptureDenied'; const PS_CHECKOUT_ORDER_APPROVED = 'CheckoutOrderApproved'; const PS_CHECKOUT_ORDER_COMPLETED = 'CheckoutOrderCompleted'; - const PS_CHECKOUT_ORDER_APPROVAL_REVERSED = 'CHECKOUT.PAYMENT-APPROVAL.REVERSED'; + const PS_CHECKOUT_ORDER_APPROVAL_REVERSED = 'CheckoutPaymentApprovalReversed'; /** * Dispatch the Event Type to manage the merchant status @@ -74,19 +74,19 @@ public function dispatchEventType($payload) switch ($payload['eventType']) { case static::PS_CHECKOUT_PAYMENT_COMPLETED: - $eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + $eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($payload['resource']['id'], $payload['orderId'], $payload['resource'])); break; case static::PS_CHECKOUT_PAYMENT_PENDING: - $eventDispatcher->dispatch(new PayPalCapturePendingEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + $eventDispatcher->dispatch(new PayPalCapturePendingEvent($payload['resource']['id'], $payload['orderId'], $payload['resource'])); break; case static::PS_CHECKOUT_PAYMENT_DENIED: - $eventDispatcher->dispatch(new PayPalCaptureDeclinedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + $eventDispatcher->dispatch(new PayPalCaptureDeclinedEvent($payload['resource']['id'], $payload['orderId'], $payload['resource'])); break; case static::PS_CHECKOUT_PAYMENT_REFUNDED: - $eventDispatcher->dispatch(new PayPalCaptureRefundedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + $eventDispatcher->dispatch(new PayPalCaptureRefundedEvent($payload['resource']['id'], $payload['orderId'], $payload['resource'])); break; case static::PS_CHECKOUT_PAYMENT_REVERSED: - $eventDispatcher->dispatch(new PayPalCaptureReversedEvent($payload['orderId'], $payload['resource']['id'], $payload['resource'])); + $eventDispatcher->dispatch(new PayPalCaptureReversedEvent($payload['resource']['id'], $payload['orderId'], $payload['resource'])); break; case static::PS_CHECKOUT_ORDER_APPROVED: $eventDispatcher->dispatch(new PayPalOrderApprovedEvent($payload['orderId'], $payload['resource'])); diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index 7313698e7..e23bd0d85 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -103,7 +103,7 @@ public static function getSubscribedEvents() ], PayPalOrderApprovalReversedEvent::class => [ ['saveApprovalReversedPayPalOrder'], - ['updateCache'], + ['clearCache'], ], ]; } @@ -235,4 +235,14 @@ public function updateCache(PayPalOrderEvent $event) $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $newOrderPayPal); } + + /** + * @param PayPalOrderApprovalReversedEvent $event + * + * @throws InvalidArgumentException + */ + public function clearCache(PayPalOrderApprovalReversedEvent $event) + { + $this->orderPayPalCache->delete($event->getOrderPayPalId()->getValue()); + } } From 11464b63ba2239eab2a7c6c95b04b419ba83ab51 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 22 Jun 2023 10:20:12 +0200 Subject: [PATCH 014/343] Update dependencies --- composer.lock | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/composer.lock b/composer.lock index acb07e15d..7a2e53a27 100644 --- a/composer.lock +++ b/composer.lock @@ -74,16 +74,16 @@ }, { "name": "giggsey/libphonenumber-for-php", - "version": "8.13.12", + "version": "8.13.14", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "218caeeeb224bf2f553597b5c3a1647ff936db64" + "reference": "31b94ef2aa349b76bb725f375e9cfa2e398a1620" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/218caeeeb224bf2f553597b5c3a1647ff936db64", - "reference": "218caeeeb224bf2f553597b5c3a1647ff936db64", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/31b94ef2aa349b76bb725f375e9cfa2e398a1620", + "reference": "31b94ef2aa349b76bb725f375e9cfa2e398a1620", "shasum": "" }, "require": { @@ -142,7 +142,7 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "source": "https://github.com/giggsey/libphonenumber-for-php" }, - "time": "2023-05-22T07:19:16+00:00" + "time": "2023-06-13T08:08:40+00:00" }, { "name": "giggsey/locale", @@ -3156,25 +3156,29 @@ }, { "name": "doctrine/deprecations", - "version": "v1.1.0", + "version": "v1.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "8cffffb2218e01f3b370bf763e00e81697725259" + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/8cffffb2218e01f3b370bf763e00e81697725259", - "reference": "8cffffb2218e01f3b370bf763e00e81697725259", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", "shasum": "" }, "require": { - "php": "^7.1|^8.0" + "php": "^7.1 || ^8.0" }, "require-dev": { "doctrine/coding-standard": "^9", - "phpunit/phpunit": "^7.5|^8.5|^9.5", - "psr/log": "^1|^2|^3" + "phpstan/phpstan": "1.4.10 || 1.10.15", + "phpstan/phpstan-phpunit": "^1.0", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "psalm/plugin-phpunit": "0.18.4", + "psr/log": "^1 || ^2 || ^3", + "vimeo/psalm": "4.30.0 || 5.12.0" }, "suggest": { "psr/log": "Allows logging deprecations via PSR-3 logger implementation" @@ -3193,9 +3197,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.0" + "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" }, - "time": "2023-05-29T18:55:17+00:00" + "time": "2023-06-03T09:27:29+00:00" }, { "name": "doctrine/instantiator", From 3cb3e7a10cd045f1fc72ea9e94b35b81e7f5523f Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Thu, 22 Jun 2023 17:33:07 +0200 Subject: [PATCH 015/343] Adding FundingSource installation into upgrade script --- upgrade/upgrade-8.3.3.0.php | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index 8c1b3e77b..a0fe4043d 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -40,6 +40,29 @@ function upgrade_module_8_3_3_0($module) $module->registerHook('displayOrderDetail'); $module->registerHook('displayHeader'); + // Installing FundingSource if table pscheckout_funding_source is empty + try { + $db = Db::getInstance(); + if (!$db->getValue("SELECT COUNT(*) FROM ". _DB_PREFIX_ ."'pscheckout_funding_source'")) { + $db->insert( + 'pscheckout_funding_source', + [ + ['name' => 'paypal', 'active' => 1, 'position' => 1, 'id_shop' => $savedShopId], + ['name' => 'paylater', 'active' => 1, 'position' => 2, 'id_shop' => $savedShopId], + ['name' => 'card', 'active' => 1, 'position' => 3, 'id_shop' => $savedShopId], + ['name' => 'bancontact', 'active' => 1, 'position' => 4, 'id_shop' => $savedShopId], + ['name' => 'eps', 'active' => 1, 'position' => 5, 'id_shop' => $savedShopId], + ['name' => 'giropay', 'active' => 1, 'position' => 6, 'id_shop' => $savedShopId], + ['name' => 'ideal', 'active' => 1, 'position' => 7, 'id_shop' => $savedShopId], + ['name' => 'mybank', 'active' => 1, 'position' => 8, 'id_shop' => $savedShopId], + ['name' => 'p24', 'active' => 1, 'position' => 9, 'id_shop' => $savedShopId], + ['name' => 'sofort', 'active' => 1, 'position' => 10, 'id_shop' => $savedShopId], + ] + ); + } + } catch (Exception $e) { + } + try { $db = Db::getInstance(); $db->delete( From 36ae129e55b6fcec6de809563dd36ce685a74d21 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Fri, 23 Jun 2023 14:28:52 +0200 Subject: [PATCH 016/343] Adding orderstatus check during update & improving fundingSource completion --- upgrade/upgrade-8.3.3.0.php | 231 ++++++++++++++++++++++++++++++++---- 1 file changed, 209 insertions(+), 22 deletions(-) diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index a0fe4043d..8dc2df067 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -40,31 +40,184 @@ function upgrade_module_8_3_3_0($module) $module->registerHook('displayOrderDetail'); $module->registerHook('displayHeader'); - // Installing FundingSource if table pscheckout_funding_source is empty try { $db = Db::getInstance(); - if (!$db->getValue("SELECT COUNT(*) FROM ". _DB_PREFIX_ ."'pscheckout_funding_source'")) { - $db->insert( - 'pscheckout_funding_source', - [ - ['name' => 'paypal', 'active' => 1, 'position' => 1, 'id_shop' => $savedShopId], - ['name' => 'paylater', 'active' => 1, 'position' => 2, 'id_shop' => $savedShopId], - ['name' => 'card', 'active' => 1, 'position' => 3, 'id_shop' => $savedShopId], - ['name' => 'bancontact', 'active' => 1, 'position' => 4, 'id_shop' => $savedShopId], - ['name' => 'eps', 'active' => 1, 'position' => 5, 'id_shop' => $savedShopId], - ['name' => 'giropay', 'active' => 1, 'position' => 6, 'id_shop' => $savedShopId], - ['name' => 'ideal', 'active' => 1, 'position' => 7, 'id_shop' => $savedShopId], - ['name' => 'mybank', 'active' => 1, 'position' => 8, 'id_shop' => $savedShopId], - ['name' => 'p24', 'active' => 1, 'position' => 9, 'id_shop' => $savedShopId], - ['name' => 'sofort', 'active' => 1, 'position' => 10, 'id_shop' => $savedShopId], - ] - ); + + // Installing FundingSource if table pscheckout_funding_source is empty or incomplete + $fundingSources = ['paypal', 'paylater', 'card', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort']; + $availableFundingSourcesByShops = []; + $maxPositionByShops = []; + $availableFundingSources = $db->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'pscheckout_funding_source'); + + if (!empty($availableFundingSources)) { + foreach ($availableFundingSources as $availableFundingSource) { + $currentPosition = (int) $availableFundingSource['position']; + $shopId = (int) $availableFundingSource['id_shop']; + if ( + !isset($maxPositionByShops[$shopId]) + || $maxPositionByShops[$shopId] < $currentPosition + ) { + $maxPositionByShops[$shopId] = $currentPosition; + } + $availableFundingSourcesByShops[$shopId][] = $availableFundingSource['name']; + } + } + + foreach (Shop::getShops(false, null, true) as $shopId) { + $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; + foreach ($fundingSources as $fundingSource) { + if (!in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true)) { + $db->insert( + 'pscheckout_funding_source', + [ + 'name' => pSQL($fundingSource), + 'active' => 1, + 'position' => (int) $currentPosition, + 'id_shop' => (int) $savedShopId, + ] + ); + ++$currentPosition; + } + } + } + + // Check module OrderState + $moduleOrderStates = [ + 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), + 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), + 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'), + 'PS_CHECKOUT_STATE_AUTHORIZED' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_AUTHORIZED'), + 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIAL_REFUND'), + 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CAPTURE'), + ]; + $moduleOrderStatesId = array_values($moduleOrderStates); + + $orderStateCollection = new PrestaShopCollection(OrderState::class); + $orderStateCollection->where('module_name', '=', $module->name); + $orderStateCollection->where('deleted', '=', '0'); + + /** @var OrderState[] $orderStates */ + $orderStates = $orderStateCollection->getResults(); + $currentModuleOrderStatesId = []; + + if (!empty($orderStates)) { + foreach ($orderStates as $orderState) { + $orderStateId = (int) $orderState->id; + if (!in_array($orderStateId, $moduleOrderStatesId, true)) { + $orderState->deleted = true; + $orderState->save(); + } else { + $currentModuleOrderStatesId[] = $orderStateId; + } + } + } + + foreach ($moduleOrderStates as $configuration_key => $id_order_state) { + if ( + !$id_order_state + || !in_array((int) $id_order_state, $currentModuleOrderStatesId, true) + ) { + switch ($configuration_key) { + case 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT', + '#34209E', + [ + 'en' => 'Waiting for PayPal payment', + 'fr' => 'En attente de paiement par PayPal', + 'es' => 'Esperando el pago con PayPal', + 'it' => 'In attesa di pagamento con PayPal', + 'nl' => 'Wachten op PayPal-betaling', + 'de' => 'Warten auf PayPal-Zahlung', + 'pl' => 'Oczekiwanie na płatność PayPal', + 'pt' => 'Aguardando pagamento pelo PayPal', + ] + ); + break; + case 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT', + '#34209E', + [ + 'en' => 'Waiting for Credit Card Payment', + 'fr' => 'En attente de paiement par Carte de Crédit', + 'es' => 'Esperando el pago con tarjeta de crédito', + 'it' => 'In attesa di pagamento con carta di credito', + 'nl' => 'Wachten op creditcard-betaling', + 'de' => 'Warten auf Kreditkartenzahlung', + 'pl' => 'Oczekiwanie na płatność kartą kredytową', + 'pt' => 'Aguardando pagamento por cartão de crédito', + ] + ); + break; + case 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT', + '#34209E', + [ + 'en' => 'Waiting for Local Payment Method Payment', + 'fr' => 'En attente de paiement par moyen de paiement local', + 'es' => 'Esperando el pago con un método de pago local', + 'it' => 'In attesa di pagamento con metodo di pagamento locale', + 'nl' => 'Wachten op nlaatselijke betaling', + 'de' => 'Warten auf Zahlung per lokaler Zahlungsmethode', + 'pl' => 'Oczekiwanie na płatność lokalnym środkiem płatności', + 'pt' => 'Aguardando pagamento pelo método de pagamento local', + ] + ); + break; + case 'PS_CHECKOUT_STATE_AUTHORIZED': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_AUTHORIZED', + '#3498D8', + [ + 'en' => 'Authorized. To be captured by merchant', + 'fr' => 'Autorisation. A capturer par le marchand', + 'es' => 'Autorizado. El vendedor lo capturará', + 'it' => 'Autorizzato. Sarà acquisito dal commerciante', + 'nl' => 'Goedgekeurd. Door retailer te registreren.', + 'de' => 'Autorisiert. Wird von Händler erfasst.', + 'pl' => 'Pomyślna autoryzacja. Transfer do przeprowadzenia przez sklep', + 'pt' => 'Autorizado. A ser capturado pelo comerciante', + ] + ); + break; + case 'PS_CHECKOUT_STATE_PARTIAL_REFUND': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_PARTIAL_REFUND', + '#01B887', + [ + 'en' => 'Partial refund', + 'fr' => 'Remboursement partiel', + 'es' => 'Reembolso parcial', + 'it' => 'Rimborso parziale', + 'nl' => 'Gedeeltelijke terugbetaling', + 'de' => 'Teilweise Rückerstattung', + 'pl' => 'Częściowy zwrot', + 'pt' => 'Reembolso parcial', + ] + ); + break; + case 'PS_CHECKOUT_STATE_WAITING_CAPTURE': + ps_checkout_create_order_state_8_3_3_0( + 'PS_CHECKOUT_STATE_WAITING_CAPTURE', + '#3498D8', + [ + 'en' => 'Waiting capture', + 'fr' => 'En attente de capture', + 'es' => 'Esperando la captura', + 'it' => 'In attesa di essere acquisito', + 'nl' => 'Wachten op registratie', + 'de' => 'Warten auf Erfassung', + 'pl' => 'Oczekiwanie na transfer', + 'pt' => 'Aguardando a captura', + ] + ); + break; + } + } } - } catch (Exception $e) { - } - try { - $db = Db::getInstance(); $db->delete( 'pscheckout_cart', 'paypal_order IS NULL' @@ -79,7 +232,8 @@ function upgrade_module_8_3_3_0($module) 0, true ); - } catch (Exception $e) { + } catch (Exception $exception) { + PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), Ps_checkout::class, $module->id, false); } // Restore initial PrestaShop shop context @@ -93,3 +247,36 @@ function upgrade_module_8_3_3_0($module) return true; } + +function ps_checkout_create_order_state_8_3_3_0($configuration_key, $color, $nameByLangIsoCode) +{ + $orderStateNameByLangId = []; + foreach ($nameByLangIsoCode as $langIsoCode => $name) { + foreach (Language::getLanguages(false) as $language) { + if (Tools::strtolower($language['iso_code']) === $langIsoCode) { + $orderStateNameByLangId[(int) $language['id_lang']] = $name; + } elseif (isset($nameByLangIsoCode['en'])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; + } + } + } + + $orderState = new OrderState(); + $orderState->name = $orderStateNameByLangId; + $orderState->module_name = 'ps_checkout'; + $orderState->unremovable = true; + $orderState->color = $color; + $orderState->delivery = false; + $orderState->shipped = false; + $orderState->pdf_delivery = false; + $orderState->pdf_invoice = false; + $orderState->hidden = false; + $orderState->invoice = false; + $orderState->send_email = false; + $orderState->paid = false; + $orderState->logable = false; + $orderState->deleted = false; + $orderState->template = []; + $orderState->save(); + Configuration::updateGlobalValue($configuration_key, $orderState->id); +} From b3f001b1568ff2cd63466e23c971457038d36050 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 26 Jun 2023 16:11:46 +0200 Subject: [PATCH 017/343] PHPStan feedbacks --- src/Event/Event.php | 2 +- tests/phpstan/phpstan-PS-1.7.neon | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Event/Event.php b/src/Event/Event.php index e1f327acd..b1a087c2c 100644 --- a/src/Event/Event.php +++ b/src/Event/Event.php @@ -28,7 +28,7 @@ class Event extends ComponentEvent { } -} else { +} elseif (class_exists(ContractEvent::class)) { // @phpstan-ignore-next-line class Event extends ContractEvent /* @phpstan-ignore-line */ { diff --git a/tests/phpstan/phpstan-PS-1.7.neon b/tests/phpstan/phpstan-PS-1.7.neon index b471fc914..93269d39c 100644 --- a/tests/phpstan/phpstan-PS-1.7.neon +++ b/tests/phpstan/phpstan-PS-1.7.neon @@ -35,6 +35,5 @@ parameters: - '#Parameter \#1 \$id of class Currency constructor expects null, int given.#' - '#Parameter \#1 \$amount_paid of method OrderCore::addOrderPayment\(\) expects float, string given.#' - '#Parameter \#3 \$amount_paid of method PaymentModuleCore::validateOrder\(\) expects float, string given.#' - - '#Class PrestaShop\\Module\\PrestashopCheckout\\Event\\Event extends unknown class Symfony\\Contracts\\EventDispatcher\\Event.#' level: 5 From 9c9d383a4d63b10e2065957722cc9caccb149a69 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Wed, 28 Jun 2023 16:47:33 +0200 Subject: [PATCH 018/343] Upgrade script for 8.3.4.0 --- upgrade/upgrade-8.3.4.0.php | 94 +++++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 upgrade/upgrade-8.3.4.0.php diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php new file mode 100644 index 000000000..2a558e7f8 --- /dev/null +++ b/upgrade/upgrade-8.3.4.0.php @@ -0,0 +1,94 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Update main function for module version 8.3.4.0 + * + * @param Ps_checkout $module + * + * @return bool + */ +function upgrade_module_8_3_4_0($module) +{ + $orderStateCollection = new PrestaShopCollection(OrderState::class); + $orderStateCollection->where('module_name', '=', $module->name); + $orderStateCollection->where('deleted', '=', '0'); + + /** @var OrderState[] $orderStates */ + $orderStates = $orderStateCollection->getResults(); + + if (!empty($orderStates)) { + foreach ($orderStates as $orderState) { + if (in_array($orderState->id, [Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT')])) { + $orderState->deleted = true; + $orderState->save(); + } + } + } + + ps_checkout_create_order_state_8_3_4_0('PS_CHECKOUT_STATE_WAITING_PAYMENT', '#34209E', [ + 'en' => 'Waiting for payment', + 'fr' => 'En attente de paiement', + 'es' => 'Esperando el pago', + 'it' => 'In attesa di pagamento', + 'nl' => 'Wachten op betaling', + 'de' => 'Warten auf Zahlung', + 'pl' => 'Oczekiwanie na płatność', + 'pt' => 'Aguardando pagamento pelo', + ]); + + return true; +} + +function ps_checkout_create_order_state_8_3_4_0($configuration_key, $color, $nameByLangIsoCode) +{ + $orderStateNameByLangId = []; + foreach ($nameByLangIsoCode as $langIsoCode => $name) { + foreach (Language::getLanguages(false) as $language) { + if (Tools::strtolower($language['iso_code']) === $langIsoCode) { + $orderStateNameByLangId[(int) $language['id_lang']] = $name; + } elseif (isset($nameByLangIsoCode['en'])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; + } + } + } + + $orderState = new OrderState(); + $orderState->name = $orderStateNameByLangId; + $orderState->module_name = 'ps_checkout'; + $orderState->unremovable = true; + $orderState->color = $color; + $orderState->delivery = false; + $orderState->shipped = false; + $orderState->pdf_delivery = false; + $orderState->pdf_invoice = false; + $orderState->hidden = false; + $orderState->invoice = false; + $orderState->send_email = false; + $orderState->paid = false; + $orderState->logable = false; + $orderState->deleted = false; + $orderState->template = []; + $orderState->save(); + Configuration::updateGlobalValue($configuration_key, $orderState->id); +} From ca5ebdb76c5e708b19808f1eef6be3aa18e0ad68 Mon Sep 17 00:00:00 2001 From: boubkerbribri Date: Tue, 4 Jul 2023 16:55:16 +0200 Subject: [PATCH 019/343] test: add data-test on payment methods block --- views/templates/hook/adminAfterHeader/promotionBlock.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/views/templates/hook/adminAfterHeader/promotionBlock.tpl b/views/templates/hook/adminAfterHeader/promotionBlock.tpl index b737bfa5b..5255ee179 100644 --- a/views/templates/hook/adminAfterHeader/promotionBlock.tpl +++ b/views/templates/hook/adminAfterHeader/promotionBlock.tpl @@ -20,7 +20,7 @@
-
+

extension {l s='One module, all payments methods.' mod='ps_checkout'}

From bbc7064223bcba6bb2e16203498d83bd956afb64 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 6 Jul 2023 09:59:55 +0200 Subject: [PATCH 020/343] Bump version to v8.3.3.1 --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index 3ca9df5c0..0724103c0 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index c660724b7..ec71b991b 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.3.0'; + const VERSION = '8.3.3.1'; const INTEGRATION_DATE = '2022-14-06'; @@ -142,7 +142,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.3.0'; + $this->version = '8.3.3.1'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From 35d27b7fdc7226fadce41075240c484704259ef4 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 11 Jul 2023 15:24:17 +0300 Subject: [PATCH 021/343] Added code to remove virtual url from admin urls --- src/Adapter/LinkAdapter.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Adapter/LinkAdapter.php b/src/Adapter/LinkAdapter.php index 98e06fae8..085f2905a 100644 --- a/src/Adapter/LinkAdapter.php +++ b/src/Adapter/LinkAdapter.php @@ -58,7 +58,10 @@ public function __construct(\Link $link = null) public function getAdminLink($controller, $withToken = true, $sfRouteParams = [], $params = []) { if ((new ShopContext())->isShop17()) { - return $this->link->getAdminLink($controller, $withToken, $sfRouteParams, $params); + $shop = \Context::getContext()->shop; + $link = $this->link->getAdminLink($controller, $withToken, $sfRouteParams, $params); + + return str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $link); } $paramsAsString = ''; From 8fe55222dede6e245fc2ffa0b22847baf54ca46e Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 11 Jul 2023 15:58:21 +0300 Subject: [PATCH 022/343] Changed the code to work on all ps versions --- src/Adapter/LinkAdapter.php | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Adapter/LinkAdapter.php b/src/Adapter/LinkAdapter.php index 085f2905a..9a377e65e 100644 --- a/src/Adapter/LinkAdapter.php +++ b/src/Adapter/LinkAdapter.php @@ -57,11 +57,12 @@ public function __construct(\Link $link = null) */ public function getAdminLink($controller, $withToken = true, $sfRouteParams = [], $params = []) { + $shop = \Context::getContext()->shop; + if ((new ShopContext())->isShop17()) { - $shop = \Context::getContext()->shop; $link = $this->link->getAdminLink($controller, $withToken, $sfRouteParams, $params); - return str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $link); + return $shop->virtual_uri !== '' ? str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $link) : $link; } $paramsAsString = ''; @@ -69,6 +70,8 @@ public function getAdminLink($controller, $withToken = true, $sfRouteParams = [] $paramsAsString .= "&$key=$value"; } - return \Tools::getShopDomainSsl(true) . __PS_BASE_URI__ . basename(_PS_ADMIN_DIR_) . '/' . $this->link->getAdminLink($controller, $withToken) . $paramsAsString; + $link = \Tools::getShopDomainSsl(true) . __PS_BASE_URI__ . basename(_PS_ADMIN_DIR_) . '/' . $this->link->getAdminLink($controller, $withToken) . $paramsAsString; + + return $shop->virtual_uri !== '' ? str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $link) : $link; } } From 6781709702acc7480b2541577755441c0a404463 Mon Sep 17 00:00:00 2001 From: sgodard Date: Tue, 11 Jul 2023 15:09:27 +0200 Subject: [PATCH 023/343] update translation --- upgrade/upgrade-8.3.4.0.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index 2a558e7f8..a2990ed5d 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -54,7 +54,7 @@ function upgrade_module_8_3_4_0($module) 'nl' => 'Wachten op betaling', 'de' => 'Warten auf Zahlung', 'pl' => 'Oczekiwanie na płatność', - 'pt' => 'Aguardando pagamento pelo', + 'pt' => 'Aguardando pagamento', ]); return true; From 5eaf4d2b276bc1ef8aaab4637e2331700ecc0c96 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Wed, 12 Jul 2023 11:22:02 +0200 Subject: [PATCH 024/343] Fixing errors in update script --- upgrade/upgrade-8.3.3.0.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index 8dc2df067..06fa1b5d1 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -66,7 +66,7 @@ function upgrade_module_8_3_3_0($module) foreach (Shop::getShops(false, null, true) as $shopId) { $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; foreach ($fundingSources as $fundingSource) { - if (!in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true)) { + if (!isset($availableFundingSourcesByShops[(int) $shopId]) || !in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true)) { $db->insert( 'pscheckout_funding_source', [ @@ -233,7 +233,8 @@ function upgrade_module_8_3_3_0($module) true ); } catch (Exception $exception) { - PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), Ps_checkout::class, $module->id, false); + PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id, false); + return false; } // Restore initial PrestaShop shop context From f98af6446f3eaf2fd259169e992aca9da768a0d2 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Wed, 12 Jul 2023 11:42:21 +0200 Subject: [PATCH 025/343] CI fixes --- upgrade/upgrade-8.3.3.0.php | 1 + 1 file changed, 1 insertion(+) diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index 06fa1b5d1..017d8c754 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -234,6 +234,7 @@ function upgrade_module_8_3_3_0($module) ); } catch (Exception $exception) { PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id, false); + return false; } From f8a8be5cd6d3c0a65e438bc74330e57cc7fa044e Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 12 Jul 2023 16:18:32 +0300 Subject: [PATCH 026/343] Added authentication route when redirecting after onboarding --- .../admin/AdminPaypalOnboardingPrestashopCheckoutController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php index 464791fa4..53904dff8 100755 --- a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php +++ b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php @@ -54,7 +54,7 @@ public function postProcess() [ 'configure' => 'ps_checkout', ] - ) + ) . '#/authentication' ); } catch (Exception $e) { $this->errors[] = $e->getMessage(); From c2cfe1c80d86221a8fc617292a67b848d443f8da Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 11 Jul 2023 10:13:54 +0200 Subject: [PATCH 027/343] Add BLIK as APM --- src/FundingSource/FundingSourceCollectionBuilder.php | 8 +++++++- views/img/blik.svg | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 views/img/blik.svg diff --git a/src/FundingSource/FundingSourceCollectionBuilder.php b/src/FundingSource/FundingSourceCollectionBuilder.php index b4ef4052c..25aba65be 100644 --- a/src/FundingSource/FundingSourceCollectionBuilder.php +++ b/src/FundingSource/FundingSourceCollectionBuilder.php @@ -107,6 +107,12 @@ public function create() $sofort->setIsEnabled($this->configuration->isEnabled('sofort')); $sofort->setCountries($this->eligibilityConstraint->getCountries('sofort')); - return [$paypal, $paylater, $card, $bancontact, $eps, $giropay, $ideal, $mybank, $p24, $sofort]; + // BLIK + $blik = new FundingSourceEntity('blik'); + $blik->setPosition($this->configuration->getPosition('blik', 11)); + $blik->setIsEnabled($this->configuration->isEnabled('blik')); + $blik->setCountries($this->eligibilityConstraint->getCountries('blik')); + + return [$paypal, $paylater, $card, $bancontact, $eps, $giropay, $ideal, $mybank, $p24, $sofort, $blik]; } } diff --git a/views/img/blik.svg b/views/img/blik.svg new file mode 100644 index 000000000..b88037d1c --- /dev/null +++ b/views/img/blik.svg @@ -0,0 +1 @@ + \ No newline at end of file From 41487855cdbbd5e88372697b44437bda58931269 Mon Sep 17 00:00:00 2001 From: sgodard Date: Thu, 29 Jun 2023 20:37:20 +0200 Subject: [PATCH 028/343] add PS_CHECKOUT_STATE_WAITING_PAYMENT --- .../State/OrderStateConfigurationKeys.php | 1 + src/OrderStates.php | 4 +- src/Translations/OrderStatesTranslations.php | 46 +++++-------------- 3 files changed, 13 insertions(+), 38 deletions(-) diff --git a/src/Order/State/OrderStateConfigurationKeys.php b/src/Order/State/OrderStateConfigurationKeys.php index 6edd8961a..acaf1cdd4 100644 --- a/src/Order/State/OrderStateConfigurationKeys.php +++ b/src/Order/State/OrderStateConfigurationKeys.php @@ -37,4 +37,5 @@ class OrderStateConfigurationKeys const WAITING_CREDIT_CARD_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'; const WAITING_LOCAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'; const WAITING_PAYPAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'; + const WAITING_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_PAYMENT'; } diff --git a/src/OrderStates.php b/src/OrderStates.php index f417523f2..b617c32b8 100644 --- a/src/OrderStates.php +++ b/src/OrderStates.php @@ -35,9 +35,7 @@ class OrderStates const BLUE_HEXA_COLOR = '#3498D8'; const GREEN_HEXA_COLOR = '#01B887'; const ORDER_STATES = [ - 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, - 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, - 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, + 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, 'PS_CHECKOUT_STATE_AUTHORIZED' => self::BLUE_HEXA_COLOR, 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => self::GREEN_HEXA_COLOR, 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => self::BLUE_HEXA_COLOR, diff --git a/src/Translations/OrderStatesTranslations.php b/src/Translations/OrderStatesTranslations.php index 50bcf6f0b..a60265521 100644 --- a/src/Translations/OrderStatesTranslations.php +++ b/src/Translations/OrderStatesTranslations.php @@ -23,35 +23,15 @@ class OrderStatesTranslations { const STANDARD_ISO_CODE = 'en'; - const PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT = [ - 'en' => 'Waiting for PayPal payment', - 'fr' => 'En attente de paiement par PayPal', - 'es' => 'Esperando el pago con PayPal', - 'it' => 'In attesa di pagamento con PayPal', - 'nl' => 'Wachten op PayPal-betaling', - 'de' => 'Warten auf PayPal-Zahlung', - 'pl' => 'Oczekiwanie na płatność PayPal', - 'pt' => 'Aguardando pagamento pelo PayPal', - ]; - const PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT = [ - 'en' => 'Waiting for Credit Card Payment', - 'fr' => 'En attente de paiement par Carte de Crédit', - 'es' => 'Esperando el pago con tarjeta de crédito', - 'it' => 'In attesa di pagamento con carta di credito', - 'nl' => 'Wachten op creditcard-betaling', - 'de' => 'Warten auf Kreditkartenzahlung', - 'pl' => 'Oczekiwanie na płatność kartą kredytową', - 'pt' => 'Aguardando pagamento por cartão de crédito', - ]; - const PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT = [ - 'en' => 'Waiting for Local Payment Method Payment', - 'fr' => 'En attente de paiement par moyen de paiement local', - 'es' => 'Esperando el pago con un método de pago local', - 'it' => 'In attesa di pagamento con metodo di pagamento locale', - 'nl' => 'Wachten op nlaatselijke betaling', - 'de' => 'Warten auf Zahlung per lokaler Zahlungsmethode', - 'pl' => 'Oczekiwanie na płatność lokalnym środkiem płatności', - 'pt' => 'Aguardando pagamento pelo método de pagamento local', + const PS_CHECKOUT_STATE_WAITING_PAYMENT = [ + 'en' => 'Waiting for payment', + 'fr' => 'En attente de paiement', + 'es' => 'Esperando el pago', + 'it' => 'In attesa di pagamento', + 'nl' => 'Wachten op betaling', + 'de' => 'Warten auf Zahlung', + 'pl' => 'Oczekiwanie na płatność', + 'pt' => 'Aguardando pagamento', ]; const PS_CHECKOUT_STATE_AUTHORIZED = [ 'en' => 'Authorized. To be captured by merchant', @@ -94,9 +74,7 @@ public function getTranslations($isoCode) $isoCode = $this->confirmIsoCode($isoCode); return [ - 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' => self::PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT[$isoCode], - 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' => self::PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT[$isoCode], - 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' => self::PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT[$isoCode], + 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => self::PS_CHECKOUT_STATE_WAITING_PAYMENT[$isoCode], 'PS_CHECKOUT_STATE_AUTHORIZED' => self::PS_CHECKOUT_STATE_AUTHORIZED[$isoCode], 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => self::PS_CHECKOUT_STATE_PARTIAL_REFUND[$isoCode], 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => self::PS_CHECKOUT_STATE_WAITING_CAPTURE[$isoCode], @@ -112,9 +90,7 @@ public function getTranslations($isoCode) */ private function confirmIsoCode($isoCode) { - if (!array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT) || - !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT) || - !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT) || + if (!array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_PAYMENT) || !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_AUTHORIZED) || !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_PARTIAL_REFUND) || !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_CAPTURE)) { From 1b1dd4e3c16b2fdf94e7ff757af36c2d35682296 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 28 Jun 2023 12:14:26 +0200 Subject: [PATCH 029/343] Change order status to canceled if exist when payment approval is reversed --- config/common.yml | 8 ++ .../GetOrderForApprovalReversedQuery.php | 50 +++++++++ ...GetOrderForApprovalReversedQueryResult.php | 102 ++++++++++++++++++ ...etOrderForApprovalReversedQueryHandler.php | 92 ++++++++++++++++ .../PayPalOrderEventSubscriber.php | 58 ++++++++-- 5 files changed, 304 insertions(+), 6 deletions(-) create mode 100644 src/Order/Query/GetOrderForApprovalReversedQuery.php create mode 100644 src/Order/Query/GetOrderForApprovalReversedQueryResult.php create mode 100644 src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php diff --git a/config/common.yml b/config/common.yml index 088128b69..0e63db593 100644 --- a/config/common.yml +++ b/config/common.yml @@ -462,6 +462,7 @@ services: PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery: "ps_checkout.query.handler.order.get_order_for_payment_pending" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery: "ps_checkout.query.handler.order.get_order_for_payment_refunded" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery: "ps_checkout.query.handler.order.get_order_for_payment_reversed" + PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery: "ps_checkout.query.handler.order.get_order_for_approval_reversed" PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query\GetClientTokenPayPalQuery: "ps_checkout.query.handler.paypal.identity.get_client_token" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery: "ps_checkout.query.handler.paypal.order.get_current_paypal_order_status" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed" @@ -497,6 +498,7 @@ services: - "@ps_checkout.cache.paypal.order" - "@ps_checkout.checkout.checker" - "@ps_checkout.paypal.order.service.check_transition_paypal_order_status" + - "@ps_checkout.order.state.service.order_state_mapper" ps_checkout.event.subscriber.paypal.capture: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\EventSubscriber\PayPalCaptureEventSubscriber' @@ -604,6 +606,12 @@ services: arguments: - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.order.get_order_for_approval_reversed: + class: 'PrestaShop\Module\PrestashopCheckout\Order\QueryHandler\GetOrderForApprovalReversedQueryHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.paypal.identity.get_client_token: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Identity\QueryHandler\GetClientTokenPayPalQueryHandler' public: true diff --git a/src/Order/Query/GetOrderForApprovalReversedQuery.php b/src/Order/Query/GetOrderForApprovalReversedQuery.php new file mode 100644 index 000000000..f9c4ec4ea --- /dev/null +++ b/src/Order/Query/GetOrderForApprovalReversedQuery.php @@ -0,0 +1,50 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Order\Query; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class GetOrderForApprovalReversedQuery +{ + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @param string $orderPayPalId + * + * @throws PayPalOrderException + */ + public function __construct($orderPayPalId) + { + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + } + + /** + * @return PayPalOrderId + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } +} diff --git a/src/Order/Query/GetOrderForApprovalReversedQueryResult.php b/src/Order/Query/GetOrderForApprovalReversedQueryResult.php new file mode 100644 index 000000000..96cc0b578 --- /dev/null +++ b/src/Order/Query/GetOrderForApprovalReversedQueryResult.php @@ -0,0 +1,102 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Order\Query; + +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\ValueObject\OrderStateId; +use PrestaShop\Module\PrestashopCheckout\Order\ValueObject\OrderId; + +class GetOrderForApprovalReversedQueryResult +{ + /** + * @var OrderId + */ + private $orderId; + + /** + * @var OrderStateId + */ + private $currentStateId; + + /** + * @var bool + */ + private $hasBeenPaid; + + /** + * @var bool + */ + private $hasBeenCanceled; + + /** + * @param int $orderId + * @param int $currentStateId + * @param bool $hasBeenPaid + * @param bool $hasBeenCanceled + * + * @throws OrderException + * @throws OrderStateException + */ + public function __construct( + $orderId, + $currentStateId, + $hasBeenPaid, + $hasBeenCanceled + ) { + $this->orderId = new OrderId($orderId); + $this->currentStateId = new OrderStateId($currentStateId); + $this->hasBeenPaid = $hasBeenPaid; + $this->hasBeenCanceled = $hasBeenCanceled; + } + + /** + * @return OrderId + */ + public function getOrderId() + { + return $this->orderId; + } + + /** + * @return OrderStateId + */ + public function getCurrentStateId() + { + return $this->currentStateId; + } + + /** + * @return bool + */ + public function hasBeenPaid() + { + return $this->hasBeenPaid; + } + + /** + * @return bool + */ + public function hasBeenCanceled() + { + return $this->hasBeenCanceled; + } +} diff --git a/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php b/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php new file mode 100644 index 000000000..4c394c044 --- /dev/null +++ b/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php @@ -0,0 +1,92 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Order\QueryHandler; + +use Configuration; +use Order; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQueryResult; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShopCollection; +use PrestaShopDatabaseException; +use PrestaShopException; +use PsCheckoutCart; +use Validate; + +class GetOrderForApprovalReversedQueryHandler +{ + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + + /** + * @param GetOrderForApprovalReversedQuery $query + * + * @return GetOrderForApprovalReversedQueryResult + * + * @throws PsCheckoutException + * @throws PrestaShopDatabaseException + * @throws PrestaShopException + */ + public function handle(GetOrderForApprovalReversedQuery $query) + { + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($query->getOrderPayPalId()->getValue()); + + if (!$psCheckoutCart) { + throw new CartNotFoundException('No PrestaShop Cart associated to this PayPal Order at this time.'); + } + + $orders = new PrestaShopCollection(Order::class); + $orders->where('id_cart', '=', $psCheckoutCart->getIdCart()); + + if (!$orders->count()) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); + } + + /** @var Order $order */ + $order = $orders->getFirst(); + + if (!Validate::isLoadedObject($order)) { + throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); + } + + $hasBeenCanceled = count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED))); + + return new GetOrderForApprovalReversedQueryResult( + (int) $order->id, + (int) $order->getCurrentState(), + (bool) $order->hasBeenPaid(), + (bool) $hasBeenCanceled + ); + } +} diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index b1ed55843..9d74b1b9c 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -22,7 +22,14 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\EventSubscriber; use PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQueryResult; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CheckTransitionPayPalOrderStatusService; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand; @@ -64,18 +71,31 @@ class PayPalOrderEventSubscriber implements EventSubscriberInterface */ private $checkTransitionPayPalOrderStatusService; + /** + * @var OrderStateMapper + */ + private $orderStateMapper; + + /** + * @var CommandBusInterface + */ + private $commandBus; + public function __construct( Ps_checkout $module, PsCheckoutCartRepository $psCheckoutCartRepository, CacheInterface $orderPayPalCache, CheckoutChecker $checkoutChecker, - CheckTransitionPayPalOrderStatusService $checkTransitionPayPalOrderStatusService + CheckTransitionPayPalOrderStatusService $checkTransitionPayPalOrderStatusService, + OrderStateMapper $orderStateMapper ) { $this->module = $module; $this->psCheckoutCartRepository = $psCheckoutCartRepository; $this->orderPayPalCache = $orderPayPalCache; $this->checkoutChecker = $checkoutChecker; $this->checkTransitionPayPalOrderStatusService = $checkTransitionPayPalOrderStatusService; + $this->orderStateMapper = $orderStateMapper; + $this->commandBus = $this->module->getService('ps_checkout.bus.command'); } /** @@ -99,6 +119,7 @@ public static function getSubscribedEvents() ], PayPalOrderApprovalReversedEvent::class => [ ['saveApprovalReversedPayPalOrder'], + ['setApprovalReversedOrderStatus'], ['clearCache'], ], ]; @@ -116,7 +137,7 @@ public function saveCreatedPayPalOrder(PayPalOrderCreatedEvent $event) return; } - $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderCommand( $event->getOrderPayPalId()->getValue(), PayPalOrderStatus::CREATED, $event->getOrderPayPal() @@ -135,7 +156,7 @@ public function saveApprovedPayPalOrder(PayPalOrderApprovedEvent $event) return; } - $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderCommand( $event->getOrderPayPalId()->getValue(), PayPalOrderStatus::APPROVED, $event->getOrderPayPal() @@ -154,7 +175,7 @@ public function saveCompletedPayPalOrder(PayPalOrderCompletedEvent $event) return; } - $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderCommand( $event->getOrderPayPalId()->getValue(), PayPalOrderStatus::COMPLETED, $event->getOrderPayPal() @@ -173,7 +194,7 @@ public function saveApprovalReversedPayPalOrder(PayPalOrderApprovalReversedEvent return; } - $this->module->getService('ps_checkout.bus.command')->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderCommand( $event->getOrderPayPalId()->getValue(), PayPalOrderStatus::REVERSED, $event->getOrderPayPal() @@ -199,7 +220,7 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) $this->checkoutChecker->continueWithAuthorization($psCheckoutCart->getIdCart(), $event->getOrderPayPal()); - $this->module->getService('ps_checkout.bus.command')->handle( + $this->commandBus->handle( new CapturePayPalOrderCommand( $event->getOrderPayPalId()->getValue(), $psCheckoutCart->getPaypalFundingSource() @@ -207,6 +228,31 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) ); } + public function setApprovalReversedOrderStatus(PayPalOrderApprovalReversedEvent $event) + { + try { + /** @var GetOrderForApprovalReversedQueryResult $order */ + $order = $this->commandBus->handle( + new GetOrderForApprovalReversedQuery( + $event->getOrderPayPalId()->getValue() + ) + ); + } catch (OrderNotFoundException $exception) { + return; + } + + if ($order->hasBeenCanceled() || $order->hasBeenPaid()) { + return; + } + + $this->commandBus->handle( + new UpdateOrderStatusCommand( + $order->getOrderId()->getValue(), + $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::CANCELED) + ) + ); + } + public function updateCache(PayPalOrderEvent $event) { $currentOrderPayPal = $this->orderPayPalCache->get($event->getOrderPayPalId()->getValue()); From ff50c920e20f18d5040ecc39b4e6658cf2592a3a Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 6 Jun 2023 17:32:50 +0300 Subject: [PATCH 030/343] Added configuration batch saving --- config/common.yml | 10 ++ .../AdminAjaxPrestashopCheckoutController.php | 117 ++++++++++++++++++ .../BatchConfigurationProcessor.php | 48 +++++++ src/OrderStates.php | 9 ++ src/Validator/BatchConfigurationValidator.php | 56 +++++++++ 5 files changed, 240 insertions(+) create mode 100644 src/Configuration/BatchConfigurationProcessor.php create mode 100644 src/Validator/BatchConfigurationValidator.php diff --git a/config/common.yml b/config/common.yml index d995b969d..c5b1367fb 100644 --- a/config/common.yml +++ b/config/common.yml @@ -277,6 +277,10 @@ services: - '@ps_checkout.express_checkout.configuration' - '@ps_checkout.pay_later.configuration' + ps_checkout.validator.batch_configuration: + class: 'PrestaShop\Module\PrestashopCheckout\Validator\BatchConfigurationValidator' + public: true + ps_checkout.cache.directory: class: 'PrestaShop\ModuleLibCacheDirectoryProvider\Cache\CacheDirectoryProvider' public: true @@ -322,6 +326,12 @@ services: - '@ps_checkout.webhook.service.secret_token' - ['@ps_checkout.webhook.handler.event.configuration_updated'] + ps_checkout.configuration.batch_processor: + class: 'PrestaShop\Module\PrestashopCheckout\Configuration\BatchConfigurationProcessor' + public: true + arguments: + - '@ps_checkout.configuration' + ps_accounts.installer: class: 'PrestaShop\PsAccountsInstaller\Installer\Installer' public: true diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index 03f4ff380..55ee062bc 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -17,13 +17,17 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ + use Monolog\Logger; +use PrestaShop\Module\PrestashopCheckout\Configuration\BatchConfigurationProcessor; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerDirectory; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFactory; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileFinder; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileReader; +use PrestaShop\Module\PrestashopCheckout\OrderStates; use PrestaShop\Module\PrestashopCheckout\Presenter\Order\OrderPresenter; use PrestaShop\Module\PrestashopCheckout\Settings\RoundingSettings; +use PrestaShop\Module\PrestashopCheckout\Validator\BatchConfigurationValidator; use Psr\SimpleCache\CacheInterface; class AdminAjaxPrestashopCheckoutController extends ModuleAdminController @@ -841,6 +845,119 @@ public function ajaxProcessCheckConfiguration() $this->exitWithResponse($response); } + public function ajaxProcessFetchConfiguration() + { + $response = []; + + $query = new DbQuery(); + $query->select('name, value, date_add, date_upd'); + $query->from('configuration'); + $query->where('name LIKE "PS_CHECKOUT_%"'); + + /** @var int|null $shopId When multishop is disabled, it returns null, so we don't have to restrict results by shop */ + $shopId = Shop::getContextShopID(true); + + // When ShopId is not NULL, we have to retrieve global values with id_shop = NULL and shop values with id_shop = ShopId + if ($shopId) { + $query->where('id_shop IS NULL OR id_shop = ' . (int) $shopId); + } + + $configurations = Db::getInstance()->executeS($query); + + $response = [ + 'httpCode' => 200, + 'status' => !empty($configurations), + 'configuration' => array_map(function ($configuration) { + return [ + 'name' => $configuration['name'], + 'value' => $configuration['value'], + ]; + }, $configurations), + ]; + + $this->exitWithResponse($response); + } + + public function ajaxProcessGetMappedOrderStates() + { + /** @var \PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration $configuration */ + $configuration = $this->module->getService('ps_checkout.configuration'); + + $mappedOrderStates = [ + OrderStates::PS_CHECKOUT_STATE_PENDING => [ + 'default' => '0', + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PENDING, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_COMPLETED => [ + 'default' => $configuration->get('PS_OS_PAYMENT'), + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_COMPLETED, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_CANCELED => [ + 'default' => $configuration->get('PS_OS_CANCELED'), + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_CANCELED, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_ERROR => [ + 'default' => $configuration->get('PS_OS_ERROR'), + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_ERROR, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_REFUNDED => [ + 'default' => $configuration->get('PS_OS_REFUND'), + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_REFUNDED, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => [ + 'default' => '0', + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_PARTIALLY_PAID => [ + 'default' => '0', + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PARTIALLY_PAID, ['default' => '0']), + ], + OrderStates::PS_CHECKOUT_STATE_AUTHORIZED => [ + 'default' => '0', + 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_AUTHORIZED, ['default' => '0']), + ], + ]; + + $this->exitWithResponse([ + 'status' => true, + 'mappedOrderStates' => $mappedOrderStates, + ]); + } + + public function ajaxProcessBatchSaveConfiguration() + { + /** @var BatchConfigurationValidator $configurationValidator */ + $configurationValidator = $this->module->getService('ps_checkout.validator.batch_configuration'); + /** @var BatchConfigurationProcessor $batchConfigurationProcessor */ + $batchConfigurationProcessor = $this->module->getService('ps_checkout.configuration.batch_processor'); + + $configuration = json_decode(Tools::getValue('configuration'), true); + try { + $configurationValidator->validateAjaxBatchConfiguration($configuration); + $batchConfigurationProcessor->saveBatchConfiguration($configuration); + + $this->exitWithResponse([ + 'status' => true, + ]); + } catch (Exception $exception) { + $this->exitWithResponse([ + 'httpCode' => 500, + 'status' => false, + 'error' => $exception->getMessage(), + ]); + } + } + + public function ajaxProcessGetOrderStates() + { + $orderStates = OrderState::getOrderStates(Context::getContext()->language->id); + + $this->exitWithResponse([ + 'status' => true, + 'orderStates' => $orderStates, + ]); + } + /** * @param array $response * diff --git a/src/Configuration/BatchConfigurationProcessor.php b/src/Configuration/BatchConfigurationProcessor.php new file mode 100644 index 000000000..2603e7103 --- /dev/null +++ b/src/Configuration/BatchConfigurationProcessor.php @@ -0,0 +1,48 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Configuration; + +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; + +class BatchConfigurationProcessor +{ + /** @var PrestaShopConfiguration */ + private $prestaShopConfiguration; + + public function __construct(PrestaShopConfiguration $prestaShopConfiguration) + { + $this->prestaShopConfiguration = $prestaShopConfiguration; + } + + /** + * @param array $configuration + * + * @return void + * + * @throws PsCheckoutException + */ + public function saveBatchConfiguration($configuration) + { + foreach ($configuration as $configurationItem) { + $this->prestaShopConfiguration->set(pSQL($configurationItem['name']), pSQL($configurationItem['value'])); + } + } +} diff --git a/src/OrderStates.php b/src/OrderStates.php index f417523f2..d6b79ec04 100644 --- a/src/OrderStates.php +++ b/src/OrderStates.php @@ -34,6 +34,15 @@ class OrderStates const DARK_BLUE_HEXA_COLOR = '#34209E'; const BLUE_HEXA_COLOR = '#3498D8'; const GREEN_HEXA_COLOR = '#01B887'; + + const PS_CHECKOUT_STATE_PENDING = 'PS_CHECKOUT_STATE_PENDING'; + const PS_CHECKOUT_STATE_COMPLETED = 'PS_CHECKOUT_STATE_COMPLETED'; + const PS_CHECKOUT_STATE_CANCELED = 'PS_CHECKOUT_STATE_CANCELED'; + const PS_CHECKOUT_STATE_ERROR = 'PS_CHECKOUT_STATE_ERROR'; + const PS_CHECKOUT_STATE_PARTIALLY_REFUNDED = 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED'; + const PS_CHECKOUT_STATE_REFUNDED = 'PS_CHECKOUT_STATE_REFUNDED'; + const PS_CHECKOUT_STATE_PARTIALLY_PAID = 'PS_CHECKOUT_STATE_PARTIALLY_PAID'; + const PS_CHECKOUT_STATE_AUTHORIZED = 'PS_CHECKOUT_STATE_AUTHORIZED'; const ORDER_STATES = [ 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, diff --git a/src/Validator/BatchConfigurationValidator.php b/src/Validator/BatchConfigurationValidator.php new file mode 100644 index 000000000..1cc375643 --- /dev/null +++ b/src/Validator/BatchConfigurationValidator.php @@ -0,0 +1,56 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Validator; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; + +class BatchConfigurationValidator +{ + const BLACKLISTED_CONFIGURATION_KEYS = [ + PayPalConfiguration::PS_CHECKOUT_PAYPAL_ID_MERCHANT, + PayPalConfiguration::PS_CHECKOUT_PAYPAL_COUNTRY_MERCHANT, + PayPalConfiguration::PS_CHECKOUT_PAYPAL_EMAIL_STATUS, + PayPalConfiguration::PS_CHECKOUT_PAYPAL_PAYMENT_STATUS, + ]; + + /** + * @param array $configuration + * + * @throws Exception + */ + public function validateAjaxBatchConfiguration($configuration) + { + if (empty($configuration) || !is_array($configuration)) { + throw new Exception("Config can't be empty"); + } + + foreach ($configuration as $configurationItem) { + if (empty($configurationItem['name']) || 0 !== strpos($configurationItem['name'], 'PS_CHECKOUT_')) { + throw new Exception('Received invalid configuration key'); + } + + if (array_search($configurationItem['name'], self::BLACKLISTED_CONFIGURATION_KEYS)) { + throw new Exception('Received blacklisted configuration key'); + } + } + } +} From 82a0762269e932314619b0217de01fdd6063616d Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 13 Jul 2023 13:01:57 +0200 Subject: [PATCH 031/343] Waiting payment order state --- .../CommandHandler/CreateOrderCommandHandler.php | 11 +---------- .../GetOrderForPaymentPendingQueryHandler.php | 3 ++- src/Order/State/Service/OrderStateMapper.php | 1 + .../PayPalCaptureEventSubscriber.php | 13 +------------ src/Presenter/Order/OrderPendingPresenter.php | 3 ++- 5 files changed, 7 insertions(+), 24 deletions(-) diff --git a/src/Order/CommandHandler/CreateOrderCommandHandler.php b/src/Order/CommandHandler/CreateOrderCommandHandler.php index ec7408102..ce8055023 100644 --- a/src/Order/CommandHandler/CreateOrderCommandHandler.php +++ b/src/Order/CommandHandler/CreateOrderCommandHandler.php @@ -155,16 +155,7 @@ public function handle(CreateOrderCommand $command) $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); } } else { - switch ($fundingSource) { - case 'card': - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); - break; - case 'paypal': - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); - break; - default: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); - } + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYMENT); } /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ diff --git a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php index 89875d65d..b04b6f19a 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php @@ -95,7 +95,8 @@ public function handle(GetOrderForPaymentPendingQuery $query) */ private function isInPending(Order $order) { - return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT))) + return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYMENT))) + || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT))) || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT))) || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT))); } diff --git a/src/Order/State/Service/OrderStateMapper.php b/src/Order/State/Service/OrderStateMapper.php index b050f4fa3..f11782f68 100644 --- a/src/Order/State/Service/OrderStateMapper.php +++ b/src/Order/State/Service/OrderStateMapper.php @@ -55,6 +55,7 @@ public function __construct(PrestaShopConfiguration $configuration) OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, ['global' => true]), OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, ['global' => true]), OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::WAITING_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_PAYMENT, ['global' => true]), ]; } diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index 59b1df49b..e7c4693c0 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -184,18 +184,7 @@ public function setPaymentPendingOrderStatus(PayPalCapturePendingEvent $event) return; } - switch ($order->getPaymentMethod()) { - case 'card': - $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT); - break; - case 'paypal': - $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT); - break; - default: - $newOrderStateId = $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT); - } - - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $newOrderStateId)); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYMENT))); } public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) diff --git a/src/Presenter/Order/OrderPendingPresenter.php b/src/Presenter/Order/OrderPendingPresenter.php index b73087dcb..0089891c7 100644 --- a/src/Presenter/Order/OrderPendingPresenter.php +++ b/src/Presenter/Order/OrderPendingPresenter.php @@ -47,7 +47,8 @@ public function present() $orderTranslations = new OrderStatesTranslations(); $orderTranslations = $orderTranslations->getTranslations($context->language->iso_code); foreach (OrderStates::ORDER_STATES as $key => $value) { - if ($key == 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' || + if ($key == 'PS_CHECKOUT_STATE_WAITING_PAYMENT' || + $key == 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' || $key == 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' || $key == 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' || $key == 'PS_CHECKOUT_STATE_WAITING_CAPTURE' From cb52ebe040a8f64007986ead5b207decc2baf171 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:49:21 +0200 Subject: [PATCH 032/343] Update upgrade files --- ps_checkout.php | 2 +- upgrade/upgrade-8.3.3.0.php | 211 +----------------------------------- upgrade/upgrade-8.3.4.0.php | 198 +++++++++++++++++++++++++++++---- 3 files changed, 178 insertions(+), 233 deletions(-) diff --git a/ps_checkout.php b/ps_checkout.php index ec71b991b..64e90fe4a 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -115,7 +115,7 @@ class Ps_checkout extends PaymentModule 'PS_CHECKOUT_LIVE_STEP_VIEWED' => false, 'PS_CHECKOUT_INTEGRATION_DATE' => self::INTEGRATION_DATE, 'PS_CHECKOUT_WEBHOOK_SECRET' => '', - 'PS_CHECKOUT_LIABILITY_SHIFT_REQ' => '1', + 'PS_CHECKOUT_LIABILITY_SHIFT_REQ' => '0', ]; public $confirmUninstall; diff --git a/upgrade/upgrade-8.3.3.0.php b/upgrade/upgrade-8.3.3.0.php index 017d8c754..4c974daea 100644 --- a/upgrade/upgrade-8.3.3.0.php +++ b/upgrade/upgrade-8.3.3.0.php @@ -42,182 +42,6 @@ function upgrade_module_8_3_3_0($module) try { $db = Db::getInstance(); - - // Installing FundingSource if table pscheckout_funding_source is empty or incomplete - $fundingSources = ['paypal', 'paylater', 'card', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort']; - $availableFundingSourcesByShops = []; - $maxPositionByShops = []; - $availableFundingSources = $db->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'pscheckout_funding_source'); - - if (!empty($availableFundingSources)) { - foreach ($availableFundingSources as $availableFundingSource) { - $currentPosition = (int) $availableFundingSource['position']; - $shopId = (int) $availableFundingSource['id_shop']; - if ( - !isset($maxPositionByShops[$shopId]) - || $maxPositionByShops[$shopId] < $currentPosition - ) { - $maxPositionByShops[$shopId] = $currentPosition; - } - $availableFundingSourcesByShops[$shopId][] = $availableFundingSource['name']; - } - } - - foreach (Shop::getShops(false, null, true) as $shopId) { - $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; - foreach ($fundingSources as $fundingSource) { - if (!isset($availableFundingSourcesByShops[(int) $shopId]) || !in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true)) { - $db->insert( - 'pscheckout_funding_source', - [ - 'name' => pSQL($fundingSource), - 'active' => 1, - 'position' => (int) $currentPosition, - 'id_shop' => (int) $savedShopId, - ] - ); - ++$currentPosition; - } - } - } - - // Check module OrderState - $moduleOrderStates = [ - 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), - 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), - 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'), - 'PS_CHECKOUT_STATE_AUTHORIZED' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_AUTHORIZED'), - 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIAL_REFUND'), - 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CAPTURE'), - ]; - $moduleOrderStatesId = array_values($moduleOrderStates); - - $orderStateCollection = new PrestaShopCollection(OrderState::class); - $orderStateCollection->where('module_name', '=', $module->name); - $orderStateCollection->where('deleted', '=', '0'); - - /** @var OrderState[] $orderStates */ - $orderStates = $orderStateCollection->getResults(); - $currentModuleOrderStatesId = []; - - if (!empty($orderStates)) { - foreach ($orderStates as $orderState) { - $orderStateId = (int) $orderState->id; - if (!in_array($orderStateId, $moduleOrderStatesId, true)) { - $orderState->deleted = true; - $orderState->save(); - } else { - $currentModuleOrderStatesId[] = $orderStateId; - } - } - } - - foreach ($moduleOrderStates as $configuration_key => $id_order_state) { - if ( - !$id_order_state - || !in_array((int) $id_order_state, $currentModuleOrderStatesId, true) - ) { - switch ($configuration_key) { - case 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT', - '#34209E', - [ - 'en' => 'Waiting for PayPal payment', - 'fr' => 'En attente de paiement par PayPal', - 'es' => 'Esperando el pago con PayPal', - 'it' => 'In attesa di pagamento con PayPal', - 'nl' => 'Wachten op PayPal-betaling', - 'de' => 'Warten auf PayPal-Zahlung', - 'pl' => 'Oczekiwanie na płatność PayPal', - 'pt' => 'Aguardando pagamento pelo PayPal', - ] - ); - break; - case 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT', - '#34209E', - [ - 'en' => 'Waiting for Credit Card Payment', - 'fr' => 'En attente de paiement par Carte de Crédit', - 'es' => 'Esperando el pago con tarjeta de crédito', - 'it' => 'In attesa di pagamento con carta di credito', - 'nl' => 'Wachten op creditcard-betaling', - 'de' => 'Warten auf Kreditkartenzahlung', - 'pl' => 'Oczekiwanie na płatność kartą kredytową', - 'pt' => 'Aguardando pagamento por cartão de crédito', - ] - ); - break; - case 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT', - '#34209E', - [ - 'en' => 'Waiting for Local Payment Method Payment', - 'fr' => 'En attente de paiement par moyen de paiement local', - 'es' => 'Esperando el pago con un método de pago local', - 'it' => 'In attesa di pagamento con metodo di pagamento locale', - 'nl' => 'Wachten op nlaatselijke betaling', - 'de' => 'Warten auf Zahlung per lokaler Zahlungsmethode', - 'pl' => 'Oczekiwanie na płatność lokalnym środkiem płatności', - 'pt' => 'Aguardando pagamento pelo método de pagamento local', - ] - ); - break; - case 'PS_CHECKOUT_STATE_AUTHORIZED': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_AUTHORIZED', - '#3498D8', - [ - 'en' => 'Authorized. To be captured by merchant', - 'fr' => 'Autorisation. A capturer par le marchand', - 'es' => 'Autorizado. El vendedor lo capturará', - 'it' => 'Autorizzato. Sarà acquisito dal commerciante', - 'nl' => 'Goedgekeurd. Door retailer te registreren.', - 'de' => 'Autorisiert. Wird von Händler erfasst.', - 'pl' => 'Pomyślna autoryzacja. Transfer do przeprowadzenia przez sklep', - 'pt' => 'Autorizado. A ser capturado pelo comerciante', - ] - ); - break; - case 'PS_CHECKOUT_STATE_PARTIAL_REFUND': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_PARTIAL_REFUND', - '#01B887', - [ - 'en' => 'Partial refund', - 'fr' => 'Remboursement partiel', - 'es' => 'Reembolso parcial', - 'it' => 'Rimborso parziale', - 'nl' => 'Gedeeltelijke terugbetaling', - 'de' => 'Teilweise Rückerstattung', - 'pl' => 'Częściowy zwrot', - 'pt' => 'Reembolso parcial', - ] - ); - break; - case 'PS_CHECKOUT_STATE_WAITING_CAPTURE': - ps_checkout_create_order_state_8_3_3_0( - 'PS_CHECKOUT_STATE_WAITING_CAPTURE', - '#3498D8', - [ - 'en' => 'Waiting capture', - 'fr' => 'En attente de capture', - 'es' => 'Esperando la captura', - 'it' => 'In attesa di essere acquisito', - 'nl' => 'Wachten op registratie', - 'de' => 'Warten auf Erfassung', - 'pl' => 'Oczekiwanie na transfer', - 'pt' => 'Aguardando a captura', - ] - ); - break; - } - } - } - $db->delete( 'pscheckout_cart', 'paypal_order IS NULL' @@ -233,7 +57,7 @@ function upgrade_module_8_3_3_0($module) true ); } catch (Exception $exception) { - PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id, false); + PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id); return false; } @@ -249,36 +73,3 @@ function upgrade_module_8_3_3_0($module) return true; } - -function ps_checkout_create_order_state_8_3_3_0($configuration_key, $color, $nameByLangIsoCode) -{ - $orderStateNameByLangId = []; - foreach ($nameByLangIsoCode as $langIsoCode => $name) { - foreach (Language::getLanguages(false) as $language) { - if (Tools::strtolower($language['iso_code']) === $langIsoCode) { - $orderStateNameByLangId[(int) $language['id_lang']] = $name; - } elseif (isset($nameByLangIsoCode['en'])) { - $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; - } - } - } - - $orderState = new OrderState(); - $orderState->name = $orderStateNameByLangId; - $orderState->module_name = 'ps_checkout'; - $orderState->unremovable = true; - $orderState->color = $color; - $orderState->delivery = false; - $orderState->shipped = false; - $orderState->pdf_delivery = false; - $orderState->pdf_invoice = false; - $orderState->hidden = false; - $orderState->invoice = false; - $orderState->send_email = false; - $orderState->paid = false; - $orderState->logable = false; - $orderState->deleted = false; - $orderState->template = []; - $orderState->save(); - Configuration::updateGlobalValue($configuration_key, $orderState->id); -} diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index a2990ed5d..1684776a9 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -30,32 +30,186 @@ */ function upgrade_module_8_3_4_0($module) { - $orderStateCollection = new PrestaShopCollection(OrderState::class); - $orderStateCollection->where('module_name', '=', $module->name); - $orderStateCollection->where('deleted', '=', '0'); - - /** @var OrderState[] $orderStates */ - $orderStates = $orderStateCollection->getResults(); - - if (!empty($orderStates)) { - foreach ($orderStates as $orderState) { - if (in_array($orderState->id, [Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT')])) { - $orderState->deleted = true; - $orderState->save(); + // Force PrestaShop to upgrade for all shop to avoid issues + $savedShopContext = Shop::getContext(); + $savedShopId = Shop::getContextShopID(); + $savedGroupShopId = Shop::getContextShopGroupID(); + Shop::setContext(Shop::CONTEXT_ALL); + + try { + $shopsList = Shop::getShops(false, null, true); + + foreach ($shopsList as $shopId) { + Configuration::updateValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '0', false, null, (int) $shopId); + } + + Configuration::updateGlobalValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '0'); + + $db = Db::getInstance(); + + // Installing FundingSource if table pscheckout_funding_source is empty or incomplete - including BLIK + $fundingSources = ['paypal', 'paylater', 'card', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort', 'blik']; + $availableFundingSourcesByShops = []; + $maxPositionByShops = []; + $availableFundingSources = $db->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'pscheckout_funding_source'); + + if (!empty($availableFundingSources)) { + foreach ($availableFundingSources as $availableFundingSource) { + $currentPosition = (int) $availableFundingSource['position']; + $shopId = (int) $availableFundingSource['id_shop']; + + if ( + !isset($maxPositionByShops[$shopId]) + || $maxPositionByShops[$shopId] < $currentPosition + ) { + $maxPositionByShops[$shopId] = $currentPosition; + } + + $availableFundingSourcesByShops[$shopId][] = $availableFundingSource['name']; + } + } + + foreach (Shop::getShops(false, null, true) as $shopId) { + $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; + foreach ($fundingSources as $fundingSource) { + if ( + !isset($availableFundingSourcesByShops[(int) $shopId]) + || !in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true) + ) { + $db->insert( + 'pscheckout_funding_source', + [ + 'name' => pSQL($fundingSource), + 'active' => 1, + 'position' => (int) $currentPosition, + 'id_shop' => (int) $savedShopId, + ] + ); + ++$currentPosition; + } } } + + // Check module OrderState + $moduleOrderStates = [ + 'PS_CHECKOUT_STATE_AUTHORIZED' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_AUTHORIZED'), + 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIAL_REFUND'), + 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CAPTURE'), + 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYMENT'), + ]; + $moduleOrderStatesId = array_values($moduleOrderStates); + $moduleOrderStatesIdToDelete = [ + (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), + (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), + (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'), + ]; + + $orderStateCollection = new PrestaShopCollection(OrderState::class); + $orderStateCollection->where('module_name', '=', $module->name); + $orderStateCollection->where('deleted', '=', '0'); + + /** @var OrderState[] $orderStates */ + $orderStates = $orderStateCollection->getResults(); + $currentModuleOrderStatesId = []; + + if (!empty($orderStates)) { + foreach ($orderStates as $orderState) { + $orderStateId = (int) $orderState->id; + if ( + !in_array($orderStateId, $moduleOrderStatesId, true) + || in_array($orderState->id, $moduleOrderStatesIdToDelete, true) + ) { + $orderState->deleted = true; + $orderState->save(); + } else { + $currentModuleOrderStatesId[] = $orderStateId; + } + } + } + + foreach ($moduleOrderStates as $configuration_key => $id_order_state) { + if ( + !$id_order_state + || !in_array((int) $id_order_state, $currentModuleOrderStatesId, true) + ) { + switch ($configuration_key) { + case 'PS_CHECKOUT_STATE_AUTHORIZED': + ps_checkout_create_order_state_8_3_4_0( + 'PS_CHECKOUT_STATE_AUTHORIZED', + '#3498D8', + [ + 'en' => 'Authorized. To be captured by merchant', + 'fr' => 'Autorisation. A capturer par le marchand', + 'es' => 'Autorizado. El vendedor lo capturará', + 'it' => 'Autorizzato. Sarà acquisito dal commerciante', + 'nl' => 'Goedgekeurd. Door retailer te registreren.', + 'de' => 'Autorisiert. Wird von Händler erfasst.', + 'pl' => 'Pomyślna autoryzacja. Transfer do przeprowadzenia przez sklep', + 'pt' => 'Autorizado. A ser capturado pelo comerciante', + ] + ); + break; + case 'PS_CHECKOUT_STATE_PARTIAL_REFUND': + ps_checkout_create_order_state_8_3_4_0( + 'PS_CHECKOUT_STATE_PARTIAL_REFUND', + '#01B887', + [ + 'en' => 'Partial refund', + 'fr' => 'Remboursement partiel', + 'es' => 'Reembolso parcial', + 'it' => 'Rimborso parziale', + 'nl' => 'Gedeeltelijke terugbetaling', + 'de' => 'Teilweise Rückerstattung', + 'pl' => 'Częściowy zwrot', + 'pt' => 'Reembolso parcial', + ] + ); + break; + case 'PS_CHECKOUT_STATE_WAITING_PAYMENT': + ps_checkout_create_order_state_8_3_4_0('PS_CHECKOUT_STATE_WAITING_PAYMENT', '#34209E', [ + 'en' => 'Waiting for payment', + 'fr' => 'En attente de paiement', + 'es' => 'Esperando el pago', + 'it' => 'In attesa di pagamento', + 'nl' => 'Wachten op betaling', + 'de' => 'Warten auf Zahlung', + 'pl' => 'Oczekiwanie na płatność', + 'pt' => 'Aguardando pagamento', + ]); + break; + case 'PS_CHECKOUT_STATE_WAITING_CAPTURE': + ps_checkout_create_order_state_8_3_4_0( + 'PS_CHECKOUT_STATE_WAITING_CAPTURE', + '#3498D8', + [ + 'en' => 'Waiting capture', + 'fr' => 'En attente de capture', + 'es' => 'Esperando la captura', + 'it' => 'In attesa di essere acquisito', + 'nl' => 'Wachten op registratie', + 'de' => 'Warten auf Erfassung', + 'pl' => 'Oczekiwanie na transfer', + 'pt' => 'Aguardando a captura', + ] + ); + break; + } + } + } + } catch (Exception $exception) { + PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id); + + return false; } - ps_checkout_create_order_state_8_3_4_0('PS_CHECKOUT_STATE_WAITING_PAYMENT', '#34209E', [ - 'en' => 'Waiting for payment', - 'fr' => 'En attente de paiement', - 'es' => 'Esperando el pago', - 'it' => 'In attesa di pagamento', - 'nl' => 'Wachten op betaling', - 'de' => 'Warten auf Zahlung', - 'pl' => 'Oczekiwanie na płatność', - 'pt' => 'Aguardando pagamento', - ]); + // Restore initial PrestaShop shop context + if (Shop::CONTEXT_SHOP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedShopId); + } elseif (Shop::CONTEXT_GROUP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedGroupShopId); + } else { + Shop::setContext($savedShopContext); + } return true; } From 84bad982b4c25d6479633c3d13dd1afd3728aec9 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 13 Jul 2023 15:57:50 +0200 Subject: [PATCH 033/343] Bump version to 8.3.4.0 --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index 0724103c0..859438ea3 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 64e90fe4a..8e81aab1c 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.3.1'; + const VERSION = '8.3.4.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -142,7 +142,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.3.1'; + $this->version = '8.3.4.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From 895705608018e6bfbed76918f8ba5cebf68e350c Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:26:56 +0200 Subject: [PATCH 034/343] Create new order status --- config/admin/services.yml | 16 - .../AdminAjaxPrestashopCheckoutController.php | 126 ++++--- ps_checkout.php | 3 +- .../CreateOrderCommandHandler.php | 8 +- ...etOrderForApprovalReversedQueryHandler.php | 2 +- .../GetOrderForPaymentDeniedQueryHandler.php | 4 +- .../GetOrderForPaymentPendingQueryHandler.php | 24 +- .../State/OrderStateConfigurationKeys.php | 38 +- src/Order/State/OrderStateInstaller.php | 212 +++++++++++ src/Order/State/Service/OrderStateMapper.php | 50 ++- src/OrderStates.php | 267 -------------- .../PayPalCaptureEventSubscriber.php | 14 +- src/Presenter/Order/OrderPendingPresenter.php | 83 ----- .../Transaction/TransactionPresenter.php | 63 ---- src/Presenter/Transaction/index.php | 28 -- src/Translations/OrderStatesTranslations.php | 102 ------ src/ValidateOrder.php | 345 ------------------ upgrade/upgrade-1.2.10.php | 17 - upgrade/upgrade-1.3.0.php | 1 - upgrade/upgrade-1.4.0.php | 98 ----- 20 files changed, 365 insertions(+), 1136 deletions(-) create mode 100644 src/Order/State/OrderStateInstaller.php delete mode 100644 src/OrderStates.php delete mode 100644 src/Presenter/Order/OrderPendingPresenter.php delete mode 100644 src/Presenter/Transaction/TransactionPresenter.php delete mode 100755 src/Presenter/Transaction/index.php delete mode 100644 src/Translations/OrderStatesTranslations.php delete mode 100644 src/ValidateOrder.php diff --git a/config/admin/services.yml b/config/admin/services.yml index b90012678..21003a8d8 100644 --- a/config/admin/services.yml +++ b/config/admin/services.yml @@ -12,19 +12,3 @@ services: ps_checkout.logger.file.reader: class: 'PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileReader' public: true - - ps_checkout.repository.orderpayment: - class: 'PrestaShop\Module\PrestashopCheckout\Repository\OrderPaymentRepository' - public: true - - ps_checkout.repository.order: - class: 'PrestaShop\Module\PrestashopCheckout\Repository\OrderRepository' - public: true - - ps_checkout.presenter.order.pending: - class: 'PrestaShop\Module\PrestashopCheckout\Presenter\Order\OrderPendingPresenter' - public: true - - ps_checkout.presenter.transaction: - class: 'PrestaShop\Module\PrestashopCheckout\Presenter\Transaction\TransactionPresenter' - public: true diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index e410da135..3f9916616 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -20,14 +20,25 @@ use Monolog\Logger; use PrestaShop\Module\PrestashopCheckout\Configuration\BatchConfigurationProcessor; +use PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration; +use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository; +use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerDirectory; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFactory; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileFinder; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileReader; -use PrestaShop\Module\PrestashopCheckout\OrderStates; +use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\LiveStep; +use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\ValueBanner; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration; use PrestaShop\Module\PrestashopCheckout\Presenter\Order\OrderPresenter; +use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; use PrestaShop\Module\PrestashopCheckout\Settings\RoundingSettings; use PrestaShop\Module\PrestashopCheckout\Validator\BatchConfigurationValidator; +use PrestaShop\Module\PrestashopCheckout\Webhook\WebhookSecretTokenService; use Psr\SimpleCache\CacheInterface; class AdminAjaxPrestashopCheckoutController extends ModuleAdminController @@ -72,7 +83,7 @@ public function postProcess() public function ajaxProcessUpdatePaymentMethodsOrder() { $paymentOptions = json_decode(Tools::getValue('paymentMethods'), true); - /** @var PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository $fundingSourceConfigurationRepository */ + /** @var FundingSourceConfigurationRepository $fundingSourceConfigurationRepository */ $fundingSourceConfigurationRepository = $this->module->getService('ps_checkout.funding_source.configuration.repository'); foreach ($paymentOptions as $key => $paymentOption) { @@ -100,7 +111,7 @@ public function ajaxProcessUpdatePaymentMode() */ public function ajaxProcessLiveStepConfirmed() { - /** @var \PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\LiveStep $stepLive */ + /** @var LiveStep $stepLive */ $stepLive = $this->module->getService('ps_checkout.step.live'); $stepLive->confirmed(true); @@ -112,7 +123,7 @@ public function ajaxProcessLiveStepConfirmed() */ public function ajaxProcessLiveStepViewed() { - /** @var \PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\LiveStep $stepLive */ + /** @var LiveStep $stepLive */ $stepLive = $this->module->getService('ps_checkout.step.live'); $stepLive->viewed(true); @@ -124,7 +135,7 @@ public function ajaxProcessLiveStepViewed() */ public function ajaxProcessValueBannerClosed() { - /** @var \PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\ValueBanner $valueBanner */ + /** @var ValueBanner $valueBanner */ $valueBanner = $this->module->getService('ps_checkout.step.value'); $valueBanner->closed(true); @@ -139,7 +150,7 @@ public function ajaxProcessValueBannerClosed() */ public function ajaxProcessEditRoundingSettings() { - /** @var PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration $paypalConfiguration */ + /** @var PayPalConfiguration $paypalConfiguration */ $paypalConfiguration = $this->module->getService('ps_checkout.paypal.configuration'); $paypalConfiguration->setRoundType(RoundingSettings::ROUND_ON_EACH_ITEM); $paypalConfiguration->setPriceRoundMode(RoundingSettings::ROUND_UP_AWAY_FROM_ZERO); @@ -148,25 +159,16 @@ public function ajaxProcessEditRoundingSettings() } /** - * AJAX: Retrieve Reporting informations + * @deprecated No more used */ public function ajaxProcessGetReportingDatas() { - try { - /** @var PrestaShop\Module\PrestashopCheckout\Presenter\Order\OrderPendingPresenter $pendingOrder */ - $pendingOrder = $this->module->getService('ps_checkout.presenter.order.pending'); - /** @var PrestaShop\Module\PrestashopCheckout\Presenter\Transaction\TransactionPresenter $transactionOrder */ - $transactionOrder = $this->module->getService('ps_checkout.presenter.transaction'); - $this->ajaxDie( - json_encode([ - 'orders' => $pendingOrder->present(), - 'transactions' => $transactionOrder->present(), - ]) - ); - } catch (Exception $exception) { - http_response_code(500); - $this->ajaxDie(json_encode(strip_tags($exception->getMessage()))); - } + $this->ajaxDie( + json_encode([ + 'orders' => [], + 'transactions' => [], + ]) + ); } /** @@ -176,7 +178,7 @@ public function ajaxProcessTogglePaymentOptionAvailability() { $paymentOption = json_decode(Tools::getValue('paymentOption'), true); - /** @var PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository $fundingSourceConfigurationRepository */ + /** @var FundingSourceConfigurationRepository $fundingSourceConfigurationRepository */ $fundingSourceConfigurationRepository = $this->module->getService('ps_checkout.funding_source.configuration.repository'); $fundingSourceConfigurationRepository->save($paymentOption); @@ -189,7 +191,7 @@ public function ajaxProcessTogglePaymentOptionAvailability() */ public function ajaxProcessUpdateCreditCardFields() { - /** @var PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration $paypalConfiguration */ + /** @var PayPalConfiguration $paypalConfiguration */ $paypalConfiguration = $this->module->getService('ps_checkout.paypal.configuration'); $paypalConfiguration->setCardPaymentEnabled((bool) Tools::getValue('hostedFieldsEnabled')); @@ -202,7 +204,7 @@ public function ajaxProcessUpdateCreditCardFields() */ public function ajaxProcessToggleECOrderPage() { - /** @var \PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration $ecConfiguration */ + /** @var ExpressCheckoutConfiguration $ecConfiguration */ $ecConfiguration = $this->module->getService('ps_checkout.express_checkout.configuration'); $ecConfiguration->setOrderPage((bool) Tools::getValue('status')); @@ -216,7 +218,7 @@ public function ajaxProcessToggleECOrderPage() */ public function ajaxProcessToggleECCheckoutPage() { - /** @var \PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration $ecConfiguration */ + /** @var ExpressCheckoutConfiguration $ecConfiguration */ $ecConfiguration = $this->module->getService('ps_checkout.express_checkout.configuration'); $ecConfiguration->setCheckoutPage(Tools::getValue('status') ? true : false); @@ -230,7 +232,7 @@ public function ajaxProcessToggleECCheckoutPage() */ public function ajaxProcessToggleECProductPage() { - /** @var \PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration $ecConfiguration */ + /** @var ExpressCheckoutConfiguration $ecConfiguration */ $ecConfiguration = $this->module->getService('ps_checkout.express_checkout.configuration'); $ecConfiguration->setProductPage(Tools::getValue('status') ? true : false); @@ -374,7 +376,7 @@ public function ajaxProcessFetchOrder() } } - /** @var \PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider $paypalOrderProvider */ + /** @var PayPalOrderProvider $paypalOrderProvider */ $paypalOrderProvider = $this->module->getService('ps_checkout.paypal.provider.order'); $paypalOrder = $paypalOrderProvider->getById($psCheckoutCart->paypal_order); @@ -382,7 +384,7 @@ public function ajaxProcessFetchOrder() $paypalOrder = []; } - /** @var \PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider $fundingSourceTranslationProvider */ + /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ $fundingSourceTranslationProvider = $this->module->getService('ps_checkout.funding_source.translation'); $presenter = new OrderPresenter($this->module, $paypalOrder); @@ -456,7 +458,7 @@ public function ajaxProcessRefundOrder() ])); } - /** @var \PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration $configurationPayPal */ + /** @var PayPalConfiguration $configurationPayPal */ $configurationPayPal = $this->module->getService('ps_checkout.paypal.configuration'); $response = (new PrestaShop\Module\PrestashopCheckout\Api\Payment\Order($this->context->link))->refund([ @@ -726,7 +728,7 @@ public function ajaxProcessSavePaypalButtonConfiguration() */ public function ajaxProcessGetOrRefreshToken() { - /** @var \PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository $psAccountRepository */ + /** @var PsAccountRepository $psAccountRepository */ $psAccountRepository = $this->module->getService('ps_checkout.repository.prestashop.account'); try { @@ -794,7 +796,7 @@ public function display() private function togglePayLaterConfiguration($method) { - /** @var \PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration $payLaterConfiguration */ + /** @var PayPalPayLaterConfiguration $payLaterConfiguration */ $payLaterConfiguration = $this->module->getService('ps_checkout.pay_later.configuration'); $payLaterConfiguration->$method(Tools::getValue('status') ? true : false); @@ -803,7 +805,7 @@ private function togglePayLaterConfiguration($method) public function ajaxProcessUpsertSecretToken() { - /** @var \PrestaShop\Module\PrestashopCheckout\Webhook\WebhookSecretTokenService $webhookSecretTokenService */ + /** @var WebhookSecretTokenService $webhookSecretTokenService */ $webhookSecretTokenService = $this->module->getService('ps_checkout.webhook.service.secret_token'); $secret = (string) Tools::getValue('body'); @@ -855,8 +857,6 @@ public function ajaxProcessCheckConfiguration() public function ajaxProcessFetchConfiguration() { - $response = []; - $query = new DbQuery(); $query->select('name, value, date_add, date_upd'); $query->from('configuration'); @@ -888,47 +888,45 @@ public function ajaxProcessFetchConfiguration() public function ajaxProcessGetMappedOrderStates() { - /** @var \PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration $configuration */ - $configuration = $this->module->getService('ps_checkout.configuration'); + /** @var OrderStateMapper $orderStateMapper */ + $orderStateMapper = $this->module->getService('ps_checkout.order.state.service.order_state_mapper'); - $mappedOrderStates = [ - OrderStates::PS_CHECKOUT_STATE_PENDING => [ + $this->exitWithResponse([ + 'status' => true, + 'mappedOrderStates' => [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING => [ 'default' => '0', - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PENDING, ['default' => '0']), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING), ], - OrderStates::PS_CHECKOUT_STATE_COMPLETED => [ - 'default' => $configuration->get('PS_OS_PAYMENT'), - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_COMPLETED, ['default' => '0']), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED => [ + 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_PAYMENT), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED), ], - OrderStates::PS_CHECKOUT_STATE_CANCELED => [ - 'default' => $configuration->get('PS_OS_CANCELED'), - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_CANCELED, ['default' => '0']), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED => [ + 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_CANCELED), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED), ], - OrderStates::PS_CHECKOUT_STATE_ERROR => [ - 'default' => $configuration->get('PS_OS_ERROR'), - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_ERROR, ['default' => '0']), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR => [ + 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_ERROR), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR), ], - OrderStates::PS_CHECKOUT_STATE_REFUNDED => [ - 'default' => $configuration->get('PS_OS_REFUND'), - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_REFUNDED, ['default' => '0']), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED => [ + 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_REFUND), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED), ], - OrderStates::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => [ 'default' => '0', - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED, ['default' => '0']), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED), ], - OrderStates::PS_CHECKOUT_STATE_PARTIALLY_PAID => [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID => [ 'default' => '0', - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_PARTIALLY_PAID, ['default' => '0']), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID), ], - OrderStates::PS_CHECKOUT_STATE_AUTHORIZED => [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED => [ 'default' => '0', - 'value' => $configuration->get(OrderStates::PS_CHECKOUT_STATE_AUTHORIZED, ['default' => '0']), + 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED), ], - ]; - - $this->exitWithResponse([ - 'status' => true, - 'mappedOrderStates' => $mappedOrderStates, + ], ]); } @@ -958,7 +956,7 @@ public function ajaxProcessBatchSaveConfiguration() public function ajaxProcessGetOrderStates() { - $orderStates = OrderState::getOrderStates(Context::getContext()->language->id); + $orderStates = OrderState::getOrderStates($this->context->language->id); $this->exitWithResponse([ 'status' => true, diff --git a/ps_checkout.php b/ps_checkout.php index 8e81aab1c..4e945f232 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -179,13 +179,14 @@ public function install() $result = parent::install() && $this->installConfiguration() && $this->installHooks() && - (new PrestaShop\Module\PrestashopCheckout\OrderStates())->installPaypalStates() && (new PrestaShop\Module\PrestashopCheckout\Database\TableManager())->createTable() && (new PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceInstaller())->createFundingSources() && $this->installTabs() && $this->disableIncompatibleCountries() && $this->disableIncompatibleCurrencies(); + (new \PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller())->install(); + // Restore initial PrestaShop shop context if (Shop::CONTEXT_SHOP === $savedShopContext) { Shop::setContext($savedShopContext, $savedShopId); diff --git a/src/Order/CommandHandler/CreateOrderCommandHandler.php b/src/Order/CommandHandler/CreateOrderCommandHandler.php index ce8055023..efa930e00 100644 --- a/src/Order/CommandHandler/CreateOrderCommandHandler.php +++ b/src/Order/CommandHandler/CreateOrderCommandHandler.php @@ -146,16 +146,16 @@ public function handle(CreateOrderCommand $command) if ($paidAmount) { switch ($this->checkOrderAmount->checkAmount((string) $paidAmount, (string) $cart->getOrderTotal(true, \Cart::BOTH))) { case CheckOrderAmount::ORDER_NOT_FULL_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID); + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID); break; case CheckOrderAmount::ORDER_FULL_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); break; case CheckOrderAmount::ORDER_TO_MUCH_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED); + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); } } else { - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYMENT); + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING); } /** @var FundingSourceTranslationProvider $fundingSourceTranslationProvider */ diff --git a/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php b/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php index 4c394c044..461fdd10c 100644 --- a/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForApprovalReversedQueryHandler.php @@ -80,7 +80,7 @@ public function handle(GetOrderForApprovalReversedQuery $query) throw new OrderNotFoundException('No PrestaShop Order associated to this PayPal Order at this time.'); } - $hasBeenCanceled = count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED))); + $hasBeenCanceled = count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED))); return new GetOrderForApprovalReversedQueryResult( (int) $order->id, diff --git a/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php index ea66ca0d7..8e8046718 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentDeniedQueryHandler.php @@ -94,7 +94,7 @@ public function handle(GetOrderForPaymentDeniedQuery $query) */ private function hasBeenError(Order $order) { - return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PAYMENT_ERROR))) - || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::CANCELED))); + return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR))) + || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED))); } } diff --git a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php index b04b6f19a..5f87d43ef 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentPendingQueryHandler.php @@ -95,9 +95,25 @@ public function handle(GetOrderForPaymentPendingQuery $query) */ private function isInPending(Order $order) { - return count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYMENT))) - || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT))) - || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT))) - || count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT))); + if (count($order->getHistory($order->id_lang, (int) Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING)))) { + return true; + } + + // Check deprecated states + $deprecatedStates = [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT, + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT, + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT, + ]; + + foreach ($deprecatedStates as $deprecatedState) { + $deprecatedStateId = (int) Configuration::getGlobalValue($deprecatedState); + + if ($deprecatedStateId && count($order->getHistory($order->id_lang, $deprecatedStateId))) { + return true; + } + } + + return false; } } diff --git a/src/Order/State/OrderStateConfigurationKeys.php b/src/Order/State/OrderStateConfigurationKeys.php index acaf1cdd4..9cf8d6868 100644 --- a/src/Order/State/OrderStateConfigurationKeys.php +++ b/src/Order/State/OrderStateConfigurationKeys.php @@ -22,20 +22,28 @@ class OrderStateConfigurationKeys { - const CANCELED = 'PS_OS_CANCELED'; - const PAYMENT_ERROR = 'PS_OS_ERROR'; - const OUT_OF_STOCK_UNPAID = 'PS_OS_OUTOFSTOCK_UNPAID'; - const OUT_OF_STOCK_PAID = 'PS_OS_OUTOFSTOCK_PAID'; - const PAYMENT_ACCEPTED = 'PS_OS_PAYMENT'; - const REFUNDED = 'PS_OS_REFUND'; + // PrestaShop native order statuses + const PS_OS_CANCELED = 'PS_OS_CANCELED'; + const PS_OS_ERROR = 'PS_OS_ERROR'; + const PS_OS_OUTOFSTOCK_UNPAID = 'PS_OS_OUTOFSTOCK_UNPAID'; + const PS_OS_OUTOFSTOCK_PAID = 'PS_OS_OUTOFSTOCK_PAID'; + const PS_OS_PAYMENT = 'PS_OS_PAYMENT'; + const PS_OS_REFUND = 'PS_OS_REFUND'; - const AUTHORIZED = 'PS_CHECKOUT_STATE_AUTHORIZED'; - //const PARTIALLY_PAID = 'PS_CHECKOUT_STATE_PARTIAL_PAYMENT'; @todo Create a Partial payment state - const PARTIALLY_PAID = 'PS_OS_PAYMENT'; - const PARTIALLY_REFUNDED = 'PS_CHECKOUT_STATE_PARTIAL_REFUND'; - const WAITING_CAPTURE = 'PS_CHECKOUT_STATE_WAITING_CAPTURE'; - const WAITING_CREDIT_CARD_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'; - const WAITING_LOCAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'; - const WAITING_PAYPAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'; - const WAITING_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_PAYMENT'; + // PrestaShop Checkout order statuses + const PS_CHECKOUT_STATE_PENDING = 'PS_CHECKOUT_STATE_PENDING'; + const PS_CHECKOUT_STATE_COMPLETED = 'PS_CHECKOUT_STATE_COMPLETED'; + const PS_CHECKOUT_STATE_CANCELED = 'PS_CHECKOUT_STATE_CANCELED'; + const PS_CHECKOUT_STATE_ERROR = 'PS_CHECKOUT_STATE_ERROR'; + const PS_CHECKOUT_STATE_REFUNDED = 'PS_CHECKOUT_STATE_REFUNDED'; + const PS_CHECKOUT_STATE_PARTIALLY_REFUNDED = 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED'; + const PS_CHECKOUT_STATE_PARTIALLY_PAID = 'PS_CHECKOUT_STATE_PARTIALLY_PAID'; + const PS_CHECKOUT_STATE_AUTHORIZED = 'PS_CHECKOUT_STATE_AUTHORIZED'; + + // PrestaShop Checkout deprecated order statuses + const PS_CHECKOUT_STATE_PARTIAL_REFUND = 'PS_CHECKOUT_STATE_PARTIAL_REFUND'; + const PS_CHECKOUT_STATE_WAITING_CAPTURE = 'PS_CHECKOUT_STATE_WAITING_CAPTURE'; + const PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'; + const PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'; + const PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT = 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'; } diff --git a/src/Order/State/OrderStateInstaller.php b/src/Order/State/OrderStateInstaller.php new file mode 100644 index 000000000..d3ad86868 --- /dev/null +++ b/src/Order/State/OrderStateInstaller.php @@ -0,0 +1,212 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Order\State; + +use Configuration; +use Language; +use OrderState; +use Tools; +use Validate; + +class OrderStateInstaller +{ + /** + * @var array + */ + private $languages; + + public function __construct() + { + $this->languages = Language::getLanguages(false); + } + + public function install() + { + Configuration::updateGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED, Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_OS_PAYMENT)); + Configuration::updateGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED, Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_OS_CANCELED)); + Configuration::updateGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR, Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_OS_ERROR)); + Configuration::updateGlobalValue(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED, Configuration::getGlobalValue(OrderStateConfigurationKeys::PS_OS_REFUND)); + + if (!$this->checkAlreadyInstalled(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING)) { + $this->createOrderState( + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING, + '#34209E', + [ + 'en' => 'Waiting for payment', + 'fr' => 'En attente de paiement', + 'es' => 'Esperando el pago', + 'it' => 'In attesa di pagamento', + 'nl' => 'Wachten op betaling', + 'de' => 'Warten auf Zahlung', + 'pl' => 'Oczekiwanie na płatność', + 'pt' => 'Aguardando pagamento', + ] + ); + } + + if (!$this->checkAlreadyInstalled(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED)) { + $this->createOrderState( + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED, + '#01B887', + [ + 'en' => 'Partial refund', + 'fr' => 'Remboursement partiel', + 'es' => 'Reembolso parcial', + 'it' => 'Rimborso parziale', + 'nl' => 'Gedeeltelijke terugbetaling', + 'de' => 'Teilweise Rückerstattung', + 'pl' => 'Częściowy zwrot', + 'pt' => 'Reembolso parcial', + ] + ); + } + + if (!$this->checkAlreadyInstalled(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID)) { + $this->createOrderState( + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID, + '#3498D8', + [ + 'en' => 'Partial payment', + 'fr' => 'Paiement partiel', + 'es' => 'Pago parcial', + 'it' => 'Pagamento parziale', + 'nl' => 'Gedeeltelijke betaling', + 'de' => 'Teilweise Zahlung', + 'pl' => 'Częściowa płatność', + 'pt' => 'Pagamento parcial', + ] + ); + } + + if (!$this->checkAlreadyInstalled(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED)) { + $this->createOrderState( + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED, + '#3498D8', + [ + 'en' => 'Authorized. To be captured by merchant', + 'fr' => 'Autorisation. A capturer par le marchand', + 'es' => 'Autorizado. El vendedor lo capturará', + 'it' => 'Autorizzato. Sarà acquisito dal commerciante', + 'nl' => 'Goedgekeurd. Door retailer te registreren.', + 'de' => 'Autorisiert. Wird von Händler erfasst.', + 'pl' => 'Pomyślna autoryzacja. Transfer do przeprowadzenia przez sklep', + 'pt' => 'Autorizado. A ser capturado pelo comerciante', + ] + ); + } + } + + /** + * @param string $configuration_key + * @param string $color + * @param array $nameByLangIsoCode + */ + private function createOrderState($configuration_key, $color, array $nameByLangIsoCode) + { + $orderState = new OrderState(); + $orderState->name = $this->fillOrderStateName($nameByLangIsoCode); + $orderState->module_name = 'ps_checkout'; + $orderState->unremovable = true; + $orderState->color = $color; + $orderState->delivery = false; + $orderState->shipped = false; + $orderState->pdf_delivery = false; + $orderState->pdf_invoice = false; + $orderState->hidden = false; + $orderState->invoice = false; + $orderState->send_email = false; + $orderState->paid = false; + $orderState->logable = false; + $orderState->deleted = false; + $orderState->template = []; + $orderState->save(); + + Configuration::updateGlobalValue($configuration_key, $orderState->id); + $this->setStateIcons($configuration_key, $orderState->id); + } + + /** + * @param array $nameByLangIsoCode + * + * @return array + */ + private function fillOrderStateName(array $nameByLangIsoCode) + { + $orderStateNameByLangId = []; + + foreach ($nameByLangIsoCode as $langIsoCode => $name) { + foreach ($this->languages as $language) { + if (Tools::strtolower($language['iso_code']) === $langIsoCode) { + $orderStateNameByLangId[(int) $language['id_lang']] = $name; + } elseif (isset($nameByLangIsoCode['en'])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; + } + } + } + + return $orderStateNameByLangId; + } + + /** + * @param string $orderStateKey + * + * @return bool + */ + private function checkAlreadyInstalled($orderStateKey) + { + $orderStateId = (int) Configuration::getGlobalValue($orderStateKey); + + if (!$orderStateId) { + return false; + } + + $orderState = new OrderState($orderStateId); + + if (!Validate::isLoadedObject($orderState)) { + return false; + } + + if ($orderState->module_name !== 'ps_checkout' || $orderState->deleted) { + return false; + } + + return true; + } + + /** + * @param string $orderStateKey + * @param int $orderStateId + */ + private function setStateIcons($orderStateKey, $orderStateId) + { + $orderStateImage = $orderStateKey === OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED ? 'refund.gif' : 'waiting.gif'; + $moduleOrderStateImgPath = _PS_MODULE_DIR_ . 'ps_checkout/views/img/OrderStatesIcons/' . $orderStateImage; + $coreOrderStateImgPath = _PS_IMG_DIR_ . 'os/' . $orderStateId . '.gif'; + + if ( + Tools::file_exists_cache($moduleOrderStateImgPath) + && !Tools::file_exists_cache($coreOrderStateImgPath) + && is_writable(_PS_IMG_DIR_ . 'os/') + ) { + Tools::copy($moduleOrderStateImgPath, $coreOrderStateImgPath); + } + } +} diff --git a/src/Order/State/Service/OrderStateMapper.php b/src/Order/State/Service/OrderStateMapper.php index f11782f68..9d773e9e1 100644 --- a/src/Order/State/Service/OrderStateMapper.php +++ b/src/Order/State/Service/OrderStateMapper.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\State\Service; +use InvalidArgumentException; use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; @@ -41,22 +42,7 @@ class OrderStateMapper public function __construct(PrestaShopConfiguration $configuration) { $this->configuration = $configuration; - $this->orderStateMapping = [ - OrderStateConfigurationKeys::CANCELED => (int) $this->configuration->get(OrderStateConfigurationKeys::CANCELED, ['global' => true]), - OrderStateConfigurationKeys::PAYMENT_ERROR => (int) $this->configuration->get(OrderStateConfigurationKeys::PAYMENT_ERROR, ['global' => true]), - OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID => (int) $this->configuration->get(OrderStateConfigurationKeys::OUT_OF_STOCK_UNPAID, ['global' => true]), - OrderStateConfigurationKeys::OUT_OF_STOCK_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::OUT_OF_STOCK_PAID, ['global' => true]), - OrderStateConfigurationKeys::PAYMENT_ACCEPTED => (int) $this->configuration->get(OrderStateConfigurationKeys::PAYMENT_ACCEPTED, ['global' => true]), /* @phpstan-ignore-line */ - OrderStateConfigurationKeys::REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::REFUNDED, ['global' => true]), - OrderStateConfigurationKeys::AUTHORIZED => (int) $this->configuration->get(OrderStateConfigurationKeys::AUTHORIZED, ['global' => true]), - OrderStateConfigurationKeys::PARTIALLY_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::PARTIALLY_PAID, ['global' => true]), /* @phpstan-ignore-line */ - OrderStateConfigurationKeys::PARTIALLY_REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::PARTIALLY_REFUNDED, ['global' => true]), - OrderStateConfigurationKeys::WAITING_CAPTURE => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_CAPTURE, ['global' => true]), - OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_CREDIT_CARD_PAYMENT, ['global' => true]), - OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_PAYPAL_PAYMENT, ['global' => true]), - OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_LOCAL_PAYMENT, ['global' => true]), - OrderStateConfigurationKeys::WAITING_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::WAITING_PAYMENT, ['global' => true]), - ]; + $this->initialize(); } /** @@ -70,7 +56,7 @@ public function getIdByKey($key) return $this->orderStateMapping[$key]; } - throw new \InvalidArgumentException(sprintf('Order state key "%s" is not mapped', var_export($key, true))); + throw new InvalidArgumentException(sprintf('Order state key "%s" is not mapped', var_export($key, true))); } /** @@ -94,6 +80,34 @@ public function getKeyById($orderCurrentState) return $orderStateMapping[$orderCurrentState]; } - throw new \InvalidArgumentException(sprintf('Order state id "%s" is not mapped', var_export($orderCurrentState, true))); + throw new InvalidArgumentException(sprintf('Order state id "%s" is not mapped', var_export($orderCurrentState, true))); + } + + private function initialize() + { + $this->orderStateMapping = [ + // PrestaShop native order statuses + OrderStateConfigurationKeys::PS_OS_CANCELED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_CANCELED, ['global' => true]), + OrderStateConfigurationKeys::PS_OS_ERROR => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_ERROR, ['global' => true]), + OrderStateConfigurationKeys::PS_OS_OUTOFSTOCK_UNPAID => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_OUTOFSTOCK_UNPAID, ['global' => true]), + OrderStateConfigurationKeys::PS_OS_OUTOFSTOCK_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_OUTOFSTOCK_PAID, ['global' => true]), + OrderStateConfigurationKeys::PS_OS_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::PS_OS_REFUND => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_OS_REFUND, ['global' => true]), + // PrestaShop Checkout order statuses + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED, ['global' => true]), + // PrestaShop Checkout deprecated order statuses + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIAL_REFUND => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIAL_REFUND, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_CAPTURE => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_CAPTURE, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT, ['global' => true]), + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT => (int) $this->configuration->get(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT, ['global' => true]), + ]; } } diff --git a/src/OrderStates.php b/src/OrderStates.php deleted file mode 100644 index 0b2e796d1..000000000 --- a/src/OrderStates.php +++ /dev/null @@ -1,267 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout; - -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Translations\OrderStatesTranslations; - -class OrderStates -{ - const MODULE_NAME = 'ps_checkout'; - const ORDER_STATE_TEMPLATE = 'payment'; - const ORDER_TABLE = 'orders'; - const ORDER_HISTORY_TABLE = 'order_history'; - const ORDER_STATE_TABLE = 'order_state'; - const ORDER_STATE_LANG_TABLE = 'order_state_lang'; - const DARK_BLUE_HEXA_COLOR = '#34209E'; - const BLUE_HEXA_COLOR = '#3498D8'; - const GREEN_HEXA_COLOR = '#01B887'; - - const PS_CHECKOUT_STATE_PENDING = 'PS_CHECKOUT_STATE_PENDING'; - const PS_CHECKOUT_STATE_COMPLETED = 'PS_CHECKOUT_STATE_COMPLETED'; - const PS_CHECKOUT_STATE_CANCELED = 'PS_CHECKOUT_STATE_CANCELED'; - const PS_CHECKOUT_STATE_ERROR = 'PS_CHECKOUT_STATE_ERROR'; - const PS_CHECKOUT_STATE_PARTIALLY_REFUNDED = 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED'; - const PS_CHECKOUT_STATE_REFUNDED = 'PS_CHECKOUT_STATE_REFUNDED'; - const PS_CHECKOUT_STATE_PARTIALLY_PAID = 'PS_CHECKOUT_STATE_PARTIALLY_PAID'; - const PS_CHECKOUT_STATE_AUTHORIZED = 'PS_CHECKOUT_STATE_AUTHORIZED'; - const ORDER_STATES = [ - 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => self::DARK_BLUE_HEXA_COLOR, - 'PS_CHECKOUT_STATE_AUTHORIZED' => self::BLUE_HEXA_COLOR, - 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => self::GREEN_HEXA_COLOR, - 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => self::BLUE_HEXA_COLOR, - ]; - - const REFUND_STATE = 'PS_CHECKOUT_STATE_PARTIAL_REFUND'; - - /** - * Insert the new paypal states if it does not exists - * Create a new order state for each ps_checkout new order states - * - * FYI: this method is also used in the upgrade-1.2.14.php file - * - * @return bool - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - */ - public function installPaypalStates() - { - foreach (self::ORDER_STATES as $state => $color) { - $orderStateId = $this->getPaypalStateId($state, $color); - $this->createPaypalStateLangs($state, $orderStateId); - $this->setStateIcons($state, $orderStateId); - } - - return true; - } - - /** - * Get the paypal state id if it already exist. - * Get the paypal state id if it doesn't exist by creating it - * - * @param string $state - * @param string $color - * - * @return int - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - */ - private function getPaypalStateId($state, $color) - { - $stateId = (int) \Configuration::getGlobalValue($state); - - // Is state ID already existing in the Configuration table ? - if (0 === $stateId || false === $this->stateAlreadyExists($stateId)) { - return $this->createPaypalStateId($state, $color); - } - - return (int) $stateId; - } - - /** - * Create the Paypal State id - * - * @param string $state - * @param string $color - * - * @return int orderStateId - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - */ - private function createPaypalStateId($state, $color) - { - $data = [ - 'module_name' => self::MODULE_NAME, - 'color' => $color, - 'unremovable' => 1, - ]; - - if (true === \Db::getInstance()->insert(self::ORDER_STATE_TABLE, $data)) { - $insertedId = (int) \Db::getInstance()->Insert_ID(); - \Configuration::updateGlobalValue($state, $insertedId); - - return $insertedId; - } - - throw new PsCheckoutException('Not able to insert the new order state', PsCheckoutException::PRESTASHOP_ORDER_STATE_ERROR); - } - - /** - * Create the Paypal States Lang - * - * @param string $state - * @param int $orderStateId - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - */ - private function createPaypalStateLangs($state, $orderStateId) - { - $languagesList = \Language::getLanguages(); - $orderStatesTranslations = new OrderStatesTranslations(); - - // For each languages in the shop, we insert a new order state name - foreach ($languagesList as $key => $lang) { - if (true === $this->stateLangAlreadyExists($orderStateId, (int) $lang['id_lang'])) { - continue; - } - - $statesTranslations = $orderStatesTranslations->getTranslations($lang['iso_code']); - $this->insertNewStateLang($orderStateId, $statesTranslations[$state], (int) $lang['id_lang']); - } - } - - /** - * Check if Paypal State language already exists in the table ORDEr_STATE_LANG_TABLE - * - * @param int $orderStateId - * @param int $langId - * - * @return bool - */ - private function stateLangAlreadyExists($orderStateId, $langId) - { - return (bool) \Db::getInstance()->getValue( - 'SELECT id_order_state - FROM `' . _DB_PREFIX_ . self::ORDER_STATE_LANG_TABLE . '` - WHERE - id_order_state = ' . (int) $orderStateId . ' - AND id_lang = ' . (int) $langId - ); - } - - /** - * Create the Paypal States Lang - * - * @param int $orderStateId - * @param string $translations - * @param int $langId - * - * @throws PsCheckoutException - * @throws \PrestaShopDatabaseException - */ - private function insertNewStateLang($orderStateId, $translations, $langId) - { - $data = [ - 'id_order_state' => $orderStateId, - 'id_lang' => (int) $langId, - 'name' => pSQL($translations), - 'template' => self::ORDER_STATE_TEMPLATE, - ]; - - if (false === \Db::getInstance()->insert(self::ORDER_STATE_LANG_TABLE, $data)) { - throw new PsCheckoutException('Not able to insert the new order state language', PsCheckoutException::PRESTASHOP_ORDER_STATE_ERROR); - } - } - - /** - * Set an icon for the current State Id - * - * @param string $state - * @param int $orderStateId - * - * @return bool - */ - private function setStateIcons($state, $orderStateId) - { - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - $iconExtension = '.gif'; - $iconToPaste = _PS_ORDER_STATE_IMG_DIR_ . $orderStateId . $iconExtension; - - if (true === file_exists($iconToPaste)) { - if (true !== is_writable($iconToPaste)) { - $module->getLogger()->error( - '[PSPInstall] file is not writable', - [ - 'file' => $iconToPaste, - ] - ); - - return false; - } - } - - if ($state === static::REFUND_STATE) { - $iconName = 'refund'; - } else { - $iconName = 'waiting'; - } - - $iconsFolderOrigin = _PS_MODULE_DIR_ . self::MODULE_NAME . '/views/img/OrderStatesIcons/'; - $iconToCopy = $iconsFolderOrigin . $iconName . $iconExtension; - - if (false === copy($iconToCopy, $iconToPaste)) { - $module->getLogger()->error( - '[PSPInstall] not able to copy icon', - [ - 'icon' => $iconName, - 'id_order_state' => $orderStateId, - ] - ); - - return false; - } - - return true; - } - - /** - * Check if OrderState already exists in the table - * - * @param int $orderStateId - * - * @return bool - */ - private function stateAlreadyExists($orderStateId) - { - $query = new \DbQuery(); - $query->select('id_order_state'); - $query->from(self::ORDER_STATE_TABLE); - $query->where('id_order_state = ' . (int) $orderStateId); - $query->where('module_name = "' . self::MODULE_NAME . '"'); - - return (bool) \Db::getInstance()->getValue($query); - } -} diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index e7c4693c0..a70254804 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -167,10 +167,10 @@ public function setPaymentCompletedOrderStatus(PayPalCaptureCompletedEvent $even switch ($this->checkOrderAmount->checkAmount((string) $order->getTotalAmount(), (string) $event->getCapture()['amount']['value'])) { case CheckOrderAmount::ORDER_FULL_PAID: case CheckOrderAmount::ORDER_TO_MUCH_PAID: - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ACCEPTED))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED))); break; case CheckOrderAmount::ORDER_NOT_FULL_PAID: - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_PAID))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID))); break; } } @@ -184,7 +184,7 @@ public function setPaymentPendingOrderStatus(PayPalCapturePendingEvent $event) return; } - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::WAITING_PAYMENT))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING))); } public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) @@ -196,7 +196,7 @@ public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) return; } - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PAYMENT_ERROR))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR))); } public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) @@ -209,9 +209,9 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) } if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $order->getTotalRefund()) == CheckOrderAmount::ORDER_NOT_FULL_PAID) { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PARTIALLY_REFUNDED))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED))); } else { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::REFUNDED))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED))); } } @@ -224,7 +224,7 @@ public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) return; } - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::REFUNDED))); + $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED))); } public function updateCache(PayPalCaptureEvent $event) diff --git a/src/Presenter/Order/OrderPendingPresenter.php b/src/Presenter/Order/OrderPendingPresenter.php deleted file mode 100644 index 0089891c7..000000000 --- a/src/Presenter/Order/OrderPendingPresenter.php +++ /dev/null @@ -1,83 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Presenter\Order; - -use PrestaShop\Module\PrestashopCheckout\Adapter\LinkAdapter; -use PrestaShop\Module\PrestashopCheckout\OrderStates; -use PrestaShop\Module\PrestashopCheckout\Presenter\PresenterInterface; -use PrestaShop\Module\PrestashopCheckout\Repository\OrderRepository; -use PrestaShop\Module\PrestashopCheckout\Translations\OrderStatesTranslations; - -/** - * Present the pending orders for the reporting - */ -class OrderPendingPresenter implements PresenterInterface -{ - /** - * present pending orders - * - * @return array - * - * @throws \PrestaShopDatabaseException - */ - public function present() - { - $link = new LinkAdapter(); - $context = \Context::getContext(); - - $orderStates = []; - $orderTranslations = new OrderStatesTranslations(); - $orderTranslations = $orderTranslations->getTranslations($context->language->iso_code); - foreach (OrderStates::ORDER_STATES as $key => $value) { - if ($key == 'PS_CHECKOUT_STATE_WAITING_PAYMENT' || - $key == 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT' || - $key == 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT' || - $key == 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT' || - $key == 'PS_CHECKOUT_STATE_WAITING_CAPTURE' - ) { - $idState = (int) \Configuration::getGlobalValue($key); - $orderStates[$idState]['color'] = $value; - $orderStates[$idState]['name'] = $orderTranslations[$key]; - } - } - - $idStates = array_map('intval', $orderStates); - - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - /** @var OrderRepository $repository */ - $repository = $module->getService('ps_checkout.repository.order'); - $orders = $repository->findByStates(\Context::getContext()->shop->id, $idStates); - - foreach ($orders as &$order) { - $order['username'] = substr($order['firstname'], 0, 1) . '. ' . $order['lastname']; - $order['userProfileLink'] = $link->getAdminLink('AdminCustomers', true, [], ['id_customer' => $order['id_customer'], 'viewcustomer' => 1]); - $order['orderLink'] = $link->getAdminLink('AdminOrders', true, [], ['id_order' => $order['id_order'], 'vieworder' => 1]); - $order['state'] = $orderStates[$order['current_state']]; - $order['before_commission'] = \Tools::displayPrice($order['total_paid'], \Currency::getCurrencyInstance($order['id_currency'])); - // TODO: Waiting for paypal infos (reporting lot 2) - $order['commission'] = '-'; - $order['total_paid'] = '-'; - } - - return $orders; - } -} diff --git a/src/Presenter/Transaction/TransactionPresenter.php b/src/Presenter/Transaction/TransactionPresenter.php deleted file mode 100644 index 318423c08..000000000 --- a/src/Presenter/Transaction/TransactionPresenter.php +++ /dev/null @@ -1,63 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Presenter\Transaction; - -use PrestaShop\Module\PrestashopCheckout\Adapter\LinkAdapter; -use PrestaShop\Module\PrestashopCheckout\Presenter\PresenterInterface; -use PrestaShop\Module\PrestashopCheckout\Repository\OrderPaymentRepository; - -/** - * Present the pending orders for the reporting - */ -class TransactionPresenter implements PresenterInterface -{ - /** - * present pending orders - * - * @return array - * - * @throws \PrestaShopDatabaseException - */ - public function present() - { - $link = new LinkAdapter(); - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - /** @var OrderPaymentRepository $repository */ - $repository = $module->getService('ps_checkout.repository.orderpayment'); - $transactions = $repository->findAllPSCheckoutModule((int) \Context::getContext()->shop->id); - - foreach ($transactions as &$transaction) { - $transaction['transactionID'] = $transaction['transaction_id']; - $transaction['order_id'] = $transaction['id_order']; - $transaction['orderLink'] = $link->getAdminLink('AdminOrders', true, [], ['id_order' => $transaction['id_order'], 'vieworder' => 1]); - $transaction['username'] = substr($transaction['firstname'], 0, 1) . '. ' . $transaction['lastname']; - $transaction['userProfileLink'] = $link->getAdminLink('AdminCustomers', true, [], ['id_customer' => $transaction['id_customer'], 'viewcustomer' => 1]); - $transaction['before_commission'] = \Tools::displayPrice($transaction['amount'], \Currency::getCurrencyInstance((int) $transaction['id_currency'])); - $transaction['type'] = strpos($transaction['amount'], '-') !== false ? 'Refund' : 'Payment'; - $transaction['typeForDisplay'] = ($transaction['type'] === 'Refund') ? $module->l('Refund', 'transactionpresenter') : $module->l('Payment', 'transactionpresenter'); - $transaction['commission'] = '-'; - $transaction['total_paid'] = '-'; - } - - return $transactions; - } -} diff --git a/src/Presenter/Transaction/index.php b/src/Presenter/Transaction/index.php deleted file mode 100755 index 296d682e8..000000000 --- a/src/Presenter/Transaction/index.php +++ /dev/null @@ -1,28 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ -header('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); -header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT'); - -header('Cache-Control: no-store, no-cache, must-revalidate'); -header('Cache-Control: post-check=0, pre-check=0', false); -header('Pragma: no-cache'); - -header('Location: ../'); -exit; diff --git a/src/Translations/OrderStatesTranslations.php b/src/Translations/OrderStatesTranslations.php deleted file mode 100644 index a60265521..000000000 --- a/src/Translations/OrderStatesTranslations.php +++ /dev/null @@ -1,102 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\Translations; - -class OrderStatesTranslations -{ - const STANDARD_ISO_CODE = 'en'; - const PS_CHECKOUT_STATE_WAITING_PAYMENT = [ - 'en' => 'Waiting for payment', - 'fr' => 'En attente de paiement', - 'es' => 'Esperando el pago', - 'it' => 'In attesa di pagamento', - 'nl' => 'Wachten op betaling', - 'de' => 'Warten auf Zahlung', - 'pl' => 'Oczekiwanie na płatność', - 'pt' => 'Aguardando pagamento', - ]; - const PS_CHECKOUT_STATE_AUTHORIZED = [ - 'en' => 'Authorized. To be captured by merchant', - 'fr' => 'Autorisation. A capturer par le marchand', - 'es' => 'Autorizado. El vendedor lo capturará', - 'it' => 'Autorizzato. Sarà acquisito dal commerciante', - 'nl' => 'Goedgekeurd. Door retailer te registreren.', - 'de' => 'Autorisiert. Wird von Händler erfasst.', - 'pl' => 'Pomyślna autoryzacja. Transfer do przeprowadzenia przez sklep', - 'pt' => 'Autorizado. A ser capturado pelo comerciante', - ]; - const PS_CHECKOUT_STATE_PARTIAL_REFUND = [ - 'en' => 'Partial refund', - 'fr' => 'Remboursement partiel', - 'es' => 'Reembolso parcial', - 'it' => 'Rimborso parziale', - 'nl' => 'Gedeeltelijke terugbetaling', - 'de' => 'Teilweise Rückerstattung', - 'pl' => 'Częściowy zwrot', - 'pt' => 'Reembolso parcial', - ]; - const PS_CHECKOUT_STATE_WAITING_CAPTURE = [ - 'en' => 'Waiting capture', - 'fr' => 'En attente de capture', - 'es' => 'Esperando la captura', - 'it' => 'In attesa di essere acquisito', - 'nl' => 'Wachten op registratie', - 'de' => 'Warten auf Erfassung', - 'pl' => 'Oczekiwanie na transfer', - 'pt' => 'Aguardando a captura', - ]; - - /** - * Get the States Translations for the table order_state_lang - * - * @return array translation list - */ - public function getTranslations($isoCode) - { - $isoCode = $this->confirmIsoCode($isoCode); - - return [ - 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => self::PS_CHECKOUT_STATE_WAITING_PAYMENT[$isoCode], - 'PS_CHECKOUT_STATE_AUTHORIZED' => self::PS_CHECKOUT_STATE_AUTHORIZED[$isoCode], - 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => self::PS_CHECKOUT_STATE_PARTIAL_REFUND[$isoCode], - 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => self::PS_CHECKOUT_STATE_WAITING_CAPTURE[$isoCode], - ]; - } - - /** - * Return an ISO which can get a result in the translations arrays - * - * @param string $isoCode - * - * @return string - */ - private function confirmIsoCode($isoCode) - { - if (!array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_PAYMENT) || - !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_AUTHORIZED) || - !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_PARTIAL_REFUND) || - !array_key_exists($isoCode, self::PS_CHECKOUT_STATE_WAITING_CAPTURE)) { - return self::STANDARD_ISO_CODE; - } - - return (string) $isoCode; - } -} diff --git a/src/ValidateOrder.php b/src/ValidateOrder.php deleted file mode 100644 index beaf555c5..000000000 --- a/src/ValidateOrder.php +++ /dev/null @@ -1,345 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout; - -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; -use Psr\SimpleCache\CacheInterface; - -/** - * @deprecated This file will be removed - * - * Class that allow to validate an order - */ -class ValidateOrder -{ - const INTENT_CAPTURE = 'CAPTURE'; - const INTENT_AUTHORIZE = 'AUTHORIZE'; - - const CAPTURE_STATUS_PENDING = 'PENDING'; - const CAPTURE_STATUS_DENIED = 'DENIED'; - const CAPTURE_STATUS_VOIDED = 'VOIDED'; - const CAPTURE_STATUS_COMPLETED = 'COMPLETED'; - const CAPTURE_STATUS_DECLINED = 'DECLINED'; - - const PAYMENT_METHOD_PAYPAL = 'paypal'; - const PAYMENT_METHOD_CARD = 'card'; - - /** - * @var string - */ - private $paypalOrderId; - - /** - * @var string - */ - private $merchantId; - - /** - * @var \Context - */ - private $context; - - /** - * @param string $paypalOrderId - * @param string $merchantId - */ - public function __construct($paypalOrderId, $merchantId) - { - $this->merchantId = $merchantId; - $this->paypalOrderId = $paypalOrderId; - $this->context = \Context::getContext(); - } - - /** - * @param array{cartId: int, amount: float, currencyId: int, secureKey: string, isExpressCheckout: bool, isHostedFields: bool, fundingSource: string, liabilityShift: string, liabilityShifted: bool, authenticationStatus: string, authenticationReason: string} $payload - */ - public function validateOrder($payload) - { - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - - // API call here - $paypalOrder = new PaypalOrder($this->paypalOrderId); - $order = $paypalOrder->getOrder(); - - if (empty($order)) { - throw new OrderNotFoundException(sprintf('Unable to retrieve Paypal Order for %s', $this->paypalOrderId), OrderNotFoundException::NOT_FOUND); - } - - if ($payload['isHostedFields']) { - $card3DSecure = (new Card3DSecure())->continueWithAuthorization($order); - - $module->getLogger()->info( - '3D Secure authentication result', - [ - 'authentication_result' => isset($order['payment_source']['card']['authentication_result']) ? $order['payment_source']['card']['authentication_result'] : null, - 'decision' => str_replace( - [ - (string) Card3DSecure::NO_DECISION, - (string) Card3DSecure::PROCEED, - (string) Card3DSecure::REJECT, - (string) Card3DSecure::RETRY, - ], - [ - \Configuration::get('PS_CHECKOUT_LIABILITY_SHIFT_REQ') ? 'Rejected, no liability shift' : 'Proceed, without liability shift', - 'Proceed, liability shift is possible', - 'Rejected', - 'Retry, ask customer to retry', - ], - (string) $card3DSecure - ), - ] - ); - - if (Card3DSecure::REJECT === $card3DSecure) { - throw new PsCheckoutException('Card Strong Customer Authentication failure', PsCheckoutException::PAYPAL_PAYMENT_CARD_SCA_FAILURE); - } - - if (Card3DSecure::RETRY === $card3DSecure) { - throw new PsCheckoutException('Card Strong Customer Authentication must be retried.', PsCheckoutException::PAYPAL_PAYMENT_CARD_SCA_UNKNOWN); - } - - if (Card3DSecure::NO_DECISION === $card3DSecure && \Configuration::get('PS_CHECKOUT_LIABILITY_SHIFT_REQ')) { - throw new PsCheckoutException('No liability shift to card issuer', PsCheckoutException::PAYPAL_PAYMENT_CARD_SCA_UNKNOWN); - } - } - - $transactionIdentifier = false === empty($order['purchase_units'][0]['payments']['captures'][0]['id']) ? $order['purchase_units'][0]['payments']['captures'][0]['id'] : ''; - $transactionStatus = false === empty($order['purchase_units'][0]['payments']['captures'][0]['status']) ? $order['purchase_units'][0]['payments']['captures'][0]['status'] : ''; - - // @todo To be refactored in v2.0.0 with Service Container - if (true === empty($order['purchase_units'][0]['payments']['captures'])) { - /** @var \Ps_checkout $module */ - $module = \Module::getInstanceByName('ps_checkout'); - - /** @var \PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider $fundingSourceTranslationProvider */ - $fundingSourceTranslationProvider = $module->getService('ps_checkout.funding_source.translation'); - - /** @var \PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $module->getService('ps_checkout.repository.pscheckoutcart'); - - /** @var \PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByCartId((int) $payload['cartId']); - - // Check if the PayPal order amount is the same than the cart amount - // We tolerate a difference of more or less 0.05 - $paypalOrderAmount = sprintf('%01.2f', $order['purchase_units'][0]['amount']['value']); - $cartAmount = sprintf('%01.2f', $this->context->cart->getOrderTotal(true, \Cart::BOTH)); - - if ($paypalOrderAmount + 0.05 < $cartAmount || $paypalOrderAmount - 0.05 > $cartAmount) { - throw new PsCheckoutException('The transaction amount doesn\'t match with the cart amount.', PsCheckoutException::DIFFERENCE_BETWEEN_TRANSACTION_AND_CART); - } - - $apiOrder = new Order($this->context->link); - - $fundingSource = false === $psCheckoutCart ? 'paypal' : $psCheckoutCart->paypal_funding; - - if ($fundingSource === 'card') { - $fundingSource .= $psCheckoutCart->isHostedFields ? '_hosted' : '_inline'; - } - - $response = $apiOrder->capture( - $order['id'], - $this->merchantId, - $fundingSource - ); // API call here - - if (false === $response['status']) { - if (false === empty($response['body']['message'])) { - (new PayPalError($response['body']['message']))->throwException(); - } - - if (false === empty($response['exceptionMessage']) && false === empty($response['exceptionCode'])) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } - - throw new PsCheckoutException(isset($response['body']['error']) ? $response['body']['error'] : 'Unknown error', PsCheckoutException::UNKNOWN); - } - - if (false === empty($response['body']['purchase_units'][0]['payments']['captures'])) { - $transactionIdentifier = $response['body']['purchase_units'][0]['payments']['captures'][0]['id']; - $transactionStatus = $response['body']['purchase_units'][0]['payments']['captures'][0]['status']; - - if ( - self::CAPTURE_STATUS_DECLINED === $transactionStatus - && false === empty($response['body']['payment_source']) - && false === empty($response['body']['payment_source'][0]['card']) - && false === empty($response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']) - ) { - $payPalProcessorResponse = new PayPalProcessorResponse( - isset($response['body']['payment_source'][0]['card']['brand']) ? $response['body']['payment_source'][0]['card']['brand'] : null, - isset($response['body']['payment_source'][0]['card']['type']) ? $response['body']['payment_source'][0]['card']['type'] : null, - isset($response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['avs_code']) ? $response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['avs_code'] : null, - isset($response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['cvv_code']) ? $response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['cvv_code'] : null, - isset($response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['response_code']) ? $response['body']['purchase_units'][0]['payments']['captures'][0]['processor_response']['response_code'] : null - ); - $payPalProcessorResponse->throwException(); - } - } - /** @var CacheInterface $orderPayPalCache */ - $orderPayPalCache = $module->getService('ps_checkout.cache.paypal.order'); - $orderPayPalCache->set($response['body']['id'], $response['body']); - - if (false === $psCheckoutCart) { - $psCheckoutCart = new \PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $payload['cartId']; - $psCheckoutCart->paypal_intent = $paypalOrder->getOrderIntent(); - $psCheckoutCart->paypal_order = $response['body']['id']; - $psCheckoutCart->paypal_status = $response['body']['status']; - $psCheckoutCartRepository->save($psCheckoutCart); - } else { - $psCheckoutCart->paypal_order = $response['body']['id']; - $psCheckoutCart->paypal_status = $response['body']['status']; - $psCheckoutCartRepository->save($psCheckoutCart); - } - - if (self::CAPTURE_STATUS_DECLINED === $transactionStatus) { - throw new PsCheckoutException(sprintf('Transaction declined by PayPal : %s', false === empty($response['body']['details']['description']) ? $response['body']['details']['description'] : 'No detail'), PsCheckoutException::PAYPAL_PAYMENT_CAPTURE_DECLINED); - } - - try { - $module->validateOrder( - $payload['cartId'], - (int) $this->getOrderState($psCheckoutCart->paypal_funding), - 0, - $fundingSourceTranslationProvider->getPaymentMethodName($psCheckoutCart->paypal_funding), - null, - [ - 'transaction_id' => $transactionIdentifier, - ], - $payload['currencyId'], - false, - $payload['secureKey'] - ); - } catch (\ErrorException $exception) { - // Notice or warning from PHP - } catch (\Exception $exception) { - throw new PsCheckoutException('PrestaShop cannot validate order', PsCheckoutException::PRESTASHOP_VALIDATE_ORDER, $exception); - } - - if (empty($module->currentOrder)) { - throw new PsCheckoutException(sprintf('PrestaShop was unable to returns Prestashop Order ID for Prestashop Cart ID : %s - Paypal Order ID : %s. This happens when PrestaShop take too long time to create an Order due to heavy processes in hooks actionValidateOrder and/or actionOrderStatusUpdate and/or actionOrderStatusPostUpdate', $payload['cartId'], $this->paypalOrderId), PsCheckoutException::PRESTASHOP_ORDER_ID_MISSING); - } - - if (false === $this->setOrdersMatrice($module->currentOrder, $this->paypalOrderId)) { - throw new PsCheckoutException(sprintf('Set Order Matrice error for Prestashop Order ID : %s and Paypal Order ID : %s', $module->currentOrder, $this->paypalOrderId), PsCheckoutException::PSCHECKOUT_ORDER_MATRICE_ERROR); - } - - if (in_array($transactionStatus, [static::CAPTURE_STATUS_COMPLETED, static::CAPTURE_STATUS_DECLINED])) { - $newOrderState = static::CAPTURE_STATUS_COMPLETED === $transactionStatus ? $this->getPaidStatusId($module->currentOrder) : (int) \Configuration::getGlobalValue('PS_OS_ERROR'); - - $orderPS = new \Order($module->currentOrder); - $currentOrderStateId = (int) $orderPS->getCurrentState(); - - // If have to change current OrderState from Waiting to Paid or Canceled - if ($currentOrderStateId !== $newOrderState) { - $orderHistory = new \OrderHistory(); - $orderHistory->id_order = $module->currentOrder; - try { - $orderHistory->changeIdOrderState($newOrderState, $module->currentOrder); - $orderHistory->addWithemail(); - } catch (\ErrorException $exception) { - // Notice or warning from PHP - // For example : https://github.com/PrestaShop/PrestaShop/issues/18837 - } catch (\Exception $exception) { - throw new PsCheckoutException('Unable to change PrestaShop OrderState', PsCheckoutException::PRESTASHOP_ORDER_STATE_ERROR, $exception); - } - } - } - } - - return [ - 'status' => false === empty($response) ? $response['body']['status'] : $order['status'], - 'paypalOrderId' => false === empty($response) ? $response['body']['id'] : $this->paypalOrderId, - 'transactionIdentifier' => $transactionIdentifier, - ]; - } - - /** - * @todo To remove when need of fallback on previous version is gone - * - * Set the matrice order values - * - * @param int $orderPrestashopId from prestashop - * @param string $orderPaypalId paypal order id - * - * @return bool - * - * @throws \PrestaShopDatabaseException - * @throws \PrestaShopException - */ - private function setOrdersMatrice($orderPrestashopId, $orderPaypalId) - { - $orderMatrice = new \OrderMatrice(); - $orderMatrice->id_order_prestashop = $orderPrestashopId; - $orderMatrice->id_order_paypal = $orderPaypalId; - - return $orderMatrice->add(); - } - - /** - * @param int $orderId Order identifier - * - * @return int OrderState identifier - * - * @throws \PrestaShopDatabaseException - * @throws \PrestaShopException - */ - private function getPaidStatusId($orderId) - { - $order = new \Order($orderId); - - if (\Validate::isLoadedObject($order) && $order->getCurrentState() == \Configuration::getGlobalValue('PS_OS_OUTOFSTOCK_UNPAID')) { - return (int) \Configuration::getGlobalValue('PS_OS_OUTOFSTOCK_PAID'); - } - - return (int) \Configuration::getGlobalValue('PS_OS_PAYMENT'); - } - - /** - * Get OrderState identifier - * - * @todo Move to a dedicated Service - * - * @param string $fundingSource - * - * @return int - */ - private function getOrderState($fundingSource) - { - switch ($fundingSource) { - case 'card': - $orderStateId = (int) \Configuration::get('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'); - break; - case 'paypal': - $orderStateId = (int) \Configuration::get('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'); - break; - default: - $orderStateId = (int) \Configuration::get('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'); - } - - return $orderStateId; - } -} diff --git a/upgrade/upgrade-1.2.10.php b/upgrade/upgrade-1.2.10.php index aa7afbd74..17f36da44 100644 --- a/upgrade/upgrade-1.2.10.php +++ b/upgrade/upgrade-1.2.10.php @@ -17,9 +17,6 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ - -use PrestaShop\Module\PrestashopCheckout\OrderStates; - if (!defined('_PS_VERSION_')) { exit; } @@ -33,20 +30,6 @@ */ function upgrade_module_1_2_10($module) { - foreach (OrderStates::ORDER_STATES as $key => $value) { - $idState = \Configuration::getGlobalValue($key); - - $update = \Db::getInstance()->update( - OrderStates::ORDER_STATE_TABLE, - ['color' => $value], - 'module_name = "ps_checkout" AND id_order_state = ' . (int) $idState - ); - - if ($update !== true) { - return false; - } - } - // Force PrestaShop to upgrade for all shop to avoid issues $savedShopContext = Shop::getContext(); $savedShopId = Shop::getContextShopID(); diff --git a/upgrade/upgrade-1.3.0.php b/upgrade/upgrade-1.3.0.php index 5ca60d4c1..520ab5f70 100644 --- a/upgrade/upgrade-1.3.0.php +++ b/upgrade/upgrade-1.3.0.php @@ -36,7 +36,6 @@ function upgrade_module_1_3_0($module) $savedGroupShopId = Shop::getContextShopGroupID(); Shop::setContext(Shop::CONTEXT_ALL); - (new PrestaShop\Module\PrestashopCheckout\OrderStates())->installPaypalStates(); $module->registerHook('actionObjectShopAddAfter'); // Restore initial PrestaShop shop context diff --git a/upgrade/upgrade-1.4.0.php b/upgrade/upgrade-1.4.0.php index 88aa59148..4c80779c0 100644 --- a/upgrade/upgrade-1.4.0.php +++ b/upgrade/upgrade-1.4.0.php @@ -59,104 +59,6 @@ function upgrade_module_1_4_0($module) } } - // Fix multiple OrderState created in multishop before 1.3.0 - $queryConfigurationResults = $db->executeS(' - SELECT c.id_configuration, c.name, c.value, c.id_shop, c.id_shop_group, os.id_order_state - FROM `' . _DB_PREFIX_ . 'configuration` AS c - LEFT JOIN `' . _DB_PREFIX_ . 'order_state` AS os ON (c.value = os.id_order_state) - WHERE c.name LIKE "PS_CHECKOUT_STATE_%" - '); - - $orderStatesToClean = [ - 'PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT', - 'PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT', - 'PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT', - 'PS_CHECKOUT_STATE_AUTHORIZED', - 'PS_CHECKOUT_STATE_PARTIAL_REFUND', - 'PS_CHECKOUT_STATE_WAITING_CAPTURE', - ]; - $orderStateRows = []; - - if (false === empty($queryConfigurationResults)) { - foreach ($queryConfigurationResults as $queryConfigurationResult) { - if (false === in_array($queryConfigurationResult['name'], $orderStatesToClean, true)) { - continue; - } - - $orderStateRows[$queryConfigurationResult['name']][] = [ - 'id_configuration' => $queryConfigurationResult['id_configuration'], - 'id_order_state' => $queryConfigurationResult['id_order_state'], - ]; - } - } - - foreach ($orderStateRows as $orderStateRow) { - $isGlobalValueSaved = false; - foreach ($orderStateRow as $index => $data) { - if (false === empty($data['id_order_state'])) { - if (false === $isGlobalValueSaved) { - // Set value global for all shops - $result = $db->update( - 'configuration', - [ - 'id_shop' => null, - 'id_shop_group' => null, - ], - 'id_configuration = ' . (int) $data['id_configuration'], - 0, - true - ); - - if ($result) { - $isGlobalValueSaved = true; - // Skip deletion of this configuration - continue; - } - } else { - // Mark this duplicated OrderState as deleted - $db->update( - 'order_state', - [ - 'deleted' => 1, - ], - 'id_order_state = ' . (int) $data['id_order_state'] - ); - } - } - - // Remove this OrderState identifier from Configuration - $db->delete( - 'configuration', - 'id_configuration = ' . (int) $data['id_configuration'] - ); - } - } - - // Mark OrderState created by older module installation who failed as deleted - $queryOrderState = new \DbQuery(); - $queryOrderState->select('id_order_state'); - $queryOrderState->from('order_state'); - $queryOrderState->where('module_name = "' . $module->name . '"'); - $queryOrderState->where('deleted = 0'); - - if (false === empty($queryConfigurationResults)) { - $queryOrderState->where('`id_order_state` NOT IN (' . implode(',', array_column($queryConfigurationResults, 'id_order_state')) . ')'); - } - - $queryOrderStateResults = $db->executeS($queryOrderState); - - if (false === empty($queryOrderStateResults)) { - foreach ($queryOrderStateResults as $queryOrderStateResult) { - $db->update( - 'order_state', - [ - 'deleted' => 1, - ], - 'id_order_state = ' . (int) $queryOrderStateResult['id_order_state'] - ); - } - } - $module->registerHook('displayAdminOrderLeft'); $module->registerHook('displayAdminOrderMainBottom'); $module->registerHook('actionAdminControllerSetMedia'); From 95bd5fa222c4fc19138162ea7cb011a2ffc93c86 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 13 Jul 2023 19:52:37 +0200 Subject: [PATCH 035/343] Update upgrade script --- upgrade/upgrade-8.3.4.0.php | 52 +++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 17 deletions(-) diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index 1684776a9..d5a5fb933 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -92,16 +92,21 @@ function upgrade_module_8_3_4_0($module) // Check module OrderState $moduleOrderStates = [ + 'PS_CHECKOUT_STATE_PENDING' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PENDING'), + 'PS_CHECKOUT_STATE_COMPLETED' => (int) Configuration::getGlobalValue('PS_OS_PAYMENT'), + 'PS_CHECKOUT_STATE_CANCELED' => (int) Configuration::getGlobalValue('PS_OS_CANCELED'), + 'PS_CHECKOUT_STATE_ERROR' => (int) Configuration::getGlobalValue('PS_OS_ERROR'), + 'PS_CHECKOUT_STATE_REFUNDED' => (int) Configuration::getGlobalValue('PS_OS_REFUND'), + 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIAL_REFUND'), + 'PS_CHECKOUT_STATE_PARTIALLY_PAID' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIALLY_PAID'), 'PS_CHECKOUT_STATE_AUTHORIZED' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_AUTHORIZED'), - 'PS_CHECKOUT_STATE_PARTIAL_REFUND' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PARTIAL_REFUND'), - 'PS_CHECKOUT_STATE_WAITING_CAPTURE' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CAPTURE'), - 'PS_CHECKOUT_STATE_WAITING_PAYMENT' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYMENT'), ]; $moduleOrderStatesId = array_values($moduleOrderStates); $moduleOrderStatesIdToDelete = [ (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_PAYPAL_PAYMENT'), (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CREDIT_CARD_PAYMENT'), (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_LOCAL_PAYMENT'), + (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_WAITING_CAPTURE'), ]; $orderStateCollection = new PrestaShopCollection(OrderState::class); @@ -149,9 +154,9 @@ function upgrade_module_8_3_4_0($module) ] ); break; - case 'PS_CHECKOUT_STATE_PARTIAL_REFUND': + case 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED': ps_checkout_create_order_state_8_3_4_0( - 'PS_CHECKOUT_STATE_PARTIAL_REFUND', + 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED', '#01B887', [ 'en' => 'Partial refund', @@ -165,8 +170,8 @@ function upgrade_module_8_3_4_0($module) ] ); break; - case 'PS_CHECKOUT_STATE_WAITING_PAYMENT': - ps_checkout_create_order_state_8_3_4_0('PS_CHECKOUT_STATE_WAITING_PAYMENT', '#34209E', [ + case 'PS_CHECKOUT_STATE_PENDING': + ps_checkout_create_order_state_8_3_4_0('PS_CHECKOUT_STATE_PENDING', '#34209E', [ 'en' => 'Waiting for payment', 'fr' => 'En attente de paiement', 'es' => 'Esperando el pago', @@ -177,23 +182,25 @@ function upgrade_module_8_3_4_0($module) 'pt' => 'Aguardando pagamento', ]); break; - case 'PS_CHECKOUT_STATE_WAITING_CAPTURE': + case 'PS_CHECKOUT_STATE_PARTIALLY_PAID': ps_checkout_create_order_state_8_3_4_0( - 'PS_CHECKOUT_STATE_WAITING_CAPTURE', + 'PS_CHECKOUT_STATE_PARTIALLY_PAID', '#3498D8', [ - 'en' => 'Waiting capture', - 'fr' => 'En attente de capture', - 'es' => 'Esperando la captura', - 'it' => 'In attesa di essere acquisito', - 'nl' => 'Wachten op registratie', - 'de' => 'Warten auf Erfassung', - 'pl' => 'Oczekiwanie na transfer', - 'pt' => 'Aguardando a captura', + 'en' => 'Partial payment', + 'fr' => 'Paiement partiel', + 'es' => 'Pago parcial', + 'it' => 'Pagamento parziale', + 'nl' => 'Gedeeltelijke betaling', + 'de' => 'Teilweise Zahlung', + 'pl' => 'Częściowa płatność', + 'pt' => 'Pagamento parcial', ] ); break; } + } else { + Configuration::updateGlobalValue($configuration_key, $id_order_state); } } } catch (Exception $exception) { @@ -245,4 +252,15 @@ function ps_checkout_create_order_state_8_3_4_0($configuration_key, $color, $nam $orderState->template = []; $orderState->save(); Configuration::updateGlobalValue($configuration_key, $orderState->id); + $orderStateImage = $configuration_key === 'PS_CHECKOUT_STATE_PARTIALLY_REFUNDED' ? 'refund.gif' : 'waiting.gif'; + $moduleOrderStateImgPath = _PS_MODULE_DIR_ . 'ps_checkout/views/img/OrderStatesIcons/' . $orderStateImage; + $coreOrderStateImgPath = _PS_IMG_DIR_ . 'os/' . $orderState->id . '.gif'; + + if ( + Tools::file_exists_cache($moduleOrderStateImgPath) + && !Tools::file_exists_cache($coreOrderStateImgPath) + && is_writable(_PS_IMG_DIR_ . 'os/') + ) { + Tools::copy($moduleOrderStateImgPath, $coreOrderStateImgPath); + } } From 9621fe5d1c5b82b4de69882e907167c8624469be Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:03:08 +0200 Subject: [PATCH 036/343] Avoid throw exception for bad request --- controllers/front/ExpressCheckout.php | 15 ++++++++++++--- controllers/front/cancel.php | 21 ++++++++++++++++----- controllers/front/check.php | 20 ++++++++++++++++---- controllers/front/create.php | 5 ++++- controllers/front/token.php | 6 ++++-- controllers/front/validate.php | 20 ++++++++++++++++---- 6 files changed, 68 insertions(+), 19 deletions(-) diff --git a/controllers/front/ExpressCheckout.php b/controllers/front/ExpressCheckout.php index 996595a17..8d8615732 100644 --- a/controllers/front/ExpressCheckout.php +++ b/controllers/front/ExpressCheckout.php @@ -52,17 +52,26 @@ public function postProcess() $bodyContent = file_get_contents('php://input'); if (empty($bodyContent)) { - throw new PsCheckoutException('Body cannot be empty', PsCheckoutException::PSCHECKOUT_VALIDATE_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $this->payload = json_decode($bodyContent, true); if (empty($this->payload)) { - throw new PsCheckoutException('Body cannot be empty', PsCheckoutException::PSCHECKOUT_VALIDATE_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } if (empty($this->payload['orderID']) || false === Validate::isGenericName($this->payload['orderID'])) { - throw new PsCheckoutException('PayPal Order identifier missing or invalid', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ diff --git a/controllers/front/cancel.php b/controllers/front/cancel.php index bf4affa6e..bccd1d7fe 100644 --- a/controllers/front/cancel.php +++ b/controllers/front/cancel.php @@ -19,7 +19,6 @@ */ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; /** @@ -41,19 +40,28 @@ public function postProcess() { try { if (false === Validate::isLoadedObject($this->context->cart)) { - throw new PsCheckoutException('No cart found.', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'No cart found.', + ]); } $bodyContent = file_get_contents('php://input'); if (empty($bodyContent)) { - throw new PsCheckoutException('Payload invalid', PsCheckoutException::PSCHECKOUT_WEBHOOK_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $bodyValues = json_decode($bodyContent, true); if (empty($bodyValues)) { - throw new PsCheckoutException('Payload invalid', PsCheckoutException::PSCHECKOUT_WEBHOOK_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; @@ -62,7 +70,10 @@ public function postProcess() $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; if (empty($orderId)) { - throw new PsCheckoutException('Missing PayPal Order Id', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Missing PayPal Order Id', + ]); } /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ diff --git a/controllers/front/check.php b/controllers/front/check.php index 69aadbe5f..bb439d5d7 100644 --- a/controllers/front/check.php +++ b/controllers/front/check.php @@ -42,19 +42,28 @@ public function postProcess() { try { if (false === Validate::isLoadedObject($this->context->cart)) { - throw new PsCheckoutException('No cart found.', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'No cart found.', + ]); } $bodyContent = file_get_contents('php://input'); if (empty($bodyContent)) { - throw new PsCheckoutException('Payload invalid', PsCheckoutException::PSCHECKOUT_WEBHOOK_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $bodyValues = json_decode($bodyContent, true); if (empty($bodyValues)) { - throw new PsCheckoutException('Payload invalid', PsCheckoutException::PSCHECKOUT_WEBHOOK_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : 'paypal'; @@ -63,7 +72,10 @@ public function postProcess() $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; if (empty($orderId)) { - throw new PsCheckoutException('Missing PayPal Order Id', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Missing PayPal Order Id', + ]); } /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ diff --git a/controllers/front/create.php b/controllers/front/create.php index 6decffa68..15b6639d6 100644 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -93,7 +93,10 @@ public function postProcess() // END Express Checkout if (false === Validate::isLoadedObject($this->context->cart)) { - throw new PsCheckoutException('No cart found.', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'No cart found.', + ]); } /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ diff --git a/controllers/front/token.php b/controllers/front/token.php index b779ab261..e315d37be 100644 --- a/controllers/front/token.php +++ b/controllers/front/token.php @@ -19,7 +19,6 @@ */ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalClientTokenProvider; /** @@ -41,7 +40,10 @@ public function postProcess() { try { if (false === Validate::isLoadedObject($this->context->cart)) { - throw new PsCheckoutException('No cart found.', PsCheckoutException::PRESTASHOP_CONTEXT_INVALID); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'No cart found.', + ]); } /** @var PayPalClientTokenProvider $clientTokenProvider */ diff --git a/controllers/front/validate.php b/controllers/front/validate.php index fe9d3f1a5..0f6c8ea8a 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -53,17 +53,26 @@ public function postProcess() $bodyContent = file_get_contents('php://input'); if (empty($bodyContent)) { - throw new PsCheckoutException('Body cannot be empty', PsCheckoutException::PSCHECKOUT_VALIDATE_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } $bodyValues = json_decode($bodyContent, true); if (empty($bodyValues)) { - throw new PsCheckoutException('Body cannot be empty', PsCheckoutException::PSCHECKOUT_VALIDATE_BODY_EMPTY); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); } if (empty($bodyValues['orderID']) || false === Validate::isGenericName($bodyValues['orderID'])) { - throw new PsCheckoutException('PayPal Order identifier invalid', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Missing PayPal Order Id', + ]); } $this->paypalOrderId = $bodyValues['orderID']; @@ -73,7 +82,10 @@ public function postProcess() $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($this->paypalOrderId); if (!Validate::isLoadedObject($psCheckoutCart)) { - throw new PsCheckoutException('The cart cannot be found', PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'No cart found.', + ]); } /** @var EventDispatcherInterface $eventDispatcher */ From 6bff90a15ea990ce01708105fd9f6673ff585c02 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 19 Jul 2023 11:08:24 +0200 Subject: [PATCH 037/343] Fix undefined constant --- src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index 9d74b1b9c..f27f134e7 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -248,7 +248,7 @@ public function setApprovalReversedOrderStatus(PayPalOrderApprovalReversedEvent $this->commandBus->handle( new UpdateOrderStatusCommand( $order->getOrderId()->getValue(), - $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::CANCELED) + $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED) ) ); } From 4251bef9922c34c0b4dd0b73a28cb806b3b28c33 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:01:38 +0200 Subject: [PATCH 038/343] Catch order status mapping exception --- .../AdminAjaxPrestashopCheckoutController.php | 53 +++++++------------ .../State/Exception/OrderStateException.php | 2 +- src/Order/State/Service/OrderStateMapper.php | 49 +++++++++++++++-- 3 files changed, 63 insertions(+), 41 deletions(-) diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index 3f9916616..b5f61f205 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -29,7 +29,8 @@ use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileReader; use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\LiveStep; use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\ValueBanner; -use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; @@ -890,43 +891,25 @@ public function ajaxProcessGetMappedOrderStates() { /** @var OrderStateMapper $orderStateMapper */ $orderStateMapper = $this->module->getService('ps_checkout.order.state.service.order_state_mapper'); + $mappedOrderStates = []; + + try { + $mappedOrderStates = $orderStateMapper->getMappedOrderStates(); + } catch (OrderStateException $exception) { + if ($exception->getCode() === OrderStateException::INVALID_MAPPING) { + (new OrderStateInstaller())->install(); + } + + $this->exitWithResponse([ + 'httpCode' => 500, + 'status' => false, + 'error' => $exception->getMessage(), + ]); + } $this->exitWithResponse([ 'status' => true, - 'mappedOrderStates' => [ - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING => [ - 'default' => '0', - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED => [ - 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_PAYMENT), - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED => [ - 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_CANCELED), - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR => [ - 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_ERROR), - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED => [ - 'default' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_OS_REFUND), - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => [ - 'default' => '0', - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID => [ - 'default' => '0', - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID), - ], - OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED => [ - 'default' => '0', - 'value' => (string) $orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED), - ], - ], + 'mappedOrderStates' => $mappedOrderStates, ]); } diff --git a/src/Order/State/Exception/OrderStateException.php b/src/Order/State/Exception/OrderStateException.php index a932c2bbc..bfb7748c4 100644 --- a/src/Order/State/Exception/OrderStateException.php +++ b/src/Order/State/Exception/OrderStateException.php @@ -25,5 +25,5 @@ class OrderStateException extends PsCheckoutException { const INVALID_ID = 1; - const TRANSITION_UNAVAILABLE = 2; + const INVALID_MAPPING = 2; } diff --git a/src/Order/State/Service/OrderStateMapper.php b/src/Order/State/Service/OrderStateMapper.php index 9d773e9e1..7d609e0f3 100644 --- a/src/Order/State/Service/OrderStateMapper.php +++ b/src/Order/State/Service/OrderStateMapper.php @@ -20,8 +20,8 @@ namespace PrestaShop\Module\PrestashopCheckout\Order\State\Service; -use InvalidArgumentException; use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; class OrderStateMapper @@ -49,6 +49,8 @@ public function __construct(PrestaShopConfiguration $configuration) * @param string $key * * @return int + * + * @throws OrderStateException */ public function getIdByKey($key) { @@ -56,21 +58,58 @@ public function getIdByKey($key) return $this->orderStateMapping[$key]; } - throw new InvalidArgumentException(sprintf('Order state key "%s" is not mapped', var_export($key, true))); + throw new OrderStateException(sprintf('Order state key "%s" is not mapped', var_export($key, true)), OrderStateException::INVALID_MAPPING); } /** * @return array + * + * @throws OrderStateException */ - public function getOrderStateMapping() + public function getMappedOrderStates() { - return $this->orderStateMapping; + return [ + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING => [ + 'default' => '0', + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED => [ + 'default' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_OS_PAYMENT), + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED => [ + 'default' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_OS_CANCELED), + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_CANCELED), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR => [ + 'default' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_OS_ERROR), + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED => [ + 'default' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_OS_REFUND), + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED => [ + 'default' => '0', + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID => [ + 'default' => '0', + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID), + ], + OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED => [ + 'default' => '0', + 'value' => (string) $this->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_AUTHORIZED), + ], + ]; } /** * @param int $orderCurrentState * * @return string + * + * @throws OrderStateException */ public function getKeyById($orderCurrentState) { @@ -80,7 +119,7 @@ public function getKeyById($orderCurrentState) return $orderStateMapping[$orderCurrentState]; } - throw new InvalidArgumentException(sprintf('Order state id "%s" is not mapped', var_export($orderCurrentState, true))); + throw new OrderStateException(sprintf('Order state id "%s" is not mapped', var_export($orderCurrentState, true)), OrderStateException::INVALID_MAPPING); } private function initialize() From 7356701665b69d366f0552abcc92a006c644abbd Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 19 Jul 2023 12:11:36 +0200 Subject: [PATCH 039/343] Fix upgrade script --- upgrade/upgrade-8.3.4.0.php | 86 ++++++++++++++++++------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index d5a5fb933..64b1234cb 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -47,49 +47,6 @@ function upgrade_module_8_3_4_0($module) $db = Db::getInstance(); - // Installing FundingSource if table pscheckout_funding_source is empty or incomplete - including BLIK - $fundingSources = ['paypal', 'paylater', 'card', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort', 'blik']; - $availableFundingSourcesByShops = []; - $maxPositionByShops = []; - $availableFundingSources = $db->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'pscheckout_funding_source'); - - if (!empty($availableFundingSources)) { - foreach ($availableFundingSources as $availableFundingSource) { - $currentPosition = (int) $availableFundingSource['position']; - $shopId = (int) $availableFundingSource['id_shop']; - - if ( - !isset($maxPositionByShops[$shopId]) - || $maxPositionByShops[$shopId] < $currentPosition - ) { - $maxPositionByShops[$shopId] = $currentPosition; - } - - $availableFundingSourcesByShops[$shopId][] = $availableFundingSource['name']; - } - } - - foreach (Shop::getShops(false, null, true) as $shopId) { - $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; - foreach ($fundingSources as $fundingSource) { - if ( - !isset($availableFundingSourcesByShops[(int) $shopId]) - || !in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true) - ) { - $db->insert( - 'pscheckout_funding_source', - [ - 'name' => pSQL($fundingSource), - 'active' => 1, - 'position' => (int) $currentPosition, - 'id_shop' => (int) $savedShopId, - ] - ); - ++$currentPosition; - } - } - } - // Check module OrderState $moduleOrderStates = [ 'PS_CHECKOUT_STATE_PENDING' => (int) Configuration::getGlobalValue('PS_CHECKOUT_STATE_PENDING'), @@ -203,6 +160,49 @@ function upgrade_module_8_3_4_0($module) Configuration::updateGlobalValue($configuration_key, $id_order_state); } } + + // Installing FundingSource if table pscheckout_funding_source is empty or incomplete - including BLIK + $fundingSources = ['paypal', 'paylater', 'card', 'bancontact', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort', 'blik']; + $availableFundingSourcesByShops = []; + $maxPositionByShops = []; + $availableFundingSources = $db->executeS('SELECT * FROM ' . _DB_PREFIX_ . 'pscheckout_funding_source'); + + if (!empty($availableFundingSources)) { + foreach ($availableFundingSources as $availableFundingSource) { + $currentPosition = (int) $availableFundingSource['position']; + $shopId = (int) $availableFundingSource['id_shop']; + + if ( + !isset($maxPositionByShops[$shopId]) + || $maxPositionByShops[$shopId] < $currentPosition + ) { + $maxPositionByShops[$shopId] = $currentPosition; + } + + $availableFundingSourcesByShops[$shopId][] = $availableFundingSource['name']; + } + } + + foreach ($shopsList as $shopId) { + $currentPosition = isset($maxPositionByShops[(int) $shopId]) ? $maxPositionByShops[(int) $shopId] + 1 : 1; + foreach ($fundingSources as $fundingSource) { + if ( + !isset($availableFundingSourcesByShops[(int) $shopId]) + || !in_array($fundingSource, $availableFundingSourcesByShops[(int) $shopId], true) + ) { + $db->insert( + 'pscheckout_funding_source', + [ + 'name' => pSQL($fundingSource), + 'active' => 1, + 'position' => (int) $currentPosition, + 'id_shop' => (int) $shopId, + ] + ); + ++$currentPosition; + } + } + } } catch (Exception $exception) { PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id); From 6f2ed5d8f94d30c934efc996325ecbb87871ea0b Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:36:56 +0200 Subject: [PATCH 040/343] Http Unauthorized --- src/Api/Payment/Client/PaymentClient.php | 5 +++++ src/Exception/PsCheckoutException.php | 1 + 2 files changed, 6 insertions(+) diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 5d41af166..d3f9946be 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -148,12 +148,17 @@ protected function post(array $options = []) * @return array * * @throws HttpTimeoutException + * @throws PsCheckoutException */ private function postWithRetry(array $options, $delay = 2, $retries = 2) { try { $response = parent::post($options); + if ($response['httpCode'] === 401) { + throw new PsCheckoutException('Unauthorized', PsCheckoutException::PSCHECKOUT_HTTP_UNAUTHORIZED); + } + if (false !== $response['status']) { return $response; } diff --git a/src/Exception/PsCheckoutException.php b/src/Exception/PsCheckoutException.php index 7272f508e..627675825 100644 --- a/src/Exception/PsCheckoutException.php +++ b/src/Exception/PsCheckoutException.php @@ -88,4 +88,5 @@ class PsCheckoutException extends \Exception const CART_ADDRESS_INVOICE_INVALID = 56; const CART_ADDRESS_DELIVERY_INVALID = 57; const CART_DELIVERY_OPTION_INVALID = 58; + const PSCHECKOUT_HTTP_UNAUTHORIZED = 59; } From 2e6f7fc6e430d7fbe63cba72841213f22b3d62d9 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Mon, 31 Jul 2023 15:49:16 +0200 Subject: [PATCH 041/343] Improve error display when card payment is refused --- controllers/front/validate.php | 5 -- .../CapturePayPalOrderCommandHandler.php | 39 +++++++++------ .../PayPalCaptureEventSubscriber.php | 49 ++++++++++++++----- 3 files changed, 60 insertions(+), 33 deletions(-) diff --git a/controllers/front/validate.php b/controllers/front/validate.php index fe9d3f1a5..87a2cdf33 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -89,11 +89,6 @@ public function postProcess() $this->sendOkResponse($this->generateResponse()); } catch (Exception $exception) { - $this->module->getLogger()->error('CheckoutCompletedEvent failed', [ - 'exception_class' => get_class($exception), - 'exception_message' => $exception->getMessage(), - 'exception_code' => $exception->getCode(), - ]); $response = $this->generateResponse(); if (!empty($response)) { diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index 07be2921d..fef3eb10f 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -30,6 +30,7 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; use PrestaShop\Module\PrestashopCheckout\PayPalError; @@ -77,22 +78,6 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) $orderPayPal = $response['body']; $capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0]; - if ( - 'DECLINED' === $capturePayPal['status'] - && false === empty($response['body']['payment_source']) - && false === empty($response['body']['payment_source'][0]['card']) - && false === empty($capturePayPal['processor_response']) - ) { - $payPalProcessorResponse = new PayPalProcessorResponse( - isset($response['body']['payment_source'][0]['card']['brand']) ? $response['body']['payment_source'][0]['card']['brand'] : null, - isset($response['body']['payment_source'][0]['card']['type']) ? $response['body']['payment_source'][0]['card']['type'] : null, - isset($capturePayPal['processor_response']['avs_code']) ? $capturePayPal['processor_response']['avs_code'] : null, - isset($capturePayPal['processor_response']['cvv_code']) ? $capturePayPal['processor_response']['cvv_code'] : null, - isset($capturePayPal['processor_response']['response_code']) ? $capturePayPal['processor_response']['response_code'] : null - ); - $payPalProcessorResponse->throwException(); - } - if ($orderPayPal['status'] === PayPalOrderStatus::COMPLETED) { $this->eventDispatcher->dispatch(new PayPalOrderCompletedEvent($orderPayPal['id'], $orderPayPal)); } @@ -104,5 +89,27 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) if ($capturePayPal['status'] === PayPalCaptureStatus::COMPLETED) { $this->eventDispatcher->dispatch(new PayPalCaptureCompletedEvent($capturePayPal['id'], $orderPayPal['id'], $capturePayPal)); } + + if ($capturePayPal['status'] === PayPalCaptureStatus::DECLINED || $capturePayPal['status'] === PayPalCaptureStatus::FAILED) { + $this->eventDispatcher->dispatch(new PayPalCaptureDeclinedEvent($capturePayPal['id'], $orderPayPal['id'], $capturePayPal)); + } + + if ( + PayPalCaptureStatus::DECLINED === $capturePayPal['status'] + && false === empty($orderPayPal['payment_source']) + && false === empty($orderPayPal['payment_source']['card']) + && false === empty($capturePayPal['processor_response']) + ) { + $payPalProcessorResponse = new PayPalProcessorResponse( + isset($orderPayPal['payment_source']['card']['brand']) ? $orderPayPal['payment_source']['card']['brand'] : null, + isset($orderPayPal['payment_source']['card']['type']) ? $orderPayPal['payment_source']['card']['type'] : null, + isset($capturePayPal['processor_response']['avs_code']) ? $capturePayPal['processor_response']['avs_code'] : null, + isset($capturePayPal['processor_response']['cvv_code']) ? $capturePayPal['processor_response']['cvv_code'] : null, + isset($capturePayPal['processor_response']['response_code']) ? $capturePayPal['processor_response']['response_code'] : null + ); + $payPalProcessorResponse->throwException(); + } elseif (PayPalCaptureStatus::DECLINED === $capturePayPal['status'] || PayPalCaptureStatus::FAILED === $capturePayPal['status']) { + throw new PsCheckoutException('PayPal declined the capture', PsCheckoutException::PAYPAL_PAYMENT_CAPTURE_DECLINED); + } } } diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index 59b1df49b..265faa609 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -24,6 +24,7 @@ use PrestaShop\Module\PrestashopCheckout\Order\Command\AddOrderPaymentCommand; use PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand; use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQuery; @@ -136,8 +137,12 @@ public function createOrder(PayPalCaptureEvent $event) public function createOrderPayment(PayPalCaptureCompletedEvent $event) { - /** @var GetOrderForPaymentCompletedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + try { + /** @var GetOrderForPaymentCompletedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if ($order->getOrderPaymentId()) { return; @@ -157,8 +162,12 @@ public function createOrderPayment(PayPalCaptureCompletedEvent $event) public function setPaymentCompletedOrderStatus(PayPalCaptureCompletedEvent $event) { - /** @var GetOrderForPaymentCompletedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + try { + /** @var GetOrderForPaymentCompletedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentCompletedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if ($order->hasBeenPaid()) { return; @@ -177,8 +186,12 @@ public function setPaymentCompletedOrderStatus(PayPalCaptureCompletedEvent $even public function setPaymentPendingOrderStatus(PayPalCapturePendingEvent $event) { - /** @var GetOrderForPaymentPendingQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentPendingQuery($event->getPayPalOrderId()->getValue())); + try { + /** @var GetOrderForPaymentPendingQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentPendingQuery($event->getPayPalOrderId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if ($order->isInPending()) { return; @@ -200,8 +213,12 @@ public function setPaymentPendingOrderStatus(PayPalCapturePendingEvent $event) public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) { - /** @var GetOrderForPaymentDeniedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentDeniedQuery($event->getPayPalOrderId()->getValue())); + try { + /** @var GetOrderForPaymentDeniedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentDeniedQuery($event->getPayPalOrderId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if ($order->hasBeenError()) { return; @@ -212,8 +229,12 @@ public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) { - /** @var GetOrderForPaymentRefundedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); + try { + /** @var GetOrderForPaymentRefundedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { return; @@ -228,8 +249,12 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) { - /** @var GetOrderForPaymentReversedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentReversedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + try { + /** @var GetOrderForPaymentReversedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentReversedQuery($event->getPayPalOrderId()->getValue(), $event->getPayPalCaptureId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { return; From 4c3eda2399edd49fddab1bb02e223778c9dac639 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 27 Jul 2023 15:26:28 +0200 Subject: [PATCH 042/343] Guzzle 5 Exception --- src/Api/GenericClient.php | 9 +++++++++ src/Api/Payment/Client/PaymentClient.php | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/src/Api/GenericClient.php b/src/Api/GenericClient.php index e390867e8..bfc17d677 100755 --- a/src/Api/GenericClient.php +++ b/src/Api/GenericClient.php @@ -29,6 +29,7 @@ use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; use Ps_checkout; +use RuntimeException; /** * Construct the client used to make call to maasland @@ -112,6 +113,14 @@ protected function post(array $options = []) $exception ) ); + } catch (RuntimeException $exception) { + return $this->handleException( + new PsCheckoutException( + $exception->getMessage(), + PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION, + $exception + ) + ); } catch (Exception $exception) { return $this->handleException($exception); } diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 5d41af166..39af3a458 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -164,6 +164,11 @@ private function postWithRetry(array $options, $delay = 2, $retries = 2) && false !== strpos($response['exceptionMessage'], 'cURL error 28') ) { throw new HttpTimeoutException($response['exceptionMessage'], PsCheckoutException::PSL_TIMEOUT); + } elseif ( + isset($response['exceptionCode']) + && $response['exceptionCode'] === PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION + ) { + throw new PsCheckoutException($response['exceptionMessage'], PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION); } if ( From 25fda0b30b285d50fc59d4f8a805d10559280b75 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 1 Aug 2023 09:29:07 +0200 Subject: [PATCH 043/343] Avoid fatal error on payment due to OS mapping - PS8 --- .../CreateOrderCommandHandler.php | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/Order/CommandHandler/CreateOrderCommandHandler.php b/src/Order/CommandHandler/CreateOrderCommandHandler.php index efa930e00..4a7e7921d 100644 --- a/src/Order/CommandHandler/CreateOrderCommandHandler.php +++ b/src/Order/CommandHandler/CreateOrderCommandHandler.php @@ -34,7 +34,9 @@ use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; +use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; use PrestaShopCollection; @@ -143,18 +145,25 @@ public function handle(CreateOrderCommand $command) $currencyId = Currency::getIdByIsoCode($capture['amount']['currency_code'], (int) $cart->id_shop); } - if ($paidAmount) { - switch ($this->checkOrderAmount->checkAmount((string) $paidAmount, (string) $cart->getOrderTotal(true, \Cart::BOTH))) { - case CheckOrderAmount::ORDER_NOT_FULL_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID); - break; - case CheckOrderAmount::ORDER_FULL_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); - break; - case CheckOrderAmount::ORDER_TO_MUCH_PAID: - $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); + try { + if ($paidAmount) { + switch ($this->checkOrderAmount->checkAmount((string) $paidAmount, (string) $cart->getOrderTotal(true, \Cart::BOTH))) { + case CheckOrderAmount::ORDER_NOT_FULL_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_PAID); + break; + case CheckOrderAmount::ORDER_FULL_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); + break; + case CheckOrderAmount::ORDER_TO_MUCH_PAID: + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_COMPLETED); + } + } else { + $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING); + } + } catch (OrderStateException $exception) { + if ($exception->getCode() === OrderStateException::INVALID_MAPPING) { + (new OrderStateInstaller())->install(); } - } else { $orderStateId = $this->psOrderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PENDING); } From d843cc269e774313b6a61ff2c477b698d69b5cbb Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 1 Aug 2023 10:55:41 +0200 Subject: [PATCH 044/343] Fix order state translations at install --- src/Order/State/OrderStateInstaller.php | 14 +++++++------- upgrade/upgrade-8.3.4.0.php | 15 ++++++++------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/Order/State/OrderStateInstaller.php b/src/Order/State/OrderStateInstaller.php index d3ad86868..cba98672a 100644 --- a/src/Order/State/OrderStateInstaller.php +++ b/src/Order/State/OrderStateInstaller.php @@ -152,13 +152,13 @@ private function fillOrderStateName(array $nameByLangIsoCode) { $orderStateNameByLangId = []; - foreach ($nameByLangIsoCode as $langIsoCode => $name) { - foreach ($this->languages as $language) { - if (Tools::strtolower($language['iso_code']) === $langIsoCode) { - $orderStateNameByLangId[(int) $language['id_lang']] = $name; - } elseif (isset($nameByLangIsoCode['en'])) { - $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; - } + foreach ($this->languages as $language) { + $languageIsoCode = Tools::strtolower($language['iso_code']); + + if (isset($nameByLangIsoCode[$languageIsoCode])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode[$languageIsoCode]; + } elseif (isset($nameByLangIsoCode['en'])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; } } diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index 64b1234cb..0630507e9 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -224,13 +224,14 @@ function upgrade_module_8_3_4_0($module) function ps_checkout_create_order_state_8_3_4_0($configuration_key, $color, $nameByLangIsoCode) { $orderStateNameByLangId = []; - foreach ($nameByLangIsoCode as $langIsoCode => $name) { - foreach (Language::getLanguages(false) as $language) { - if (Tools::strtolower($language['iso_code']) === $langIsoCode) { - $orderStateNameByLangId[(int) $language['id_lang']] = $name; - } elseif (isset($nameByLangIsoCode['en'])) { - $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; - } + + foreach (Language::getLanguages(false) as $language) { + $languageIsoCode = Tools::strtolower($language['iso_code']); + + if (isset($nameByLangIsoCode[$languageIsoCode])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode[$languageIsoCode]; + } elseif (isset($nameByLangIsoCode['en'])) { + $orderStateNameByLangId[(int) $language['id_lang']] = $nameByLangIsoCode['en']; } } From 57a95789313d36cf49360207374a07ea0ffe2d9a Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 1 Aug 2023 12:40:15 +0300 Subject: [PATCH 045/343] Added maintenance link --- src/Adapter/LinkAdapter.php | 14 ++++++++++++-- src/Presenter/Store/Modules/ContextModule.php | 3 ++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/src/Adapter/LinkAdapter.php b/src/Adapter/LinkAdapter.php index 9a377e65e..7eaefcdda 100644 --- a/src/Adapter/LinkAdapter.php +++ b/src/Adapter/LinkAdapter.php @@ -60,9 +60,19 @@ public function getAdminLink($controller, $withToken = true, $sfRouteParams = [] $shop = \Context::getContext()->shop; if ((new ShopContext())->isShop17()) { - $link = $this->link->getAdminLink($controller, $withToken, $sfRouteParams, $params); + $adminLink = $this->link->getAdminLink($controller, $withToken, $sfRouteParams, $params); - return $shop->virtual_uri !== '' ? str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $link) : $link; + if ($shop->virtual_uri !== '') { + $adminLink = str_replace($shop->physical_uri . $shop->virtual_uri, $shop->physical_uri, $adminLink); + } + + // We have problems with links in our zoid application, since some links generated don't have domain they redirect to CDN domain + // Routes that use new symfony router are returned without the domain + if (strpos($adminLink, 'http') !== 0) { + return \Tools::getShopDomainSsl(true) . $adminLink; + } + + return $adminLink; } $paramsAsString = ''; diff --git a/src/Presenter/Store/Modules/ContextModule.php b/src/Presenter/Store/Modules/ContextModule.php index b28bf5ab1..0d1bb4119 100644 --- a/src/Presenter/Store/Modules/ContextModule.php +++ b/src/Presenter/Store/Modules/ContextModule.php @@ -154,7 +154,7 @@ public function present() 'shopsTree' => $this->getShopsTree(), 'faq' => $this->getFaq(), 'language' => $this->psContext->getLanguage(), - 'prestashopCheckoutAjax' => (new LinkAdapter($this->psContext->getLink()))->getAdminLink('AdminAjaxPrestashopCheckout'), + 'prestashopCheckoutAjax' => $this->getGeneratedLink('AdminAjaxPrestashopCheckout'), 'translations' => $this->translations->getTranslations(), 'readmeUrl' => $this->getReadme(), 'cguUrl' => $this->getCgu(), @@ -170,6 +170,7 @@ public function present() 'countriesLink' => $this->getGeneratedLink('AdminCountries'), 'currenciesLink' => $this->getGeneratedLink('AdminCurrencies'), 'paymentPreferencesLink' => $this->getGeneratedLink($this->shopContext->isShop17() ? 'AdminPaymentPreferences' : 'AdminPayment'), + 'maintenanceLink' => $this->getGeneratedLink('AdminMaintenance'), 'overridesExist' => $this->overridesExist(), 'submitIdeaLink' => $this->getSubmitIdeaLink(), 'orderTotal' => (new OrderRepository())->count($this->psContext->getShopId()), From a25bc6545ab4aacedadbb1a0e463c04d0c725afb Mon Sep 17 00:00:00 2001 From: boubkerbribri Date: Wed, 2 Aug 2023 16:38:13 +0200 Subject: [PATCH 046/343] test: add data-test for E2E tests on ajaxPaypalOrder.tpl --- views/templates/admin/ajaxPayPalOrder.tpl | 12 ++++++------ views/templates/admin/ajaxPayPalOrderLegacy.tpl | 12 ++++++------ 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/views/templates/admin/ajaxPayPalOrder.tpl b/views/templates/admin/ajaxPayPalOrder.tpl index 2b446fcfe..397ffd403 100644 --- a/views/templates/admin/ajaxPayPalOrder.tpl +++ b/views/templates/admin/ajaxPayPalOrder.tpl @@ -16,7 +16,7 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 *} -
+
{if !$orderPayPal}
@@ -50,22 +50,22 @@

{l s='PayPal Order' mod='ps_checkout'}

{l s='Reference' mod='ps_checkout'}
-
{$orderPayPal.id|escape:'html':'UTF-8'}
+
{$orderPayPal.id|escape:'html':'UTF-8'}
{l s='Status' mod='ps_checkout'}
-
+
{$orderPayPal.status.translated|escape:'html':'UTF-8'}
{l s='Total' mod='ps_checkout'}
-
{$orderPayPal.total}
+
{$orderPayPal.total}
{l s='Balance' mod='ps_checkout'}
-
{$orderPayPal.balance}
+
{$orderPayPal.balance}
{l s='Payment mode' mod='ps_checkout'}
-
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl index 698ff896d..e0388a11a 100644 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -16,7 +16,7 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 *} -
+
{if !$orderPayPal}
@@ -50,22 +50,22 @@

{l s='PayPal Order' mod='ps_checkout'}

{l s='Reference' mod='ps_checkout'}
-
{$orderPayPal.id|escape:'html':'UTF-8'}
+
{$orderPayPal.id|escape:'html':'UTF-8'}
{l s='Status' mod='ps_checkout'}
-
+
{$orderPayPal.status.translated|escape:'html':'UTF-8'}
{l s='Total' mod='ps_checkout'}
-
{$orderPayPal.total}
+
{$orderPayPal.total}
{l s='Balance' mod='ps_checkout'}
-
{$orderPayPal.balance}
+
{$orderPayPal.balance}
{l s='Payment mode' mod='ps_checkout'}
-
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
From 42bcf8ac388014c941bdd03bbc55bcf48d73c55d Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:12:22 +0200 Subject: [PATCH 047/343] PAYSHIP-2420 --- .../QueryHandler/GetOrderForPaymentRefundedQueryHandler.php | 2 +- .../EventSubscriber/PayPalCaptureEventSubscriber.php | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php index 4f1c51a39..3ba5b28b5 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentRefundedQueryHandler.php @@ -86,7 +86,7 @@ public function handle(GetOrderForPaymentRefundedQuery $query) (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), $this->hasBeenTotallyRefunded($totalRefund, $order), - (string) $order->getTotalProductsWithTaxes(), /* @phpstan-ignore-line */ + (string) $order->getTotalPaid(), (string) $totalRefund, (int) $order->id_currency ); diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index 4242e7683..c4979cc41 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -229,7 +229,11 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) return; } - if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $order->getTotalRefund()) == CheckOrderAmount::ORDER_NOT_FULL_PAID) { + $capture = $event->getCapture(); + // In case there no OrderSlip for this refund, we use the refund amount from payload + $totalRefunded = $order->getTotalRefund() ? $order->getTotalRefund() : $capture['amount']['value']; + + if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $totalRefunded) === CheckOrderAmount::ORDER_NOT_FULL_PAID) { $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED))); } else { $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED))); From 6a388f3ea229a107cb610928454c03a9971cbf8e Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 8 Aug 2023 11:13:34 +0200 Subject: [PATCH 048/343] Http Unauthorized PS8 --- src/Api/Payment/Client/PaymentClient.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 4e8023412..ccb7781d2 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -155,7 +155,7 @@ private function postWithRetry(array $options, $delay = 2, $retries = 2) try { $response = parent::post($options); - if ($response['httpCode'] === 401) { + if ($response['httpCode'] === 401 || false !== strpos($response['exceptionMessage'], 'Unauthorized')) { throw new PsCheckoutException('Unauthorized', PsCheckoutException::PSCHECKOUT_HTTP_UNAUTHORIZED); } From 8b452ea88a40ad7c95d2dd988a1c426915130c97 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 9 Aug 2023 17:22:45 +0200 Subject: [PATCH 049/343] Fix upgrade script --- upgrade/upgrade-8.3.4.0.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/upgrade/upgrade-8.3.4.0.php b/upgrade/upgrade-8.3.4.0.php index 0630507e9..0ebfd9b6c 100644 --- a/upgrade/upgrade-8.3.4.0.php +++ b/upgrade/upgrade-8.3.4.0.php @@ -155,6 +155,8 @@ function upgrade_module_8_3_4_0($module) ] ); break; + default: + Configuration::updateGlobalValue($configuration_key, $id_order_state); } } else { Configuration::updateGlobalValue($configuration_key, $id_order_state); From 620358957a3953c5ff53894fb1e43a2793937f5c Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 22 Aug 2023 11:42:30 +0200 Subject: [PATCH 050/343] PAYSHIP-2455 clear cache after order patch --- controllers/front/check.php | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/controllers/front/check.php b/controllers/front/check.php index bb439d5d7..3a4cc0896 100644 --- a/controllers/front/check.php +++ b/controllers/front/check.php @@ -22,6 +22,7 @@ use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use Psr\SimpleCache\CacheInterface; /** * This controller receive ajax call on customer click on a payment button @@ -118,6 +119,10 @@ public function postProcess() throw new PsCheckoutException(sprintf('Unable to patch PayPal Order - Exception %s : %s', $response['exceptionCode'], $response['exceptionMessage']), PsCheckoutException::PSCHECKOUT_UPDATE_ORDER_HANDLE_ERROR); } + /** @var CacheInterface $orderPayPalCache */ + $orderPayPalCache = $this->module->getService('ps_checkout.cache.paypal.order'); + $orderPayPalCache->delete($psCheckoutCart->getPaypalOrderId()); + $this->module->getLogger()->info( 'PayPal Order patched', [ From 6cb0abda0b78eb7c47bef7c2290a4e0c6d0ddb36 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 22 Aug 2023 13:23:20 +0200 Subject: [PATCH 051/343] Fix http exception management --- src/Api/GenericClient.php | 38 ++++++++++++++++---------------------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/src/Api/GenericClient.php b/src/Api/GenericClient.php index bfc17d677..f0ef52135 100755 --- a/src/Api/GenericClient.php +++ b/src/Api/GenericClient.php @@ -22,6 +22,7 @@ use Exception; use GuzzleHttp\Psr7\Request; +use Http\Client\Exception\NetworkException; use Http\Client\Exception\TransferException; use Link; use Module; @@ -29,7 +30,6 @@ use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; use Ps_checkout; -use RuntimeException; /** * Construct the client used to make call to maasland @@ -101,26 +101,14 @@ public function __construct() */ protected function post(array $options = []) { + $request = new Request('POST', $this->getRoute(), [], json_encode($options)); + try { - $response = $this->client->sendRequest( - new Request('POST', $this->getRoute(), [], json_encode($options)) - ); - } catch (TransferException $exception) { - return $this->handleException( - new PsCheckoutException( - $exception->getMessage(), - PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION, - $exception - ) - ); - } catch (RuntimeException $exception) { - return $this->handleException( - new PsCheckoutException( - $exception->getMessage(), - PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION, - $exception - ) - ); + $response = $this->client->sendRequest($request); + } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { + return $this->handleException(new NetworkException($exception->getMessage(), $request, $exception)); + } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { + return $this->handleException(new TransferException($exception->getMessage(), 0, $exception)); } catch (Exception $exception) { return $this->handleException($exception); } @@ -232,6 +220,12 @@ private function handleException(Exception $exception) { $body = ''; $httpCode = 500; + $exceptionCode = $exception->getCode(); + $exceptionMessage = $exception->getMessage(); + + if ($exception instanceof NetworkException || $exception instanceof TransferException) { + $exceptionCode = PsCheckoutException::PSCHECKOUT_HTTP_EXCEPTION; + } if (method_exists($exception, 'getResponse')) { $response = $exception->getResponse(); @@ -243,8 +237,8 @@ private function handleException(Exception $exception) 'status' => false, 'httpCode' => $httpCode, 'body' => $body, - 'exceptionCode' => $exception->getCode(), - 'exceptionMessage' => $exception->getMessage(), + 'exceptionCode' => $exceptionCode, + 'exceptionMessage' => $exceptionMessage, ]; } From cd45b928a72a1f348007f0766e9611b7d0a3169a Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:30:45 +0200 Subject: [PATCH 052/343] Guzzle Ring exceptions only occurs with Guzzle 5 --- src/Api/GenericClient.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Api/GenericClient.php b/src/Api/GenericClient.php index f0ef52135..d1f3dee0f 100755 --- a/src/Api/GenericClient.php +++ b/src/Api/GenericClient.php @@ -105,10 +105,6 @@ protected function post(array $options = []) try { $response = $this->client->sendRequest($request); - } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { - return $this->handleException(new NetworkException($exception->getMessage(), $request, $exception)); - } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { - return $this->handleException(new TransferException($exception->getMessage(), 0, $exception)); } catch (Exception $exception) { return $this->handleException($exception); } From ce5b2d45b230e898b75efbd2d00f3cd6be742f55 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Fri, 15 Sep 2023 16:48:48 +0200 Subject: [PATCH 053/343] Fixing wrong module configuration when multishop enabled --- ps_checkout.php | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/ps_checkout.php b/ps_checkout.php index 4e945f232..0913967aa 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -1272,6 +1272,7 @@ public function hookActionObjectShopAddAfter(array $params) { /** @var Shop $shop */ $shop = $params['object']; + $now = date('Y-m-d H:i:s'); $toggleShopConfigurationCommandHandler = new \PrestaShop\Module\PrestashopCheckout\Configuration\ToggleShopConfigurationCommandHandler(); $toggleShopConfigurationCommandHandler->handle( @@ -1281,9 +1282,45 @@ public function hookActionObjectShopAddAfter(array $params) ) ); - $this->installConfiguration(); + foreach ($this->configurationList as $name => $value) { + if (Configuration::hasKey($name, null, (int) $shop->id_shop_group, (int) $shop->id)) { + Db::getInstance()->update( + 'configuration', + [ + 'name' => pSQL($name), + 'value' => pSQL($value), + 'date_add' => pSQL($now), + 'date_upd' => pSQL($now), + 'id_shop' => (int) $shop->id, + 'id_shop_group' => (int) $shop->id_shop_group, + ], + 'name = \'' . pSQL($name) . '\', id_shop = ' . (int) $shop->id . ', id_shop_group = ' . (int) $shop->id_shop_group, + 1, + true, + false + ); + } else { + Db::getInstance()->insert( + 'configuration', + [ + 'name' => pSQL($name), + 'value' => pSQL($value), + 'date_add' => pSQL($now), + 'date_upd' => pSQL($now), + 'id_shop' => (int) $shop->id, + 'id_shop_group' => (int) $shop->id_shop_group, + ], + true, + false + ); + } + + Configuration::set($name, $value, (int) $shop->id_shop_group, (int) $shop->id); + } + $this->addCheckboxCarrierRestrictionsForModule([(int) $shop->id]); $this->addCheckboxCountryRestrictionsForModule([(int) $shop->id]); + if ($this->currencies_mode === 'checkbox') { $this->addCheckboxCurrencyRestrictionsForModule([(int) $shop->id]); } elseif ($this->currencies_mode === 'radio') { From 88c612cebaa28c3d25fe5d5cf6625c6849ae545b Mon Sep 17 00:00:00 2001 From: sgodard Date: Wed, 20 Sep 2023 16:12:14 +0200 Subject: [PATCH 054/343] Change Webhook url generation --- src/Api/Payment/Client/PaymentClient.php | 14 ++++-------- src/Routing/Router.php | 29 ++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/src/Api/Payment/Client/PaymentClient.php b/src/Api/Payment/Client/PaymentClient.php index 5d41af166..f2b99494e 100755 --- a/src/Api/Payment/Client/PaymentClient.php +++ b/src/Api/Payment/Client/PaymentClient.php @@ -20,7 +20,6 @@ namespace PrestaShop\Module\PrestashopCheckout\Api\Payment\Client; -use Configuration; use Context; use GuzzleHttp\Event\Emitter; use GuzzleHttp\HandlerStack; @@ -34,6 +33,7 @@ use PrestaShop\Module\PrestashopCheckout\Exception\HttpTimeoutException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Logger\LoggerConfiguration; +use PrestaShop\Module\PrestashopCheckout\Routing\Router; use PrestaShop\Module\PrestashopCheckout\ShopContext; use PrestaShop\Module\PrestashopCheckout\Version\Version; use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; @@ -69,6 +69,9 @@ public function __construct(Link $link, $client = null) /** @var LoggerInterface $logger */ $logger = $module->getService('ps_checkout.logger'); + /** @var Router $router */ + $router = $module->getService('ps_checkout.prestashop.router'); + $clientConfiguration = [ 'base_url' => (new PaymentEnv())->getPaymentApiUrl(), 'verify' => $this->getVerify(), @@ -79,14 +82,7 @@ public function __construct(Link $link, $client = null) 'Accept' => 'application/json', 'Authorization' => 'Bearer ' . $this->token, // Token we get from PsAccounts 'Shop-Id' => $this->shopUid, // Shop UUID we get from PsAccounts - 'Hook-Url' => $this->link->getModuleLink( - 'ps_checkout', - 'DispatchWebHook', - [], - true, - (int) Configuration::get('PS_LANG_DEFAULT'), - (int) Context::getContext()->shop->id - ), + 'Hook-Url' => $router->getDispatchWebhookLink((int) Context::getContext()->shop->id), 'Bn-Code' => (new ShopContext())->getBnCode(), 'Module-Version' => $version->getSemVersion(), // version of the module 'Prestashop-Version' => _PS_VERSION_, // prestashop version diff --git a/src/Routing/Router.php b/src/Routing/Router.php index c91652130..c6328521c 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -20,7 +20,9 @@ namespace PrestaShop\Module\PrestashopCheckout\Routing; +use Configuration; use Context; +use Shop; class Router { @@ -35,10 +37,37 @@ public function __construct() } /** + * @param int|null $orderId + * * @return string */ public function getContactLink($orderId = null) { return $this->context->link->getPageLink('contact', true, $this->context->language->id, ['id_order' => (int) $orderId]); } + + /** + * @param int $idShop + * + * @return string + */ + public function getDispatchWebhookLink($idShop) + { + $idLang = Configuration::get('PS_LANG_DEFAULT'); + + return $this->getBaseLink($idShop) . 'index.php?controller=DispatchWebHook&module=ps_checkout&fc=module&id_lang=' . (int) $idLang . '&id_shop=' . (int) $idShop; + } + + /** + * @param int $idShop + * + * @return string + */ + private function getBaseLink($idShop) + { + $shop = new Shop($idShop); + $base = Configuration::get('PS_SSL_ENABLED') ? 'https://' . $shop->domain_ssl : 'http://' . $shop->domain; + + return $base . $shop->physical_uri; + } } From 046cad9b9208aebc3a36745915302592f2c14e60 Mon Sep 17 00:00:00 2001 From: sgodard Date: Wed, 20 Sep 2023 17:43:48 +0200 Subject: [PATCH 055/343] Return url on validate --- controllers/front/validate.php | 62 ++++++++++++++------- src/Builder/Payload/OrderPayloadBuilder.php | 17 ++---- src/Routing/Router.php | 8 +++ 3 files changed, 57 insertions(+), 30 deletions(-) diff --git a/controllers/front/validate.php b/controllers/front/validate.php index dc6cf76a9..be9c2ab95 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -50,22 +50,29 @@ class Ps_CheckoutValidateModuleFrontController extends AbstractFrontController public function postProcess() { try { - $bodyContent = file_get_contents('php://input'); - - if (empty($bodyContent)) { - $this->exitWithResponse([ - 'httpCode' => 400, - 'body' => 'Payload invalid', - ]); - } - - $bodyValues = json_decode($bodyContent, true); - - if (empty($bodyValues)) { - $this->exitWithResponse([ - 'httpCode' => 400, - 'body' => 'Payload invalid', - ]); + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + $bodyContent = file_get_contents('php://input'); + + if (empty($bodyContent)) { + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); + } + + $bodyValues = json_decode($bodyContent, true); + + if (empty($bodyValues)) { + $this->exitWithResponse([ + 'httpCode' => 400, + 'body' => 'Payload invalid', + ]); + } + } else { + $bodyValues = [ + 'orderID' => Tools::getValue('token'), + 'payerID' => Tools::getValue('PayerID'), + ]; } if (empty($bodyValues['orderID']) || false === Validate::isGenericName($bodyValues['orderID'])) { @@ -94,9 +101,9 @@ public function postProcess() $eventDispatcher->dispatch(new CheckoutCompletedEvent( $psCheckoutCart->getIdCart(), $this->paypalOrderId, - (isset($bodyValues['fundingSource']) && Validate::isGenericName($bodyValues['fundingSource'])) ? $bodyValues['fundingSource'] : null, - isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout'], - isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields'] + (isset($bodyValues['fundingSource']) && Validate::isGenericName($bodyValues['fundingSource'])) ? $bodyValues['fundingSource'] : $psCheckoutCart->getPaypalFundingSource(), + isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout'] || $psCheckoutCart->isExpressCheckout(), + isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields'] || $psCheckoutCart->isHostedFields() )); $this->sendOkResponse($this->generateResponse()); @@ -200,6 +207,23 @@ private function sendOkResponse($response) $cart = new Cart($psCheckoutCart->getIdCart()); + if ($_SERVER['REQUEST_METHOD'] === 'GET') { + Tools::redirect($this->context->link->getPageLink( + 'order-confirmation', + true, + (int) $order->id_lang, + [ + 'paypal_status' => $response['status'], + 'paypal_order' => $response['paypalOrderId'], + 'paypal_transaction' => $response['transactionIdentifier'], + 'id_cart' => $psCheckoutCart->getIdCart(), + 'id_module' => (int) $this->module->id, + 'id_order' => (int) $order->id, + 'key' => $cart->secure_key, + ] + )); + } + $this->exitWithResponse([ 'status' => true, 'httpCode' => 200, diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index c53b45e9e..65b2903f0 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -20,12 +20,14 @@ namespace PrestaShop\Module\PrestashopCheckout\Builder\Payload; +use Context; use libphonenumber\PhoneNumberType; use libphonenumber\PhoneNumberUtil; use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PaypalCountryCodeMatrice; +use PrestaShop\Module\PrestashopCheckout\Routing\Router; /** * Build the payload for creating paypal order @@ -161,7 +163,7 @@ public function buildBaseNode() ]; $psCheckoutCartCollection = new \PrestaShopCollection('PsCheckoutCart'); - $psCheckoutCartCollection->where('id_cart', '=', (int) \Context::getContext()->cart->id); + $psCheckoutCartCollection->where('id_cart', '=', (int) Context::getContext()->cart->id); /** @var \PsCheckoutCart|false $psCheckoutCart */ $psCheckoutCart = $psCheckoutCartCollection->getFirst(); @@ -292,6 +294,8 @@ public function buildApplicationContextNode() $context = \Context::getContext(); /** @var \Ps_checkout $module */ $module = \Module::getInstanceByName('ps_checkout'); + /** @var Router $router */ + $router = $module->getService('ps_checkout.prestashop.router'); $node['application_context'] = [ 'brand_name' => \Configuration::get( 'PS_SHOP_NAME', @@ -300,16 +304,7 @@ public function buildApplicationContextNode() (int) $context->shop->id ), 'shipping_preference' => $this->expressCheckout ? 'GET_FROM_FILE' : 'SET_PROVIDED_ADDRESS', - 'return_url' => \Context::getContext()->link->getPageLink( - 'order-confirmation', - true, - (int) $context->cart->id_lang, - [ - 'id_cart' => (int) $context->cart->id, - 'key' => $context->cart->secure_key, - 'id_module' => (int) $module->id, - ] - ), + 'return_url' => $router->getCheckoutValidateLink(), ]; $this->getPayload()->addAndMergeItems($node); diff --git a/src/Routing/Router.php b/src/Routing/Router.php index c91652130..83dce9573 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -34,6 +34,14 @@ public function __construct() $this->context = Context::getContext(); } + /** + * @return string + */ + public function getCheckoutValidateLink() + { + return $this->context->link->getModuleLink('ps_checkout', 'validate', [], true, $this->context->language->id, $this->context->shop->id); + } + /** * @return string */ From 794fdfcc0faf74cc3cff99c05410407e862860e7 Mon Sep 17 00:00:00 2001 From: sgodard Date: Wed, 12 Jul 2023 17:29:22 +0200 Subject: [PATCH 056/343] Add PayPal env value --- classes/PsCheckoutCart.php | 18 ++++++++++++++++++ .../AdminAjaxPrestashopCheckoutController.php | 2 ++ controllers/front/create.php | 8 +++++++- src/Database/TableManager.php | 1 + src/PayPal/PayPalConfiguration.php | 2 +- views/css/adminOrderView.css | 14 ++++++++++++++ views/templates/admin/ajaxPayPalOrder.tpl | 10 ++++++++++ .../templates/admin/ajaxPayPalOrderLegacy.tpl | 10 ++++++++++ 8 files changed, 63 insertions(+), 2 deletions(-) mode change 100644 => 100755 classes/PsCheckoutCart.php mode change 100644 => 100755 controllers/front/create.php mode change 100644 => 100755 views/css/adminOrderView.css mode change 100644 => 100755 views/templates/admin/ajaxPayPalOrder.tpl mode change 100644 => 100755 views/templates/admin/ajaxPayPalOrderLegacy.tpl diff --git a/classes/PsCheckoutCart.php b/classes/PsCheckoutCart.php old mode 100644 new mode 100755 index 7c4bfd8ce..075bde1d2 --- a/classes/PsCheckoutCart.php +++ b/classes/PsCheckoutCart.php @@ -70,6 +70,11 @@ class PsCheckoutCart extends ObjectModel */ public $paypal_authorization_expire; + /** + * @var string PayPal environment information + */ + public $environment = 'LIVE'; + /** * @var bool */ @@ -147,6 +152,11 @@ class PsCheckoutCart extends ObjectModel 'allow_null' => true, 'required' => false, ], + 'environment' => [ + 'type' => self::TYPE_STRING, + 'allow_null' => true, + 'required' => false, + ], 'isExpressCheckout' => [ 'type' => self::TYPE_BOOL, 'validate' => 'isBool', @@ -246,6 +256,14 @@ public function getPaypalAuthorizationExpireDate() return $this->paypal_authorization_expire; } + /** + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } + /** * @return bool */ diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index b5f61f205..c3621345c 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -32,6 +32,7 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; +use PrestaShop\Module\PrestashopCheckout\PayPal\Mode; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration; @@ -398,6 +399,7 @@ public function ajaxProcessFetchOrder() 'orderPaymentDisplayName' => $fundingSourceTranslationProvider->getPaymentMethodName($psCheckoutCart->paypal_funding), 'orderPaymentLogoUri' => $this->module->getPathUri() . 'views/img/' . $psCheckoutCart->paypal_funding . '.svg', 'psCheckoutCart' => $psCheckoutCart, + 'isProductionEnv' => $psCheckoutCart->getEnvironment() === Mode::LIVE, ]); $this->ajaxDie(json_encode([ diff --git a/controllers/front/create.php b/controllers/front/create.php old mode 100644 new mode 100755 index 15b6639d6..217416853 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -22,6 +22,7 @@ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; /** @@ -145,6 +146,8 @@ public function postProcess() $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; + /** @var PayPalConfiguration $configuration */ + $configuration = $this->module->getService('ps_checkout.paypal.configuration'); $this->module->getLogger()->info( 'PayPal Order created', @@ -154,6 +157,8 @@ public function postProcess() 'isExpressCheckout' => $isExpressCheckout, 'isHostedFields' => $isHostedFields, 'id_cart' => (int) $this->context->cart->id, + 'environment' => $configuration->getPaymentMode(), + 'intent' => $configuration->getIntent(), ] ); @@ -165,9 +170,10 @@ public function postProcess() $psCheckoutCart->paypal_funding = $fundingSource; $psCheckoutCart->paypal_order = $response['body']['id']; $psCheckoutCart->paypal_status = $response['body']['status']; - $psCheckoutCart->paypal_intent = 'AUTHORIZE' === Configuration::get('PS_CHECKOUT_INTENT') ? 'AUTHORIZE' : 'CAPTURE'; + $psCheckoutCart->paypal_intent = $configuration->getIntent(); $psCheckoutCart->paypal_token = $response['body']['client_token']; $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); + $psCheckoutCart->environment = $configuration->getPaymentMode(); $psCheckoutCart->isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; $psCheckoutCart->isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; $psCheckoutCartRepository->save($psCheckoutCart); diff --git a/src/Database/TableManager.php b/src/Database/TableManager.php index 006c3de10..0a4c9c06c 100644 --- a/src/Database/TableManager.php +++ b/src/Database/TableManager.php @@ -64,6 +64,7 @@ public function createTable() `paypal_token` text DEFAULT NULL, `paypal_token_expire` datetime NULL, `paypal_authorization_expire` datetime NULL, + `environment` varchar(20) NULL, `isExpressCheckout` tinyint(1) unsigned DEFAULT 0 NOT NULL, `isHostedFields` tinyint(1) unsigned DEFAULT 0 NOT NULL, `date_add` datetime NOT NULL, diff --git a/src/PayPal/PayPalConfiguration.php b/src/PayPal/PayPalConfiguration.php index 974b157aa..0b296c306 100644 --- a/src/PayPal/PayPalConfiguration.php +++ b/src/PayPal/PayPalConfiguration.php @@ -70,7 +70,7 @@ public function __construct(PrestaShopConfiguration $configuration, PayPalCodeRe */ public function getIntent() { - return Intent::CAPTURE === $this->configuration->get(self::INTENT) ? Intent::CAPTURE : Intent::AUTHORIZE; + return Intent::AUTHORIZE === $this->configuration->get(self::INTENT) ? Intent::AUTHORIZE : Intent::CAPTURE; } /** diff --git a/views/css/adminOrderView.css b/views/css/adminOrderView.css old mode 100644 new mode 100755 index 4d9bdbdf7..ce7e6e1d0 --- a/views/css/adminOrderView.css +++ b/views/css/adminOrderView.css @@ -8,6 +8,20 @@ color: #fff; } +#ps_checkout .badge.badge-paypal-environment-sandbox { + background-color: #5E5E5E; + color: #FFF; + font-size: 12px; + border-radius: 10px!important; +} + +#ps_checkout .badge.badge-paypal-environment-live { + background-color: #174EEF; + color: #FFF; + font-size: 12px; + border-radius: 10px!important; +} + #ps_checkout .panel-wrapper { flex-grow: 1; padding: 18px 14px; diff --git a/views/templates/admin/ajaxPayPalOrder.tpl b/views/templates/admin/ajaxPayPalOrder.tpl old mode 100644 new mode 100755 index 397ffd403..6c4e74737 --- a/views/templates/admin/ajaxPayPalOrder.tpl +++ b/views/templates/admin/ajaxPayPalOrder.tpl @@ -66,6 +66,16 @@
{$orderPayPal.balance}
{l s='Payment mode' mod='ps_checkout'}
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+
+ + {if $isProductionEnv} + {l s='Production Environment' mod='ps_checkout'} + {else} + {l s='Test Environment' mod='ps_checkout'} + {/if} + +
+
diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl old mode 100644 new mode 100755 index e0388a11a..8752e8829 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -66,6 +66,16 @@
{$orderPayPal.balance}
{l s='Payment mode' mod='ps_checkout'}
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+
+ + {if $isProductionEnv} + {l s='Production Environment' mod='ps_checkout'} + {else} + {l s='Test Environment' mod='ps_checkout'} + {/if} + +
+
From 415d2c6c14825fc9256407d66dd6b57e837ff73d Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:43:21 +0200 Subject: [PATCH 057/343] Bump module version to 8.3.5.0 --- config.xml | 2 +- ps_checkout.php | 4 ++-- upgrade/upgrade-8.3.5.0.php | 43 +++++++++++++++++++++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 upgrade/upgrade-8.3.5.0.php diff --git a/config.xml b/config.xml index 859438ea3..e55961760 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 4e945f232..d85376fb8 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.4.0'; + const VERSION = '8.3.5.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -142,7 +142,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.4.0'; + $this->version = '8.3.5.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; diff --git a/upgrade/upgrade-8.3.5.0.php b/upgrade/upgrade-8.3.5.0.php new file mode 100644 index 000000000..6017f0d68 --- /dev/null +++ b/upgrade/upgrade-8.3.5.0.php @@ -0,0 +1,43 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Update main function for module version 8.3.5.0 + * + * @param Ps_checkout $module + * + * @return bool + */ +function upgrade_module_8_3_5_0($module) +{ + try { + $db = Db::getInstance(); + $db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` ADD COLUMN `environment` varchar(20) DEFAULT NULL;'); + } catch (Exception $exception) { + PrestaShopLogger::addLog($exception->getMessage(), 4, 1, 'Module', $module->id); + + return false; + } + + return true; +} From 2dc47405a9cf0ba75af9344d2e4100cd3dfb3aec Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 25 Sep 2023 16:33:05 +0200 Subject: [PATCH 058/343] PHPStan feedback --- src/Routing/Router.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Routing/Router.php b/src/Routing/Router.php index c8488be7f..e5665dd00 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -37,8 +37,6 @@ public function __construct() } /** - * @param int|null $orderId - * * @return string */ public function getCheckoutValidateLink() @@ -47,6 +45,8 @@ public function getCheckoutValidateLink() } /** + * @param int|null $orderId + * * @return string */ public function getContactLink($orderId = null) From 560043b8571b213d5b0cb24aed755d26bb83752c Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:18:02 +0200 Subject: [PATCH 059/343] Set default environment value --- classes/PsCheckoutCart.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/classes/PsCheckoutCart.php b/classes/PsCheckoutCart.php index 075bde1d2..8aa101127 100755 --- a/classes/PsCheckoutCart.php +++ b/classes/PsCheckoutCart.php @@ -71,7 +71,7 @@ class PsCheckoutCart extends ObjectModel public $paypal_authorization_expire; /** - * @var string PayPal environment information + * @var string|null PayPal environment information */ public $environment = 'LIVE'; @@ -261,7 +261,7 @@ public function getPaypalAuthorizationExpireDate() */ public function getEnvironment() { - return $this->environment; + return $this->environment === 'SANDBOX' ? $this->environment : 'LIVE'; } /** From a1f698562fb0922b565483c3a8a9ae5a4dbece6e Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 25 Sep 2023 17:21:58 +0200 Subject: [PATCH 060/343] Add amount data to logs --- controllers/front/create.php | 1 + 1 file changed, 1 insertion(+) diff --git a/controllers/front/create.php b/controllers/front/create.php index 217416853..066250cfd 100755 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -157,6 +157,7 @@ public function postProcess() 'isExpressCheckout' => $isExpressCheckout, 'isHostedFields' => $isHostedFields, 'id_cart' => (int) $this->context->cart->id, + 'amount' => $this->context->cart->getOrderTotal(true, Cart::BOTH), 'environment' => $configuration->getPaymentMode(), 'intent' => $configuration->getIntent(), ] From bed7d0b5ad8c0bf6a3ceeb4573f955243789b10e Mon Sep 17 00:00:00 2001 From: sgodard Date: Wed, 27 Sep 2023 09:48:30 +0200 Subject: [PATCH 061/343] rollback webhook url generation --- src/Routing/Router.php | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/Routing/Router.php b/src/Routing/Router.php index e5665dd00..55ced542c 100644 --- a/src/Routing/Router.php +++ b/src/Routing/Router.php @@ -61,9 +61,7 @@ public function getContactLink($orderId = null) */ public function getDispatchWebhookLink($idShop) { - $idLang = Configuration::get('PS_LANG_DEFAULT'); - - return $this->getBaseLink($idShop) . 'index.php?controller=DispatchWebHook&module=ps_checkout&fc=module&id_lang=' . (int) $idLang . '&id_shop=' . (int) $idShop; + return $this->context->link->getModuleLink('ps_checkout', 'DispatchWebHook', [], true, (int) Configuration::get('PS_LANG_DEFAULT'), (int) $idShop); } /** From 19a43c2123e59e4adec1c46d95d2e50848cfe0fe Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 28 Sep 2023 11:04:47 +0200 Subject: [PATCH 062/343] Avoid fetch PayPal Order on another environment --- .../AdminAjaxPrestashopCheckoutController.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index c3621345c..8046c0e63 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -378,6 +378,24 @@ public function ajaxProcessFetchOrder() } } + /** @var PayPalConfiguration $configurationPayPal */ + $configurationPayPal = $this->module->getService('ps_checkout.paypal.configuration'); + + if ($configurationPayPal->getPaymentMode() !== $psCheckoutCart->getEnvironment()) { + http_response_code(422); + $this->ajaxDie(json_encode([ + 'status' => false, + 'errors' => [ + strtr( + $this->l('PayPal Order [PAYPAL_ORDER_ID] is not in the same environment as PrestaShop Checkout'), + [ + '[PAYPAL_ORDER_ID]' => $psCheckoutCart->paypal_order, + ] + ), + ], + ])); + } + /** @var PayPalOrderProvider $paypalOrderProvider */ $paypalOrderProvider = $this->module->getService('ps_checkout.paypal.provider.order'); From 19dc9ae7f64e2d9e2407c2d337f6c528ef18ec82 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 9 Oct 2023 18:08:51 +0200 Subject: [PATCH 063/343] Check Db table at module install --- src/Database/TableManager.php | 37 +++++++++++++++++++++++++++++++++-- 1 file changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Database/TableManager.php b/src/Database/TableManager.php index 0a4c9c06c..501385036 100644 --- a/src/Database/TableManager.php +++ b/src/Database/TableManager.php @@ -46,7 +46,7 @@ public function __construct(\Db $db = null) */ public function createTable() { - return $this->db->execute(' + $result = $this->db->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'pscheckout_order_matrice` ( `id_order_matrice` int(10) unsigned NOT NULL AUTO_INCREMENT, `id_order_prestashop` int(10) unsigned NOT NULL, @@ -81,6 +81,10 @@ public function createTable() INDEX (`id_shop`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; '); + + $this->checkTable(); + + return (bool) $result; } /** @@ -90,8 +94,8 @@ public function createTable() */ public function dropTable() { + // Avoid to loose PayPal data if module is reset or uninstall return true; - //return $this->db->execute('DROP TABLE IF EXISTS `' . _DB_PREFIX_ . 'pscheckout_cart`'); } /** @@ -109,4 +113,33 @@ public function populatePsCartFromOrderMatrice() INNER JOIN `' . _DB_PREFIX_ . 'orders` AS o ON (om.id_order_prestashop = o.id_order) '); } + + /** + * Check if existing database is up to date + * Due to `CREATE TABLE IF NOT EXISTS` we need to check if table is up to date + * + * @return void + */ + public function checkTable() + { + $databaseFields = []; + $fields = $this->db->executeS('SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'pscheckout_cart`'); + + if (!empty($fields)) { + foreach ($fields as $field) { + $databaseFields[] = $field['Field']; + if ($field['Field'] === 'paypal_token' && $field['Type'] !== 'text') { + $this->db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` CHANGE `paypal_token` `paypal_token` text DEFAULT NULL;'); + } + } + } + + $objectDefinition = \PsCheckoutCart::$definition; + $objectFields = array_keys($objectDefinition['fields']); + $missingFields = array_diff($objectFields, $databaseFields); + + if (in_array('environment', $missingFields, true)) { + $this->db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` ADD COLUMN `environment` varchar(20) DEFAULT NULL;'); + } + } } From 7d3499e4ed0579010508cc286f9a7dbb06ad1537 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:37:08 +0200 Subject: [PATCH 064/343] Fix autoload with Opcache allows importing conflicting class name to namespace --- src/Event/Event.php | 12 +++++------ src/Event/SymfonyEventDispatcherFactory.php | 22 ++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/Event/Event.php b/src/Event/Event.php index b1a087c2c..878dae3d7 100644 --- a/src/Event/Event.php +++ b/src/Event/Event.php @@ -20,17 +20,17 @@ namespace PrestaShop\Module\PrestashopCheckout\Event; -use Symfony\Component\EventDispatcher\Event as ComponentEvent; -use Symfony\Contracts\EventDispatcher\Event as ContractEvent; +use Symfony\Component\EventDispatcher\Event as SymfonyComponentEvent; +use Symfony\Contracts\EventDispatcher\Event as SymfonyContractEvent; -if (class_exists(ComponentEvent::class)) { +if (class_exists(SymfonyComponentEvent::class)) { // @phpstan-ignore-next-line - class Event extends ComponentEvent + class Event extends SymfonyComponentEvent { } -} elseif (class_exists(ContractEvent::class)) { +} elseif (class_exists(SymfonyContractEvent::class)) { // @phpstan-ignore-next-line - class Event extends ContractEvent /* @phpstan-ignore-line */ + class Event extends SymfonyContractEvent /* @phpstan-ignore-line */ { } } diff --git a/src/Event/SymfonyEventDispatcherFactory.php b/src/Event/SymfonyEventDispatcherFactory.php index a3df62e25..f6908f820 100644 --- a/src/Event/SymfonyEventDispatcherFactory.php +++ b/src/Event/SymfonyEventDispatcherFactory.php @@ -22,11 +22,11 @@ use PrestaShop\Module\PrestashopCheckout\Logger\LoggerConfiguration; use Psr\Log\LoggerInterface; -use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; -use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\EventDispatcher\Debug\TraceableEventDispatcher as SymfonyTraceableEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcher as SymfonyEventDispatcher; +use Symfony\Component\EventDispatcher\EventDispatcherInterface as SymfonyEventDispatcherInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface as SymfonyEventSubscriberInterface; +use Symfony\Component\Stopwatch\Stopwatch as SymfonyStopwatch; class SymfonyEventDispatcherFactory { @@ -51,19 +51,19 @@ public function __construct(LoggerInterface $logger, LoggerConfiguration $config } /** - * @param EventSubscriberInterface[] $eventSubscribers + * @param SymfonyEventSubscriberInterface[] $eventSubscribers * - * @return EventDispatcherInterface + * @return SymfonyEventDispatcherInterface */ public function create(array $eventSubscribers) { $eventDispatcher = LoggerConfiguration::LEVEL_DEBUG === $this->configuration->getLevel() - ? new TraceableEventDispatcher( - new EventDispatcher(), - new Stopwatch(), + ? new SymfonyTraceableEventDispatcher( + new SymfonyEventDispatcher(), + new SymfonyStopwatch(), $this->logger ) - : new EventDispatcher(); + : new SymfonyEventDispatcher(); foreach ($eventSubscribers as $eventSubscriber) { $eventDispatcher->addSubscriber($eventSubscriber); From 33ba85943ceff7d08c9f36b6ac6ff10583fafd94 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:53:43 +0200 Subject: [PATCH 065/343] Fix onboarding check --- src/Repository/PsAccountRepository.php | 47 +++++++++++--------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/src/Repository/PsAccountRepository.php b/src/Repository/PsAccountRepository.php index cee589403..1e45463d3 100644 --- a/src/Repository/PsAccountRepository.php +++ b/src/Repository/PsAccountRepository.php @@ -37,6 +37,7 @@ class PsAccountRepository /** * @param PrestaShopConfiguration $configuration + * @param PsAccounts $psAccountsFacade */ public function __construct(PrestaShopConfiguration $configuration, PsAccounts $psAccountsFacade) { @@ -58,42 +59,28 @@ public function getOnboardedAccount() return new PsAccount( $this->getIdToken(), $this->getRefreshToken(), - $this->getEmail(), - $this->getLocalId() -// $this->getPsxForm() + $this->getEmail() ); } /** - * Retrieve the status of the psx form : return true if the form is completed, otherwise return false. - * If on ready, the merchant doesn't need to complete the form, so return true to act like if the - * user complete the form + * @deprecated 3.0.0 Moved to PS Accounts * * @return bool */ public function psxFormIsCompleted() { - // TODO: Remove all code related to PSX form. Since it's not used any more we return true to be sure to not make any breaking changes return true; -// -// if (getenv('PLATEFORM') === 'PSREADY') { // if on ready, the user is already onboarded -// return true; -// } -// -// return !empty($this->getPsxForm()); } /** - * Get the status of the firebase onboarding - * Only check idToken: is the only one truly mandatory + * Check if user and shop are linked with PS Accounts * * @return bool */ public function onBoardingIsCompleted() { - return !empty($this->getIdToken()); - // Commented out because psx form is no longer used - // && $this->psxFormIsCompleted(); + return $this->isAccountLinked(); } /** @@ -121,11 +108,15 @@ public function getIdToken() return false; } - return (string) $this->psAccountsService->getOrRefreshToken(); + try { + return (string) $this->psAccountsService->getOrRefreshToken(); + } catch (Exception $e) { + return false; + } } /** - * Get firebase localId from database + * @deprecated PS Accounts v5.1.1 * * @return string|bool */ @@ -157,13 +148,11 @@ public function getRefreshToken() * * @param bool $toArray * - * @return string|bool|array + * @return string|array */ public function getPsxForm($toArray = false) { - $form = $this->configuration->get(PsAccount::PS_CHECKOUT_PSX_FORM); - - return $toArray ? json_decode($form, true) : $form; + return $toArray ? '' : []; } /** @@ -196,15 +185,17 @@ public function isEmailValidated() /** * @return bool - * - * @throws Exception */ public function isAccountLinked() { - if (!$this->psAccountsService) { + if (!$this->psAccountsService || !method_exists($this->psAccountsService, 'isAccountLinked')) { return false; } - return $this->psAccountsService->isAccountLinked(); + try { + return $this->psAccountsService->isAccountLinked(); + } catch (Exception $e) { + return false; + } } } From 2224ba851beffbfcb4b695d0ad32bb1a9ccc73d9 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:54:14 +0200 Subject: [PATCH 066/343] Fix performance issue --- ps_checkout.php | 77 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 74 insertions(+), 3 deletions(-) diff --git a/ps_checkout.php b/ps_checkout.php index 8fc10569b..50e84d6fa 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -134,6 +134,8 @@ class Ps_checkout extends PaymentModule * @var \PrestaShop\ModuleLibServiceContainer\DependencyInjection\ServiceContainer */ private $serviceContainer; + private static $merchantIsValid; + private static $currencyIsAllowed; public function __construct() { @@ -393,6 +395,10 @@ public function uninstallTabs() */ public function hookDisplayPersonalInformationTop() { + if (!$this->merchantIsValid()) { + return ''; + } + return $this->display(__FILE__, 'views/templates/hook/displayPersonalInformationTop.tpl'); } @@ -503,6 +509,10 @@ public function hookDisplayProductAdditionalInfo() */ public function hookDisplayFooterProduct() { + if (!$this->merchantIsValid()) { + return ''; + } + return $this->display(__FILE__, 'views/templates/hook/displayFooterProduct.tpl'); } @@ -552,6 +562,10 @@ public function hookActionObjectProductInCartDeleteAfter() */ public function hookActionAfterDeleteProductInCart() { + if (!$this->merchantIsValid()) { + return; + } + /** @var \PrestaShop\Module\PrestashopCheckout\ShopContext $shopContext */ $shopContext = $this->getService('ps_checkout.context.shop'); @@ -567,6 +581,10 @@ public function hookActionAfterDeleteProductInCart() */ public function hookActionCartUpdateQuantityBefore() { + if (!$this->merchantIsValid()) { + return; + } + if (false === Validate::isLoadedObject($this->context->cart)) { return; } @@ -592,6 +610,10 @@ public function hookActionCartUpdateQuantityBefore() */ public function hookActionBeforeCartUpdateQty() { + if (!$this->merchantIsValid()) { + return; + } + /** @var \PrestaShop\Module\PrestashopCheckout\ShopContext $shopContext */ $shopContext = $this->getService('ps_checkout.context.shop'); @@ -713,6 +735,10 @@ public function hookPaymentOptions(array $params) */ public function hookDisplayOrderConfirmation(array $params) { + if (!$this->merchantIsValid()) { + return ''; + } + /** @var Order $order */ $order = (isset($params['objOrder'])) ? $params['objOrder'] : $params['order']; @@ -740,20 +766,46 @@ public function hookDisplayOrderConfirmation(array $params) */ public function checkCurrency($cart) { + if (isset(static::$currencyIsAllowed[$cart->id_currency])) { + return static::$currencyIsAllowed[$cart->id_currency]; + } + + /** @var \PrestaShop\Module\PrestashopCheckout\Repository\PayPalCodeRepository $codeRepository */ + $codeRepository = $this->getService('ps_checkout.repository.paypal.code'); $currency_order = Currency::getCurrencyInstance($cart->id_currency); + $isCurrencySupported = false; + + foreach (array_keys($codeRepository->getCurrencyCodes()) as $supportedCurrencyCode) { + if (strcasecmp($supportedCurrencyCode, $currency_order->iso_code) === 0) { + $isCurrencySupported = true; + } + } + + if (!$isCurrencySupported) { + static::$currencyIsAllowed[$cart->id_currency] = false; + + return false; + } + /** @var array $currencies_module */ $currencies_module = $this->getCurrency($cart->id_currency); if (empty($currencies_module)) { + static::$currencyIsAllowed[$cart->id_currency] = false; + return false; } foreach ($currencies_module as $currency_module) { if ($currency_order->id == $currency_module['id_currency']) { + static::$currencyIsAllowed[$cart->id_currency] = true; + return true; } } + static::$currencyIsAllowed[$cart->id_currency] = false; + return false; } @@ -876,10 +928,13 @@ public function hookActionAdminControllerSetMedia() */ public function merchantIsValid() { - /** @var \PrestaShop\Module\PrestashopCheckout\Validator\MerchantValidator $merchantValidator */ - $merchantValidator = $this->getService('ps_checkout.validator.merchant'); + if (static::$merchantIsValid === null) { + /** @var \PrestaShop\Module\PrestashopCheckout\Validator\MerchantValidator $merchantValidator */ + $merchantValidator = $this->getService('ps_checkout.validator.merchant'); + static::$merchantIsValid = $merchantValidator->merchantIsValid(); + } - return $merchantValidator->merchantIsValid(); + return static::$merchantIsValid; } /** @@ -1205,6 +1260,10 @@ public function getLogger() */ public function hookDisplayInvoiceLegalFreeText(array $params) { + if (!$this->merchantIsValid()) { + return ''; + } + /** @var \Order $order */ $order = $params['order']; @@ -1515,6 +1574,10 @@ private function getCheckoutPageUrl() public function hookDisplayHeader() { + if (!$this->merchantIsValid()) { + return ''; + } + $controller = Tools::getValue('controller'); if (empty($controller) && isset($this->context->controller->php_self)) { @@ -1687,6 +1750,10 @@ public function hookActionObjectShopDeleteAfter(array $params) */ public function hookDisplayPaymentReturn(array $params) { + if (!$this->merchantIsValid()) { + return ''; + } + /** @var Order $order */ $order = (isset($params['objOrder'])) ? $params['objOrder'] : $params['order']; @@ -1713,6 +1780,10 @@ public function hookDisplayPaymentReturn(array $params) */ public function hookDisplayOrderDetail(array $params) { + if (!$this->merchantIsValid()) { + return ''; + } + /** @var Order $order */ $order = $params['order']; From 51c9a723d390c2c22f966d5504f92d68642afa8c Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 18 Oct 2023 10:23:08 +0300 Subject: [PATCH 067/343] Removed authentication redirect after onboarding --- .../admin/AdminPaypalOnboardingPrestashopCheckoutController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php index 53904dff8..464791fa4 100755 --- a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php +++ b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php @@ -54,7 +54,7 @@ public function postProcess() [ 'configure' => 'ps_checkout', ] - ) . '#/authentication' + ) ); } catch (Exception $e) { $this->errors[] = $e->getMessage(); From 69295c2a4cb2553bd61aeba057cdc67382116a1b Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 18 Oct 2023 10:23:08 +0300 Subject: [PATCH 068/343] Removed authentication redirect after onboarding --- .../admin/AdminPaypalOnboardingPrestashopCheckoutController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php index 53904dff8..464791fa4 100755 --- a/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php +++ b/controllers/admin/AdminPaypalOnboardingPrestashopCheckoutController.php @@ -54,7 +54,7 @@ public function postProcess() [ 'configure' => 'ps_checkout', ] - ) . '#/authentication' + ) ); } catch (Exception $e) { $this->errors[] = $e->getMessage(); From b1434431377140eb5d4883b935e502890e901df9 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 20 Oct 2023 15:13:02 +0200 Subject: [PATCH 069/343] Fix useless hosted-fields component if card disabled --- src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php index fbfe4f038..80319694e 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php @@ -216,7 +216,7 @@ private function shouldIncludeButtonsComponent() */ private function shouldIncludeHostedFieldsComponent() { - if ('order' !== $this->getPageName()) { + if ('order' !== $this->getPageName() || in_array('card', $this->getFundingSourcesDisabled(), true)) { return false; } From 02147bf9d161f67995a4964a64e8239f10e99a52 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 20 Oct 2023 16:42:50 +0200 Subject: [PATCH 070/343] Fix duplicate column name environment on upgrade --- upgrade/upgrade-8.3.5.0.php | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/upgrade/upgrade-8.3.5.0.php b/upgrade/upgrade-8.3.5.0.php index 6017f0d68..d987daff8 100644 --- a/upgrade/upgrade-8.3.5.0.php +++ b/upgrade/upgrade-8.3.5.0.php @@ -32,7 +32,20 @@ function upgrade_module_8_3_5_0($module) { try { $db = Db::getInstance(); - $db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` ADD COLUMN `environment` varchar(20) DEFAULT NULL;'); + $databaseFields = []; + $fields = $db->executeS('SHOW COLUMNS FROM `' . _DB_PREFIX_ . 'pscheckout_cart`'); + + if (!empty($fields)) { + foreach ($fields as $field) { + if (isset($field['Field'])) { + $databaseFields[] = $field['Field']; + } + } + } + + if (!empty($databaseFields) && !in_array('environment', $databaseFields, true)) { + $db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` ADD COLUMN `environment` varchar(20) DEFAULT NULL;'); + } } catch (Exception $exception) { PrestaShopLogger::addLog($exception->getMessage(), 4, 1, 'Module', $module->id); From 09a0df481a83c11bab209151726f3ec60e2d8774 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Tue, 24 Oct 2023 14:04:13 +0200 Subject: [PATCH 071/343] Remove duplicate PayPal logo displayed --- ps_checkout.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/ps_checkout.php b/ps_checkout.php index 50e84d6fa..0405e7429 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -421,6 +421,10 @@ public function hookDisplayExpressCheckout() $paymentOptions = []; foreach ($fundingSourceProvider->getAll() as $fundingSource) { + if ($fundingSource->name === 'paylater') { + continue; + } + if ($count === 8) { break; } @@ -471,6 +475,10 @@ public function hookDisplayProductAdditionalInfo() $paymentOptions = []; foreach ($fundingSourceProvider->getAll() as $fundingSource) { + if ($fundingSource->name === 'paylater') { + continue; + } + if ($count === 8) { break; } From db14970ab2c1321b6298b6f57bb9f4fa733221fc Mon Sep 17 00:00:00 2001 From: sgodard Date: Tue, 7 Nov 2023 23:49:56 +0100 Subject: [PATCH 072/343] fix builder with invoice issue --- src/Builder/Payload/CreateOrderPayloadBuilder.php | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Builder/Payload/CreateOrderPayloadBuilder.php b/src/Builder/Payload/CreateOrderPayloadBuilder.php index 035ceb19e..d100cfb8d 100644 --- a/src/Builder/Payload/CreateOrderPayloadBuilder.php +++ b/src/Builder/Payload/CreateOrderPayloadBuilder.php @@ -152,12 +152,12 @@ public function buildPayerNode() ], 'email_address' => !empty($this->data['customer']['email']) ? $this->data['customer']['email'] : '', 'address' => [ - 'address_line_1' => !empty($this->data['invoiceAddress']['address1']) ? $this->data['deliveryAddress']['address1'] : '', - 'address_line_2' => !empty($this->data['invoiceAddress']['address2']) ? $this->data['deliveryAddress']['address2'] : '', - 'admin_area_1' => !empty($this->data['invoiceAddressState']['name']) ? $this->data['deliveryAddressState']['name'] : '', - 'admin_area_2' => !empty($this->data['invoiceAddress']['city']) ? $this->data['deliveryAddress']['city'] : '', - 'country_code' => !empty($this->data['invoiceAddressCountry']['iso_code']) ? $this->data['deliveryAddressCountry']['iso_code'] : '', - 'postal_code' => !empty($this->data['invoiceAddress']['postcode']) ? $this->data['deliveryAddress']['postcode'] : '', + 'address_line_1' => !empty($this->data['invoiceAddress']['address1']) ? $this->data['invoiceAddress']['address1'] : '', + 'address_line_2' => !empty($this->data['invoiceAddress']['address2']) ? $this->data['invoiceAddress']['address2'] : '', + 'admin_area_1' => !empty($this->data['invoiceAddressState']['name']) ? $this->data['invoiceAddressState']['name'] : '', + 'admin_area_2' => !empty($this->data['invoiceAddress']['city']) ? $this->data['invoiceAddress']['city'] : '', + 'country_code' => !empty($this->data['invoiceAddressCountry']['iso_code']) ? $this->data['invoiceAddressCountry']['iso_code'] : '', + 'postal_code' => !empty($this->data['invoiceAddress']['postcode']) ? $this->data['invoiceAddress']['postcode'] : '', ], ]; From eb5230d36430f56cd5bef5665ec288b863315eab Mon Sep 17 00:00:00 2001 From: sgodard Date: Mon, 13 Nov 2023 12:11:20 +0100 Subject: [PATCH 073/343] Fix order total paid value with discount --- .../QueryHandler/GetOrderForPaymentCompletedQueryHandler.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php index f8f95d858..c8614feb5 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php @@ -94,8 +94,8 @@ public function handle(GetOrderForPaymentCompletedQuery $query) (int) $order->id_cart, (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), - (string) $order->getTotalProductsWithTaxes(), /* @phpstan-ignore-line */ - (string) $order->getTotalPaid(), + (string) $order->total_paid, /* @phpstan-ignore-line */ + (string) $order->total_paid_real, (int) $order->id_currency, $psCheckoutCart->getPaypalFundingSource(), $orderPaymentId From 769037cf4b4b5ebf70f710191d0e32f34323cc08 Mon Sep 17 00:00:00 2001 From: sgodard Date: Mon, 13 Nov 2023 12:29:27 +0100 Subject: [PATCH 074/343] update version --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index e55961760..3be2ea93e 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 0405e7429..b0e6ca203 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.5.0'; + const VERSION = '8.3.5.1'; const INTEGRATION_DATE = '2022-14-06'; @@ -144,7 +144,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.5.0'; + $this->version = '8.3.5.1'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From e36351ef862300fd47e28ea2021fe08469b25c0d Mon Sep 17 00:00:00 2001 From: Matthias RAIGNE <5262628+Matt75@users.noreply.github.com> Date: Mon, 13 Nov 2023 14:10:26 +0100 Subject: [PATCH 075/343] Update src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php --- .../QueryHandler/GetOrderForPaymentCompletedQueryHandler.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php index c8614feb5..5cb2def84 100644 --- a/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php +++ b/src/Order/QueryHandler/GetOrderForPaymentCompletedQueryHandler.php @@ -94,7 +94,7 @@ public function handle(GetOrderForPaymentCompletedQuery $query) (int) $order->id_cart, (int) $order->getCurrentState(), (bool) $order->hasBeenPaid(), - (string) $order->total_paid, /* @phpstan-ignore-line */ + (string) $order->total_paid, (string) $order->total_paid_real, (int) $order->id_currency, $psCheckoutCart->getPaypalFundingSource(), From be8e45a5d6f97e73557fd22682e5604c57e41ede Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 30 Nov 2023 12:50:57 +0100 Subject: [PATCH 076/343] Fix service definition yaml syntax --- config/common.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/config/common.yml b/config/common.yml index 36755e0c7..d1f5fd08b 100644 --- a/config/common.yml +++ b/config/common.yml @@ -14,11 +14,7 @@ services: ps_checkout.module.version: class: 'PrestaShop\Module\PrestashopCheckout\Version\Version' - factory: - [ - 'PrestaShop\Module\PrestashopCheckout\Version\Version', - "buildFromString", - ] + factory: ["PrestaShop\\Module\\PrestashopCheckout\\Version\\Version", "buildFromString"] public: true arguments: - '@=service("ps_checkout.module").version' From 157d4ea95eeb5133b604c49ffac46cbaa9c7a9c5 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 30 Nov 2023 16:52:34 +0100 Subject: [PATCH 077/343] PAYSHIP-2557 PS Accounts psx source name --- ps_checkout.php | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/ps_checkout.php b/ps_checkout.php index b0e6ca203..0a751e403 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -526,18 +526,34 @@ public function hookDisplayFooterProduct() public function getContent() { - /** @var \PrestaShop\PsAccountsInstaller\Installer\Facade\PsAccounts $psAccountsFacade */ - $psAccountsFacade = $this->getService('ps_accounts.facade'); - $env = new \PrestaShop\Module\PrestashopCheckout\Environment\Env(); + try { + /** @var \PrestaShop\PsAccountsInstaller\Installer\Facade\PsAccounts $psAccountsFacade */ + $psAccountsFacade = $this->getService('ps_accounts.facade'); + /** @var \PrestaShop\PsAccountsInstaller\Installer\Presenter\InstallerPresenter $psAccountsPresenter */ + $psAccountsPresenter = $psAccountsFacade->getPsAccountsPresenter(); + // @phpstan-ignore-next-line + $contextPsAccounts = $psAccountsPresenter->present($this->name); + } catch (Exception $exception) { + $contextPsAccounts = []; + $this->getLogger()->error( + 'Failed to get PsAccounts context', + [ + 'exception' => get_class($exception), + 'exceptionCode' => $exception->getCode(), + 'exceptionMessage' => $exception->getMessage(), + ] + ); + } /** @var \PrestaShop\Module\PrestashopCheckout\Presenter\Store\StorePresenter $storePresenter */ $storePresenter = $this->getService('ps_checkout.store.store'); Media::addJsDef([ 'store' => $storePresenter->present(), - 'contextPsAccounts' => $psAccountsFacade->getPsAccountsPresenter()->present(), + 'contextPsAccounts' => $contextPsAccounts, ]); + $env = new \PrestaShop\Module\PrestashopCheckout\Environment\Env(); $boSdkUrl = $env->getEnv('CHECKOUT_BO_SDK_URL'); if (substr($boSdkUrl, -3) !== '.js') { $boSdkVersion = $env->getEnv('CHECKOUT_BO_SDK_VERSION'); From 13e9fae9f0d22ce34e92d9e59c76dedd919f529e Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:18:40 +0100 Subject: [PATCH 078/343] Bump module version to 8.3.5.2 --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index 3be2ea93e..ec66308ea 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index b0e6ca203..fdc54b4c4 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.5.1'; + const VERSION = '8.3.5.2'; const INTEGRATION_DATE = '2022-14-06'; @@ -144,7 +144,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.5.1'; + $this->version = '8.3.5.2'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From 160f2647f660d097e7ba5d612dee5e58b8c5ec14 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:20:37 +0100 Subject: [PATCH 079/343] Expose Ps Account isAccountLinked and ShopUuid --- .../AdminAjaxPrestashopCheckoutController.php | 27 +++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index 8046c0e63..10a283ec7 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -753,19 +753,24 @@ public function ajaxProcessGetOrRefreshToken() $psAccountRepository = $this->module->getService('ps_checkout.repository.prestashop.account'); try { - $token = $psAccountRepository->getIdToken(); - - $this->ajaxDie(json_encode([ + $this->exitWithResponse([ + 'httpCode' => 200, 'status' => true, - 'token' => $token, - ], JSON_PRETTY_PRINT)); - } catch (\Exception $exception) { - http_response_code($exception->getCode()); - - $this->ajaxDie(json_encode([ + 'token' => $psAccountRepository->getIdToken(), + 'shopId' => $psAccountRepository->getShopUuid(), + 'isAccountLinked' => $psAccountRepository->isAccountLinked(), + ]); + } catch (Exception $exception) { + $this->exitWithResponse([ + 'httpCode' => 500, 'status' => false, - 'error' => $exception->getMessage(), - ], JSON_PRETTY_PRINT)); + 'error' => sprintf( + '%s %d : %s', + get_class($exception), + $exception->getCode(), + $exception->getMessage() + ), + ]); } } From 34fa0176639e6f88f4dc666781ac16c6f11db408 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 18 Dec 2023 18:33:59 +0100 Subject: [PATCH 080/343] PAYSHIP-2609 no capture for canceled order --- src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index f27f134e7..5cf29b9a9 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -214,7 +214,7 @@ public function capturePayPalOrder(PayPalOrderApprovedEvent $event) return; } - if ($psCheckoutCart->getPaypalStatus() === PayPalOrderStatus::COMPLETED) { + if (in_array($psCheckoutCart->getPaypalStatus(), [PayPalOrderStatus::COMPLETED, PayPalOrderStatus::CANCELED], true)) { return; } From 845e5541abbdabae4f110266410b11bdcf2c5426 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Fri, 22 Dec 2023 17:48:16 +0200 Subject: [PATCH 081/343] Initial commit --- .../common/card-fields.component.js | 211 ++++++++++++++++++ .../common/payment-option.component.js | 30 ++- _dev/js/front/src/service/paypal.service.js | 191 +++++++++++++++- .../extra/types/paypal-sdk.typedef.doc.js | 1 + .../PayPalSdkLink/PayPalSdkLinkBuilder.php | 2 + views/css/payments.css | 2 +- views/css/payments16.css | 2 +- views/templates/hook/displayPayment.tpl | 7 +- views/templates/hook/paymentOptions.tpl | 7 +- 9 files changed, 442 insertions(+), 11 deletions(-) create mode 100644 _dev/js/front/src/components/common/card-fields.component.js diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js new file mode 100644 index 000000000..154094982 --- /dev/null +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -0,0 +1,211 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ +import { BaseComponent } from '../../core/dependency-injection/base.component'; + +export class CardFieldsComponent extends BaseComponent { + static Inject = { + config: 'PsCheckoutConfig', + payPalService: 'PayPalService', + psCheckoutApi: 'PsCheckoutApi', + psCheckoutService: 'PsCheckoutService' + }; + + created() { + this.data.name = this.props.fundingSource.name; + this.data.validity = false; + + this.data.HTMLElement = this.props.HTMLElement; + this.data.HTMLElementBaseButton = this.getBaseButton(); + this.data.HTMLElementButton = null; + this.data.HTMLElementButtonWrapper = this.getButtonWrapper(); + this.data.HTMLElementCardHolderName = this.getCardHolderName(); + this.data.HTMLElementCardNumber = this.getCardNumber(); + this.data.HTMLElementCardCVV = this.getCardCVV(); + this.data.HTMLElementCardExpirationDate = this.getCardExpirationDate(); + this.data.HTMLElementSection = this.getSection(); + } + + getBaseButton() { + const buttonSelector = `#payment-confirmation button`; + return document.querySelector(buttonSelector); + } + + getButtonWrapper() { + const buttonWrapper = `.ps_checkout-button[data-funding-source=${this.data.name}]`; + return document.querySelector(buttonWrapper); + } + + getCardHolderName() { + const cardHolderNameId = '#ps_checkout-hosted-fields-card-holder-name'; + return document.getElementById(cardHolderNameId); + } + getCardNumber() { + const cardNumberId = '#ps_checkout-hosted-fields-card-number'; + return document.getElementById(cardNumberId); + } + + getCardCVV() { + const cardCVVId = '#ps_checkout-hosted-fields-card-cvv'; + return document.getElementById(cardCVVId); + } + + getCardExpirationDate() { + const cardExpirationDateId = + '#ps_checkout-hosted-fields-card-expiration-date'; + return document.getElementById(cardExpirationDateId); + } + + getSection() { + const sectionSelector = `.js-payment-ps_checkout-${this.data.name}`; + return document.querySelector(sectionSelector); + } + + getContingencies() { + switch (this.config.cardFieldsContingencies) { + case '3D_SECURE': + case 'SCA_ALWAYS': + return ['SCA_ALWAYS']; + case 'NONE': + return undefined; + default: + return ['SCA_WHEN_REQUIRED']; + } + } + + isSubmittable() { + return this.data.conditions + ? this.data.conditions.isChecked() && this.data.validity + : this.data.validity; + } + + renderPayPalCardFields() { + this.payPalService + .getCardFields( + { + name: '#ps_checkout-hosted-fields-card-holder-name', + number: '#ps_checkout-hosted-fields-card-number', + cvv: '#ps_checkout-hosted-fields-card-cvv', + expirationDate: '#ps_checkout-hosted-fields-card-expiration-date' + }, + { + createOrder: async (data) => + this.psCheckoutApi + .postCreateOrder({ + ...data, + fundingSource: this.data.name, + isCardFields: true, + // vault: storeCardInVault + }) + .catch(error => { + this.data.notification.showError( + `${error.message} ${error.name}` + ); + }) + } + ) + .then(cardFields => { + if (this.data.HTMLElement !== null) { + cardFields.on('validityChange', event => { + this.data.validity = + Object.keys(event.fields) + .map(name => event.fields[name]) + .map(({ isValid }) => { + return isValid; + }) + .filter(validity => validity === false).length === 0; + + this.data.HTMLElementSection.classList.toggle( + 'disabled', + !this.isSubmittable() + ); + + this.isSubmittable() + ? this.data.HTMLElementButton.removeAttribute('disabled') + : this.data.HTMLElementButton.setAttribute('disabled', ''); + }); + + this.data.HTMLElementButton.addEventListener('click', event => { + event.preventDefault(); + this.data.loader.show(); + // this.data.HTMLElementButton.classList.toggle('disabled', true); + this.data.HTMLElementButton.setAttribute('disabled', ''); + + cardFields + .submit({ + contingencies: this.getContingencies() + }) + .then(payload => { + const data = payload; + + // Backend requirement + data.orderID = data.orderId; + delete data.orderId; + + return this.psCheckoutApi.postValidateOrder({ + ...data, + fundingSource: this.data.name, + isHostedFields: true + }); + }) + .catch(error => { + let message = error.message || ''; + + if (!message) { + message = `Unknown error, code: ${error.code || 'none'}, description: ${error.description || 'none'}`; + } + + this.data.loader.hide(); + this.data.notification.showError(message); + this.data.HTMLElementButton.removeAttribute('disabled'); + }); + }); + } + }); + } + + renderButton() { + this.data.HTMLElementButton = this.data.HTMLElementBaseButton.cloneNode( + true + ); + + this.data.HTMLElementButtonWrapper.append(this.data.HTMLElementButton); + this.data.HTMLElementButton.classList.remove('disabled'); + this.data.HTMLElementButton.style.display = ''; + this.data.HTMLElementButton.disabled = !this.isSubmittable(); + + this.data.conditions && + this.data.conditions.onChange(() => { + // In some PS versions, the handler fails to disable the button because of the timing. + setTimeout(() => { + this.data.HTMLElementButton.disabled = !this.isSubmittable(); + }, 0); + }); + } + + render() { + this.data.conditions = this.app.root.children.conditionsCheckbox; + this.data.notification = this.app.root.children.notification; + this.data.loader = this.app.root.children.loader; + + this.renderButton(); + this.renderPayPalCardFields(); + + return this; + } +} diff --git a/_dev/js/front/src/components/common/payment-option.component.js b/_dev/js/front/src/components/common/payment-option.component.js index c207c5c51..78db6351b 100644 --- a/_dev/js/front/src/components/common/payment-option.component.js +++ b/_dev/js/front/src/components/common/payment-option.component.js @@ -22,6 +22,7 @@ import { HostedFieldsComponent } from './hosted-fields.component'; import { MarkComponent } from './marker.component'; import { SmartButtonComponent } from './smart-button.component'; import { PaymentFieldsComponent } from "./payment-fields.component"; +import {CardFieldsComponent} from "./card-fields.component"; /** * @typedef PaymentOptionComponentProps @@ -48,6 +49,7 @@ export class PaymentOptionComponent extends BaseComponent { this.data.HTMLElementMark = this.props.HTMLElementMark || null; this.data.HTMLElementHostedFields = this.getHostedFields(); + this.data.HTMLElementCardFields = this.getCardFields(); this.data.HTMLElementSmartButton = this.getSmartButton(); this.data.HTMLElementPaymentFields = this.getPaymentFields(); } @@ -67,6 +69,16 @@ export class PaymentOptionComponent extends BaseComponent { ); } + getCardFields() { + const cardFieldsFormId = 'ps_checkout-hosted-fields-form'; + + return ( + this.data.name === 'card' + && this.config.hostedFieldsEnabled + && document.getElementById(cardFieldsFormId) + ); + } + getPaymentFields() { const container = `pay-with-${this.data.HTMLElement.id}-form`; const APM = ['bancontact', 'blik', 'eps', 'giropay', 'ideal', 'mybank', 'p24', 'sofort']; @@ -165,7 +177,17 @@ export class PaymentOptionComponent extends BaseComponent { this.data.HTMLElementHostedFields.style.display = 'none'; } - if (this.data.HTMLElementHostedFields && isHostedFieldsEligible) { + let isCardFieldsEligible = this.payPalService.isCardFieldsEligible(); + if (this.data.HTMLElementCardFields && !isCardFieldsEligible) { + this.data.HTMLElementCardFields.style.display = 'none'; + } + + if (this.data.HTMLElementCardFields && isCardFieldsEligible) { + this.children.cardFields = new CardFieldsComponent(this.app, { + fundingSource: this.props.fundingSource, + HTMLElement: this.data.HTMLElementCardFields + }).render(); + } else if (this.data.HTMLElementHostedFields && isHostedFieldsEligible) { this.children.hostedFields = new HostedFieldsComponent(this.app, { fundingSource: this.props.fundingSource, @@ -185,9 +207,11 @@ export class PaymentOptionComponent extends BaseComponent { fundingSource: this.data.name, HTMLElement: this.data.HTMLElement, HTMLElementContainer: this.data.HTMLElementContainer, - HTMLElementBinary: this.data.HTMLElementHostedFields && isHostedFieldsEligible + HTMLElementBinary: this.data.HTMLElementCardFields && isCardFieldsEligible ? + this.children.cardFields.data.HTMLElementButton.parentElement : + (this.data.HTMLElementHostedFields && isHostedFieldsEligible ? this.children.hostedFields.data.HTMLElementButton.parentElement - : this.data.HTMLElementSmartButton + : this.data.HTMLElementSmartButton) } }) ); diff --git a/_dev/js/front/src/service/paypal.service.js b/_dev/js/front/src/service/paypal.service.js index 8ec33bc13..4786c8d51 100644 --- a/_dev/js/front/src/service/paypal.service.js +++ b/_dev/js/front/src/service/paypal.service.js @@ -244,7 +244,7 @@ export class PayPalService extends BaseClass { // Change card bg depending on card type if (event.cards.length === 1) { - document.querySelector('.defautl-credit-card').style.display = + document.querySelector('.default-credit-card').style.display = 'none'; const cardImage = document.getElementById('card-image'); @@ -262,7 +262,7 @@ export class PayPalService extends BaseClass { }); } } else { - document.querySelector('.defautl-credit-card').style.display = + document.querySelector('.default-credit-card').style.display = 'block'; const cardImage = document.getElementById('card-image'); cardImage.className = ''; @@ -279,6 +279,184 @@ export class PayPalService extends BaseClass { }); } + /** + * @param {*} fieldSelectors + * @param {string} fieldSelectors.number + * @param {string} fieldSelectors.cvv + * @param {string} fieldSelectors.expirationDate + * @param {PaypalHostedFieldsEvents} events + * @returns {*} + */ + getCardFields(fieldSelectors, events) { + const style = { + ...{ + input: { + 'font-size': '17px', + 'font-family': 'helvetica, tahoma, calibri, sans-serif', + color: '#3a3a3a' + }, + ':focus': { + color: 'black' + } + }, + ...(this.configPayPal.hostedFieldsCustomization || {}), + ...(window.ps_checkout.hostedFieldsCustomization || {}) + }; + + return this.sdk.CardFields(events).render({ + styles: style, + fields: { + name: { + selector: fieldSelectors.name, + placeholder: this.$('paypal.hosted-fields.placeholder.card-number') + }, + number: { + selector: fieldSelectors.number, + placeholder: this.$('paypal.hosted-fields.placeholder.card-number') + }, + cvv: { + selector: fieldSelectors.cvv, + placeholder: this.$('paypal.hosted-fields.placeholder.cvv') + }, + expirationDate: { + selector: fieldSelectors.expirationDate, + placeholder: this.$( + 'paypal.hosted-fields.placeholder.expiration-date' + ) + } + }, + ...events + }) + .then(cardFields => { + const nameField = document.querySelector(fieldSelectors.name); + const numberField = document.querySelector(fieldSelectors.number); + const cvvField = document.querySelector(fieldSelectors.cvv); + const expirationDateField = document.querySelector( + fieldSelectors.expirationDate + ); + + const cardNameField = cardFields.NameField(); + const cardNumberField = cardFields.NumberField(); + const cardExpiryField = cardFields.ExpiryField(); + const cardCvvField = cardFields.CVVField(); + + try { + cardNameField.render(nameField); + cardNumberField.render(numberField); + cardCvvField.render(cvvField); + cardExpiryField.render(expirationDateField); + } catch (e) { + return console.error("Failed to render CardFields", e); + } + + const cardHolderNameLabel = document.querySelector( + `label[for="${c.id}"]` + ); + const numberLabel = document.querySelector( + `label[for="${numberField.id}"]` + ); + const cvvLabel = document.querySelector(`label[for="${cvvField.id}"]`); + const expirationDateLabel = document.querySelector( + `label[for="${expirationDateField.id}"]` + ); + + numberLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.card-number' + ); + cvvLabel.innerHTML = this.$('paypal.hosted-fields.label.cvv'); + expirationDateLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.expiration-date' + ); + + return cardFields; + }) + .then(cardFields => { + cardFields.on('focus', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsFocus', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + }); + cardFields.on('blur', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsBlur', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + }); + cardFields.on('empty', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsEmpty', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + }); + cardFields.on('notEmpty', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsNotEmpty', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + }); + cardFields.on('validityChange', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsValidityChange', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + }); + cardFields.on('inputSubmitRequest', () => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsInputSubmitRequest', { + detail: { ps_checkout: window.ps_checkout } + }) + ); + }); + cardFields.on('cardTypeChange', event => { + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsCardTypeChange', { + detail: { ps_checkout: window.ps_checkout, event: event } + }) + ); + + // Change card bg depending on card type + if (event.cards.length === 1) { + document.querySelector('.default-credit-card').style.display = + 'none'; + + const cardImage = document.getElementById('card-image'); + cardImage.className = ''; + cardImage.classList.add(event.cards[0].type); + + document.querySelector('header').classList.add('header-slide'); + + // Change the CVV length for AmericanExpress cards + if (event.cards[0].code.size === 4) { + cardFields.setAttribute({ + field: 'cvv', + attribute: 'placeholder', + value: 'XXXX' + }); + } + } else { + document.querySelector('.default-credit-card').style.display = + 'block'; + const cardImage = document.getElementById('card-image'); + cardImage.className = ''; + + cardFields.setAttribute({ + field: 'cvv', + attribute: 'placeholder', + value: 'XXX' + }); + } + }); + + return cardFields; + }); + } + getEligibleFundingSources(cache = false) { if (!this.eligibleFundingSources || cache) { const paypalFundingSources = this.sdk.getFundingSources(); @@ -293,8 +471,9 @@ export class PayPalService extends BaseClass { mark: this.sdk.Marks({ fundingSource }) })) .filter((fundingSource) => { - if (fundingSource.name === 'card' && this.configPrestaShop.hostedFieldsEnabled && !this.isHostedFieldsEligible()) { - console.error('Hosted Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)'); + if (fundingSource.name === 'card' && this.configPrestaShop.hostedFieldsEnabled && !this.isCardFieldsEligible()) { + console.log(this.configPrestaShop.hostedFieldsEnabled, this.isCardFieldsEligible()); + console.error('Card Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)'); } console.log(fundingSource.name, fundingSource.mark.isEligible()); @@ -313,6 +492,10 @@ export class PayPalService extends BaseClass { return this.sdk.HostedFields && this.sdk.HostedFields.isEligible(); } + isCardFieldsEligible() { + return this.sdk.CardFields && this.sdk.CardFields().isEligible(); + } + /** * @param {string} placement * @param {string} amount diff --git a/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js b/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js index a46949c77..a521325bc 100644 --- a/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js +++ b/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js @@ -38,6 +38,7 @@ * @property {function} Marks.isEligible * @property {function} Marks.render * @property {object} HostedFields + * @property {object} CardFields * @property {function} HostedFields.isEligible * @property {function} HostedFields.render * @property {object} Messages diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php index 80319694e..33d8b8e0f 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php @@ -87,6 +87,7 @@ public function buildLink() if ($this->shouldIncludeHostedFieldsComponent()) { $components[] = 'hosted-fields'; + $components[] = 'card-fields'; } if ($this->shouldIncludeMessagesComponent()) { @@ -106,6 +107,7 @@ public function buildLink() if ('SANDBOX' === $this->configuration->getPaymentMode()) { $params['debug'] = 'true'; // $params['buyer-country'] = $this->getCountry(); + $params['buyer-country'] = 'US'; } $fundingSourcesDisabled = $this->getFundingSourcesDisabled(); diff --git a/views/css/payments.css b/views/css/payments.css index 45f1a259f..4c9f8758e 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -181,7 +181,7 @@ backface-visibility: hidden; } -.defautl-credit-card { +.default-credit-card { position: absolute; top: 2px; right: 10px; diff --git a/views/css/payments16.css b/views/css/payments16.css index 3ebf26025..152579c2d 100644 --- a/views/css/payments16.css +++ b/views/css/payments16.css @@ -198,7 +198,7 @@ backface-visibility: hidden; } -#ps_checkout-hosted-fields-form .defautl-credit-card { +#ps_checkout-hosted-fields-form .default-credit-card { position: absolute; top: 2px; right: 10px; diff --git a/views/templates/hook/displayPayment.tpl b/views/templates/hook/displayPayment.tpl index f609fb529..14a952c96 100644 --- a/views/templates/hook/displayPayment.tpl +++ b/views/templates/hook/displayPayment.tpl @@ -50,11 +50,16 @@ {if $fundingSource == 'card' && $isHostedFieldsAvailable}
+
+ +
+
+
- +
diff --git a/views/templates/hook/paymentOptions.tpl b/views/templates/hook/paymentOptions.tpl index 80c346b1f..f329f1583 100644 --- a/views/templates/hook/paymentOptions.tpl +++ b/views/templates/hook/paymentOptions.tpl @@ -28,11 +28,16 @@ *} +
+ +
+
+
- +
From 848405f15500b0c522536bf8b244e08585ab1d07 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 10 Jan 2024 16:26:57 +0200 Subject: [PATCH 082/343] Working cardfields component --- _dev/js/front/src/api/ps-checkout.api.js | 2 +- .../common/card-fields.component.js | 299 ++++++++++++++---- _dev/js/front/src/service/paypal.service.js | 240 +++++--------- .../extra/types/paypal-sdk.typedef.doc.js | 20 ++ ps_checkout.php | 2 + views/css/payments.css | 30 +- views/templates/hook/displayPayment.tpl | 4 +- views/templates/hook/paymentOptions.tpl | 22 +- 8 files changed, 386 insertions(+), 233 deletions(-) diff --git a/_dev/js/front/src/api/ps-checkout.api.js b/_dev/js/front/src/api/ps-checkout.api.js index 43ab71fe4..3e641867b 100644 --- a/_dev/js/front/src/api/ps-checkout.api.js +++ b/_dev/js/front/src/api/ps-checkout.api.js @@ -216,7 +216,7 @@ export class PsCheckoutApi extends BaseClass { paypal_transaction ); - window.location.href = confirmationUrl.toString(); + // window.location.href = confirmationUrl.toString(); } if (response.error && 'INSTRUMENT_DECLINED' === response.error) { diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 154094982..6f8d0e693 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -16,21 +16,60 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ + +/** + * @typedef PaypalCardFieldCardField + * @type {*} + * + * @property {boolean} isEmpty + * @property {boolean} isValid + * @property {boolean} isPotentiallyValid + * @property {boolean} isFocused + */ + +/** + * @typedef PaypalCardFieldsEvent + * @type {*} + * + * @property {string} emittedBy + * @property {boolean} isFormValid + * @property {String[]} errors + * @property {*} fields + * @property {PaypalCardFieldCardField} fields.cardCvvField + * @property {PaypalCardFieldCardField} fields.cardExpiryField + * @property {PaypalCardFieldCardField} fields.cardNameField + * @property {PaypalCardFieldCardField} fields.cardNumberField + */ + import { BaseComponent } from '../../core/dependency-injection/base.component'; export class CardFieldsComponent extends BaseComponent { static Inject = { config: 'PsCheckoutConfig', + configPayPal: 'PayPalSdkConfig', payPalService: 'PayPalService', psCheckoutApi: 'PsCheckoutApi', - psCheckoutService: 'PsCheckoutService' + psCheckoutService: 'PsCheckoutService', + sdk: 'PayPalSDK', }; created() { this.data.name = this.props.fundingSource.name; this.data.validity = false; + /** + * @property {PaypalCardFieldsEvent} data.cardFieldsState + */ + this.data.cardFieldsState = {}; + + this.data.cardFieldsFocused = { + name: false, + number: false, + expiry: false, + cvv: false, + } this.data.HTMLElement = this.props.HTMLElement; + this.data.HTMLElementCardForm = this.getCardForm(); this.data.HTMLElementBaseButton = this.getBaseButton(); this.data.HTMLElementButton = null; this.data.HTMLElementButtonWrapper = this.getButtonWrapper(); @@ -39,45 +78,67 @@ export class CardFieldsComponent extends BaseComponent { this.data.HTMLElementCardCVV = this.getCardCVV(); this.data.HTMLElementCardExpirationDate = this.getCardExpirationDate(); this.data.HTMLElementSection = this.getSection(); + + this.data.clearCardFields = () => {console.log('CLEAR CARD FIELDS')}; } + + getCardForm() { + const cardFromSelector = `#ps_checkout-hosted-fields-form`; + return document.querySelector(cardFromSelector); + } getBaseButton() { const buttonSelector = `#payment-confirmation button`; return document.querySelector(buttonSelector); } - getButtonWrapper() { const buttonWrapper = `.ps_checkout-button[data-funding-source=${this.data.name}]`; return document.querySelector(buttonWrapper); } getCardHolderName() { - const cardHolderNameId = '#ps_checkout-hosted-fields-card-holder-name'; + const cardHolderNameId = '#ps_checkout-hosted-fields-card-name'; return document.getElementById(cardHolderNameId); } getCardNumber() { const cardNumberId = '#ps_checkout-hosted-fields-card-number'; return document.getElementById(cardNumberId); } - getCardCVV() { const cardCVVId = '#ps_checkout-hosted-fields-card-cvv'; return document.getElementById(cardCVVId); } - getCardExpirationDate() { const cardExpirationDateId = '#ps_checkout-hosted-fields-card-expiration-date'; return document.getElementById(cardExpirationDateId); } - getSection() { const sectionSelector = `.js-payment-ps_checkout-${this.data.name}`; return document.querySelector(sectionSelector); } + getCardNameFieldError() { + const cardNameErrorSelector = `#ps_checkout-hosted-fields-error-name`; + return document.querySelector(cardNameErrorSelector); + } + + getCardNumberFieldError() { + const cardNameErrorSelector = `#ps_checkout-hosted-fields-error-number`; + return document.querySelector(cardNameErrorSelector); + } + + getCardExpiryFieldError() { + const cardNameErrorSelector = `#ps_checkout-hosted-fields-error-expiry`; + return document.querySelector(cardNameErrorSelector); + } + + getCardCvvFieldError() { + const cardNameErrorSelector = `#ps_checkout-hosted-fields-error-cvv`; + return document.querySelector(cardNameErrorSelector); + } getContingencies() { - switch (this.config.cardFieldsContingencies) { + switch (this.config.hostedFieldsContingencies) { case '3D_SECURE': case 'SCA_ALWAYS': return ['SCA_ALWAYS']; @@ -94,91 +155,213 @@ export class CardFieldsComponent extends BaseComponent { : this.data.validity; } + setFieldFocus(fieldName) { + this.data.cardFieldsFocused[fieldName] = true; + } + + toggleCardNameFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardNameField; + const hideError = isFocused || !this.data.cardFieldsFocused.name || isValid || isPotentiallyValid; + + this.getCardNameFieldError().classList.toggle('hidden', hideError) + } + + toggleCardNumberFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardNumberField; + const hideError = isPotentiallyValid && (isFocused || !this.data.cardFieldsFocused.number || isValid); + + this.getCardNumberFieldError().classList.toggle('hidden', hideError) + } + + toggleCardExpiryFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardExpiryField; + const hideError = isPotentiallyValid && (isFocused || !this.data.cardFieldsFocused.expiry || isValid); + + this.getCardExpiryFieldError().classList.toggle('hidden', hideError) + } + toggleCardCvvFieldError() { + const { isFocused, isEmpty, isValid, isPotentiallyValid } = + this.data.cardFieldsState.fields.cardCvvField; + const hideError = isPotentiallyValid && (isFocused || !this.data.cardFieldsFocused.cvv || isValid); + + this.getCardCvvFieldError().classList.toggle('hidden', hideError) + } + + toggleCardFieldErrors() { + this.toggleCardNameFieldError(); + this.toggleCardNumberFieldError(); + this.toggleCardExpiryFieldError(); + this.toggleCardCvvFieldError(); + } + + /** + * @param {PaypalCardFieldsEvent} event + */ + updateCardFieldsState(event) { + this.setFieldFocus(event.emittedBy); + this.data.validity = event.isFormValid; + this.data.cardFieldsState = event; + + this.isSubmittable() + ? this.data.HTMLElementButton.removeAttribute('disabled') + : this.data.HTMLElementButton.setAttribute('disabled', ''); + + this.toggleCardFieldErrors(); + } + + clearCardFields() { + console.log('CARD-FIELDS', this.data.cardFields); + if (this.data.cardFields) { + this.data.cardFields.NameField.clear(); + this.data.card.NumberField.clear() + this.data.card.CVVField.clear() + this.data.card.ExpiryField.clear() + } + } + renderPayPalCardFields() { + this.data.HTMLElementCardForm.classList.toggle('loading', true); + + const style = { + ...{ + input: { + 'font-size': '17px', + 'font-family': 'helvetica, tahoma, calibri, sans-serif', + color: '#3a3a3a' + }, + ':focus': { + color: 'black' + } + }, + ...(this.configPayPal.hostedFieldsCustomization || {}), + ...(window.ps_checkout.hostedFieldsCustomization || {}) + }; + this.payPalService .getCardFields( { - name: '#ps_checkout-hosted-fields-card-holder-name', + name: '#ps_checkout-hosted-fields-card-name', number: '#ps_checkout-hosted-fields-card-number', cvv: '#ps_checkout-hosted-fields-card-cvv', expirationDate: '#ps_checkout-hosted-fields-card-expiration-date' }, { - createOrder: async (data) => - this.psCheckoutApi + style, + createOrder: async (data) => { + console.log('createOrder', data); + this.data.HTMLElementButton.setAttribute('disabled', true); + + return this.psCheckoutApi .postCreateOrder({ ...data, fundingSource: this.data.name, isCardFields: true, // vault: storeCardInVault }) + .then(data => { + return data; + }) .catch(error => { this.data.notification.showError( `${error.message} ${error.name}` ); }) - } - ) - .then(cardFields => { - if (this.data.HTMLElement !== null) { - cardFields.on('validityChange', event => { - this.data.validity = - Object.keys(event.fields) - .map(name => event.fields[name]) - .map(({ isValid }) => { - return isValid; - }) - .filter(validity => validity === false).length === 0; + }, + onApprove: async (data) => { + console.log('onApprove', data); + return this.psCheckoutApi.postValidateOrder({ + ...data, + fundingSource: this.data.name, + isHostedFields: true + }) + .then(data => { + this.clearCardFields(); + return data; + }) + .catch(error => { + let message = error.message || ''; - this.data.HTMLElementSection.classList.toggle( - 'disabled', - !this.isSubmittable() - ); + if (!message) { + message = `Unknown error, code: ${error.code || 'none'}, description: ${error.description || 'none'}`; + } - this.isSubmittable() - ? this.data.HTMLElementButton.removeAttribute('disabled') - : this.data.HTMLElementButton.setAttribute('disabled', ''); - }); + this.data.loader.hide(); + this.data.notification.showError(message); + this.data.HTMLElementButton.removeAttribute('disabled'); + }) + }, + onError: async (error) => { + console.log('onError', error); + let message = error.message || ''; + this.data.notification.showError(message); + this.data.HTMLElementButton.removeAttribute('disabled'); + }, + inputEvents: { + /** + * @param {PaypalCardFieldsEvent} event + */ + onChange: (event) => { + this.updateCardFieldsState(event); + this.data.cardFields = event; + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onFocus: (event) => { + this.updateCardFieldsState(event); + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsFocus', { + detail: { ps_checkout: window.ps_checkout, event } + }) + ); + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onBlur: (event) => { + this.updateCardFieldsState(event); + this.data.clearCardFields(); + window.ps_checkout.events.dispatchEvent( + new CustomEvent('hostedFieldsBlur', { + detail: { ps_checkout: window.ps_checkout, event } + }) + ); + }, + /** + * @param {PaypalCardFieldsEvent} event + */ + onInputSubmitRequest: (event) => { + this.updateCardFieldsState(event); + console.log('SUBMIT', event, this.sdk.CardFields()); + }, + } + }, + ) + .then(cardFields => { + this.data.clearCardFields = cardFields.clear; + console.log(cardFields, this.data.cardFields); + this.data.HTMLElementCardForm.classList.toggle('loading', false); + if (this.data.HTMLElement !== null) { this.data.HTMLElementButton.addEventListener('click', event => { event.preventDefault(); this.data.loader.show(); // this.data.HTMLElementButton.classList.toggle('disabled', true); this.data.HTMLElementButton.setAttribute('disabled', ''); - cardFields - .submit({ - contingencies: this.getContingencies() - }) - .then(payload => { - const data = payload; - - // Backend requirement - data.orderID = data.orderId; - delete data.orderId; - - return this.psCheckoutApi.postValidateOrder({ - ...data, - fundingSource: this.data.name, - isHostedFields: true - }); - }) - .catch(error => { - let message = error.message || ''; - - if (!message) { - message = `Unknown error, code: ${error.code || 'none'}, description: ${error.description || 'none'}`; - } - - this.data.loader.hide(); - this.data.notification.showError(message); - this.data.HTMLElementButton.removeAttribute('disabled'); - }); + cardFields.submit({contingencies: this.getContingencies()}).then((data) => { + console.log(this.data.cardFields); + }); }); } }); } + + renderButton() { this.data.HTMLElementButton = this.data.HTMLElementBaseButton.cloneNode( true diff --git a/_dev/js/front/src/service/paypal.service.js b/_dev/js/front/src/service/paypal.service.js index 4786c8d51..b0a2b6571 100644 --- a/_dev/js/front/src/service/paypal.service.js +++ b/_dev/js/front/src/service/paypal.service.js @@ -45,6 +45,17 @@ * @property {string} text.align */ +/** + * @typedef PayPayCardFieldsOptions + * @type {*} + * + * @property {function} createOrder + * @property {function} onApprove + * @property {function} onError + * @property {function} inputEvents + * @property {object} style + */ + /** * @typedef PaypalPayLaterOfferEvents * @type {*} @@ -281,180 +292,85 @@ export class PayPalService extends BaseClass { /** * @param {*} fieldSelectors + * @param {string} fieldSelectors.name * @param {string} fieldSelectors.number * @param {string} fieldSelectors.cvv * @param {string} fieldSelectors.expirationDate - * @param {PaypalHostedFieldsEvents} events - * @returns {*} + * + * @param {PayPayCardFieldsOptions} options + * + * @returns {PayPalSdk.CardFields} */ - getCardFields(fieldSelectors, events) { - const style = { - ...{ - input: { - 'font-size': '17px', - 'font-family': 'helvetica, tahoma, calibri, sans-serif', - color: '#3a3a3a' - }, - ':focus': { - color: 'black' - } - }, - ...(this.configPayPal.hostedFieldsCustomization || {}), - ...(window.ps_checkout.hostedFieldsCustomization || {}) - }; - - return this.sdk.CardFields(events).render({ - styles: style, - fields: { - name: { - selector: fieldSelectors.name, - placeholder: this.$('paypal.hosted-fields.placeholder.card-number') - }, - number: { - selector: fieldSelectors.number, - placeholder: this.$('paypal.hosted-fields.placeholder.card-number') - }, - cvv: { - selector: fieldSelectors.cvv, - placeholder: this.$('paypal.hosted-fields.placeholder.cvv') - }, - expirationDate: { - selector: fieldSelectors.expirationDate, - placeholder: this.$( - 'paypal.hosted-fields.placeholder.expiration-date' - ) - } - }, - ...events - }) - .then(cardFields => { - const nameField = document.querySelector(fieldSelectors.name); - const numberField = document.querySelector(fieldSelectors.number); - const cvvField = document.querySelector(fieldSelectors.cvv); - const expirationDateField = document.querySelector( - fieldSelectors.expirationDate - ); - - const cardNameField = cardFields.NameField(); - const cardNumberField = cardFields.NumberField(); - const cardExpiryField = cardFields.ExpiryField(); - const cardCvvField = cardFields.CVVField(); - - try { - cardNameField.render(nameField); - cardNumberField.render(numberField); - cardCvvField.render(cvvField); - cardExpiryField.render(expirationDateField); - } catch (e) { - return console.error("Failed to render CardFields", e); - } + async getCardFields(fieldSelectors, options) { + const cardFields = this.sdk.CardFields(options); - const cardHolderNameLabel = document.querySelector( - `label[for="${c.id}"]` - ); - const numberLabel = document.querySelector( - `label[for="${numberField.id}"]` - ); - const cvvLabel = document.querySelector(`label[for="${cvvField.id}"]`); - const expirationDateLabel = document.querySelector( - `label[for="${expirationDateField.id}"]` - ); + const nameFieldSelector = document.querySelector(fieldSelectors.name); + const numberFieldSelector = document.querySelector(fieldSelectors.number); + const expiryFieldSelector = document.querySelector( + fieldSelectors.expirationDate + ); + const cvvFieldSelector = document.querySelector(fieldSelectors.cvv); - numberLabel.innerHTML = this.$( - 'paypal.hosted-fields.label.card-number' - ); - cvvLabel.innerHTML = this.$('paypal.hosted-fields.label.cvv'); - expirationDateLabel.innerHTML = this.$( - 'paypal.hosted-fields.label.expiration-date' - ); - return cardFields; - }) - .then(cardFields => { - cardFields.on('focus', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsFocus', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - }); - cardFields.on('blur', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsBlur', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - }); - cardFields.on('empty', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsEmpty', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - }); - cardFields.on('notEmpty', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsNotEmpty', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - }); - cardFields.on('validityChange', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsValidityChange', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - }); - cardFields.on('inputSubmitRequest', () => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsInputSubmitRequest', { - detail: { ps_checkout: window.ps_checkout } - }) - ); - }); - cardFields.on('cardTypeChange', event => { - window.ps_checkout.events.dispatchEvent( - new CustomEvent('hostedFieldsCardTypeChange', { - detail: { ps_checkout: window.ps_checkout, event: event } - }) - ); - - // Change card bg depending on card type - if (event.cards.length === 1) { - document.querySelector('.default-credit-card').style.display = - 'none'; + const nameField = cardFields.NameField({ + placeholder: this.$('paypal.hosted-fields.placeholder.card-name') + }); + const numberField = cardFields.NumberField({ + placeholder: this.$('paypal.hosted-fields.placeholder.card-number') + }); + const expiryField = cardFields.ExpiryField({ + placeholder: this.$('paypal.hosted-fields.placeholder.expiration-date') + }); + const cvvField = cardFields.CVVField({ + placeholder: this.$( + 'paypal.hosted-fields.placeholder.cvv' + ) + }); - const cardImage = document.getElementById('card-image'); - cardImage.className = ''; - cardImage.classList.add(event.cards[0].type); - document.querySelector('header').classList.add('header-slide'); + // await Promise.all( + // [ + // cardNameField.render(nameField), + // cardNumberField.render(numberField), + // cardCvvField.render(cvvField), + // cardExpiryField.render(expirationDateField) + // ] + // ).catch(e => { + // return console.error("Failed to render CardFields", e); + // }) ; + + try { + await numberField.render(numberFieldSelector); + await expiryField.render(expiryFieldSelector); + await cvvField.render(cvvFieldSelector); + await nameField.render(nameFieldSelector); + } catch (e) { + return console.error("Failed to render CardFields", e); + } - // Change the CVV length for AmericanExpress cards - if (event.cards[0].code.size === 4) { - cardFields.setAttribute({ - field: 'cvv', - attribute: 'placeholder', - value: 'XXXX' - }); - } - } else { - document.querySelector('.default-credit-card').style.display = - 'block'; - const cardImage = document.getElementById('card-image'); - cardImage.className = ''; + const nameLabel = document.querySelector( + `label[for="${nameFieldSelector.id}"]` + ); + const numberLabel = document.querySelector( + `label[for="${numberFieldSelector.id}"]` + ); + const cvvLabel = document.querySelector(`label[for="${cvvFieldSelector.id}"]`); + const expirationDateLabel = document.querySelector( + `label[for="${expiryFieldSelector.id}"]` + ); - cardFields.setAttribute({ - field: 'cvv', - attribute: 'placeholder', - value: 'XXX' - }); - } - }); + nameLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.card-name' + ); + numberLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.card-number' + ); + cvvLabel.innerHTML = this.$('paypal.hosted-fields.label.cvv'); + expirationDateLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.expiration-date' + ); - return cardFields; - }); + return cardFields; } getEligibleFundingSources(cache = false) { diff --git a/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js b/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js index a521325bc..75dbdfaed 100644 --- a/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js +++ b/_dev/js/front/src/utils/extra/types/paypal-sdk.typedef.doc.js @@ -21,6 +21,19 @@ * This file exists only for documentative purposes */ +/** + * @typedef PayPalCardField + * + * @property {function} addClass + * @property {function} clear + * @property {function} focus + * @property {function} removeAttribute + * @property {function} removeClass + * @property {function} render + * @property {function} setAttribute + * @property {function} setMessage + */ + /** * @typedef PayPalSdk * @type {object} @@ -39,6 +52,13 @@ * @property {function} Marks.render * @property {object} HostedFields * @property {object} CardFields + * @property {PayPalCardField} CardFields.CVVField + * @property {PayPalCardField} CardFields.ExpiryField + * @property {PayPalCardField} CardFields.NameField + * @property {PayPalCardField} CardFields.NumberField + * @property {function} CardFields.getState + * @property {function} CardFields.isEligible + * @property {function} CardFields.submit * @property {function} HostedFields.isEligible * @property {function} HostedFields.render * @property {object} Messages diff --git a/ps_checkout.php b/ps_checkout.php index 74e721f4e..3e4f7c862 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -1125,6 +1125,8 @@ public function hookActionFrontControllerSetMedia() 'checkout.order.amount.total' => $this->l('The total amount of your order comes to'), 'checkout.order.included.tax' => $this->l('(tax incl.)'), 'checkout.order.confirm.label' => $this->l('Please confirm your order by clicking "I confirm my order".'), + 'paypal.hosted-fields.label.card-name' => $this->l('Card holder name'), + 'paypal.hosted-fields.placeholder.card-name' => $this->l('Card holder name'), 'paypal.hosted-fields.label.card-number' => $this->l('Card number'), 'paypal.hosted-fields.placeholder.card-number' => $this->l('Card number'), 'paypal.hosted-fields.label.expiration-date' => $this->l('Expiry date'), diff --git a/views/css/payments.css b/views/css/payments.css index 4c9f8758e..616de4b4e 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -323,17 +323,22 @@ margin: 0 0.25em; } +#ps_checkout-hosted-fields-card-name { + position: relative; + /*height: 45px;*/ + margin-top: 6px; +} #ps_checkout-hosted-fields-card-number { position: relative; - height: 45px; + /*height: 45px;*/ margin-top: 6px; } #ps_checkout-hosted-fields-card-expiration-date { - height: 45px; + /*height: 45px;*/ margin-top: 6px; } #ps_checkout-hosted-fields-card-cvv { - height: 45px; + /*height: 45px;*/ margin-top: 6px; } @@ -506,6 +511,14 @@ label[for="ps_checkout-hosted-fields-card-cvv"] { display: table-cell; } +#ps_checkout-hosted-fields-form.loading > *:not(#ps_checkout-hosted-fields-form-loader){ + display: none; +} + +#ps_checkout-hosted-fields-form:not(.loading) > #ps_checkout-hosted-fields-form-loader{ + display: none; +} + #cart .ps-checkout-express-separator { text-align: center; } @@ -513,3 +526,14 @@ label[for="ps_checkout-hosted-fields-card-cvv"] { .cart-detailed-totals + #ps-checkout-pp-message-container{ padding: 0 1rem 1rem; } + +#ps_checkout-hosted-fields-error-name.hidden, +#ps_checkout-hosted-fields-error-number.hidden, +#ps_checkout-hosted-fields-error-expiry.hidden, +#ps_checkout-hosted-fields-error-cvv.hidden { + display: none; +} + +#ps_checkout-hosted-fields-form-loader { + text-align: center; +} diff --git a/views/templates/hook/displayPayment.tpl b/views/templates/hook/displayPayment.tpl index 14a952c96..88c84466e 100644 --- a/views/templates/hook/displayPayment.tpl +++ b/views/templates/hook/displayPayment.tpl @@ -51,8 +51,8 @@ {if $fundingSource == 'card' && $isHostedFieldsAvailable}
- -
+ +
diff --git a/views/templates/hook/paymentOptions.tpl b/views/templates/hook/paymentOptions.tpl index f329f1583..21c96bdac 100644 --- a/views/templates/hook/paymentOptions.tpl +++ b/views/templates/hook/paymentOptions.tpl @@ -26,16 +26,18 @@ * * Script tags will be removed and some HTML5 element can cause an Exception due to DOMDocument class *} - - + +
+ +
- -
+ +
-
+
@@ -44,7 +46,7 @@
-
+
@@ -56,9 +58,15 @@
-
+
+
+ + + + +
From 1ff4f0d402e6a631c421cbc8c421cde8a22971e7 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 23 Jan 2024 16:11:04 +0200 Subject: [PATCH 086/343] Fixed card fields styling --- .../js/front/src/components/common/card-fields.component.js | 6 +++++- .../front/src/components/common/payment-option.component.js | 1 - .../default-selectors/default-selectors-ps1_6.js | 1 + 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 66b7da85c..adc16e41f 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -170,10 +170,14 @@ export class CardFieldsComponent extends BaseComponent { input: { 'font-size': '17px', 'font-family': 'helvetica, tahoma, calibri, sans-serif', - color: '#3a3a3a' + color: '#3a3a3a', + padding: '8px 12px' }, ':focus': { color: 'black' + }, + body: { + padding: '0px' } }, ...(this.configPayPal.hostedFieldsCustomization || {}), diff --git a/_dev/js/front/src/components/common/payment-option.component.js b/_dev/js/front/src/components/common/payment-option.component.js index 5648b80b0..76cb926c7 100644 --- a/_dev/js/front/src/components/common/payment-option.component.js +++ b/_dev/js/front/src/components/common/payment-option.component.js @@ -18,7 +18,6 @@ */ import { BaseComponent } from '../../core/dependency-injection/base.component'; -import { HostedFieldsComponent } from './hosted-fields.component'; import { MarkComponent } from './marker.component'; import { SmartButtonComponent } from './smart-button.component'; import { PaymentFieldsComponent } from "./payment-fields.component"; diff --git a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js index 45a6117c9..9651d76c4 100644 --- a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js +++ b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_6.js @@ -45,6 +45,7 @@ export const DefaultSelectors1_6 = { PAY_LATER_BANNER_CONTAINER: '.header-container', CARD_FIELDS: { + FORM: '#ps_checkout-card-fields-form', NAME: '#ps_checkout-card-fields-card-name', NUMBER: '#ps_checkout-card-fields-card-number', EXPIRY: '#ps_checkout-card-fields-card-expiry', From 669099177c1a1bf4ed0cb84c19188e3789cc69df Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Wed, 24 Jan 2024 16:00:01 +0100 Subject: [PATCH 087/343] Stop displaying Sofort on checkout page --- .../PayPalSdkLink/PayPalSdkLinkBuilder.php | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php index 80319694e..5a185d0d8 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php @@ -51,6 +51,9 @@ class PayPalSdkLinkBuilder /** @var ExpressCheckoutConfiguration */ private $expressCheckoutConfiguration; + /** @var array */ + private static $cache = []; + /** * @param PayPalConfiguration $configuration * @param PayPalPayLaterConfiguration $payLaterConfiguration @@ -347,6 +350,50 @@ private function getLocale() return ''; } + // TODO : Remove everything Sofort related after October 2024 when its no longer supported by PayPal + private function isSofortAvailableForMerchant() + { + if (isset(self::$cache['sofortAvailability'])) { + return self::$cache['sofortAvailability']; + } + + $query = new \DbQuery(); + $query->select('date_add'); + $query->from('configuration'); + $query->where('name = "PS_CHECKOUT_PAYPAL_EMAIL_STATUS"'); + + $shopId = \Shop::getContextShopID(true); + if ($shopId) { + $query->where('id_shop IS NULL OR id_shop = ' . (int) $shopId); + } + + $dateAdd = \Db::getInstance()->getValue($query); + $dtZone = new \DateTimeZone('UTC'); + + $createdAt = new \DateTime($dateAdd, $dtZone); + $now = new \DateTime('now', $dtZone); + $deprecationDate = new \DateTime('2024-02-01', $dtZone); + $unavailabilityDate = new \DateTime('2024-09-30', $dtZone); + + if ($now > $unavailabilityDate) { + // Sofort is totally unavailable after September 30, 2024. + self::$cache['sofortAvailability'] = false; + + return false; + } + + if ($now > $deprecationDate && $createdAt >= $deprecationDate) { + // Sofort is unavailable for merchants onboarded after February 01, 2024. + self::$cache['sofortAvailability'] = false; + + return false; + } + + self::$cache['sofortAvailability'] = true; + + return true; + } + private function getEligibleAlternativePaymentMethods() { $fundingSourcesEnabled = []; @@ -414,6 +461,7 @@ private function getEligibleAlternativePaymentMethods() && $fundingSource['name'] === 'sofort' && (($context->currency->iso_code === 'EUR' && in_array($country, ['AT', 'BE', 'DE', 'ES', 'NL'], true)) || ($context->currency->iso_code === 'GBP' && in_array($country, ['GB', 'UK'], true))) + && $this->isSofortAvailableForMerchant() ) { $fundingSourcesEnabled[] = $fundingSource['name']; } From d0cdeb008794018f306bb6860b1a2eca65297202 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Wed, 24 Jan 2024 14:05:47 +0100 Subject: [PATCH 088/343] Adding message for PAYMENT_SOURCE_CANNOT_BE_USED error --- controllers/front/validate.php | 4 ++++ src/Exception/PayPalException.php | 1 + src/PayPalError.php | 2 ++ 3 files changed, 7 insertions(+) diff --git a/controllers/front/validate.php b/controllers/front/validate.php index be9c2ab95..1ef8999c7 100644 --- a/controllers/front/validate.php +++ b/controllers/front/validate.php @@ -402,6 +402,10 @@ private function handleException(Exception $exception) $exceptionMessageForCustomer = $this->module->l('Transaction expired, please try again.'); $notifyCustomerService = false; break; + case PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED: + $exceptionMessageForCustomer = $this->module->l('The selected payment method does not support this type of transaction. Please choose another payment method or contact support for assistance.'); + $notifyCustomerService = false; + break; } } diff --git a/src/Exception/PayPalException.php b/src/Exception/PayPalException.php index c95725238..015b0a728 100644 --- a/src/Exception/PayPalException.php +++ b/src/Exception/PayPalException.php @@ -153,4 +153,5 @@ class PayPalException extends PsCheckoutException const PAYMENT_DENIED = 128; const CARD_BRAND_NOT_SUPPORTED = 129; const RESOURCE_NOT_FOUND = 130; + const PAYMENT_SOURCE_CANNOT_BE_USED = 131; } diff --git a/src/PayPalError.php b/src/PayPalError.php index 8a6d2eea5..7d30d09c1 100644 --- a/src/PayPalError.php +++ b/src/PayPalError.php @@ -303,6 +303,8 @@ public function throwException() throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED); case 'RESOURCE_NOT_FOUND': throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND); + case 'PAYMENT_SOURCE_CANNOT_BE_USED': + throw new PayPalException('The provided payment source cannot be used to pay for the order. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED); default: throw new PayPalException($this->message, PayPalException::UNKNOWN); } From 9163269791573f034774369e1a8d8f3611e8702c Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 31 Jan 2024 16:09:26 +0100 Subject: [PATCH 089/343] Bump version to 8.3.5.3 --- config.xml | 2 +- ps_checkout.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config.xml b/config.xml index ec66308ea..53e4406a8 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index 74e721f4e..8a0c38c98 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.5.2'; + const VERSION = '8.3.5.3'; const INTEGRATION_DATE = '2022-14-06'; @@ -144,7 +144,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.5.2'; + $this->version = '8.3.5.3'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; From ce7a74e6e4057b40c62edc53fa753cc076419bed Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 31 Jan 2024 17:38:58 +0100 Subject: [PATCH 090/343] Sofort is unavailable for merchants who have not onboarded yet --- src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php index 5a185d0d8..504c85d2b 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php @@ -360,7 +360,7 @@ private function isSofortAvailableForMerchant() $query = new \DbQuery(); $query->select('date_add'); $query->from('configuration'); - $query->where('name = "PS_CHECKOUT_PAYPAL_EMAIL_STATUS"'); + $query->where('name = "PS_CHECKOUT_PAYPAL_ID_MERCHANT"'); $shopId = \Shop::getContextShopID(true); if ($shopId) { @@ -368,10 +368,17 @@ private function isSofortAvailableForMerchant() } $dateAdd = \Db::getInstance()->getValue($query); - $dtZone = new \DateTimeZone('UTC'); - $createdAt = new \DateTime($dateAdd, $dtZone); + if (empty($dateAdd) || strpos($dateAdd, '0000-00-00') !== false) { + // Sofort is unavailable for merchants who have not onboarded yet. + self::$cache['sofortAvailability'] = false; + + return false; + } + + $dtZone = new \DateTimeZone('UTC'); $now = new \DateTime('now', $dtZone); + $createdAt = new \DateTime($dateAdd, $dtZone); $deprecationDate = new \DateTime('2024-02-01', $dtZone); $unavailabilityDate = new \DateTime('2024-09-30', $dtZone); From 847bb43fb80a8eef107d3cac9f36b38cd2723626 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 31 Jan 2024 17:45:23 +0200 Subject: [PATCH 091/343] Added payment_source and supplementary_data to order creation --- .../common/card-fields.component.js | 2 +- controllers/front/check.php | 2 +- controllers/front/create.php | 2 +- .../Payload/CreateOrderPayloadBuilder.php | 90 ++++++++++--- src/Builder/Payload/OrderPayloadBuilder.php | 127 +++++++++++++++--- src/Handler/CreatePaypalOrderHandler.php | 35 ++--- .../Builder/CreateOrderPayloadBuilderTest.php | 77 +++++++++++ views/templates/markup/cardFields.tpl | 8 +- 8 files changed, 275 insertions(+), 68 deletions(-) diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index adc16e41f..4836c3cfa 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -201,7 +201,7 @@ export class CardFieldsComponent extends BaseComponent { .postCreateOrder({ ...data, fundingSource: this.data.name, - isCardFields: true, + isHostedFields: true, // vault: storeCardInVault }) .then(data => { diff --git a/controllers/front/check.php b/controllers/front/check.php index 3a4cc0896..7f38cc04e 100644 --- a/controllers/front/check.php +++ b/controllers/front/check.php @@ -100,7 +100,7 @@ public function postProcess() if (false === empty($psCheckoutCart->paypal_order)) { $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout || empty($this->context->cart->id_address_delivery), true, $psCheckoutCart->paypal_order); + $response = $paypalOrder->handle($isExpressCheckout || empty($this->context->cart->id_address_delivery), $psCheckoutCart->paypal_funding === 'card', true, $psCheckoutCart->paypal_order); if (false === $response['status']) { $this->module->getLogger()->error( diff --git a/controllers/front/create.php b/controllers/front/create.php index 066250cfd..182b4da49 100755 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -131,7 +131,7 @@ public function postProcess() } $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout); + $response = $paypalOrder->handle($isExpressCheckout, isset($bodyValues['fundingSource']) && $bodyValues['fundingSource'] === 'card'); if (false === $response['status']) { throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); diff --git a/src/Builder/Payload/CreateOrderPayloadBuilder.php b/src/Builder/Payload/CreateOrderPayloadBuilder.php index d100cfb8d..d0f04af7c 100644 --- a/src/Builder/Payload/CreateOrderPayloadBuilder.php +++ b/src/Builder/Payload/CreateOrderPayloadBuilder.php @@ -57,6 +57,11 @@ public function buildFullPayload() if (empty($this->data['ps_checkout']['isUpdate'])) { $this->buildApplicationContextNode(); } + + if ($this->data['ps_checkout']['isCard']) { + $this->buildPaymentSourceNode(); + $this->buildSupplementaryDataNode(); + } } /** @@ -127,14 +132,7 @@ public function buildShippingNode() . (!empty($this->data['deliveryAddress']['lastname']) ? $this->data['deliveryAddress']['lastname'] : '') ), ], - 'address' => [ - 'address_line_1' => !empty($this->data['deliveryAddress']['address1']) ? $this->data['deliveryAddress']['address1'] : '', - 'address_line_2' => !empty($this->data['deliveryAddress']['address2']) ? $this->data['deliveryAddress']['address2'] : '', - 'admin_area_1' => !empty($this->data['deliveryAddressState']['name']) ? $this->data['deliveryAddressState']['name'] : '', - 'admin_area_2' => !empty($this->data['deliveryAddress']['city']) ? $this->data['deliveryAddress']['city'] : '', - 'country_code' => !empty($this->data['deliveryAddressCountry']['iso_code']) ? $this->data['deliveryAddressCountry']['iso_code'] : '', - 'postal_code' => !empty($this->data['deliveryAddress']['postcode']) ? $this->data['deliveryAddress']['postcode'] : '', - ], + 'address' => $this->getAddressPortable('deliveryAddress'), ]; $this->getPayload()->addAndMergeItems($node); @@ -151,14 +149,7 @@ public function buildPayerNode() 'surname' => !empty($this->data['invoiceAddress']['lastname']) ? $this->data['invoiceAddress']['lastname'] : '', ], 'email_address' => !empty($this->data['customer']['email']) ? $this->data['customer']['email'] : '', - 'address' => [ - 'address_line_1' => !empty($this->data['invoiceAddress']['address1']) ? $this->data['invoiceAddress']['address1'] : '', - 'address_line_2' => !empty($this->data['invoiceAddress']['address2']) ? $this->data['invoiceAddress']['address2'] : '', - 'admin_area_1' => !empty($this->data['invoiceAddressState']['name']) ? $this->data['invoiceAddressState']['name'] : '', - 'admin_area_2' => !empty($this->data['invoiceAddress']['city']) ? $this->data['invoiceAddress']['city'] : '', - 'country_code' => !empty($this->data['invoiceAddressCountry']['iso_code']) ? $this->data['invoiceAddressCountry']['iso_code'] : '', - 'postal_code' => !empty($this->data['invoiceAddress']['postcode']) ? $this->data['invoiceAddress']['postcode'] : '', - ], + 'address' => $this->getAddressPortable('invoiceAddress'), ]; // Add optional birthdate if provided @@ -282,6 +273,71 @@ public function buildAmountBreakdownNode() $this->getPayload()->addAndMergeItems($node); } + private function buildPaymentSourceNode() + { + $node = [ + 'payment_source' => [ + 'card' => [ + 'name' => $this->data['invoiceAddress']['firstname'] . ' ' . $this->data['invoiceAddress']['lastname'], + 'billing_address' => $this->getAddressPortable('invoiceAddress'), + 'attributes' => [ + 'verification' => [ + 'method' => $this->data['ps_checkout']['3DS'], + ], + ], + ], + ], + ]; + + $this->getPayload()->addAndMergeItems($node); + } + + private function buildSupplementaryDataNode() + { + $payload = $this->getPayload()->getArray(); + $node = [ + 'supplementary_data' => [ + 'card' => [ + 'level_2' => [ +// 'invoice_id' => '', + 'tax_total' => $payload['amount']['breakdown']['tax_total'], + ], + 'level_3' => [ + 'shipping_amount' => $payload['amount']['breakdown']['shipping'], + 'duty_amount' => [ + 'currency_code' => $payload['amount']['currency_code'], + 'value' => $payload['amount']['value'], + ], + 'discount_amount' => $payload['amount']['breakdown']['discount'], + 'shipping_address' => $this->getAddressPortable('deliveryAddress'), + 'line_items' => $payload['items'], + ], + ], + ], + ]; + + $this->getPayload()->addAndMergeItems($node); + } + + /** + * @param "deliveryAddress"|"invoiceAddress" $addressType + * + * @return string[] + */ + private function getAddressPortable($addressType) + { + $address = $this->data[$addressType]; + + return [ + 'address_line_1' => !empty($address['address1']) ? $address['address1'] : '', + 'address_line_2' => !empty($address['address2']) ? $address['address2'] : '', + 'admin_area_1' => !empty($this->data["{$addressType}State"]['name']) ? $this->data["{$addressType}State"]['name'] : '', + 'admin_area_2' => !empty($address['city']) ? $address['city'] : '', + 'country_code' => !empty($this->data["{$addressType}Country"]['iso_code']) ? $this->data["{$addressType}Country"]['iso_code'] : '', + 'postal_code' => !empty($address['postcode']) ? $address['postcode'] : '', + ]; + } + /** * Get decimal to round correspondent to the payment currency used * Advise from PayPal: Always round to 2 decimals except for HUF, JPY and TWD @@ -305,7 +361,7 @@ private function getNbDecimalToRound() */ private function formatAmount($amount) { - return sprintf("%01.{$this->getNbDecimalToRound()}f", $amount); + return sprintf("%01.{$this->getNbDecimalToRound()}F", $amount); } /** diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index 65b2903f0..b3fc263cd 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -65,6 +65,11 @@ class OrderPayloadBuilder extends Builder implements PayloadBuilderInterface */ private $isPatch; + /** + * @var bool + */ + private $isCard = false; + /** * @param array $cart * @param bool $isPatch @@ -102,6 +107,11 @@ public function buildFullPayload() if (false === $this->isUpdate) { $this->buildApplicationContextNode(); } + + if ($this->isCard) { + $this->buildPaymentSourceNode(); + $this->buildSupplementaryDataNode(); + } } /** @@ -128,6 +138,11 @@ public function buildMinimalPayload() if (false === $this->isUpdate) { $this->buildApplicationContextNode(); } + + if ($this->isCard) { + $this->buildPaymentSourceNode(); + $this->buildSupplementaryDataNode(); + } } /** @@ -190,9 +205,6 @@ public function buildBaseNode() */ public function buildShippingNode() { - $countryCodeMatrice = new PaypalCountryCodeMatrice(); - $shippingCountryIsoCode = $this->getCountryIsoCodeById($this->cart['addresses']['shipping']->id_country); - $gender = new \Gender($this->cart['customer']->id_gender, $this->cart['language']->id); $genderName = $gender->name; @@ -200,14 +212,7 @@ public function buildShippingNode() 'name' => [ 'full_name' => $genderName . ' ' . $this->cart['addresses']['shipping']->lastname . ' ' . $this->cart['addresses']['shipping']->firstname, ], - 'address' => [ - 'address_line_1' => (string) $this->cart['addresses']['shipping']->address1, - 'address_line_2' => (string) $this->cart['addresses']['shipping']->address2, - 'admin_area_1' => (string) $this->getStateNameById($this->cart['addresses']['shipping']->id_state), - 'admin_area_2' => (string) $this->cart['addresses']['shipping']->city, - 'country_code' => (string) $countryCodeMatrice->getPaypalIsoCode($shippingCountryIsoCode), - 'postal_code' => (string) $this->cart['addresses']['shipping']->postcode, - ], + 'address' => $this->getAddressPortable('shipping'), ]; $this->getPayload()->addAndMergeItems($node); @@ -218,7 +223,6 @@ public function buildShippingNode() */ public function buildPayerNode() { - $countryCodeMatrice = new PaypalCountryCodeMatrice(); $payerCountryIsoCode = $this->getCountryIsoCodeById($this->cart['addresses']['invoice']->id_country); /** @var \Ps_checkout $module */ $module = \Module::getInstanceByName('ps_checkout'); @@ -229,14 +233,7 @@ public function buildPayerNode() 'surname' => (string) $this->cart['addresses']['invoice']->lastname, ], 'email_address' => (string) $this->cart['customer']->email, - 'address' => [ - 'address_line_1' => (string) $this->cart['addresses']['invoice']->address1, - 'address_line_2' => (string) $this->cart['addresses']['invoice']->address2, - 'admin_area_1' => (string) $this->getStateNameById($this->cart['addresses']['invoice']->id_state), //The highest level sub-division in a country, which is usually a province, state, or ISO-3166-2 subdivision. - 'admin_area_2' => (string) $this->cart['addresses']['invoice']->city, // A city, town, or village. Smaller than admin_area_level_1 - 'country_code' => (string) $countryCodeMatrice->getPaypalIsoCode($payerCountryIsoCode), - 'postal_code' => (string) $this->cart['addresses']['invoice']->postcode, - ], + 'address' => $this->getAddressPortable('invoice'), ]; // Add optional birthdate if provided @@ -406,6 +403,78 @@ public function buildAmountBreakdownNode() $this->getPayload()->addAndMergeItems($node); } + private function buildPaymentSourceNode() + { + /** @var \Ps_checkout $module */ + $module = \Module::getInstanceByName('ps_checkout'); + /** @var PayPalConfiguration $paypalConfiguration */ + $paypalConfiguration = $module->getService('ps_checkout.paypal.configuration'); + + $node = [ + 'payment_source' => [ + 'card' => [ + 'name' => $this->cart['addresses']['invoice']->firstname . ' ' . $this->cart['addresses']['invoice']->lastname, + 'billing_address' => $this->getAddressPortable('invoice'), + 'attributes' => [ + 'verification' => [ + 'method' => $paypalConfiguration->getHostedFieldsContingencies(), + ], + ], + ], + ], + ]; + + $this->getPayload()->addAndMergeItems($node); + } + + private function buildSupplementaryDataNode() + { + $payload = $this->getPayload()->getArray(); + $node = [ + 'supplementary_data' => [ + 'card' => [ + 'level_2' => [ +// 'invoice_id' => '', + 'tax_total' => $payload['amount']['breakdown']['tax_total'], + ], + 'level_3' => [ + 'shipping_amount' => $payload['amount']['breakdown']['shipping'], + 'duty_amount' => [ + 'currency_code' => $payload['amount']['currency_code'], + 'value' => $payload['amount']['value'], + ], + 'discount_amount' => $payload['amount']['breakdown']['discount'], + 'shipping_address' => $this->getAddressPortable('shipping'), + 'line_items' => $payload['items'], + ], + ], + ], + ]; + + $this->getPayload()->addAndMergeItems($node); + } + + /** + * @param "shipping"|"invoice" $addressType + * + * @return string[] + */ + private function getAddressPortable($addressType) + { + $countryCodeMatrice = new PaypalCountryCodeMatrice(); + $address = $this->cart['addresses'][$addressType]; + $payerCountryIsoCode = $this->getCountryIsoCodeById($address->id_country); + + return [ + 'address_line_1' => $address->address1, + 'address_line_2' => $address->address2, + 'admin_area_1' => (string) $this->getStateNameById($address->id_state), + 'admin_area_2' => $address->city, + 'country_code' => (string) $countryCodeMatrice->getPaypalIsoCode($payerCountryIsoCode), + 'postal_code' => $address->postcode, + ]; + } + /** * Function that allow to truncate fields to match the * paypal api requirements @@ -447,7 +516,7 @@ private function getNbDecimalToRound() */ private function formatAmount($amount) { - return sprintf("%01.{$this->getNbDecimalToRound()}f", $amount); + return sprintf("%01.{$this->getNbDecimalToRound()}F", $amount); } /** @@ -514,6 +583,22 @@ public function setPaypalOrderId($id) $this->paypalOrderId = $id; } + /** + * @return bool + */ + public function isCard() + { + return $this->isCard; + } + + /** + * @param bool $isCard + */ + public function setIsCard($isCard) + { + $this->isCard = $isCard; + } + /** * Getter $paypalOrderId */ diff --git a/src/Handler/CreatePaypalOrderHandler.php b/src/Handler/CreatePaypalOrderHandler.php index 4831ce300..d7599d2b0 100644 --- a/src/Handler/CreatePaypalOrderHandler.php +++ b/src/Handler/CreatePaypalOrderHandler.php @@ -57,7 +57,7 @@ public function __construct(Context $context = null) * * @throws PsCheckoutException */ - public function handle($expressCheckout = false, $updateOrder = false, $paypalOrderId = null) + public function handle($expressCheckout = false, $isCardPayment = false, $updateOrder = false, $paypalOrderId = null) { // Present an improved cart in order to create the payload $cartPresenter = (new CartPresenter())->present(); @@ -70,32 +70,21 @@ public function handle($expressCheckout = false, $updateOrder = false, $paypalOr /** @var ShopContext $shopContext */ $shopContext = $module->getService('ps_checkout.context.shop'); - // Build full payload in 1.7 - if ($shopContext->isShop17()) { - // enable express checkout mode if in express checkout - if (true === $expressCheckout) { - $builder->setExpressCheckout(true); - } + $builder->setIsCard($isCardPayment); - // enable update mode if we build an order for update it - if (true === $updateOrder) { - $builder->setIsUpdate(true); - $builder->setPaypalOrderId($paypalOrderId); - } + // enable express checkout mode if in express checkout + $builder->setExpressCheckout($expressCheckout); + + // enable update mode if we build an order for update it + $builder->setIsUpdate($updateOrder); + if ($updateOrder) { + $builder->setPaypalOrderId($paypalOrderId); + } + if ($shopContext->isShop17()) { + // Build full payload in 1.7 $builder->buildFullPayload(); } else { - // enable express checkout mode if in express checkout - if (true === $expressCheckout) { - $builder->setExpressCheckout(true); - } - - // enable update mode if we build an order for update it - if (true === $updateOrder) { - $builder->setIsUpdate(true); - $builder->setPaypalOrderId($paypalOrderId); - } - // if on 1.6 always build minimal payload $builder->buildMinimalPayload(); } diff --git a/tests/Unit/Builder/CreateOrderPayloadBuilderTest.php b/tests/Unit/Builder/CreateOrderPayloadBuilderTest.php index 544f1f407..20e6fb25a 100644 --- a/tests/Unit/Builder/CreateOrderPayloadBuilderTest.php +++ b/tests/Unit/Builder/CreateOrderPayloadBuilderTest.php @@ -46,6 +46,81 @@ public function testOneProductWithPaidShippingRoundingCompliant() } } + public function testPaymentSourceAppearsInPayload() + { + foreach ($this->cartDataProvider() as $scenario => $cartData) { + $orderPayloadBuilder = new CreateOrderPayloadBuilder(array_merge( + $cartData, + $this->shippingAddressDataProvider(), + $this->invoiceAddressDataProvider(), + $this->customerDataProvider(), + $this->currencyDataProvider(), + $this->languageDataProvider(), + $this->shopDataProvider(), + $this->moduleDataProvider() + )); + $orderPayloadBuilder->buildFullPayload(); + $payload = $orderPayloadBuilder->presentPayload()->getArray(); + + echo 'Starting ' . $scenario . PHP_EOL; + $this->assertArrayHasKey('payment_source', $payload); + $this->assertEquals($this->moduleDataProvider()['ps_checkout']['3DS'], $payload['payment_source']['card']['attributes']['verification']['method']); + } + } + + public function testSupplementaryDataAppearsInPayload() + { + foreach ($this->cartDataProvider() as $scenario => $cartData) { + $orderPayloadBuilder = new CreateOrderPayloadBuilder(array_merge( + $cartData, + $this->shippingAddressDataProvider(), + $this->invoiceAddressDataProvider(), + $this->customerDataProvider(), + $this->currencyDataProvider(), + $this->languageDataProvider(), + $this->shopDataProvider(), + $this->moduleDataProvider() + )); + $orderPayloadBuilder->buildFullPayload(); + $payload = $orderPayloadBuilder->presentPayload()->getArray(); + + echo 'Starting ' . $scenario . PHP_EOL; + $this->assertArrayHasKey('supplementary_data', $payload); + $this->assertEquals($payload['amount']['breakdown']['tax_total'], $payload['supplementary_data']['card']['level_2']['tax_total']); + $this->assertEquals($payload['amount']['currency_code'], $payload['supplementary_data']['card']['level_3']['duty_amount']['currency_code']); + $this->assertEquals($payload['amount']['value'], $payload['supplementary_data']['card']['level_3']['duty_amount']['value']); + $this->assertEquals($payload['amount']['breakdown']['shipping'], $payload['supplementary_data']['card']['level_3']['shipping_amount']); + $this->assertEquals($payload['amount']['breakdown']['discount'], $payload['supplementary_data']['card']['level_3']['discount_amount']); + $this->assertEquals($payload['shipping']['address'], $payload['supplementary_data']['card']['level_3']['shipping_address']); + $this->assertEquals($payload['items'], $payload['supplementary_data']['card']['level_3']['line_items']); + } + } + + public function testPaymentIsNotCard() + { + foreach ($this->cartDataProvider() as $scenario => $cartData) { + $orderPayloadBuilder = new CreateOrderPayloadBuilder(array_merge( + $cartData, + $this->shippingAddressDataProvider(), + $this->invoiceAddressDataProvider(), + $this->customerDataProvider(), + $this->currencyDataProvider(), + $this->languageDataProvider(), + $this->shopDataProvider(), + array_replace_recursive($this->moduleDataProvider(), ['ps_checkout' => [ + 'isCard' => false, + ]]) + )); + $orderPayloadBuilder->buildFullPayload(); + $payload = $orderPayloadBuilder->presentPayload()->getArray(); + + echo 'Starting ' . $scenario . PHP_EOL; + + $this->assertArrayNotHasKey('payment_source', $payload); + $this->assertArrayNotHasKey('supplementary_data', $payload); + } + } + private function checkAmountCalculation(array $payload) { $itemTotalAmount = 0; @@ -5516,6 +5591,8 @@ private function moduleDataProvider() 'roundType' => '1', 'roundMode' => '2', 'isExpressCheckout' => false, + 'isCard' => true, + '3DS' => 'SCA_WHEN_REQUIRED', ], ]; } diff --git a/views/templates/markup/cardFields.tpl b/views/templates/markup/cardFields.tpl index ad645ed95..0443c5718 100644 --- a/views/templates/markup/cardFields.tpl +++ b/views/templates/markup/cardFields.tpl @@ -30,24 +30,24 @@
-
+
-
+
-
+
-
+
i From 61600266bac039b2e31e53af56b74dac77cbc796 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 1 Feb 2024 16:46:41 +0100 Subject: [PATCH 092/343] Remove PayPal Client Token --- config/common.yml | 13 -- controllers/front/token.php | 11 -- ps_checkout.php | 14 +-- src/Api/Payment/Order.php | 70 ----------- .../Event/PayPalClientTokenUpdatedEvent.php | 111 ------------------ .../PayPalIdentityEventSubscriber.php | 50 -------- .../Query/GetClientTokenPayPalQuery.php | 68 ----------- .../Query/GetClientTokenPayPalQueryResult.php | 90 -------------- .../GetClientTokenPayPalQueryHandler.php | 82 ------------- src/PayPal/PayPalClientTokenProvider.php | 82 ------------- src/Validator/FrontControllerValidator.php | 21 ---- 11 files changed, 1 insertion(+), 611 deletions(-) delete mode 100644 src/PayPal/Identity/Event/PayPalClientTokenUpdatedEvent.php delete mode 100644 src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php delete mode 100644 src/PayPal/Identity/Query/GetClientTokenPayPalQuery.php delete mode 100644 src/PayPal/Identity/Query/GetClientTokenPayPalQueryResult.php delete mode 100644 src/PayPal/Identity/QueryHandler/GetClientTokenPayPalQueryHandler.php delete mode 100644 src/PayPal/PayPalClientTokenProvider.php diff --git a/config/common.yml b/config/common.yml index d1f5fd08b..5727a740b 100644 --- a/config/common.yml +++ b/config/common.yml @@ -351,12 +351,6 @@ services: arguments: - "@ps_checkout.cache.paypal.order" - ps_checkout.paypal.provider.client_token: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\PayPalClientTokenProvider' - public: true - arguments: - - "@ps_checkout.repository.pscheckoutcart" - ps_checkout.prestashop.router: class: 'PrestaShop\Module\PrestashopCheckout\Routing\Router' public: true @@ -469,7 +463,6 @@ services: PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery: "ps_checkout.query.handler.order.get_order_for_payment_refunded" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery: "ps_checkout.query.handler.order.get_order_for_payment_reversed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery: "ps_checkout.query.handler.order.get_order_for_approval_reversed" - PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query\GetClientTokenPayPalQuery: "ps_checkout.query.handler.paypal.identity.get_client_token" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery: "ps_checkout.query.handler.paypal.order.get_current_paypal_order_status" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_order_confirmation" @@ -618,12 +611,6 @@ services: arguments: - "@ps_checkout.repository.pscheckoutcart" - ps_checkout.query.handler.paypal.identity.get_client_token: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Identity\QueryHandler\GetClientTokenPayPalQueryHandler' - public: true - arguments: - - "@ps_checkout.event.dispatcher" - ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCheckoutCompletedQueryHandler' public: true diff --git a/controllers/front/token.php b/controllers/front/token.php index e315d37be..9e0f238c9 100644 --- a/controllers/front/token.php +++ b/controllers/front/token.php @@ -19,7 +19,6 @@ */ use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalClientTokenProvider; /** * This controller receive ajax call to retrieve a PayPal Client Token @@ -46,9 +45,6 @@ public function postProcess() ]); } - /** @var PayPalClientTokenProvider $clientTokenProvider */ - $clientTokenProvider = $this->module->getService('ps_checkout.paypal.provider.client_token'); - /** @var \PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository $psCheckoutCartRepository */ $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); @@ -60,13 +56,6 @@ public function postProcess() $psCheckoutCart->id_cart = (int) $this->context->cart->id; } - if ($psCheckoutCart->isPaypalClientTokenExpired()) { - $psCheckoutCart->paypal_order = ''; - $psCheckoutCart->paypal_token = $clientTokenProvider->getPayPalClientToken(); - $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); - $psCheckoutCartRepository->save($psCheckoutCart); - } - $this->exitWithResponse([ 'status' => true, 'httpCode' => 200, diff --git a/ps_checkout.php b/ps_checkout.php index c543dd8f7..e89c217db 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -1055,22 +1055,10 @@ public function hookActionFrontControllerSetMedia() if (false !== $psCheckoutCart && $psCheckoutCart->isOrderAvailable()) { $payPalOrderId = $psCheckoutCart->getPaypalOrderId(); $cartFundingSource = $psCheckoutCart->getPaypalFundingSource(); + $payPalClientToken = $psCheckoutCart->getPaypalClientToken(); } // END To be refactored in services - if ($frontControllerValidator->shouldGeneratePayPalClientToken($controller) - && $payPalConfiguration->isHostedFieldsEnabled() && in_array($payPalConfiguration->getCardHostedFieldsStatus(), ['SUBSCRIBED', 'LIMITED'], true) - ) { - try { - /** @var \PrestaShop\Module\PrestashopCheckout\PayPal\PayPalClientTokenProvider $clientTokenProvider */ - $clientTokenProvider = $this->getService('ps_checkout.paypal.provider.client_token'); - - $payPalClientToken = $clientTokenProvider->getPayPalClientToken(); - } catch (Exception $exception) { - $this->getLogger()->warning('Unable to retrieve PayPal Client Token', ['exception' => $exception]); - } - } - Media::addJsDef([ $this->name . 'Version' => $version->getSemVersion(), $this->name . 'AutoRenderDisabled' => (bool) Configuration::get('PS_CHECKOUT_AUTO_RENDER_DISABLED'), diff --git a/src/Api/Payment/Order.php b/src/Api/Payment/Order.php index e76528aa0..9b4b9db89 100644 --- a/src/Api/Payment/Order.php +++ b/src/Api/Payment/Order.php @@ -22,7 +22,6 @@ namespace PrestaShop\Module\PrestashopCheckout\Api\Payment; use PrestaShop\Module\PrestashopCheckout\Api\Payment\Client\PaymentClient; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; /** * Handle order requests @@ -122,73 +121,4 @@ public function patch($payload) return $this->post($payload); } - - /** - * @param string $merchantId - * - * @return string - * - * @throws PsCheckoutException - */ - public function generateClientToken($merchantId) - { - $this->setRoute('/payments/order/generate_client_token'); - - $response = $this->post([ - 'return_payload' => true, - 'payee' => [ - 'merchant_id' => $merchantId, - ], - ]); - - if (empty($response['body']) || empty($response['body']['client_token'])) { - $exception = null; - - if (!empty($response['exceptionMessage'])) { - $exception = new \Exception($response['exceptionMessage'], $response['exceptionCode']); - } - - throw new PsCheckoutException('Unable to retrieve PayPal Client Token', PsCheckoutException::MISSING_PAYPAL_CLIENT_TOKEN, $exception); - } - - return $response['body']['client_token']; - } - - /** - * @param string $merchantId - * @param int|null $customerId - * - * @return array{client_token:string, id_token: string, expires_in: int} - * - * @throws PsCheckoutException - */ - public function getClientToken($merchantId, $customerId = null) - { - $this->setRoute('/payments/order/generate_client_token'); - - $payload = [ - 'return_payload' => true, - 'payee' => [ - 'merchant_id' => $merchantId, - ], - ]; - - if ($customerId) { - $payload['customer_id'] = $customerId; - } - - $response = $this->post($payload); - - if (empty($response['body']) || empty($response['body']['client_token'])) { - $exception = null; - - if (!empty($response['exceptionMessage'])) { - $exception = new \Exception($response['exceptionMessage'], $response['exceptionCode']); - } - - throw new PsCheckoutException('Unable to retrieve PayPal Client Token', PsCheckoutException::MISSING_PAYPAL_CLIENT_TOKEN, $exception); - } - - return $response['body']; - } } diff --git a/src/PayPal/Identity/Event/PayPalClientTokenUpdatedEvent.php b/src/PayPal/Identity/Event/PayPalClientTokenUpdatedEvent.php deleted file mode 100644 index 296f9a53f..000000000 --- a/src/PayPal/Identity/Event/PayPalClientTokenUpdatedEvent.php +++ /dev/null @@ -1,111 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Event; - -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; -use PrestaShop\Module\PrestashopCheckout\Event\Event; - -class PayPalClientTokenUpdatedEvent extends Event -{ - /** - * @var CartId - */ - private $cartId; - - /** - * @var string - */ - private $token; - - /** - * @var string - */ - private $tokenId; - - /** - * @var int - */ - private $expireIn; - - /** - * @var int - */ - private $createdAt; - - /** - * @param int $cartId - * @param string $token - * @param string $tokenId - * @param int $expireIn - * @param int $createdAt - * - * @throws CartException - */ - public function __construct($cartId, $token, $tokenId, $expireIn, $createdAt) - { - $this->cartId = new CartId($cartId); - $this->token = $token; - $this->tokenId = $tokenId; - $this->expireIn = $expireIn; - $this->createdAt = $createdAt; - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } - - /** - * @return string - */ - public function getToken() - { - return $this->token; - } - - /** - * @return string - */ - public function getIdToken() - { - return $this->tokenId; - } - - /** - * @return int - */ - public function getExpireIn() - { - return $this->expireIn; - } - - /** - * @return int - */ - public function getCreatedAt() - { - return $this->createdAt; - } -} diff --git a/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php b/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php deleted file mode 100644 index a0b5c15d4..000000000 --- a/src/PayPal/Identity/EventSubscriber/PayPalIdentityEventSubscriber.php +++ /dev/null @@ -1,50 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\EventSubscriber; - -use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; -use Symfony\Component\EventDispatcher\EventSubscriberInterface; - -class PayPalIdentityEventSubscriber implements EventSubscriberInterface -{ - /** - * @var CommandBusInterface - */ - private $commandBus; - - /** - * @param CommandBusInterface $commandBus - */ - public function __construct(CommandBusInterface $commandBus) - { - $this->commandBus = $commandBus; - } - - /** - * {@inheritdoc} - */ - public static function getSubscribedEvents() - { - return [ - //PayPalClientTokenUpdatedEvent::class => '', - ]; - } -} diff --git a/src/PayPal/Identity/Query/GetClientTokenPayPalQuery.php b/src/PayPal/Identity/Query/GetClientTokenPayPalQuery.php deleted file mode 100644 index ac7c5c1d9..000000000 --- a/src/PayPal/Identity/Query/GetClientTokenPayPalQuery.php +++ /dev/null @@ -1,68 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query; - -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; -use PrestaShop\Module\PrestashopCheckout\Customer\Exception\CustomerException; -use PrestaShop\Module\PrestashopCheckout\Customer\ValueObject\CustomerId; - -class GetClientTokenPayPalQuery -{ - /** - * @var CartId - */ - private $cartId; - - /** - * @var CustomerId|null - */ - private $customerId; - - /** - * @param int $cartId - * @param int|null $customerId - * - * @throws CartException - * @throws CustomerException - */ - public function __construct($cartId, $customerId = null) - { - $this->cartId = new CartId($cartId); - $this->customerId = $customerId ? new CustomerId($customerId) : null; - } - - /** - * @return CartId - */ - public function getCartId() - { - return $this->cartId; - } - - /** - * @return CustomerId|null - */ - public function getCustomerId() - { - return $this->customerId; - } -} diff --git a/src/PayPal/Identity/Query/GetClientTokenPayPalQueryResult.php b/src/PayPal/Identity/Query/GetClientTokenPayPalQueryResult.php deleted file mode 100644 index 500a6069d..000000000 --- a/src/PayPal/Identity/Query/GetClientTokenPayPalQueryResult.php +++ /dev/null @@ -1,90 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query; - -class GetClientTokenPayPalQueryResult -{ - /** - * @var string - */ - private $clientToken; - - /** - * @var string - */ - private $idToken; - - /** - * @var int - */ - private $expiresIn; - - /** - * @var int - */ - private $createdAt; - - /** - * @param string $clientToken - * @param string $idToken - * @param int $expiresIn - * @param int $createdAt - */ - public function __construct($clientToken, $idToken, $expiresIn, $createdAt) - { - $this->clientToken = $clientToken; - $this->idToken = $idToken; - $this->expiresIn = $expiresIn; - $this->createdAt = $createdAt; - } - - /** - * @return string - */ - public function getClientToken() - { - return $this->clientToken; - } - - /** - * @return string - */ - public function getIdToken() - { - return $this->idToken; - } - - /** - * @return int - */ - public function getExpiresIn() - { - return $this->expiresIn; - } - - /** - * @return int - */ - public function getCreatedAt() - { - return $this->createdAt; - } -} diff --git a/src/PayPal/Identity/QueryHandler/GetClientTokenPayPalQueryHandler.php b/src/PayPal/Identity/QueryHandler/GetClientTokenPayPalQueryHandler.php deleted file mode 100644 index 507571f00..000000000 --- a/src/PayPal/Identity/QueryHandler/GetClientTokenPayPalQueryHandler.php +++ /dev/null @@ -1,82 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Identity\QueryHandler; - -use Configuration; -use Context; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; -use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; -use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Event\PayPalClientTokenUpdatedEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query\GetClientTokenPayPalQuery; -use PrestaShop\Module\PrestashopCheckout\PayPal\Identity\Query\GetClientTokenPayPalQueryResult; - -class GetClientTokenPayPalQueryHandler -{ - /** - * @var EventDispatcherInterface - */ - private $eventDispatcher; - - /** - * @param EventDispatcherInterface $eventDispatcher - */ - public function __construct(EventDispatcherInterface $eventDispatcher) - { - $this->eventDispatcher = $eventDispatcher; - } - - /** - * @param GetClientTokenPayPalQuery $clientTokenPayPalQuery - * - * @return GetClientTokenPayPalQueryResult - * - * @throws CartException - * @throws PsCheckoutException - */ - public function handle(GetClientTokenPayPalQuery $clientTokenPayPalQuery) - { - $createdAt = time(); - $context = Context::getContext(); - $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); - $customerId = $clientTokenPayPalQuery->getCustomerId()->getValue(); - $apiOrder = new Order($context->link); - $response = $apiOrder->getClientToken($merchantId, $customerId); - - $this->eventDispatcher->dispatch( - new PayPalClientTokenUpdatedEvent( - $clientTokenPayPalQuery->getCartId()->getValue(), - $response['client_token'], - $response['id_token'], - (int) $response['expires_in'], - $createdAt - ) - ); - - return new GetClientTokenPayPalQueryResult( - $response['client_token'], - $response['id_token'], - (int) $response['expires_in'], - $createdAt - ); - } -} diff --git a/src/PayPal/PayPalClientTokenProvider.php b/src/PayPal/PayPalClientTokenProvider.php deleted file mode 100644 index 9d8119794..000000000 --- a/src/PayPal/PayPalClientTokenProvider.php +++ /dev/null @@ -1,82 +0,0 @@ - - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - */ - -namespace PrestaShop\Module\PrestashopCheckout\PayPal; - -use Configuration; -use Context; -use DateTime; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use PrestaShopDatabaseException; -use PrestaShopException; -use PsCheckoutCart; -use Validate; - -class PayPalClientTokenProvider -{ - /** - * @var PsCheckoutCartRepository - */ - private $psCheckoutCartRepository; - - public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) - { - $this->psCheckoutCartRepository = $psCheckoutCartRepository; - } - - /** - * @return string - * - * @throws PsCheckoutException - * @throws PrestaShopDatabaseException - * @throws PrestaShopException - */ - public function getPayPalClientToken() - { - $context = Context::getContext(); - - if (!Validate::isLoadedObject($context->cart)) { - return ''; - } - - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId((int) $context->cart->id); - - if ($psCheckoutCart && !$psCheckoutCart->isPaypalClientTokenExpired()) { - return $psCheckoutCart->getPaypalClientToken(); - } - - $apiOrder = new Order($context->link); - $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); - $clientToken = $apiOrder->generateClientToken($merchantId); - - if (!$psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $context->cart->id; - } - - $psCheckoutCart->paypal_token = $clientToken; - $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); - $this->psCheckoutCartRepository->save($psCheckoutCart); - - return $clientToken; - } -} diff --git a/src/Validator/FrontControllerValidator.php b/src/Validator/FrontControllerValidator.php index f50e5fba8..55cee0ddc 100644 --- a/src/Validator/FrontControllerValidator.php +++ b/src/Validator/FrontControllerValidator.php @@ -120,25 +120,4 @@ public function shouldLoadFrontCss($controller) return false; } - - /** - * @param string $controller - * - * @return bool - */ - public function shouldGeneratePayPalClientToken($controller) - { - if (false === $this->merchantValidator->merchantIsValid()) { - return false; - } - - switch ($controller) { - // Payment step - case 'orderopc': - case 'order': - return true; - } - - return false; - } } From 39a183581f35f2063bbf71ddc54714d2940f5061 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 1 Feb 2024 17:22:04 +0100 Subject: [PATCH 093/343] Bump version to 8.3.6.0 --- config.xml | 2 +- ps_checkout.php | 4 +- upgrade/upgrade-8.3.6.0.php | 75 +++++++++++++++++++++++++++++++++++++ 3 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 upgrade/upgrade-8.3.6.0.php diff --git a/config.xml b/config.xml index 53e4406a8..742ae303a 100644 --- a/config.xml +++ b/config.xml @@ -2,7 +2,7 @@ ps_checkout - + diff --git a/ps_checkout.php b/ps_checkout.php index e89c217db..da4baccef 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -123,7 +123,7 @@ class Ps_checkout extends PaymentModule // Needed in order to retrieve the module version easier (in api call headers) than instanciate // the module each time to get the version - const VERSION = '8.3.5.3'; + const VERSION = '8.3.6.0'; const INTEGRATION_DATE = '2022-14-06'; @@ -144,7 +144,7 @@ public function __construct() // We cannot use the const VERSION because the const is not computed by addons marketplace // when the zip is uploaded - $this->version = '8.3.5.3'; + $this->version = '8.3.6.0'; $this->author = 'PrestaShop'; $this->currencies = true; $this->currencies_mode = 'checkbox'; diff --git a/upgrade/upgrade-8.3.6.0.php b/upgrade/upgrade-8.3.6.0.php new file mode 100644 index 000000000..2c11c9813 --- /dev/null +++ b/upgrade/upgrade-8.3.6.0.php @@ -0,0 +1,75 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ +if (!defined('_PS_VERSION_')) { + exit; +} + +/** + * Update main function for module version 8.3.6.0 + * + * @param Ps_checkout $module + * + * @return bool + */ +function upgrade_module_8_3_6_0($module) +{ + // Force PrestaShop to upgrade for all shop to avoid issues + $savedShopContext = Shop::getContext(); + $savedShopId = Shop::getContextShopID(); + $savedGroupShopId = Shop::getContextShopGroupID(); + Shop::setContext(Shop::CONTEXT_ALL); + + try { + $shopsList = Shop::getShops(false, null, true); + + foreach ($shopsList as $shopId) { + // Require the liability shift for all shops + Configuration::updateValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '1', false, null, (int) $shopId); + + // Update global value only if it is not already set to SCA_ALWAYS + if (Configuration::get('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', null, null, $shopId) !== 'SCA_ALWAYS') { + Configuration::updateValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED', false, null, (int) $shopId); + } + } + + // Require the liability shift for all shops + Configuration::updateGlobalValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '1'); + + // Update global value only if it is not already set to SCA_ALWAYS + if (Configuration::getGlobalValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES') !== 'SCA_ALWAYS') { + Configuration::updateGlobalValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED'); + } + } catch (Exception $exception) { + PrestaShopLogger::addLog($exception->getMessage(), 3, $exception->getCode(), 'Module', $module->id); + + return false; + } + + // Restore initial PrestaShop shop context + if (Shop::CONTEXT_SHOP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedShopId); + } elseif (Shop::CONTEXT_GROUP === $savedShopContext) { + Shop::setContext($savedShopContext, $savedGroupShopId); + } else { + Shop::setContext($savedShopContext); + } + + return true; +} From cc4a93258d8f01aabf619dbbb85435d42f94ac04 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Tue, 6 Feb 2024 10:33:45 +0200 Subject: [PATCH 094/343] Removed hosted-fields component from sdk link --- src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php index 0e2758a50..3a1aa5351 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php @@ -89,7 +89,6 @@ public function buildLink() } if ($this->shouldIncludeHostedFieldsComponent()) { - $components[] = 'hosted-fields'; $components[] = 'card-fields'; } From 11d85632e67491b90cd213f70d8d9dbf5f036832 Mon Sep 17 00:00:00 2001 From: Bastien Tafforeau Date: Mon, 18 Dec 2023 10:45:34 +0100 Subject: [PATCH 095/343] Load PayPal SDK with PayPal package --- _dev/js/front/package.json | 1 + .../components/common/paypal-sdk.component.js | 49 ++++++------------- .../common/paypal-sdk.component.spec.js | 16 +++++- _dev/js/front/src/config/paypal-sdk.config.js | 38 +++++++------- .../html-templates/prestashop-site-1_7.js | 12 +++++ 5 files changed, 62 insertions(+), 54 deletions(-) diff --git a/_dev/js/front/package.json b/_dev/js/front/package.json index 2c0715c3c..859d3dc00 100644 --- a/_dev/js/front/package.json +++ b/_dev/js/front/package.json @@ -33,6 +33,7 @@ "webpack-merge": "^5.3.0" }, "dependencies": { + "@paypal/paypal-js": "^7.1.1", "@ungap/event-target": "^0.2.2", "classlist-polyfill": "^1.2.0", "promise-polyfill": "8.1.3", diff --git a/_dev/js/front/src/components/common/paypal-sdk.component.js b/_dev/js/front/src/components/common/paypal-sdk.component.js index 154a5d454..53e54bc8d 100644 --- a/_dev/js/front/src/components/common/paypal-sdk.component.js +++ b/_dev/js/front/src/components/common/paypal-sdk.component.js @@ -17,6 +17,7 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ import { BaseComponent } from '../../core/dependency-injection/base.component'; +import { loadScript } from '@paypal/paypal-js'; export class PayPalSdkComponent extends BaseComponent { static Inject = { @@ -29,44 +30,22 @@ export class PayPalSdkComponent extends BaseComponent { } render() { - const script = document.createElement('script'); - - script.setAttribute('async', ''); - script.setAttribute('id', this.config.id); - script.setAttribute('src', this.config.src); - script.setAttribute('data-namespace', this.config.namespace); - - if (this.config.card3dsEnabled) { - script.setAttribute('data-enable-3ds', ''); - } - - if (this.config.cspNonce) { - script.setAttribute('data-csp-nonce', this.config.cspNonce); - } - - if (this.config.orderId) { - script.setAttribute('data-order-id', this.config.orderId); - } - - if (this.config.partnerAttributionId) { - script.setAttribute('data-partner-attribution-id', this.config.partnerAttributionId); - } - - script.setAttribute('data-client-token', this.props.token); - - document.head.appendChild(script); - this.promise = new Promise((resolve, reject) => { - script.onload = () => { - this.sdk = window[this.config.namespace]; - resolve(this.sdk); - }; - - script.onerror = () => { - reject(); - }; + loadScript(this.config.sdkConfig) + .then((paypal) => { + this.sdk = paypal; + resolve(this.sdk); + }) + .catch((error) => { + console.error('Unable to load the PayPal JavaScript SDK.', error); + reject(); + }); }); return this; } + + reload() { + this.render(); + } } diff --git a/_dev/js/front/src/components/common/paypal-sdk.component.spec.js b/_dev/js/front/src/components/common/paypal-sdk.component.spec.js index 942fdcbda..1ebc3543e 100644 --- a/_dev/js/front/src/components/common/paypal-sdk.component.spec.js +++ b/_dev/js/front/src/components/common/paypal-sdk.component.spec.js @@ -18,12 +18,15 @@ */ import { PayPalSdkConfig } from '../../config/paypal-sdk.config'; import { PayPalSdkComponent } from './paypal-sdk.component'; +import * as PrestashopSite1_7 from '../../../test/mocks/html-templates/prestashop-site-1_7'; function buildDIContainerMock() { + PrestashopSite1_7.mockCheckoutVars(); + const payPalSdkConfig = new PayPalSdkConfig(); return { container: { PayPalSdkConfig: { - ...PayPalSdkConfig, + ...payPalSdkConfig, id: 'foo', src: 'url', namespace: 'fooNamespace' @@ -129,4 +132,15 @@ describe('src/components/common/paypal-sdk.component.spec.js', () => { } }); }); + + test('::render() with new Sdk', () => { + const diContainer = buildDIContainerMock(); + const payPalSdkComponent = new PayPalSdkComponent(diContainer); + + expect(payPalSdkComponent.render()).toBe(payPalSdkComponent); + + return payPalSdkComponent.promise.then(() => { + expect(payPalSdkComponent.sdk).toBeDefined(); + }); + }); }); diff --git a/_dev/js/front/src/config/paypal-sdk.config.js b/_dev/js/front/src/config/paypal-sdk.config.js index 1b5a07ab8..51161f92a 100644 --- a/_dev/js/front/src/config/paypal-sdk.config.js +++ b/_dev/js/front/src/config/paypal-sdk.config.js @@ -16,22 +16,24 @@ * @copyright Since 2007 PrestaShop SA and Contributors * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ -export const PayPalSdkConfig = { - id: 'ps_checkoutPayPalSdkScript', - namespace: 'ps_checkoutPayPalSdkInstance', - src: window.ps_checkoutPayPalSdkUrl, - card3dsEnabled: window.ps_checkout3dsEnabled, - cspNonce: window.ps_checkoutCspNonce, - orderId: window.ps_checkoutPayPalOrderId, - fundingSource: window.ps_checkoutFundingSource, - clientToken: window.ps_checkoutPayPalClientToken, - buttonCustomization: window.ps_checkoutPayPalButtonConfiguration, - paymentFieldsCustomization: window.ps_checkout.paymentFieldsCustomization, - expressCheckoutButtonCustomization: - window.ps_checkoutExpressCheckoutButtonCustomization, - hostedFieldsCustomization: - window.ps_checkoutHostedFieldsCustomizationConfiguration, - payLaterOfferMessageCustomization: window.ps_checkoutPayLaterOfferMessageCustomization, - payLaterOfferBannerCustomization: window.ps_checkoutPayLaterOfferBannerCustomization, - partnerAttributionId: window.ps_checkoutPartnerAttributionId +export const PayPalSdkConfig = () => { + return { + sdkConfig: { + dataNamespace: 'ps_checkoutPayPalSdkInstance', + orderId: window.ps_checkoutPayPalOrderId, + dataClientToken: window.ps_checkoutPayPalClientToken, + ...window.ps_checkoutPayPalSdkConfig + }, + buttonCustomization: window.ps_checkoutPayPalButtonConfiguration, + paymentFieldsCustomization: window.ps_checkout.paymentFieldsCustomization, + expressCheckoutButtonCustomization: + window.ps_checkoutExpressCheckoutButtonCustomization, + hostedFieldsCustomization: + window.ps_checkoutHostedFieldsCustomizationConfiguration, + payLaterOfferMessageCustomization: + window.ps_checkoutPayLaterOfferMessageCustomization, + payLaterOfferBannerCustomization: + window.ps_checkoutPayLaterOfferBannerCustomization, + partnerAttributionId: window.ps_checkoutPartnerAttributionId + }; }; diff --git a/_dev/js/front/test/mocks/html-templates/prestashop-site-1_7.js b/_dev/js/front/test/mocks/html-templates/prestashop-site-1_7.js index 16f2b5793..c1a3735c8 100644 --- a/_dev/js/front/test/mocks/html-templates/prestashop-site-1_7.js +++ b/_dev/js/front/test/mocks/html-templates/prestashop-site-1_7.js @@ -33,3 +33,15 @@ export function mockProductPage() { .getElementById('product-details') .setAttribute('data-product', JSON.stringify(PRODUCT_DATASET)); } + +export function mockCheckoutVars() { + window.ps_checkoutPayPalOrderId = ''; + window.ps_checkoutPayPalClientToken = ''; + window.ps_checkoutPayPalSdkConfig = { + clientId: 'test', + currency: 'EUR', + intent: 'capture', + integrationDate: '2022-14-06', + components: 'marks,funding-eligibility,buttons' + }; +} From 1d2c229a9db3694396de5e9da000a4ff34e561fc Mon Sep 17 00:00:00 2001 From: Laurynas Date: Mon, 2 Oct 2023 17:30:36 +0300 Subject: [PATCH 096/343] Replaced paypal script creation with using paypal npm lib --- _dev/js/front/package-lock.json | 10562 +--------------- .../common/payment-option.component.js | 2 +- _dev/js/front/src/service/paypal.service.js | 66 +- .../prestashop-ps1_7.service.js | 2 +- .../default-selectors-ps1_7-hummingbird.js | 47 + .../query-selector-ps1_7.service.js | 5 +- config/common.yml | 5 +- ps_checkout.php | 44 +- .../PayPalSdkConfigurationBuilder.php} | 37 +- .../index.php | 0 views/css/payments.css | 7 +- .../hook/displayPaymentByBinaries.tpl | 2 +- views/templates/hook/displayPaymentReturn.tpl | 2 +- views/templates/hook/header.tpl | 21 - 14 files changed, 164 insertions(+), 10638 deletions(-) create mode 100644 _dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js rename src/Builder/{PayPalSdkLink/PayPalSdkLinkBuilder.php => PayPalSdkConfiguration/PayPalSdkConfigurationBuilder.php} (93%) rename src/Builder/{PayPalSdkLink => PayPalSdkConfiguration}/index.php (100%) delete mode 100644 views/templates/hook/header.tpl diff --git a/_dev/js/front/package-lock.json b/_dev/js/front/package-lock.json index 29e3b08db..6f1ba4611 100644 --- a/_dev/js/front/package-lock.json +++ b/_dev/js/front/package-lock.json @@ -1,10512 +1,7 @@ { "name": "ps_checkout-frontoffice", - "lockfileVersion": 2, "requires": true, - "packages": { - "": { - "name": "ps_checkout-frontoffice", - "license": "AFL-3.0", - "dependencies": { - "@ungap/event-target": "^0.2.2", - "classlist-polyfill": "^1.2.0", - "promise-polyfill": "8.1.3", - "url-polyfill": "^1.1.9", - "whatwg-fetch": "^3.0.0" - }, - "devDependencies": { - "@babel/core": "^7.12.3", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/preset-env": "^7.12.1", - "babel-eslint": "^10.1.0", - "babel-jest": "^26.6.3", - "babel-loader": "^8.1.0", - "bottlejs": "^2.0.0", - "core-js": "^3.7.0", - "eslint": "^7.12.1", - "eslint-plugin-babel": "^5.3.1", - "eslint-plugin-prettier": "^3.1.4", - "jest": "^26.6.3", - "prettier": "^2.1.2", - "regenerator-runtime": "^0.13.7", - "terser-webpack-plugin": "^4.2.3", - "webpack": "^4.44.2", - "webpack-cli": "^4.1.0", - "webpack-merge": "^5.3.0" - } - }, - "node_modules/@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/@babel/compat-data": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.12.1.tgz", - "integrity": "sha512-725AQupWJZ8ba0jbKceeFblZTY90McUBWMwHhkFQ9q1zKPJ95GUktljFcgcsIVwRnTnRKlcYzfiNImg5G9m6ZQ==", - "dev": true - }, - "node_modules/@babel/core": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.12.3.tgz", - "integrity": "sha512-0qXcZYKZp3/6N2jKYVxZv0aNCsxTSVCiK72DTiTYZAu7sjg73W0/aynWjMbiGd87EQL4WyA8reiJVh92AVla9g==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helpers": "^7.12.1", - "@babel/parser": "^7.12.3", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "convert-source-map": "^1.7.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.1", - "json5": "^2.1.2", - "lodash": "^4.17.19", - "resolve": "^1.3.2", - "semver": "^5.4.1", - "source-map": "^0.5.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/core/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.12.1.tgz", - "integrity": "sha512-DB+6rafIdc9o72Yc3/Ph5h+6hUjeOp66pF0naQBgUFFuPqzQwIlPTm3xZR7YNvduIMtkDIj2t21LSQwnbCrXvg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1", - "jsesc": "^2.5.1", - "source-map": "^0.5.0" - } - }, - "node_modules/@babel/generator/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.10.4.tgz", - "integrity": "sha512-XQlqKQP4vXFB7BN8fEEerrmYvHp3fK/rBkRFz9jaJbzK0B1DSfej9Kc7ZzE8Z/OnId1jpJdNAZ3BFQjWG68rcA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.10.4.tgz", - "integrity": "sha512-L0zGlFrGWZK4PbT8AszSfLTM5sDU1+Az/En9VrdT8/LmEiJt4zXt+Jve9DCAnQcbqDhCI+29y/L93mrDzddCcg==", - "dev": true, - "dependencies": { - "@babel/helper-explode-assignable-expression": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.12.1.tgz", - "integrity": "sha512-jtBEif7jsPwP27GPHs06v4WBV0KrE8a/P7n0N0sSvHn2hwUCYnolP/CLmz51IzAW4NlN+HuoBtb9QcwnRo9F/g==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.12.1", - "@babel/helper-validator-option": "^7.12.1", - "browserslist": "^4.12.0", - "semver": "^5.5.0" - } - }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.12.1.tgz", - "integrity": "sha512-hkL++rWeta/OVOBTRJc9a5Azh5mt5WgZUGAKMD8JM141YsE08K//bp1unBBieO6rUKkIPyUE0USQ30jAy3Sk1w==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4" - } - }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.12.1.tgz", - "integrity": "sha512-rsZ4LGvFTZnzdNZR5HZdmJVuXK8834R5QkF3WvcnBhrlVtF0HSIUC6zbreL9MgjTywhKokn8RIYRiq99+DLAxA==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-regex": "^7.10.4", - "regexpu-core": "^4.7.1" - } - }, - "node_modules/@babel/helper-define-map": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-define-map/-/helper-define-map-7.10.5.tgz", - "integrity": "sha512-fMw4kgFB720aQFXSVaXr79pjjcW5puTCM16+rECJ/plGS+zByelE8l9nCpV1GibxTnFVmUuYG9U8wYfQHdzOEQ==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/types": "^7.10.5", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-explode-assignable-expression": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.12.1.tgz", - "integrity": "sha512-dmUwH8XmlrUpVqgtZ737tK88v07l840z9j3OEhCLwKTkjlvKpfqXVIZ0wpK3aeOxspwGrf/5AP5qLx4rO3w5rA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-function-name": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", - "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", - "dev": true, - "dependencies": { - "@babel/helper-get-function-arity": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-get-function-arity": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", - "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-hoist-variables": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.10.4.tgz", - "integrity": "sha512-wljroF5PgCk2juF69kanHVs6vrLwIPNp6DLD+Lrl3hoQ3PpPPikaDRNFA+0t81NOoMt2DL6WW/mdU8k4k6ZzuA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.12.1.tgz", - "integrity": "sha512-k0CIe3tXUKTRSoEx1LQEPFU9vRQfqHtl+kf8eNnDqb4AUJEy5pz6aIiog+YWtVm2jpggjS1laH68bPsR+KWWPQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-module-imports": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.12.1.tgz", - "integrity": "sha512-ZeC1TlMSvikvJNy1v/wPIazCu3NdOwgYZLIkmIyAsGhqkNpiDoQQRmaCK8YP4Pq3GPTLPV9WXaPCJKvx06JxKA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.12.1.tgz", - "integrity": "sha512-QQzehgFAZ2bbISiCpmVGfiGux8YVFXQ0abBic2Envhej22DVXV9nCFaS5hIQbkyo1AdGb+gNME2TSh3hYJVV/w==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-simple-access": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/helper-validator-identifier": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.10.4.tgz", - "integrity": "sha512-n3UGKY4VXwXThEiKrgRAoVPBMqeoPgHVqiHZOanAJCG9nQUL2pLRQirUzl0ioKclHGpGqRgIOkgcIJaIWLpygg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.10.4.tgz", - "integrity": "sha512-O4KCvQA6lLiMU9l2eawBPMf1xPP8xPfB3iEQw150hOVTqj/rfXz0ThTb4HEzqQfs2Bmo5Ay8BzxfzVtBrr9dVg==", - "dev": true - }, - "node_modules/@babel/helper-regex": { - "version": "7.10.5", - "resolved": "https://registry.npmjs.org/@babel/helper-regex/-/helper-regex-7.10.5.tgz", - "integrity": "sha512-68kdUAzDrljqBrio7DYAEgCoJHxppJOERHOgOrDN7WjOzP0ZQ1LsSDRXcemzVZaLvjaJsJEESb6qt+znNuENDg==", - "dev": true, - "dependencies": { - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.12.1.tgz", - "integrity": "sha512-9d0KQCRM8clMPcDwo8SevNs+/9a8yWVVmaE80FGJcEP8N1qToREmWEGnBn8BUlJhYRFz6fqxeRL1sl5Ogsed7A==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-wrap-function": "^7.10.4", - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.12.1.tgz", - "integrity": "sha512-zJjTvtNJnCFsCXVi5rUInstLd/EIVNmIKA1Q9ynESmMBWPWd+7sdR+G4/wdu+Mppfep0XLyG2m7EBPvjCeFyrw==", - "dev": true, - "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.12.1", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-simple-access": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.12.1.tgz", - "integrity": "sha512-OxBp7pMrjVewSSC8fXDFrHrBcJATOOFssZwv16F3/6Xtc138GHybBfPbm9kfiqQHKhYQrlamWILwlDCeyMFEaA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", - "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", - "dev": true, - "dependencies": { - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/helper-split-export-declaration": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", - "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.11.0" - } - }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", - "dev": true - }, - "node_modules/@babel/helper-validator-option": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.1.tgz", - "integrity": "sha512-YpJabsXlJVWP0USHjnC/AQDTLlZERbON577YUVO/wLpqyj6HAtVYnWaQaN0iUN+1/tWn3c+uKKXjRut5115Y2A==", - "dev": true - }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.12.3.tgz", - "integrity": "sha512-Cvb8IuJDln3rs6tzjW3Y8UeelAOdnpB8xtQ4sme2MSZ9wOxrbThporC0y/EtE16VAtoyEfLM404Xr1e0OOp+ow==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/helpers": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.12.1.tgz", - "integrity": "sha512-9JoDSBGoWtmbay98efmT2+mySkwjzeFeAL9BuWNoVQpkPFQF8SIIFUfY5os9u8wVzglzoiPRSW7cuJmBDUt43g==", - "dev": true, - "dependencies": { - "@babel/template": "^7.10.4", - "@babel/traverse": "^7.12.1", - "@babel/types": "^7.12.1" - } - }, - "node_modules/@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - } - }, - "node_modules/@babel/highlight/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/@babel/highlight/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/@babel/highlight/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/highlight/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/parser": { - "version": "7.12.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.12.3.tgz", - "integrity": "sha512-kFsOS0IbsuhO5ojF8Hc8z/8vEIOkylVBrjiZUbLTE3XFe0Qi+uu6HjzQixkFaqr0ZPAMZcBVxEwmsnsLPZ2Xsw==", - "dev": true, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/@babel/plugin-proposal-async-generator-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.12.1.tgz", - "integrity": "sha512-d+/o30tJxFxrA1lhzJqiUcEJdI6jKlNregCv5bASeGf2Q4MXmnwH7viDo7nhx1/ohf09oaH8j1GVYG/e3Yqk6A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.12.1.tgz", - "integrity": "sha512-cKp3dlQsFsEs5CWKnN7BnSHOd0EOW8EKpEjkoz1pO2E5KzIDNV9Ros1b0CnmbVgAGXJubOYVBOGCT1OmJwOI7w==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-dynamic-import": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.12.1.tgz", - "integrity": "sha512-a4rhUSZFuq5W8/OO8H7BL5zspjnc1FLd9hlOxIK/f7qG4a0qsqk8uvF/ywgBA8/OmjsapjpvaEOYItfGG1qIvQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-dynamic-import": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-export-namespace-from": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.12.1.tgz", - "integrity": "sha512-6CThGf0irEkzujYS5LQcjBx8j/4aQGiVv7J9+2f7pGfxqyKh3WnmVJYW3hdrQjyksErMGBPQrCnHfOtna+WLbw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3" - } - }, - "node_modules/@babel/plugin-proposal-json-strings": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.12.1.tgz", - "integrity": "sha512-GoLDUi6U9ZLzlSda2Df++VSqDJg3CG+dR0+iWsv6XRw1rEq+zwt4DirM9yrxW6XWaTpmai1cWJLMfM8qQJf+yw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-json-strings": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-logical-assignment-operators": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.12.1.tgz", - "integrity": "sha512-k8ZmVv0JU+4gcUGeCDZOGd0lCIamU/sMtIiX3UWnUc5yzgq6YUGyEolNYD+MLYKfSzgECPcqetVcJP9Afe/aCA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-nullish-coalescing-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.12.1.tgz", - "integrity": "sha512-nZY0ESiaQDI1y96+jk6VxMOaL4LPo/QDHBqL+SF3/vl6dHkTwHlOI8L4ZwuRBHgakRBw5zsVylel7QPbbGuYgg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-numeric-separator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.12.1.tgz", - "integrity": "sha512-MR7Ok+Af3OhNTCxYVjJZHS0t97ydnJZt/DbR4WISO39iDnhiD8XHrY12xuSJ90FFEGjir0Fzyyn7g/zY6hxbxA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-numeric-separator": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-object-rest-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.12.1.tgz", - "integrity": "sha512-s6SowJIjzlhx8o7lsFx5zmY4At6CTtDvgNQDdPzkBQucle58A6b/TTeEBYtyDgmcXjUTM+vE8YOGHZzzbc/ioA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-transform-parameters": "^7.12.1" - } - }, - "node_modules/@babel/plugin-proposal-optional-catch-binding": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.12.1.tgz", - "integrity": "sha512-hFvIjgprh9mMw5v42sJWLI1lzU5L2sznP805zeT6rySVRA0Y18StRhDqhSxlap0oVgItRsB6WSROp4YnJTJz0g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-optional-chaining": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.12.1.tgz", - "integrity": "sha512-c2uRpY6WzaVDzynVY9liyykS+kVU+WRZPMPYpkelXH8KBt1oXoI89kPbZKKG/jDT5UK92FTW2fZkZaJhdiBabw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", - "@babel/plugin-syntax-optional-chaining": "^7.8.0" - } - }, - "node_modules/@babel/plugin-proposal-private-methods": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.12.1.tgz", - "integrity": "sha512-mwZ1phvH7/NHK6Kf8LP7MYDogGV+DKB1mryFOEwx5EBNQrosvIczzZFTUmWaeujd5xT6G1ELYWUz3CutMhjE1w==", - "dev": true, - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-proposal-unicode-property-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.1.tgz", - "integrity": "sha512-MYq+l+PvHuw/rKUz1at/vb6nCnQ2gmJBNaM62z0OgH7B2W1D9pvkpYtlti9bGtizNIU1K3zm4bZF9F91efVY0w==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/@babel/plugin-syntax-async-generators": { - "version": "7.8.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", - "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-bigint": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz", - "integrity": "sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-class-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.1.tgz", - "integrity": "sha512-U40A76x5gTwmESz+qiqssqmeEsKvcSyvtgktrm0uzcARAmM9I1jR221f6Oq+GmHrcD+LvZDag1UTOTe2fL3TeA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-syntax-dynamic-import": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", - "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-export-namespace-from": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", - "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.3" - } - }, - "node_modules/@babel/plugin-syntax-import-meta": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz", - "integrity": "sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-syntax-json-strings": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", - "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-logical-assignment-operators": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", - "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-syntax-nullish-coalescing-operator": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", - "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-numeric-separator": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", - "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-syntax-object-rest-spread": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", - "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-catch-binding": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", - "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-optional-chaining": { - "version": "7.8.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", - "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.8.0" - } - }, - "node_modules/@babel/plugin-syntax-top-level-await": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.1.tgz", - "integrity": "sha512-i7ooMZFS+a/Om0crxZodrTzNEPJHZrlMVGMTEpFAj6rYY/bKCddB0Dk/YxfPuYXOopuhKk/e1jV6h+WUU9XN3A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.12.1.tgz", - "integrity": "sha512-5QB50qyN44fzzz4/qxDPQMBCTHgxg3n0xRBLJUmBlLoU/sFvxVWGZF/ZUfMVDQuJUKXaBhbupxIzIfZ6Fwk/0A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.12.1.tgz", - "integrity": "sha512-SDtqoEcarK1DFlRJ1hHRY5HvJUj5kX4qmtpMAm2QnhOlyuMC4TMdCRgW6WXpv93rZeYNeLP22y8Aq2dbcDRM1A==", - "dev": true, - "dependencies": { - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-remap-async-to-generator": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.1.tgz", - "integrity": "sha512-5OpxfuYnSgPalRpo8EWGPzIYf0lHBWORCkj5M0oLBwHdlux9Ri36QqGW3/LR13RSVOAoUUMzoPI/jpE4ABcHoA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.12.1.tgz", - "integrity": "sha512-zJyAC9sZdE60r1nVQHblcfCj29Dh2Y0DOvlMkcqSo0ckqjiCwNiUezUKw+RjOCwGfpLRwnAeQ2XlLpsnGkvv9w==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.12.1.tgz", - "integrity": "sha512-/74xkA7bVdzQTBeSUhLLJgYIcxw/dpEpCdRDiHgPJ3Mv6uC11UhjpOhl72CgqbBCmt1qtssCyB2xnJm1+PFjog==", - "dev": true, - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.10.4", - "@babel/helper-define-map": "^7.10.4", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-optimise-call-expression": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1", - "@babel/helper-split-export-declaration": "^7.10.4", - "globals": "^11.1.0" - } - }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.12.1.tgz", - "integrity": "sha512-vVUOYpPWB7BkgUWPo4C44mUQHpTZXakEqFjbv8rQMg7TC6S6ZhGZ3otQcRH6u7+adSlE5i0sp63eMC/XGffrzg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.12.1.tgz", - "integrity": "sha512-fRMYFKuzi/rSiYb2uRLiUENJOKq4Gnl+6qOv5f8z0TZXg3llUwUhsNNwrwaT/6dUhJTzNpBr+CUvEWBtfNY1cw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.1.tgz", - "integrity": "sha512-B2pXeRKoLszfEW7J4Hg9LoFaWEbr/kzo3teWHmtFCszjRNa/b40f9mfeqZsIDLLt/FjwQ6pz/Gdlwy85xNckBA==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.1.tgz", - "integrity": "sha512-iRght0T0HztAb/CazveUpUQrZY+aGKKaWXMJ4uf9YJtqxSUe09j3wteztCUDRHs+SRAL7yMuFqUsLoAKKzgXjw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.1.tgz", - "integrity": "sha512-7tqwy2bv48q+c1EHbXK0Zx3KXd2RVQp6OC7PbwFNt/dPTAV3Lu5sWtWuAj8owr5wqtWnqHfl2/mJlUmqkChKug==", - "dev": true, - "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.12.1.tgz", - "integrity": "sha512-Zaeq10naAsuHo7heQvyV0ptj4dlZJwZgNAtBYBnu5nNKJoW62m0zKcIEyVECrUKErkUkg6ajMy4ZfnVZciSBhg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.1.tgz", - "integrity": "sha512-JF3UgJUILoFrFMEnOJLJkRHSk6LUSXLmEFsA23aR2O5CSLUxbeUX1IZ1YQ7Sn0aXb601Ncwjx73a+FVqgcljVw==", - "dev": true, - "dependencies": { - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.1.tgz", - "integrity": "sha512-+PxVGA+2Ag6uGgL0A5f+9rklOnnMccwEBzwYFL3EUaKuiyVnUipyXncFcfjSkbimLrODoqki1U9XxZzTvfN7IQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.1.tgz", - "integrity": "sha512-1sxePl6z9ad0gFMB9KqmYofk34flq62aqMt9NqliS/7hPEpURUCMbyHXrMPlo282iY7nAvUB1aQd5mg79UD9Jg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.12.1.tgz", - "integrity": "sha512-tDW8hMkzad5oDtzsB70HIQQRBiTKrhfgwC/KkJeGsaNFTdWhKNt/BiE8c5yj19XiGyrxpbkOfH87qkNg1YGlOQ==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.12.1.tgz", - "integrity": "sha512-dY789wq6l0uLY8py9c1B48V8mVL5gZh/+PQ5ZPrylPYsnAvnEMjqsUXkuoDVPeVK+0VyGar+D08107LzDQ6pag==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-simple-access": "^7.12.1", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.12.1.tgz", - "integrity": "sha512-Hn7cVvOavVh8yvW6fLwveFqSnd7rbQN3zJvoPNyNaQSvgfKmDBO9U1YL9+PCXGRlZD9tNdWTy5ACKqMuzyn32Q==", - "dev": true, - "dependencies": { - "@babel/helper-hoist-variables": "^7.10.4", - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-identifier": "^7.10.4", - "babel-plugin-dynamic-import-node": "^2.3.3" - } - }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.12.1.tgz", - "integrity": "sha512-aEIubCS0KHKM0zUos5fIoQm+AZUMt1ZvMpqz0/H5qAQ7vWylr9+PLYurT+Ic7ID/bKLd4q8hDovaG3Zch2uz5Q==", - "dev": true, - "dependencies": { - "@babel/helper-module-transforms": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.1.tgz", - "integrity": "sha512-tB43uQ62RHcoDp9v2Nsf+dSM8sbNodbEicbQNA53zHz8pWUhsgHSJCGpt7daXxRydjb0KnfmB+ChXOv3oADp1Q==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.1.tgz", - "integrity": "sha512-+eW/VLcUL5L9IvJH7rT1sT0CzkdUTvPrXC2PXTn/7z7tXLBuKvezYbGdxD5WMRoyvyaujOq2fWoKl869heKjhw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.1.tgz", - "integrity": "sha512-AvypiGJH9hsquNUn+RXVcBdeE3KHPZexWRdimhuV59cSoOt5kFBmqlByorAeUlGG2CJWd0U+4ZtNKga/TB0cAw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-replace-supers": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.12.1.tgz", - "integrity": "sha512-xq9C5EQhdPK23ZeCdMxl8bbRnAgHFrw5EOC3KJUsSylZqdkCaFEXxGSBuTSObOpiiHHNyb82es8M1QYgfQGfNg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.1.tgz", - "integrity": "sha512-6MTCR/mZ1MQS+AwZLplX4cEySjCpnIF26ToWo942nqn8hXSm7McaHQNeGx/pt7suI1TWOWMfa/NgBhiqSnX0cQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.12.1.tgz", - "integrity": "sha512-gYrHqs5itw6i4PflFX3OdBPMQdPbF4bj2REIUxlMRUFk0/ZOAIpDFuViuxPjUL7YC8UPnf+XG7/utJvqXdPKng==", - "dev": true, - "dependencies": { - "regenerator-transform": "^0.14.2" - } - }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.1.tgz", - "integrity": "sha512-pOnUfhyPKvZpVyBHhSBoX8vfA09b7r00Pmm1sH+29ae2hMTKVmSp4Ztsr8KBKjLjx17H0eJqaRC3bR2iThM54A==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.1.tgz", - "integrity": "sha512-GFZS3c/MhX1OusqB1MZ1ct2xRzX5ppQh2JU1h2Pnfk88HtFTM+TWQqJNfwkmxtPQtb/s1tk87oENfXJlx7rSDw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.12.1.tgz", - "integrity": "sha512-vuLp8CP0BE18zVYjsEBZ5xoCecMK6LBMMxYzJnh01rxQRvhNhH1csMMmBfNo5tGpGO+NhdSNW2mzIvBu3K1fng==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" - } - }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.1.tgz", - "integrity": "sha512-CiUgKQ3AGVk7kveIaPEET1jNDhZZEl1RPMWdTBE1799bdz++SwqDHStmxfCtDfBhQgCl38YRiSnrMuUMZIWSUQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-regex": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.12.1.tgz", - "integrity": "sha512-b4Zx3KHi+taXB1dVRBhVJtEPi9h1THCeKmae2qP0YdUHIFhVjtpqqNfxeVAa1xeHVhAy4SbHxEwx5cltAu5apw==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.1.tgz", - "integrity": "sha512-EPGgpGy+O5Kg5pJFNDKuxt9RdmTgj5sgrus2XVeMp/ZIbOESadgILUbm50SNpghOh3/6yrbsH+NB5+WJTmsA7Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.1.tgz", - "integrity": "sha512-I8gNHJLIc7GdApm7wkVnStWssPNbSRMPtgHdmH3sRM1zopz09UWPS4x5V4n1yz/MIWTVnJ9sp6IkuXdWM4w+2Q==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.1.tgz", - "integrity": "sha512-SqH4ClNngh/zGwHZOOQMTD+e8FGWexILV+ePMyiDJttAWRh5dhDL8rcl5lSgU3Huiq6Zn6pWTMvdPAb21Dwdyg==", - "dev": true, - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.12.1.tgz", - "integrity": "sha512-H8kxXmtPaAGT7TyBvSSkoSTUK6RHh61So05SyEbpmr0MCZrsNYn7mGMzzeYoOUCdHzww61k8XBft2TaES+xPLg==", - "dev": true, - "dependencies": { - "@babel/compat-data": "^7.12.1", - "@babel/helper-compilation-targets": "^7.12.1", - "@babel/helper-module-imports": "^7.12.1", - "@babel/helper-plugin-utils": "^7.10.4", - "@babel/helper-validator-option": "^7.12.1", - "@babel/plugin-proposal-async-generator-functions": "^7.12.1", - "@babel/plugin-proposal-class-properties": "^7.12.1", - "@babel/plugin-proposal-dynamic-import": "^7.12.1", - "@babel/plugin-proposal-export-namespace-from": "^7.12.1", - "@babel/plugin-proposal-json-strings": "^7.12.1", - "@babel/plugin-proposal-logical-assignment-operators": "^7.12.1", - "@babel/plugin-proposal-nullish-coalescing-operator": "^7.12.1", - "@babel/plugin-proposal-numeric-separator": "^7.12.1", - "@babel/plugin-proposal-object-rest-spread": "^7.12.1", - "@babel/plugin-proposal-optional-catch-binding": "^7.12.1", - "@babel/plugin-proposal-optional-chaining": "^7.12.1", - "@babel/plugin-proposal-private-methods": "^7.12.1", - "@babel/plugin-proposal-unicode-property-regex": "^7.12.1", - "@babel/plugin-syntax-async-generators": "^7.8.0", - "@babel/plugin-syntax-class-properties": "^7.12.1", - "@babel/plugin-syntax-dynamic-import": "^7.8.0", - "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.0", - "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.0", - "@babel/plugin-syntax-numeric-separator": "^7.10.4", - "@babel/plugin-syntax-object-rest-spread": "^7.8.0", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.0", - "@babel/plugin-syntax-optional-chaining": "^7.8.0", - "@babel/plugin-syntax-top-level-await": "^7.12.1", - "@babel/plugin-transform-arrow-functions": "^7.12.1", - "@babel/plugin-transform-async-to-generator": "^7.12.1", - "@babel/plugin-transform-block-scoped-functions": "^7.12.1", - "@babel/plugin-transform-block-scoping": "^7.12.1", - "@babel/plugin-transform-classes": "^7.12.1", - "@babel/plugin-transform-computed-properties": "^7.12.1", - "@babel/plugin-transform-destructuring": "^7.12.1", - "@babel/plugin-transform-dotall-regex": "^7.12.1", - "@babel/plugin-transform-duplicate-keys": "^7.12.1", - "@babel/plugin-transform-exponentiation-operator": "^7.12.1", - "@babel/plugin-transform-for-of": "^7.12.1", - "@babel/plugin-transform-function-name": "^7.12.1", - "@babel/plugin-transform-literals": "^7.12.1", - "@babel/plugin-transform-member-expression-literals": "^7.12.1", - "@babel/plugin-transform-modules-amd": "^7.12.1", - "@babel/plugin-transform-modules-commonjs": "^7.12.1", - "@babel/plugin-transform-modules-systemjs": "^7.12.1", - "@babel/plugin-transform-modules-umd": "^7.12.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.1", - "@babel/plugin-transform-new-target": "^7.12.1", - "@babel/plugin-transform-object-super": "^7.12.1", - "@babel/plugin-transform-parameters": "^7.12.1", - "@babel/plugin-transform-property-literals": "^7.12.1", - "@babel/plugin-transform-regenerator": "^7.12.1", - "@babel/plugin-transform-reserved-words": "^7.12.1", - "@babel/plugin-transform-shorthand-properties": "^7.12.1", - "@babel/plugin-transform-spread": "^7.12.1", - "@babel/plugin-transform-sticky-regex": "^7.12.1", - "@babel/plugin-transform-template-literals": "^7.12.1", - "@babel/plugin-transform-typeof-symbol": "^7.12.1", - "@babel/plugin-transform-unicode-escapes": "^7.12.1", - "@babel/plugin-transform-unicode-regex": "^7.12.1", - "@babel/preset-modules": "^0.1.3", - "@babel/types": "^7.12.1", - "core-js-compat": "^3.6.2", - "semver": "^5.5.0" - } - }, - "node_modules/@babel/preset-modules": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", - "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", - "@babel/plugin-transform-dotall-regex": "^7.4.4", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - } - }, - "node_modules/@babel/runtime": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.12.1.tgz", - "integrity": "sha512-J5AIf3vPj3UwXaAzb5j1xM4WAQDX3EMgemF8rjCP3SoW09LfRKAXQKt6CoVYl230P6iWdRcBbnLDDdnqWxZSCA==", - "dev": true, - "dependencies": { - "regenerator-runtime": "^0.13.4" - } - }, - "node_modules/@babel/template": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", - "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/parser": "^7.10.4", - "@babel/types": "^7.10.4" - } - }, - "node_modules/@babel/traverse": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.12.1.tgz", - "integrity": "sha512-MA3WPoRt1ZHo2ZmoGKNqi20YnPt0B1S0GTZEPhhd+hw2KGUzBlHuVunj6K4sNuK+reEvyiPwtp0cpaqLzJDmAw==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.10.4", - "@babel/generator": "^7.12.1", - "@babel/helper-function-name": "^7.10.4", - "@babel/helper-split-export-declaration": "^7.11.0", - "@babel/parser": "^7.12.1", - "@babel/types": "^7.12.1", - "debug": "^4.1.0", - "globals": "^11.1.0", - "lodash": "^4.17.19" - } - }, - "node_modules/@babel/types": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.12.1.tgz", - "integrity": "sha512-BzSY3NJBKM4kyatSOWh3D/JJ2O3CVzBybHWxtgxnggaxEuaSTTDqeiSb/xk9lrkw2Tbqyivw5ZU4rT+EfznQsA==", - "dev": true, - "dependencies": { - "@babel/helper-validator-identifier": "^7.10.4", - "lodash": "^4.17.19", - "to-fast-properties": "^2.0.0" - } - }, - "node_modules/@bcoe/v8-coverage": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz", - "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==", - "dev": true - }, - "node_modules/@cnakazawa/watch": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@cnakazawa/watch/-/watch-1.0.4.tgz", - "integrity": "sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==", - "dev": true, - "dependencies": { - "exec-sh": "^0.3.2", - "minimist": "^1.2.0" - }, - "bin": { - "watch": "cli.js" - }, - "engines": { - "node": ">=0.1.95" - } - }, - "node_modules/@eslint/eslintrc": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz", - "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==", - "dev": true, - "dependencies": { - "ajv": "^6.12.4", - "debug": "^4.1.1", - "espree": "^7.3.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.2.1", - "js-yaml": "^3.13.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "strip-json-comments": "^3.1.1" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/@eslint/eslintrc/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", - "integrity": "sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.3.1", - "find-up": "^4.1.0", - "get-package-type": "^0.1.0", - "js-yaml": "^3.13.1", - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/load-nyc-config/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@istanbuljs/schema": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.3.tgz", - "integrity": "sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@jest/console": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-26.6.2.tgz", - "integrity": "sha512-IY1R2i2aLsLr7Id3S6p2BA82GNWryt4oSvEXLAKc+L2zdi89dSkE8xC1C+0kpATG4JhBJREnQOH7/zmccM2B0g==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^26.6.2", - "jest-util": "^26.6.2", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-26.6.3.tgz", - "integrity": "sha512-xvV1kKbhfUqFVuZ8Cyo+JPpipAHHAV3kcDBftiduK8EICXmTFddryy3P7NfZt8Pv37rA9nEJBKCCkglCPt/Xjw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/reporters": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-changed-files": "^26.6.2", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-resolve-dependencies": "^26.6.3", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "jest-watcher": "^26.6.2", - "micromatch": "^4.0.2", - "p-each-series": "^2.1.0", - "rimraf": "^3.0.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/core/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@jest/environment": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-26.6.2.tgz", - "integrity": "sha512-nFy+fHl28zUrRsCeMB61VDThV1pVTtlEokBRgqPrcT1JNq4yRNIyTHfyht6PqtUvY9IsuLGTrbG8kPXjSZIZwA==", - "dev": true, - "dependencies": { - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/fake-timers": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.6.2.tgz", - "integrity": "sha512-14Uleatt7jdzefLPYM3KLcnUl1ZNikaKq34enpb5XG9i81JpppDb5muZvonvKyrl7ftEHkKS5L5/eB/kxJ+bvA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@sinonjs/fake-timers": "^6.0.1", - "@types/node": "*", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/globals": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-26.6.2.tgz", - "integrity": "sha512-85Ltnm7HlB/KesBUuALwQ68YTU72w9H2xW9FjZ1eL1U3lhtefjjl5c2MiUbpXt/i6LaPRvoOFJ22yCBSfQ0JIA==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/types": "^26.6.2", - "expect": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/reporters": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-26.6.2.tgz", - "integrity": "sha512-h2bW53APG4HvkOnVMo8q3QXa6pcaNt1HkwVsOPMBV6LD/q9oSpxNSYZQYkAnjdMjrJ86UuYeLo+aEZClV6opnw==", - "dev": true, - "dependencies": { - "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.2.4", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-instrument": "^4.0.3", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "jest-haste-map": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "slash": "^3.0.0", - "source-map": "^0.6.0", - "string-length": "^4.0.1", - "terminal-link": "^2.0.0", - "v8-to-istanbul": "^7.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "node-notifier": "^8.0.0" - } - }, - "node_modules/@jest/source-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-26.6.2.tgz", - "integrity": "sha512-YwYcCwAnNmOVsZ8mr3GfnzdXDAl4LaenZP5z+G0c8bzC9/dugL8zRmxZzdoTl4IaS3CryS1uWnROLPFmb6lVvA==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0", - "graceful-fs": "^4.2.4", - "source-map": "^0.6.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-result": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-26.6.2.tgz", - "integrity": "sha512-5O7H5c/7YlojphYNrK02LlDIV2GNPYisKwHm2QTKjNZeEzezCbwYs9swJySv2UfPMyZ0VdsmMv7jIlD/IKYQpQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/test-sequencer": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.6.3.tgz", - "integrity": "sha512-YHlVIjP5nfEyjlrSr8t/YdNfU/1XEt7c5b4OxcXCjyRhjzLYu/rO69/WHPuYcbCWkz8kAeZVZp2N2+IOLLEPGw==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-runner": "^26.6.3", - "jest-runtime": "^26.6.3" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/transform": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-26.6.2.tgz", - "integrity": "sha512-E9JjhUgNzvuQ+vVAL21vlyfy12gP0GhazGgJC4h6qUt1jSdUXGWJ1wfu/X7Sd8etSgxV4ovT1pb9v5D6QW4XgA==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/types": "^26.6.2", - "babel-plugin-istanbul": "^6.0.0", - "chalk": "^4.0.0", - "convert-source-map": "^1.4.0", - "fast-json-stable-stringify": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-haste-map": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-util": "^26.6.2", - "micromatch": "^4.0.2", - "pirates": "^4.0.1", - "slash": "^3.0.0", - "source-map": "^0.6.1", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@jest/types": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-26.6.2.tgz", - "integrity": "sha512-fC6QCp7Sc5sX6g8Tvbmj4XUTbyrik0akgRy03yjXbQaBWWNWGE7SGtJk98m0N8nzegD/7SggrUlivxo5ax4KWQ==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", - "@types/node": "*", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/@npmcli/move-file": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.0.1.tgz", - "integrity": "sha512-Uv6h1sT+0DrblvIrolFtbvM1FgWm+/sy4B3pvLp67Zys+thcukzS5ekn7HsZFGpWP4Q3fYJCljbWQE/XivMRLw==", - "dev": true, - "dependencies": { - "mkdirp": "^1.0.4" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@npmcli/move-file/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@sinonjs/commons": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", - "integrity": "sha512-sruwd86RJHdsVf/AtBoijDmUqJp3B6hF/DGC23C+JaegnDHaZyewCjoVGTdg3J0uz3Zs7NnIT05OBOmML72lQw==", - "dev": true, - "dependencies": { - "type-detect": "4.0.8" - } - }, - "node_modules/@sinonjs/fake-timers": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz", - "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, - "node_modules/@types/babel__core": { - "version": "7.1.12", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.12.tgz", - "integrity": "sha512-wMTHiiTiBAAPebqaPiPDLFA4LYPKr6Ph0Xq/6rq1Ur3v66HXyG+clfR9CNETkD7MQS8ZHvpQOtA53DLws5WAEQ==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" - } - }, - "node_modules/@types/babel__generator": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.2.tgz", - "integrity": "sha512-MdSJnBjl+bdwkLskZ3NGFp9YcXGx5ggLpQQPqtgakVhsWK0hTtNYhjpZLlWQTviGTvF8at+Bvli3jV7faPdgeQ==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.0", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.0.tgz", - "integrity": "sha512-NTPErx4/FiPCGScH7foPyr+/1Dkzkni+rHiYHHoTjvwou7AQzJkNeD60A9CXRy+ZEN2B1bggmkTMCDb+Mv5k+A==", - "dev": true, - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.11.0.tgz", - "integrity": "sha512-kSjgDMZONiIfSH1Nxcr5JIRMwUetDki63FSQfpTCz8ogF3Ulqm8+mr5f78dUYs6vMiB6gBusQqfQmBvHZj/lwg==", - "dev": true, - "dependencies": { - "@babel/types": "^7.3.0" - } - }, - "node_modules/@types/graceful-fs": { - "version": "4.1.5", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.5.tgz", - "integrity": "sha512-anKkLmZZ+xm4p8JWBf4hElkM4XR+EZeA2M9BAkkTldmcyDY4mbdIJnRghDJH3Ov5ooY7/UAoENtmdMSkaAd7Cw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/istanbul-lib-coverage": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.3.tgz", - "integrity": "sha512-sz7iLqvVUg1gIedBOvlkxPlc8/uVzyS5OwGz1cKjXzkl3FpL3al0crU8YGU1WoHkxn0Wxbw5tyi6hvzJKNzFsw==", - "dev": true - }, - "node_modules/@types/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "*" - } - }, - "node_modules/@types/istanbul-reports": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.0.tgz", - "integrity": "sha512-nwKNbvnwJ2/mndE9ItP/zc2TCzw6uuodnF4EHYWD+gCQDVBuRQL5UzbZD0/ezy1iKsFU2ZQiDqg4M9dN4+wZgA==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-report": "*" - } - }, - "node_modules/@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", - "dev": true - }, - "node_modules/@types/node": { - "version": "14.14.6", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.6.tgz", - "integrity": "sha512-6QlRuqsQ/Ox/aJEQWBEJG7A9+u7oSYl3mem/K8IzxXG/kAGbV1YPD9Bg9Zw3vyxC/YP+zONKwy8hGkSt1jxFMw==", - "dev": true - }, - "node_modules/@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", - "dev": true - }, - "node_modules/@types/prettier": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.1.tgz", - "integrity": "sha512-DxZZbyMAM9GWEzXL+BMZROWz9oo6A9EilwwOMET2UVu2uZTqMWS5S69KVtuVKaRjCUpcrOXRalet86/OpG4kqw==", - "dev": true - }, - "node_modules/@types/stack-utils": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.0.tgz", - "integrity": "sha512-RJJrrySY7A8havqpGObOB4W92QXKJo63/jFLLgpvOtsGUqbQZ9Sbgl35KMm1DjC6j7AvmmU2bIno+3IyEaemaw==", - "dev": true - }, - "node_modules/@types/yargs": { - "version": "15.0.13", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-15.0.13.tgz", - "integrity": "sha512-kQ5JNTrbDv3Rp5X2n/iUu37IJBDU2gsZ5R/g1/KHOOEc5IKfUFjXT6DENPGduh08I/pamwtEq4oul7gUqKTQDQ==", - "dev": true, - "dependencies": { - "@types/yargs-parser": "*" - } - }, - "node_modules/@types/yargs-parser": { - "version": "20.2.0", - "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-20.2.0.tgz", - "integrity": "sha512-37RSHht+gzzgYeobbG+KWryeAW8J33Nhr69cjTqSYymXVZEN9NbRYWoYlRtDhHKPVT1FyNKwaTPC1NynKZpzRA==", - "dev": true - }, - "node_modules/@ungap/event-target": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/@ungap/event-target/-/event-target-0.2.2.tgz", - "integrity": "sha512-z0bsRd8APns6CDBVEPEj3p82TiFc1UY8uWNhL+T0ydpQqnpHyPVwwgJ4FC5KP85sLbg80+g+h644UTatwKNi/g==" - }, - "node_modules/@webassemblyjs/ast": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.9.0.tgz", - "integrity": "sha512-C6wW5L+b7ogSDVqymbkkvuW9kruN//YisMED04xzeBBqjHa2FYnmvOlS6Xj68xWQRgWvI9cIglsjFowH/RJyEA==", - "dev": true, - "dependencies": { - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.9.0.tgz", - "integrity": "sha512-TG5qcFsS8QB4g4MhrxK5TqfdNe7Ey/7YL/xN+36rRjl/BlGE/NcBvJcqsRgCP6Z92mRE+7N50pRIi8SmKUbcQA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.9.0.tgz", - "integrity": "sha512-NcMLjoFMXpsASZFxJ5h2HZRcEhDkvnNFOAKneP5RbKRzaWJN36NC4jqQHKwStIhGXu5mUWlUUk7ygdtrO8lbmw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.9.0.tgz", - "integrity": "sha512-qZol43oqhq6yBPx7YM3m9Bv7WMV9Eevj6kMi6InKOuZxhw+q9hOkvq5e/PpKSiLfyetpaBnogSbNCfBwyB00CA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-code-frame": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.9.0.tgz", - "integrity": "sha512-ERCYdJBkD9Vu4vtjUYe8LZruWuNIToYq/ME22igL+2vj2dQ2OOujIZr3MEFvfEaqKoVqpsFKAGsRdBSBjrIvZA==", - "dev": true, - "dependencies": { - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-fsm": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-fsm/-/helper-fsm-1.9.0.tgz", - "integrity": "sha512-OPRowhGbshCb5PxJ8LocpdX9Kl0uB4XsAjl6jH/dWKlk/mzsANvhwbiULsaiqT5GZGT9qinTICdj6PLuM5gslw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-module-context": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-module-context/-/helper-module-context-1.9.0.tgz", - "integrity": "sha512-MJCW8iGC08tMk2enck1aPW+BE5Cw8/7ph/VGZxwyvGbJwjktKkDK7vy7gAmMDx88D7mhDTCNKAW5tED+gZ0W8g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.9.0.tgz", - "integrity": "sha512-R7FStIzyNcd7xKxCZH5lE0Bqy+hGTwS3LJjuv1ZVxd9O7eHCedSdrId/hMOd20I+v8wDXEn+bjfKDLzTepoaUw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.9.0.tgz", - "integrity": "sha512-XnMB8l3ek4tvrKUUku+IVaXNHz2YsJyOOmz+MMkZvh8h1uSJpSen6vYnw3IoQ7WwEuAhL8Efjms1ZWjqh2agvw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.9.0.tgz", - "integrity": "sha512-dcX8JuYU/gvymzIHc9DgxTzUUTLexWwt8uCTWP3otys596io0L5aW02Gb1RjYpx2+0Jus1h4ZFqjla7umFniTg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.9.0.tgz", - "integrity": "sha512-ENVzM5VwV1ojs9jam6vPys97B/S65YQtv/aanqnU7D8aSoHFX8GyhGg0CMfyKNIHBuAVjy3tlzd5QMMINa7wpw==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.9.0.tgz", - "integrity": "sha512-GZbQlWtopBTP0u7cHrEx+73yZKrQoBMpwkGEIqlacljhXCkVM1kMQge/Mf+csMJAjEdSwhOyLAS0AoR3AG5P8w==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.9.0.tgz", - "integrity": "sha512-FgHzBm80uwz5M8WKnMTn6j/sVbqilPdQXTWraSjBwFXSYGirpkSWE2R9Qvz9tNiTKQvoKILpCuTjBKzOIm0nxw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/helper-wasm-section": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-opt": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "@webassemblyjs/wast-printer": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.9.0.tgz", - "integrity": "sha512-cPE3o44YzOOHvlsb4+E9qSqjc9Qf9Na1OO/BHFy4OI91XDE14MjFN4lTMezzaIWdPqHnsTodGGNP+iRSYfGkjA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.9.0.tgz", - "integrity": "sha512-Qkjgm6Anhm+OMbIL0iokO7meajkzQD71ioelnfPEj6r4eOFuqm4YC3VBPqXjFyyNwowzbMD+hizmprP/Fwkl2A==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-buffer": "1.9.0", - "@webassemblyjs/wasm-gen": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.9.0.tgz", - "integrity": "sha512-9+wkMowR2AmdSWQzsPEjFU7njh8HTO5MqO8vjwEHuM+AMHioNqSBONRdr0NQQ3dVQrzp0s8lTcYqzUdb7YgELA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-wasm-bytecode": "1.9.0", - "@webassemblyjs/ieee754": "1.9.0", - "@webassemblyjs/leb128": "1.9.0", - "@webassemblyjs/utf8": "1.9.0" - } - }, - "node_modules/@webassemblyjs/wast-parser": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-parser/-/wast-parser-1.9.0.tgz", - "integrity": "sha512-qsqSAP3QQ3LyZjNC/0jBJ/ToSxfYJ8kYyuiGvtn/8MK89VrNEfwj7BPQzJVHi0jGTRK2dGdJ5PRqhtjzoww+bw==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/floating-point-hex-parser": "1.9.0", - "@webassemblyjs/helper-api-error": "1.9.0", - "@webassemblyjs/helper-code-frame": "1.9.0", - "@webassemblyjs/helper-fsm": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.9.0.tgz", - "integrity": "sha512-2J0nE95rHXHyQ24cWjMKJ1tqB/ds8z/cyeOZxJhcb+rW+SQASVjuznUSmdz5GpVJTzU8JkhYut0D3siFDD6wsA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/wast-parser": "1.9.0", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webpack-cli/info": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.0.2.tgz", - "integrity": "sha512-FEfLQwmN4pXZSYSrtp+KC84rFanoCIxXFpS2wUvviDCE2fnajwxw2GXzbj83IlH4Dl8Wq8kJjavVwvxv3YJmnw==", - "dev": true, - "dependencies": { - "envinfo": "^7.7.3" - } - }, - "node_modules/@webpack-cli/serve": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.0.1.tgz", - "integrity": "sha512-WGMaTMTK6NOe29Hw1WBEok9vGLfKg5C6jWzNOS/6HH1YadR+RL+TRWRcSyc81Dzulljhk/Ree9mrDM4Np9GGOQ==", - "dev": true - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abab": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz", - "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", - "dev": true - }, - "node_modules/acorn": { - "version": "6.4.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.2.tgz", - "integrity": "sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-globals": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz", - "integrity": "sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==", - "dev": true, - "dependencies": { - "acorn": "^7.1.1", - "acorn-walk": "^7.1.1" - } - }, - "node_modules/acorn-globals/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz", - "integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==", - "dev": true - }, - "node_modules/acorn-walk": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", - "integrity": "sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aggregate-error": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", - "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, - "dependencies": { - "clean-stack": "^2.0.0", - "indent-string": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "node_modules/ajv-errors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-1.0.1.tgz", - "integrity": "sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ==", - "dev": true - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true - }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "dev": true, - "dependencies": { - "type-fest": "^0.11.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/anymatch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz", - "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-back": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.1.tgz", - "integrity": "sha512-Z/JnaVEXv+A9xabHzN43FiiiWEE7gPCRXMrVmRm00tWbjZRul1iHm7ECzlyNq1p4a4ATXz+G9FJ3GqGOkOV3fg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/asn1.js": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-5.4.1.tgz", - "integrity": "sha512-+I//4cYPccV8LdmBLiX8CYvf9Sp3vQsrqu2QNXRcrbiWvcx/UdlFiqUJJzxRQxgsZmvhXhn4cSKeSmoFjVdupA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/asn1.js/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-1.5.0.tgz", - "integrity": "sha512-EDsgawzwoun2CZkCgtxJbv392v4nbk9XDD06zI+kQYoBM/3RBWLlEyJARDOmhAAosBjWACEkKL6S+lIZtcAubA==", - "dev": true, - "dependencies": { - "object-assign": "^4.1.1", - "util": "0.10.3" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assert/node_modules/inherits": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.1.tgz", - "integrity": "sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE=", - "dev": true - }, - "node_modules/assert/node_modules/util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.3.tgz", - "integrity": "sha1-evsa/lCAUkZInj23/g7TeTNqwPk=", - "dev": true, - "dependencies": { - "inherits": "2.0.1" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/async-each": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.3.tgz", - "integrity": "sha512-z/WhQ5FPySLdvREByI2vZiTWwCnF0moMJ1hK9YQwDTHKh6I7/uSckMetoRGb5UBZPC1z0jlw+n/XCgjeH7y1AQ==", - "dev": true, - "optional": true - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "dev": true - }, - "node_modules/babel-eslint": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/babel-eslint/-/babel-eslint-10.1.0.tgz", - "integrity": "sha512-ifWaTHQ0ce+448CYop8AdrQiBsGrnC+bMgfyKFdi6EsPLTAWG+QfyDeM6OH+FmWnKvEq5NnBMLvlBUPKQZoDSg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@babel/parser": "^7.7.0", - "@babel/traverse": "^7.7.0", - "@babel/types": "^7.7.0", - "eslint-visitor-keys": "^1.0.0", - "resolve": "^1.12.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-26.6.3.tgz", - "integrity": "sha512-pl4Q+GAVOHwvjrck6jKjvmGhnO3jHX/xuB9d27f+EJZ/6k+6nMuPjorrYp7s++bKKdANwzElBWnLWaObvTnaZA==", - "dev": true, - "dependencies": { - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/babel__core": "^7.1.7", - "babel-plugin-istanbul": "^6.0.0", - "babel-preset-jest": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-loader": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.1.0.tgz", - "integrity": "sha512-7q7nC1tYOrqvUrN3LQK4GwSk/TQorZSOlO9C+RZDZpODgyN4ZlCqE5q9cDsyWOliN+aU9B4JX01xK9eJXowJLw==", - "dev": true, - "dependencies": { - "find-cache-dir": "^2.1.0", - "loader-utils": "^1.4.0", - "mkdirp": "^0.5.3", - "pify": "^4.0.1", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 6.9" - } - }, - "node_modules/babel-loader/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-loader/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/babel-loader/node_modules/schema-utils": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", - "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.5", - "ajv": "^6.12.4", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 8.9.0" - } - }, - "node_modules/babel-plugin-dynamic-import-node": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", - "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", - "dev": true, - "dependencies": { - "object.assign": "^4.1.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz", - "integrity": "sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^4.0.0", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-jest-hoist": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.6.2.tgz", - "integrity": "sha512-PO9t0697lNTmcEHH69mdtYiOIkkOlj9fySqfO3K1eCcdISevLAE0xY59VLLUj0SoiPiTX/JU2CYFpILydUa5Lw==", - "dev": true, - "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.0.0", - "@types/babel__traverse": "^7.0.6" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/babel-preset-current-node-syntax": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz", - "integrity": "sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==", - "dev": true, - "dependencies": { - "@babel/plugin-syntax-async-generators": "^7.8.4", - "@babel/plugin-syntax-bigint": "^7.8.3", - "@babel/plugin-syntax-class-properties": "^7.8.3", - "@babel/plugin-syntax-import-meta": "^7.8.3", - "@babel/plugin-syntax-json-strings": "^7.8.3", - "@babel/plugin-syntax-logical-assignment-operators": "^7.8.3", - "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", - "@babel/plugin-syntax-numeric-separator": "^7.8.3", - "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", - "@babel/plugin-syntax-optional-chaining": "^7.8.3", - "@babel/plugin-syntax-top-level-await": "^7.8.3" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/babel-preset-jest": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.6.2.tgz", - "integrity": "sha512-YvdtlVm9t3k777c5NPQIv6cxFFFapys25HiUmuSgHwIZhfifweR5c5Sf5nwE3MAbfu327CYSvps8Yx6ANLyleQ==", - "dev": true, - "dependencies": { - "babel-plugin-jest-hoist": "^26.6.2", - "babel-preset-current-node-syntax": "^1.0.0" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", - "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.1.0.tgz", - "integrity": "sha512-1Yj8h9Q+QDF5FzhMs/c9+6UntbD5MkRfRwac8DoEm9ZfUBZ7tZ55YcGVAzEe4bXsdQHEk+s9S5wsOKVdZrw0tQ==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", - "dev": true - }, - "node_modules/bn.js": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.1.3.tgz", - "integrity": "sha512-GkTiFpjFtUzU9CbMeJ5iazkCzGL3jrhzerzZIuqLABjbwRaFt33I9tUdSNryIptM+RxDet6OKm2WnLXzW51KsQ==", - "dev": true - }, - "node_modules/bottlejs": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bottlejs/-/bottlejs-2.0.0.tgz", - "integrity": "sha512-Qhz5dd1YPTOHw0gZ1a1WpJ/oEWsq09BMMbxczeAPgubISij+ZFfah7wfOlHFeFQpWLQkS9TGz5C54tV6v0BlFA==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/braces/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/brorand": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", - "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=", - "dev": true - }, - "node_modules/browser-process-hrtime": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", - "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==", - "dev": true - }, - "node_modules/browserify-aes": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", - "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", - "dev": true, - "dependencies": { - "buffer-xor": "^1.0.3", - "cipher-base": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.3", - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/browserify-cipher": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", - "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", - "dev": true, - "dependencies": { - "browserify-aes": "^1.0.4", - "browserify-des": "^1.0.0", - "evp_bytestokey": "^1.0.0" - } - }, - "node_modules/browserify-des": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", - "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "des.js": "^1.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/browserify-rsa": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.0.1.tgz", - "integrity": "sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ=", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "randombytes": "^2.0.1" - } - }, - "node_modules/browserify-rsa/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/browserify-sign": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.1.tgz", - "integrity": "sha512-/vrA5fguVAKKAVTNJjgSm1tRQDHUU6DbwO9IROu/0WAzC8PKhucDSh18J0RMvVeHAn5puMd+QHC2erPRNf8lmg==", - "dev": true, - "dependencies": { - "bn.js": "^5.1.1", - "browserify-rsa": "^4.0.1", - "create-hash": "^1.2.0", - "create-hmac": "^1.1.7", - "elliptic": "^6.5.3", - "inherits": "^2.0.4", - "parse-asn1": "^5.1.5", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - } - }, - "node_modules/browserify-sign/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/browserify-zlib": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", - "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", - "dev": true, - "dependencies": { - "pako": "~1.0.5" - } - }, - "node_modules/browserslist": { - "version": "4.14.6", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.14.6.tgz", - "integrity": "sha512-zeFYcUo85ENhc/zxHbiIp0LGzzTrE2Pv2JhxvS7kpUb9Q9D38kUX6Bie7pGutJ/5iF5rOxE7CepAuWD56xJ33A==", - "dev": true, - "dependencies": { - "caniuse-lite": "^1.0.30001154", - "electron-to-chromium": "^1.3.585", - "escalade": "^3.1.1", - "node-releases": "^1.1.65" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/bser": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.1.1.tgz", - "integrity": "sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==", - "dev": true, - "dependencies": { - "node-int64": "^0.4.0" - } - }, - "node_modules/buffer": { - "version": "4.9.2", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz", - "integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==", - "dev": true, - "dependencies": { - "base64-js": "^1.0.2", - "ieee754": "^1.1.4", - "isarray": "^1.0.0" - } - }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", - "dev": true - }, - "node_modules/buffer-xor": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", - "integrity": "sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk=", - "dev": true - }, - "node_modules/builtin-status-codes": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", - "integrity": "sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug=", - "dev": true - }, - "node_modules/cacache": { - "version": "12.0.4", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-12.0.4.tgz", - "integrity": "sha512-a0tMB40oefvuInr4Cwb3GerbL9xTj1D5yg0T5xrjGCGyfvbxseIXX7BAO/u/hIXdafzOI5JC3wDwHyf24buOAQ==", - "dev": true, - "dependencies": { - "bluebird": "^3.5.5", - "chownr": "^1.1.1", - "figgy-pudding": "^3.5.1", - "glob": "^7.1.4", - "graceful-fs": "^4.1.15", - "infer-owner": "^1.0.3", - "lru-cache": "^5.1.1", - "mississippi": "^3.0.0", - "mkdirp": "^0.5.1", - "move-concurrently": "^1.0.1", - "promise-inflight": "^1.0.1", - "rimraf": "^2.6.3", - "ssri": "^6.0.1", - "unique-filename": "^1.1.1", - "y18n": "^4.0.0" - } - }, - "node_modules/cacache/node_modules/lru-cache": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", - "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", - "dev": true, - "dependencies": { - "yallist": "^3.0.2" - } - }, - "node_modules/cacache/node_modules/yallist": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", - "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", - "dev": true - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cache-base/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/call-bind": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", - "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "get-intrinsic": "^1.0.0" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001154", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001154.tgz", - "integrity": "sha512-y9DvdSti8NnYB9Be92ddMZQrcOe04kcQtcxtBx4NkB04+qZ+JUWotnXBJTmxlKudhxNTQ3RRknMwNU2YQl/Org==", - "dev": true - }, - "node_modules/capture-exit": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", - "integrity": "sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==", - "dev": true, - "dependencies": { - "rsvp": "^4.8.4" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true - }, - "node_modules/chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/char-regex": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/char-regex/-/char-regex-1.0.2.tgz", - "integrity": "sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/chokidar": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.4.3.tgz", - "integrity": "sha512-DtM3g7juCXQxFVSNPNByEC2+NImtBuxQQvWlHunpJIS5Ocr0lG306cC7FCi7cEA0fzmybPUIl4txBIobk1gGOQ==", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - }, - "engines": { - "node": ">= 8.10.0" - } - }, - "node_modules/chokidar/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "optional": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "optional": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/chokidar/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/chokidar/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "optional": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-trace-event": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", - "integrity": "sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ==", - "dev": true, - "dependencies": { - "tslib": "^1.9.0" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/ci-info": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", - "dev": true - }, - "node_modules/cipher-base": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - } - }, - "node_modules/cjs-module-lexer": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-0.6.0.tgz", - "integrity": "sha512-uc2Vix1frTfnuzxxu1Hp4ktSvM3QaI4oXl4ZUqL1wjTu/BGki9TrCWoqLTg/drR1KwAEarXuRFCG2Svr1GxPFw==", - "dev": true - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/classlist-polyfill": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/classlist-polyfill/-/classlist-polyfill-1.2.0.tgz", - "integrity": "sha1-k1vC39lFiodrJ5YXUUY4vKqWSi4=" - }, - "node_modules/clean-stack": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", - "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/cliui/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/clone-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/clone-deep/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "engines": { - "iojs": ">= 1.0.0", - "node": ">= 0.12.0" - } - }, - "node_modules/collect-v8-coverage": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", - "integrity": "sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==", - "dev": true - }, - "node_modules/collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, - "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/colorette": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz", - "integrity": "sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==", - "dev": true - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.0.tgz", - "integrity": "sha512-Ew1clU4pkUeo6AFVDFxCbnN7GIZfXl48HIOQeFQnkO3oOqvpI7wdqtLRwv9iOCZ/7A+z4csVZeiDdEcj8g6Wiw==", - "dev": true, - "dependencies": { - "array-back": "^4.0.0", - "chalk": "^2.4.2", - "table-layout": "^1.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/command-line-usage/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/command-line-usage/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/command-line-usage/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/commander": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.0.tgz", - "integrity": "sha512-zP4jEKbe8SHzKJYQmq8Y9gYjtO/POJLgIdKgV7B9qNmABVFVc+ctqSX6iXh4mCpJfRBOabiZ2YKPg8ciDw6C+Q==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "dev": true - }, - "node_modules/component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "dev": true - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/console-browserify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", - "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==", - "dev": true - }, - "node_modules/constants-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", - "integrity": "sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=", - "dev": true - }, - "node_modules/convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.1" - } - }, - "node_modules/convert-source-map/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/copy-concurrently": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/copy-concurrently/-/copy-concurrently-1.0.5.tgz", - "integrity": "sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A==", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "fs-write-stream-atomic": "^1.0.8", - "iferr": "^0.1.5", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.0" - } - }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/core-js": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.7.0.tgz", - "integrity": "sha512-NwS7fI5M5B85EwpWuIwJN4i/fbisQUwLwiSNUWeXlkAZ0sbBjLEvLvFLf1uzAUV66PcEPt4xCGCmOZSxVf3xzA==", - "dev": true, - "hasInstallScript": true - }, - "node_modules/core-js-compat": { - "version": "3.6.5", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.6.5.tgz", - "integrity": "sha512-7ItTKOhOZbznhXAQ2g/slGg1PJV5zDO/WdkTwi7UEOJmkvsE32PWvx6mKtDjiMpjnR2CNf6BAD6sSxIlv7ptng==", - "dev": true, - "dependencies": { - "browserslist": "^4.8.5", - "semver": "7.0.0" - } - }, - "node_modules/core-js-compat/node_modules/semver": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", - "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "node_modules/create-ecdh": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", - "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "elliptic": "^6.5.3" - } - }, - "node_modules/create-ecdh/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/create-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", - "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.1", - "inherits": "^2.0.1", - "md5.js": "^1.3.4", - "ripemd160": "^2.0.1", - "sha.js": "^2.4.0" - } - }, - "node_modules/create-hmac": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", - "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", - "dev": true, - "dependencies": { - "cipher-base": "^1.0.3", - "create-hash": "^1.1.0", - "inherits": "^2.0.1", - "ripemd160": "^2.0.0", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - } - }, - "node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" - } - }, - "node_modules/cross-spawn/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/cross-spawn/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "dev": true, - "dependencies": { - "shebang-regex": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cross-spawn/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" - } - }, - "node_modules/crypto-browserify": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", - "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", - "dev": true, - "dependencies": { - "browserify-cipher": "^1.0.0", - "browserify-sign": "^4.0.0", - "create-ecdh": "^4.0.0", - "create-hash": "^1.1.0", - "create-hmac": "^1.1.0", - "diffie-hellman": "^5.0.0", - "inherits": "^2.0.1", - "pbkdf2": "^3.0.3", - "public-encrypt": "^4.0.0", - "randombytes": "^2.0.0", - "randomfill": "^1.0.3" - }, - "engines": { - "node": "*" - } - }, - "node_modules/cssom": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz", - "integrity": "sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==", - "dev": true - }, - "node_modules/cssstyle": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-2.3.0.tgz", - "integrity": "sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==", - "dev": true, - "dependencies": { - "cssom": "~0.3.6" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cssstyle/node_modules/cssom": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", - "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==", - "dev": true - }, - "node_modules/cyclist": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", - "dev": true - }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/data-urls": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-2.0.0.tgz", - "integrity": "sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/debug": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.2.0.tgz", - "integrity": "sha512-IX2ncY78vDTjZMFUdmsvIRFY2Cf4FnD0wRs+nQwJU8Lu99/tPFdb0VybiiMTPe3I6rQmwsqQqRBvxU+bZ/I8sg==", - "dev": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decimal.js": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.2.1.tgz", - "integrity": "sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==", - "dev": true - }, - "node_modules/decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.2.2.tgz", - "integrity": "sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", - "dev": true, - "dependencies": { - "object-keys": "^1.0.12" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/define-property/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/des.js": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.0.1.tgz", - "integrity": "sha512-Q0I4pfFrv2VPd34/vfLrFOoRmlYj3OV50i7fskps1jZWK1kApMWWT9G6RRUeYedLcBDIhnSDaUvJMb3AhUlaEA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0" - } - }, - "node_modules/detect-newline": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", - "integrity": "sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/diff-sequences": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.6.2.tgz", - "integrity": "sha512-Mv/TDa3nZ9sbc5soK+OoA74BsS3mL37yixCvUAQkiuA4Wz6YtwP/K47n2rv2ovzHZvoiQeA5FTQOschKkEwB0Q==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/diffie-hellman": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", - "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "miller-rabin": "^4.0.0", - "randombytes": "^2.0.0" - } - }, - "node_modules/diffie-hellman/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/domain-browser": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-1.2.0.tgz", - "integrity": "sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA==", - "dev": true, - "engines": { - "node": ">=0.4", - "npm": ">=1.2" - } - }, - "node_modules/domexception": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-2.0.1.tgz", - "integrity": "sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==", - "dev": true, - "dependencies": { - "webidl-conversions": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/domexception/node_modules/webidl-conversions": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-5.0.0.tgz", - "integrity": "sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/duplexify": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", - "integrity": "sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "node_modules/electron-to-chromium": { - "version": "1.3.586", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.586.tgz", - "integrity": "sha512-or8FCbQCRlPZHkOoqBULOI9hzTiStVIQqDLgAPt8pzY+swTrW+89vsqd24Zn+Iv4guAJLxRBD6OR5AmbpabGDA==", - "dev": true - }, - "node_modules/elliptic": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", - "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", - "dev": true, - "dependencies": { - "bn.js": "^4.4.0", - "brorand": "^1.0.1", - "hash.js": "^1.0.0", - "hmac-drbg": "^1.0.0", - "inherits": "^2.0.1", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.0" - } - }, - "node_modules/elliptic/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/emittery": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.7.2.tgz", - "integrity": "sha512-A8OG5SR/ij3SsJdWDJdkkSYUjQdCUx6APQXem0SaEePBSRg4eymGYwBkKo1Y6DU+af/Jn2dBQqDBvjnr9Vi8nQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/emittery?sponsor=1" - } - }, - "node_modules/emoji-regex": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", - "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "dev": true, - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/enhanced-resolve": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-4.3.0.tgz", - "integrity": "sha512-3e87LvavsdxyoCfGusJnrZ5G8SLPOFeHSNpZI/ATL9a5leXo2k0w6MKnbqhdBad9qTobSfB20Ld7UmgoNbAZkQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "memory-fs": "^0.5.0", - "tapable": "^1.0.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/enhanced-resolve/node_modules/memory-fs": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.5.0.tgz", - "integrity": "sha512-jA0rdU5KoQMC0e6ppoNRtpp6vjFq6+NY7r8hywnC7V+1Xj/MtHwGIbB1QaK/dunyjWteJzmkpd7ooeWg10T7GA==", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - }, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/enquirer": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", - "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", - "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1" - }, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/envinfo": { - "version": "7.7.3", - "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.7.3.tgz", - "integrity": "sha512-46+j5QxbPWza0PB1i15nZx0xQ4I/EfQxg9J8Had3b408SV63nEtor2e+oiY63amTo9KTuh2a3XLObNwduxYwwA==", - "dev": true, - "bin": { - "envinfo": "dist/cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/errno": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", - "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==", - "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "dev": true, - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/escodegen": { - "version": "1.14.3", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.3.tgz", - "integrity": "sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==", - "dev": true, - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=4.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "dev": true, - "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/eslint": { - "version": "7.12.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz", - "integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@eslint/eslintrc": "^0.2.1", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.0", - "esquery": "^1.2.0", - "esutils": "^2.0.2", - "file-entry-cache": "^5.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.0.0", - "globals": "^12.1.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash": "^4.17.19", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^5.2.3", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/eslint-plugin-babel": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-babel/-/eslint-plugin-babel-5.3.1.tgz", - "integrity": "sha512-VsQEr6NH3dj664+EyxJwO4FCYm/00JhYb3Sk3ft8o+fpKuIfQ9TaW6uVUfvwMXHcf/lsnRIoyFPsLMyiWCSL/g==", - "dev": true, - "dependencies": { - "eslint-rule-composer": "^0.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-plugin-prettier": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.4.tgz", - "integrity": "sha512-jZDa8z76klRqo+TdGDTFJSavwbnWK2ZpqGKNZ+VvweMW516pDUMmQ2koXvxEE4JhzNvTv+radye/bWGBmA6jmg==", - "dev": true, - "dependencies": { - "prettier-linter-helpers": "^1.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/eslint-rule-composer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz", - "integrity": "sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-scope": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz", - "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==", - "dev": true, - "dependencies": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/eslint/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint/node_modules/eslint-visitor-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.0.0.tgz", - "integrity": "sha512-QudtT6av5WXels9WjIM7qz1XD1cWGvX4gGXvp/zBn9nXG02D0utdU3Em2m/QjTnrsk6bBjmCygl3rmj118msQQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "12.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-12.4.0.tgz", - "integrity": "sha512-BWICuzzDvDoH54NHKCseDanAhE3CeDorgDL5MT6LMXXj2WCnd9UC2szdk4AWLfjdgNBCXLUanXYcpBBKOSWGwg==", - "dev": true, - "dependencies": { - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/semver": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz", - "integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/espree": { - "version": "7.3.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.0.tgz", - "integrity": "sha512-dksIWsvKCixn1yrEXO8UosNSxaDoSYpq9reEjZSbHLpT5hpaCAKTLBwq0RHtLrIr+c0ByiYzWT8KTMRzoRCNlw==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.2.0", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/esquery": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", - "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", - "dev": true, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", - "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/events": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.2.0.tgz", - "integrity": "sha512-/46HWwbfCX2xTawVfkKLGxMifJYQBWMwY1mjywRtb4c9x8l5NP3KoJtnIOiL1hfdRkIuYhETxQlo62IF8tcnlg==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/evp_bytestokey": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", - "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", - "dev": true, - "dependencies": { - "md5.js": "^1.3.4", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/exec-sh": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.3.4.tgz", - "integrity": "sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==", - "dev": true - }, - "node_modules/execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/expect": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/expect/-/expect-26.6.2.tgz", - "integrity": "sha512-9/hlOBkQl2l/PLHJx6JjoDF6xPKcJEsUlWKb23rKE7KzeDqUZKXKNMW27KIue5JMdBV9HgmoJPcc8HtO85t9IA==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-styles": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-regex-util": "^26.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true - }, - "node_modules/extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, - "dependencies": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extend-shallow/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "dependencies": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extglob/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true, - "engines": [ - "node >=0.6.0" - ] - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "dev": true - }, - "node_modules/fast-diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", - "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", - "dev": true - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "node_modules/fb-watchman": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.1.tgz", - "integrity": "sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==", - "dev": true, - "dependencies": { - "bser": "2.1.1" - } - }, - "node_modules/figgy-pudding": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.2.tgz", - "integrity": "sha512-0btnI/H8f2pavGMN8w40mlSKOfTK2SVJmBfBeVIj3kNw0swwgzyRq0d5TJVOwodFmtvpPeWPN/MCcfuWF0Ezbw==", - "dev": true - }, - "node_modules/file-entry-cache": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz", - "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==", - "dev": true, - "dependencies": { - "flat-cache": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/fill-range/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/find-cache-dir": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", - "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^3.0.2", - "pkg-dir": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/find-cache-dir/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/flat-cache": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz", - "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==", - "dev": true, - "dependencies": { - "flatted": "^2.0.0", - "rimraf": "2.6.3", - "write": "1.0.3" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", - "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/flatted": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.2.tgz", - "integrity": "sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA==", - "dev": true - }, - "node_modules/flush-write-stream": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.1.1.tgz", - "integrity": "sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "readable-stream": "^2.3.6" - } - }, - "node_modules/for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "dev": true, - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 0.12" - } - }, - "node_modules/fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, - "dependencies": { - "map-cache": "^0.2.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/from2": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.0" - } - }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-write-stream-atomic": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "iferr": "^0.1.5", - "imurmurhash": "^0.1.4", - "readable-stream": "1 || 2" - } - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "node_modules/fsevents": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.3.tgz", - "integrity": "sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "node_modules/functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", - "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", - "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", - "dev": true, - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "dev": true, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.1.tgz", - "integrity": "sha512-ZnWP+AmS1VUaLgTRy47+zKtjTxz+0xMpx3I52i+aalBK1QP19ggLF3Db89KJX7kjfOfP2eoa01qc++GwPgufPg==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1", - "has": "^1.0.3", - "has-symbols": "^1.0.1" - } - }, - "node_modules/get-package-type": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", - "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0" - } - }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/glob-parent": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.1.tgz", - "integrity": "sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ==", - "dev": true, - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/globals": { - "version": "11.12.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "dev": true - }, - "node_modules/growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true, - "optional": true - }, - "node_modules/har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", - "dev": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "dev": true, - "dependencies": { - "function-bind": "^1.1.1" - }, - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "dependencies": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-value/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/has-values/node_modules/kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/hash-base": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz", - "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.4", - "readable-stream": "^3.6.0", - "safe-buffer": "^5.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/hash-base/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/hash.js": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", - "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "minimalistic-assert": "^1.0.1" - } - }, - "node_modules/hmac-drbg": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", - "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", - "dev": true, - "dependencies": { - "hash.js": "^1.0.3", - "minimalistic-assert": "^1.0.0", - "minimalistic-crypto-utils": "^1.0.1" - } - }, - "node_modules/hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", - "dev": true - }, - "node_modules/html-encoding-sniffer": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz", - "integrity": "sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==", - "dev": true, - "dependencies": { - "whatwg-encoding": "^1.0.5" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/html-escaper": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", - "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", - "dev": true - }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" - } - }, - "node_modules/https-browserify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", - "dev": true - }, - "node_modules/human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true, - "engines": { - "node": ">=8.12.0" - } - }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "dev": true, - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "dev": true - }, - "node_modules/iferr": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", - "dev": true - }, - "node_modules/ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "dev": true, - "engines": { - "node": ">= 4" - } - }, - "node_modules/import-fresh": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.2.tgz", - "integrity": "sha512-cTPNrlvJT6twpYy+YmKUKrTSjWFs3bjYjAhCwm+z4EOCubZxAuO+hHpRN64TqjEaYSHs7tJAE0w1CKMGmsG/lw==", - "dev": true, - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/import-local": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", - "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", - "dev": true, - "dependencies": { - "pkg-dir": "^4.2.0", - "resolve-cwd": "^3.0.0" - }, - "bin": { - "import-local-fixture": "fixtures/cli.js" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/import-local/node_modules/pkg-dir": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", - "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", - "dev": true, - "dependencies": { - "find-up": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true, - "engines": { - "node": ">=0.8.19" - } - }, - "node_modules/indent-string": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", - "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/infer-owner": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", - "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true, - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true - }, - "node_modules/interpret": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", - "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/ip-regex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-accessor-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "node_modules/is-ci": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-2.0.0.tgz", - "integrity": "sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==", - "dev": true, - "dependencies": { - "ci-info": "^2.0.0" - }, - "bin": { - "is-ci": "bin.js" - } - }, - "node_modules/is-core-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz", - "integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==", - "dev": true, - "dependencies": { - "has": "^1.0.3" - } - }, - "node_modules/is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-data-descriptor/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-descriptor/node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-docker": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.1.1.tgz", - "integrity": "sha512-ZOoqiXfEwtGknTiuDEy8pN2CfE3TxMHprvNer1mXiqwkOT77Rw3YVrUQ52EqAOU3QAWDQ+bQdx7HJzrv7LS2Hw==", - "dev": true, - "optional": true, - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/is-generator-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-2.1.0.tgz", - "integrity": "sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "dev": true, - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-potential-custom-element-name": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz", - "integrity": "sha1-DFLlS8yjkbssSUsh6GJtczbG45c=", - "dev": true - }, - "node_modules/is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "node_modules/is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-wsl": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "node_modules/istanbul-lib-coverage": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz", - "integrity": "sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-instrument/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-report": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz", - "integrity": "sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==", - "dev": true, - "dependencies": { - "istanbul-lib-coverage": "^3.0.0", - "make-dir": "^3.0.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "dev": true, - "dependencies": { - "semver": "^6.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } - }, - "node_modules/istanbul-lib-source-maps": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz", - "integrity": "sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-reports": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.0.2.tgz", - "integrity": "sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==", - "dev": true, - "dependencies": { - "html-escaper": "^2.0.0", - "istanbul-lib-report": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/jest": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-26.6.3.tgz", - "integrity": "sha512-lGS5PXGAzR4RF7V5+XObhqz2KZIDUA1yD0DG6pBVmy10eh0ZIXQImRuzocsI/N2XZ1GrLFwTS27In2i2jlpq1Q==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "import-local": "^3.0.2", - "jest-cli": "^26.6.3" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-changed-files": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.6.2.tgz", - "integrity": "sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "execa": "^4.0.0", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-cli": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-26.6.3.tgz", - "integrity": "sha512-GF9noBSa9t08pSyl3CY4frMrqp+aQXFGFkf5hEPbh/pIUFYWMK6ZLTfbmadxJVcJrdRoChlWQsA2VkJcDFK8hg==", - "dev": true, - "dependencies": { - "@jest/core": "^26.6.3", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "import-local": "^3.0.2", - "is-ci": "^2.0.0", - "jest-config": "^26.6.3", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "prompts": "^2.0.1", - "yargs": "^15.4.1" - }, - "bin": { - "jest": "bin/jest.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-config": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-26.6.3.tgz", - "integrity": "sha512-t5qdIj/bCj2j7NFVHb2nFB4aUdfucDn3JRKgrZnplb8nieAirAzRSHP8uDEd+qV6ygzg9Pz4YG7UTJf94LPSyg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.1.0", - "@jest/test-sequencer": "^26.6.3", - "@jest/types": "^26.6.2", - "babel-jest": "^26.6.3", - "chalk": "^4.0.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.1", - "graceful-fs": "^4.2.4", - "jest-environment-jsdom": "^26.6.2", - "jest-environment-node": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-jasmine2": "^26.6.3", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - }, - "peerDependencies": { - "ts-node": ">=9.0.0" - }, - "peerDependenciesMeta": { - "ts-node": { - "optional": true - } - } - }, - "node_modules/jest-diff": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-26.6.2.tgz", - "integrity": "sha512-6m+9Z3Gv9wN0WFVasqjCL/06+EFCMTqDEUl/b87HYK2rAPTyfz4ZIuSlPhY51PIQRWx5TaxeF1qmXKe9gfN3sA==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-docblock": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-26.0.0.tgz", - "integrity": "sha512-RDZ4Iz3QbtRWycd8bUEPxQsTlYazfYn/h5R65Fc6gOfwozFhoImx+affzky/FFBuqISPTqjXomoIGJVKBWoo0w==", - "dev": true, - "dependencies": { - "detect-newline": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-each": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-26.6.2.tgz", - "integrity": "sha512-Mer/f0KaATbjl8MCJ+0GEpNdqmnVmDYqCTJYTvoo7rqmRiDllmp2AYN+06F93nXcY3ur9ShIjS+CO/uD+BbH4A==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-jsdom": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.6.2.tgz", - "integrity": "sha512-jgPqCruTlt3Kwqg5/WVFyHIOJHsiAvhcp2qiR2QQstuG9yWox5+iHpU3ZrcBxW14T4fe5Z68jAfLRh7joCSP2Q==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2", - "jsdom": "^16.4.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-environment-node": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.6.2.tgz", - "integrity": "sha512-zhtMio3Exty18dy8ee8eJ9kjnRyZC1N4C1Nt/VShN1apyXc8rWGtJ9lI7vqiWcyyXS4BVSEn9lxAM2D+07/Tag==", - "dev": true, - "dependencies": { - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "jest-mock": "^26.6.2", - "jest-util": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-get-type": { - "version": "26.3.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz", - "integrity": "sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-haste-map": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.6.2.tgz", - "integrity": "sha512-easWIJXIw71B2RdR8kgqpjQrbMRWQBgiBwXYEhtGUTaX+doCjBheluShdDMeR8IMfJiTqH4+zfhtg29apJf/8w==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/graceful-fs": "^4.1.2", - "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.4", - "jest-regex-util": "^26.0.0", - "jest-serializer": "^26.6.2", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "micromatch": "^4.0.2", - "sane": "^4.0.3", - "walker": "^1.0.7" - }, - "engines": { - "node": ">= 10.14.2" - }, - "optionalDependencies": { - "fsevents": "^2.1.2" - } - }, - "node_modules/jest-jasmine2": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", - "integrity": "sha512-kPKUrQtc8aYwBV7CqBg5pu+tmYXlvFlSFYn18ev4gPFtrRzB15N2gW/Roew3187q2w2eHuu0MU9TJz6w0/nPEg==", - "dev": true, - "dependencies": { - "@babel/traverse": "^7.1.0", - "@jest/environment": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "co": "^4.6.0", - "expect": "^26.6.2", - "is-generator-fn": "^2.0.0", - "jest-each": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "pretty-format": "^26.6.2", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-leak-detector": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.6.2.tgz", - "integrity": "sha512-i4xlXpsVSMeKvg2cEKdfhh0H39qlJlP5Ex1yQxwF9ubahboQYMgTtz5oML35AVA3B4Eu+YsmwaiKVev9KCvLxg==", - "dev": true, - "dependencies": { - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-matcher-utils": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.6.2.tgz", - "integrity": "sha512-llnc8vQgYcNqDrqRDXWwMr9i7rS5XFiCwvh6DTP7Jqa2mqpcCBBlpCbn+trkG0KNhPu/h8rzyBkriOtBstvWhw==", - "dev": true, - "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-message-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.6.2.tgz", - "integrity": "sha512-rGiLePzQ3AzwUshu2+Rn+UMFk0pHN58sOG+IaJbk5Jxuqo3NYO1U2/MIR4S1sKgsoYSXSzdtSa0TgrmtUwEbmA==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "micromatch": "^4.0.2", - "pretty-format": "^26.6.2", - "slash": "^3.0.0", - "stack-utils": "^2.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-mock": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-26.6.2.tgz", - "integrity": "sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-pnp-resolver": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", - "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "engines": { - "node": ">=6" - }, - "peerDependencies": { - "jest-resolve": "*" - }, - "peerDependenciesMeta": { - "jest-resolve": { - "optional": true - } - } - }, - "node_modules/jest-regex-util": { - "version": "26.0.0", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz", - "integrity": "sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A==", - "dev": true, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.6.2.tgz", - "integrity": "sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^26.6.2", - "read-pkg-up": "^7.0.1", - "resolve": "^1.18.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-resolve-dependencies": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.6.3.tgz", - "integrity": "sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-snapshot": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runner": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-26.6.3.tgz", - "integrity": "sha512-atgKpRHnaA2OvByG/HpGA4g6CSPS/1LK0jK3gATJAoptC1ojltpmVlYC3TYgdmGp+GLuhzpH30Gvs36szSL2JQ==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "emittery": "^0.7.1", - "exit": "^0.1.2", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-docblock": "^26.0.0", - "jest-haste-map": "^26.6.2", - "jest-leak-detector": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "jest-runtime": "^26.6.3", - "jest-util": "^26.6.2", - "jest-worker": "^26.6.2", - "source-map-support": "^0.5.6", - "throat": "^5.0.0" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-runtime": { - "version": "26.6.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.6.3.tgz", - "integrity": "sha512-lrzyR3N8sacTAMeonbqpnSka1dHNux2uk0qqDXVkMv2c/A3wYnvQ4EXuI013Y6+gSKSCxdaczvf4HF0mVXHRdw==", - "dev": true, - "dependencies": { - "@jest/console": "^26.6.2", - "@jest/environment": "^26.6.2", - "@jest/fake-timers": "^26.6.2", - "@jest/globals": "^26.6.2", - "@jest/source-map": "^26.6.2", - "@jest/test-result": "^26.6.2", - "@jest/transform": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/yargs": "^15.0.0", - "chalk": "^4.0.0", - "cjs-module-lexer": "^0.6.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.4", - "jest-config": "^26.6.3", - "jest-haste-map": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-mock": "^26.6.2", - "jest-regex-util": "^26.0.0", - "jest-resolve": "^26.6.2", - "jest-snapshot": "^26.6.2", - "jest-util": "^26.6.2", - "jest-validate": "^26.6.2", - "slash": "^3.0.0", - "strip-bom": "^4.0.0", - "yargs": "^15.4.1" - }, - "bin": { - "jest-runtime": "bin/jest-runtime.js" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-serializer": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.6.2.tgz", - "integrity": "sha512-S5wqyz0DXnNJPd/xfIzZ5Xnp1HrJWBczg8mMfMpN78OJ5eDxXyf+Ygld9wX1DnUWbIbhM1YDY95NjR4CBXkb2g==", - "dev": true, - "dependencies": { - "@types/node": "*", - "graceful-fs": "^4.2.4" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.6.2.tgz", - "integrity": "sha512-OLhxz05EzUtsAmOMzuupt1lHYXCNib0ECyuZ/PZOx9TrZcC8vL0x+DUG3TL+GLX3yHG45e6YGjIm0XwDc3q3og==", - "dev": true, - "dependencies": { - "@babel/types": "^7.0.0", - "@jest/types": "^26.6.2", - "@types/babel__traverse": "^7.0.4", - "@types/prettier": "^2.0.0", - "chalk": "^4.0.0", - "expect": "^26.6.2", - "graceful-fs": "^4.2.4", - "jest-diff": "^26.6.2", - "jest-get-type": "^26.3.0", - "jest-haste-map": "^26.6.2", - "jest-matcher-utils": "^26.6.2", - "jest-message-util": "^26.6.2", - "jest-resolve": "^26.6.2", - "natural-compare": "^1.4.0", - "pretty-format": "^26.6.2", - "semver": "^7.3.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-snapshot/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/jest-util": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-26.6.2.tgz", - "integrity": "sha512-MDW0fKfsn0OI7MS7Euz6h8HNDXVQ0gaM9uW6RjfDmd1DAFcaxX9OqIakHIqhbnmF08Cf2DLDG+ulq8YQQ0Lp0Q==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "@types/node": "*", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.4", - "is-ci": "^2.0.0", - "micromatch": "^4.0.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-26.6.2.tgz", - "integrity": "sha512-NEYZ9Aeyj0i5rQqbq+tpIOom0YS1u2MVu6+euBsvpgIme+FOfRmoC4R5p0JiAUpaFvFy24xgrpMknarR/93XjQ==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "camelcase": "^6.0.0", - "chalk": "^4.0.0", - "jest-get-type": "^26.3.0", - "leven": "^3.1.0", - "pretty-format": "^26.6.2" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-validate/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/jest-watcher": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.6.2.tgz", - "integrity": "sha512-WKJob0P/Em2csiVthsI68p6aGKTIcsfjH9Gsx1f0A3Italz43e3ho0geSAVsmj09RWOELP1AZ/DXyJgOgDKxXQ==", - "dev": true, - "dependencies": { - "@jest/test-result": "^26.6.2", - "@jest/types": "^26.6.2", - "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "jest-util": "^26.6.2", - "string-length": "^4.0.1" - }, - "engines": { - "node": ">= 10.14.2" - } - }, - "node_modules/jest-worker": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-26.6.2.tgz", - "integrity": "sha512-KWYVV1c4i+jbMpaBC+U++4Va0cp8OisU185o73T1vo99hqi7w8tSJfUXYswwqqrjzwxa6KpRK54WhPvwf5w6PQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "merge-stream": "^2.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true - }, - "node_modules/js-yaml": { - "version": "3.14.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", - "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", - "dev": true, - "dependencies": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true - }, - "node_modules/jsdom": { - "version": "16.4.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-16.4.0.tgz", - "integrity": "sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==", - "dev": true, - "dependencies": { - "abab": "^2.0.3", - "acorn": "^7.1.1", - "acorn-globals": "^6.0.0", - "cssom": "^0.4.4", - "cssstyle": "^2.2.0", - "data-urls": "^2.0.0", - "decimal.js": "^10.2.0", - "domexception": "^2.0.1", - "escodegen": "^1.14.1", - "html-encoding-sniffer": "^2.0.1", - "is-potential-custom-element-name": "^1.0.0", - "nwsapi": "^2.2.0", - "parse5": "5.1.1", - "request": "^2.88.2", - "request-promise-native": "^1.0.8", - "saxes": "^5.0.0", - "symbol-tree": "^3.2.4", - "tough-cookie": "^3.0.1", - "w3c-hr-time": "^1.0.2", - "w3c-xmlserializer": "^2.0.0", - "webidl-conversions": "^6.1.0", - "whatwg-encoding": "^1.0.5", - "whatwg-mimetype": "^2.3.0", - "whatwg-url": "^8.0.0", - "ws": "^7.2.3", - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - }, - "peerDependencies": { - "canvas": "^2.5.0" - }, - "peerDependenciesMeta": { - "canvas": { - "optional": true - } - } - }, - "node_modules/jsdom/node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", - "dev": true - }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "dev": true - }, - "node_modules/json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true - }, - "node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true - }, - "node_modules/json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true - }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "node_modules/json5": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.1.3.tgz", - "integrity": "sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/leven": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", - "integrity": "sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/levn": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", - "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1", - "type-check": "~0.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=", - "dev": true - }, - "node_modules/loader-runner": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.4.0.tgz", - "integrity": "sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw==", - "dev": true, - "engines": { - "node": ">=4.3.0 <5.0.0 || >=5.10" - } - }, - "node_modules/loader-utils": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", - "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", - "dev": true, - "dependencies": { - "big.js": "^5.2.2", - "emojis-list": "^3.0.0", - "json5": "^1.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/loader-utils/node_modules/json5": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", - "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", - "dev": true, - "dependencies": { - "minimist": "^1.2.0" - }, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", - "dev": true - }, - "node_modules/lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/make-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", - "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", - "dev": true, - "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/make-dir/node_modules/pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, - "dependencies": { - "tmpl": "1.0.x" - } - }, - "node_modules/map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, - "dependencies": { - "object-visit": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/md5.js": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", - "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/memory-fs": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", - "dev": true, - "dependencies": { - "errno": "^0.1.3", - "readable-stream": "^2.0.1" - } - }, - "node_modules/merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "dev": true - }, - "node_modules/micromatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.2.tgz", - "integrity": "sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==", - "dev": true, - "dependencies": { - "braces": "^3.0.1", - "picomatch": "^2.0.5" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/micromatch/node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/micromatch/node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/miller-rabin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", - "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", - "dev": true, - "dependencies": { - "bn.js": "^4.0.0", - "brorand": "^1.0.1" - }, - "bin": { - "miller-rabin": "bin/miller-rabin" - } - }, - "node_modules/miller-rabin/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/mime-db": { - "version": "1.45.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.45.0.tgz", - "integrity": "sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.28", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.28.tgz", - "integrity": "sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==", - "dev": true, - "dependencies": { - "mime-db": "1.45.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/minimalistic-assert": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", - "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", - "dev": true - }, - "node_modules/minimalistic-crypto-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", - "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=", - "dev": true - }, - "node_modules/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==", - "dev": true - }, - "node_modules/minipass": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz", - "integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==", - "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minipass-collect": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", - "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-flush": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", - "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/minipass-pipeline": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", - "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "dev": true, - "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/mississippi": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz", - "integrity": "sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA==", - "dev": true, - "dependencies": { - "concat-stream": "^1.5.0", - "duplexify": "^3.4.2", - "end-of-stream": "^1.1.0", - "flush-write-stream": "^1.0.0", - "from2": "^2.1.0", - "parallel-transform": "^1.1.0", - "pump": "^3.0.0", - "pumpify": "^1.3.3", - "stream-each": "^1.1.0", - "through2": "^2.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, - "dependencies": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "dependencies": { - "is-plain-object": "^2.0.4" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mixin-deep/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "dependencies": { - "minimist": "^1.2.5" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, - "node_modules/move-concurrently": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1", - "copy-concurrently": "^1.0.0", - "fs-write-stream-atomic": "^1.0.8", - "mkdirp": "^0.5.1", - "rimraf": "^2.5.4", - "run-queue": "^1.0.3" - } - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/nan": { - "version": "2.14.2", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", - "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", - "dev": true, - "optional": true - }, - "node_modules/nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "node_modules/neo-async": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", - "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", - "dev": true - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", - "dev": true - }, - "node_modules/node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node_modules/node-libs-browser": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.2.1.tgz", - "integrity": "sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q==", - "dev": true, - "dependencies": { - "assert": "^1.1.1", - "browserify-zlib": "^0.2.0", - "buffer": "^4.3.0", - "console-browserify": "^1.1.0", - "constants-browserify": "^1.0.0", - "crypto-browserify": "^3.11.0", - "domain-browser": "^1.1.1", - "events": "^3.0.0", - "https-browserify": "^1.0.0", - "os-browserify": "^0.3.0", - "path-browserify": "0.0.1", - "process": "^0.11.10", - "punycode": "^1.2.4", - "querystring-es3": "^0.2.0", - "readable-stream": "^2.3.3", - "stream-browserify": "^2.0.1", - "stream-http": "^2.7.2", - "string_decoder": "^1.0.0", - "timers-browserify": "^2.0.4", - "tty-browserify": "0.0.0", - "url": "^0.11.0", - "util": "^0.11.0", - "vm-browserify": "^1.0.1" - } - }, - "node_modules/node-libs-browser/node_modules/punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true - }, - "node_modules/node-modules-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz", - "integrity": "sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/node-notifier": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.1.tgz", - "integrity": "sha512-BvEXF+UmsnAfYfoapKM9nGxnP+Wn7P91YfXmrKnfcYCx6VBeoN5Ez5Ogck6I8Bi5k4RlpqRYaw75pAwzX9OphA==", - "dev": true, - "optional": true, - "dependencies": { - "growly": "^1.3.0", - "is-wsl": "^2.2.0", - "semver": "^7.3.2", - "shellwords": "^0.1.1", - "uuid": "^8.3.0", - "which": "^2.0.2" - } - }, - "node_modules/node-notifier/node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "dev": true, - "optional": true, - "dependencies": { - "is-docker": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/node-notifier/node_modules/semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "dev": true, - "optional": true, - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/node-releases": { - "version": "1.1.65", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.65.tgz", - "integrity": "sha512-YpzJOe2WFIW0V4ZkJQd/DGR/zdVwc/pI4Nl1CZrBO19FdRcSTmsuhdttw9rsTzzJLrNcSloLiBbEYx1C4f6gpA==", - "dev": true - }, - "node_modules/normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "dev": true, - "dependencies": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "dev": true, - "dependencies": { - "path-key": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nwsapi": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", - "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==", - "dev": true - }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, - "dependencies": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-copy/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", - "dev": true, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, - "dependencies": { - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object-visit/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.assign": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", - "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.0", - "define-properties": "^1.1.3", - "has-symbols": "^1.0.1", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/object.pick/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "dev": true, - "dependencies": { - "mimic-fn": "^2.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", - "dev": true, - "dependencies": { - "deep-is": "^0.1.3", - "fast-levenshtein": "^2.0.6", - "levn": "^0.4.1", - "prelude-ls": "^1.2.1", - "type-check": "^0.4.0", - "word-wrap": "^1.2.3" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/os-browserify": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", - "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", - "dev": true - }, - "node_modules/p-each-series": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-each-series/-/p-each-series-2.2.0.tgz", - "integrity": "sha512-ycIL2+1V32th+8scbpTvyHNaHe02z0sjgh91XXjAk+ZeXoPN4Z46DVUnzdso0aX4KckKw0FNNFHdjZ2UsZvxiA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-map": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", - "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parallel-transform": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/parallel-transform/-/parallel-transform-1.2.0.tgz", - "integrity": "sha512-P2vSmIu38uIlvdcU7fDkyrxj33gTUy/ABO5ZUbGowxNCopBq/OoD42bP4UmMrJoPyk4Uqf0mu3mtWBhHCZD8yg==", - "dev": true, - "dependencies": { - "cyclist": "^1.0.1", - "inherits": "^2.0.3", - "readable-stream": "^2.1.5" - } - }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "dev": true, - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/parse-asn1": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.6.tgz", - "integrity": "sha512-RnZRo1EPU6JBnra2vGHj0yhp6ebyjBZpmUCLHWiFhxlzvBCCpAuZ7elsBp1PVAbQN0/04VD/19rfzlBSwLstMw==", - "dev": true, - "dependencies": { - "asn1.js": "^5.2.0", - "browserify-aes": "^1.0.0", - "evp_bytestokey": "^1.0.0", - "pbkdf2": "^3.0.3", - "safe-buffer": "^5.1.1" - } - }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "dev": true, - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/parse5": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-5.1.1.tgz", - "integrity": "sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==", - "dev": true - }, - "node_modules/pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-browserify": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.1.tgz", - "integrity": "sha512-BapA40NHICOS+USX9SN4tyhq+A2RrN/Ws5F0Z5aMHDp98Fl86lX8Oti8B7uN93L4Ifv4fHOEA+pQw87gmMO/lQ==", - "dev": true - }, - "node_modules/path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "dev": true, - "optional": true - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true - }, - "node_modules/pbkdf2": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz", - "integrity": "sha512-4Ejy1OPxi9f2tt1rRV7Go7zmfDQ+ZectEQz3VGUQhgq62HtIRPDyG/JtnwIxs6x3uNMwo2V7q1fMvKjb+Tnpqg==", - "dev": true, - "dependencies": { - "create-hash": "^1.1.2", - "create-hmac": "^1.1.4", - "ripemd160": "^2.0.1", - "safe-buffer": "^5.0.1", - "sha.js": "^2.4.8" - }, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true - }, - "node_modules/picomatch": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz", - "integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==", - "dev": true, - "engines": { - "node": ">=8.6" - } - }, - "node_modules/pirates": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.1.tgz", - "integrity": "sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==", - "dev": true, - "dependencies": { - "node-modules-regexp": "^1.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", - "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", - "dev": true, - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "dev": true, - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "dev": true, - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "dev": true, - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/prelude-ls": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", - "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", - "dev": true, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/prettier": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.1.2.tgz", - "integrity": "sha512-16c7K+x4qVlJg9rEbXl7HEGmQyZlG4R9AgP+oHKRMsMsuk8s+ATStlf1NpDqyBI1HpVyfjLOeMhH2LvuNvV5Vg==", - "dev": true, - "bin": { - "prettier": "bin-prettier.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/prettier-linter-helpers": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", - "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", - "dev": true, - "dependencies": { - "fast-diff": "^1.1.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/pretty-format": { - "version": "26.6.2", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-26.6.2.tgz", - "integrity": "sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==", - "dev": true, - "dependencies": { - "@jest/types": "^26.6.2", - "ansi-regex": "^5.0.0", - "ansi-styles": "^4.0.0", - "react-is": "^17.0.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=", - "dev": true, - "engines": { - "node": ">= 0.6.0" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "dev": true - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/promise-inflight": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", - "dev": true - }, - "node_modules/promise-polyfill": { - "version": "8.1.3", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz", - "integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==" - }, - "node_modules/prompts": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.0.tgz", - "integrity": "sha512-awZAKrk3vN6CroQukBL+R9051a4R3zCZBlJm/HBfrSZ8iTpYix3VX1vU4mveiLpiwmOJT4wokTF9m6HUk4KqWQ==", - "dev": true, - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/prr": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", - "dev": true - }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "dev": true - }, - "node_modules/public-encrypt": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", - "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", - "dev": true, - "dependencies": { - "bn.js": "^4.1.0", - "browserify-rsa": "^4.0.0", - "create-hash": "^1.1.0", - "parse-asn1": "^5.0.0", - "randombytes": "^2.0.1", - "safe-buffer": "^5.1.2" - } - }, - "node_modules/public-encrypt/node_modules/bn.js": { - "version": "4.11.9", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", - "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==", - "dev": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/pumpify": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/pumpify/-/pumpify-1.5.1.tgz", - "integrity": "sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==", - "dev": true, - "dependencies": { - "duplexify": "^3.6.0", - "inherits": "^2.0.3", - "pump": "^2.0.0" - } - }, - "node_modules/pumpify/node_modules/pump": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", - "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/querystring": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/querystring-es3": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", - "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", - "dev": true, - "engines": { - "node": ">=0.4.x" - } - }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/randomfill": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", - "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", - "dev": true, - "dependencies": { - "randombytes": "^2.0.5", - "safe-buffer": "^5.1.0" - } - }, - "node_modules/react-is": { - "version": "17.0.1", - "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.1.tgz", - "integrity": "sha512-NAnt2iGDXohE5LI7uBnLnqvLQMtzhkiAOLXTmv+qnF9Ky7xAPcX8Up/xWIhxvLVGJvuLiNc4xQLtuqDRzb4fSA==", - "dev": true - }, - "node_modules/read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "dev": true, - "dependencies": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/read-pkg-up": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz", - "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==", - "dev": true, - "dependencies": { - "find-up": "^4.1.0", - "read-pkg": "^5.2.0", - "type-fest": "^0.8.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/read-pkg/node_modules/type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", - "dev": true, - "optional": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/rechoir": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", - "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", - "dev": true, - "dependencies": { - "resolve": "^1.9.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", - "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", - "dev": true - }, - "node_modules/regenerate-unicode-properties": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", - "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.13.7", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true - }, - "node_modules/regenerator-transform": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", - "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.8.4" - } - }, - "node_modules/regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/regexpp": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.1.0.tgz", - "integrity": "sha512-ZOIzd8yVsQQA7j8GCSlPGXwg5PfmA1mrq0JP4nGhh54LaKN3xdai/vHUDu74pKwV8OxseMS65u2NImosQcSD0Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/regexpu-core": { - "version": "4.7.1", - "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", - "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", - "dev": true, - "dependencies": { - "regenerate": "^1.4.0", - "regenerate-unicode-properties": "^8.2.0", - "regjsgen": "^0.5.1", - "regjsparser": "^0.6.4", - "unicode-match-property-ecmascript": "^1.0.4", - "unicode-match-property-value-ecmascript": "^1.2.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", - "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", - "dev": true - }, - "node_modules/regjsparser": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.4.tgz", - "integrity": "sha512-64O87/dPDgfk8/RQqC4gkZoGyyWFIEUTTh80CU6CWuK5vkCGyekIx+oKcEIYtP/RAxSQltCZHCNu/mdd7fqlJw==", - "dev": true, - "dependencies": { - "jsesc": "~0.5.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, - "node_modules/regjsparser/node_modules/jsesc": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", - "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "dev": true - }, - "node_modules/repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/request-promise-core": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.4.tgz", - "integrity": "sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==", - "dev": true, - "dependencies": { - "lodash": "^4.17.19" - }, - "engines": { - "node": ">=0.10.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.9.tgz", - "integrity": "sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==", - "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", - "dev": true, - "dependencies": { - "request-promise-core": "1.1.4", - "stealthy-require": "^1.1.1", - "tough-cookie": "^2.3.3" - }, - "engines": { - "node": ">=0.12.0" - }, - "peerDependencies": { - "request": "^2.34" - } - }, - "node_modules/request-promise-native/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/request/node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "dev": true, - "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true, - "bin": { - "uuid": "bin/uuid" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "dev": true - }, - "node_modules/resolve": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz", - "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==", - "dev": true, - "dependencies": { - "is-core-module": "^2.0.0", - "path-parse": "^1.0.6" - } - }, - "node_modules/resolve-cwd": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", - "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", - "dev": true, - "dependencies": { - "resolve-from": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-cwd/node_modules/resolve-from": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", - "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, - "node_modules/ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true, - "engines": { - "node": ">=0.12" - } - }, - "node_modules/rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/ripemd160": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", - "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", - "dev": true, - "dependencies": { - "hash-base": "^3.0.0", - "inherits": "^2.0.1" - } - }, - "node_modules/rsvp": { - "version": "4.8.5", - "resolved": "https://registry.npmjs.org/rsvp/-/rsvp-4.8.5.tgz", - "integrity": "sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==", - "dev": true, - "engines": { - "node": "6.* || >= 7.*" - } - }, - "node_modules/run-queue": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", - "dev": true, - "dependencies": { - "aproba": "^1.1.1" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, - "node_modules/safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, - "dependencies": { - "ret": "~0.1.10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, - "node_modules/sane": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-4.1.0.tgz", - "integrity": "sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==", - "dev": true, - "dependencies": { - "@cnakazawa/watch": "^1.0.3", - "anymatch": "^2.0.0", - "capture-exit": "^2.0.0", - "exec-sh": "^0.3.2", - "execa": "^1.0.0", - "fb-watchman": "^2.0.0", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5" - }, - "bin": { - "sane": "src/cli.js" - }, - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/sane/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/sane/node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/sane/node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sane/node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "dependencies": { - "path-key": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/sane/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/saxes": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", - "integrity": "sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==", - "dev": true, - "dependencies": { - "xmlchars": "^2.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/schema-utils": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-1.0.0.tgz", - "integrity": "sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g==", - "dev": true, - "dependencies": { - "ajv": "^6.1.0", - "ajv-errors": "^1.0.0", - "ajv-keywords": "^3.1.0" - }, - "engines": { - "node": ">= 4" - } - }, - "node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "dev": true, - "bin": { - "semver": "bin/semver" - } - }, - "node_modules/serialize-javascript": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-4.0.0.tgz", - "integrity": "sha512-GaNA54380uFefWghODBWEGisLZFj00nS5ACs6yHa9nLqlLpVLO8ChDGeKRjZnV4Nh4n0Qi7nhYZD/9fCPzEqkw==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "node_modules/set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "dev": true, - "dependencies": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "dependencies": { - "isobject": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/set-value/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/setimmediate": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", - "dev": true - }, - "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", - "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" - }, - "bin": { - "sha.js": "bin.js" - } - }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, - "dependencies": { - "shebang-regex": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true, - "optional": true - }, - "node_modules/signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "dev": true - }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true - }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/slice-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz", - "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.0", - "astral-regex": "^1.0.0", - "is-fullwidth-code-point": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/slice-ansi/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/slice-ansi/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/slice-ansi/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "node_modules/snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, - "dependencies": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, - "dependencies": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "dependencies": { - "kind-of": "^6.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-node/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, - "dependencies": { - "kind-of": "^3.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon-util/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/snapdragon/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "dependencies": { - "is-extendable": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/snapdragon/node_modules/source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-list-map": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", - "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", - "dev": true - }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "node_modules/source-map-support": { - "version": "0.5.19", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", - "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, - "node_modules/source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "node_modules/spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "dev": true, - "dependencies": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", - "dev": true - }, - "node_modules/spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "dev": true, - "dependencies": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "node_modules/spdx-license-ids": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==", - "dev": true - }, - "node_modules/split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, - "dependencies": { - "extend-shallow": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "node_modules/sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "dev": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ssri": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-6.0.1.tgz", - "integrity": "sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA==", - "dev": true, - "dependencies": { - "figgy-pudding": "^3.5.1" - } - }, - "node_modules/stack-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", - "integrity": "sha512-gL//fkxfWUsIlFL2Tl42Cl6+HFALEaB1FU76I/Fy+oZjRreP7OPMXFlGbxM7NQsI0ZpUfw76sHnv0WNYuTb7Iw==", - "dev": true, - "dependencies": { - "escape-string-regexp": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/stack-utils/node_modules/escape-string-regexp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", - "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, - "dependencies": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/static-extend/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/stream-browserify": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.2.tgz", - "integrity": "sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg==", - "dev": true, - "dependencies": { - "inherits": "~2.0.1", - "readable-stream": "^2.0.2" - } - }, - "node_modules/stream-each": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/stream-each/-/stream-each-1.2.3.tgz", - "integrity": "sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "stream-shift": "^1.0.0" - } - }, - "node_modules/stream-http": { - "version": "2.8.3", - "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.8.3.tgz", - "integrity": "sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw==", - "dev": true, - "dependencies": { - "builtin-status-codes": "^3.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.3.6", - "to-arraybuffer": "^1.0.0", - "xtend": "^4.0.0" - } - }, - "node_modules/stream-shift": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz", - "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", - "dev": true - }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/string-length": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", - "integrity": "sha512-PKyXUd0LK0ePjSOnWn34V2uD6acUWev9uy0Ft05k0E8xRW+SKcA0F7eMr7h5xlzfn+4O3N+55rduYyet3Jk+jw==", - "dev": true, - "dependencies": { - "char-regex": "^1.0.2", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/string-width": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", - "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", - "dev": true, - "dependencies": { - "emoji-regex": "^7.0.1", - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^5.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/string-width/node_modules/ansi-regex": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", - "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/string-width/node_modules/strip-ansi": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", - "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", - "dev": true, - "dependencies": { - "ansi-regex": "^4.1.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/supports-hyperlinks": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz", - "integrity": "sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0", - "supports-color": "^7.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/symbol-tree": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", - "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", - "dev": true - }, - "node_modules/table": { - "version": "5.4.6", - "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz", - "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==", - "dev": true, - "dependencies": { - "ajv": "^6.10.2", - "lodash": "^4.17.14", - "slice-ansi": "^2.1.0", - "string-width": "^3.0.0" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/table-layout": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.1.tgz", - "integrity": "sha512-dEquqYNJiGwY7iPfZ3wbXDI944iqanTSchrACLL2nOB+1r+h1Nzu2eH+DuPPvWvm5Ry7iAPeFlgEtP5bIp5U7Q==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/tapable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-1.1.3.tgz", - "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "dev": true, - "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/tar/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terminal-link": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", - "integrity": "sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==", - "dev": true, - "dependencies": { - "ansi-escapes": "^4.2.1", - "supports-hyperlinks": "^2.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/terser": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-4.8.0.tgz", - "integrity": "sha512-EAPipTNeWsb/3wLPeup1tVPaXfIaU68xMnVdPafIL1TV05OhASArYyIfFvnvJCNrR2NIOvDVNNTFRa+Re2MWyw==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.6.1", - "source-map-support": "~0.5.12" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/terser-webpack-plugin": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-4.2.3.tgz", - "integrity": "sha512-jTgXh40RnvOrLQNgIkwEKnQ8rmHjHK4u+6UBEi+W+FPmvb+uo+chJXntKe7/3lW5mNysgSWD60KyesnhW8D6MQ==", - "dev": true, - "dependencies": { - "cacache": "^15.0.5", - "find-cache-dir": "^3.3.1", - "jest-worker": "^26.5.0", - "p-limit": "^3.0.2", - "schema-utils": "^3.0.0", - "serialize-javascript": "^5.0.1", - "source-map": "^0.6.1", - "terser": "^5.3.4", - "webpack-sources": "^1.4.3" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/cacache": { - "version": "15.0.5", - "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.0.5.tgz", - "integrity": "sha512-lloiL22n7sOjEEXdL8NAjTgv9a1u43xICE9/203qonkZUCj5X1UEWIdf2/Y0d6QcCtMzbKQyhrcDbdvlZTs/+A==", - "dev": true, - "dependencies": { - "@npmcli/move-file": "^1.0.1", - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "glob": "^7.1.4", - "infer-owner": "^1.0.4", - "lru-cache": "^6.0.0", - "minipass": "^3.1.1", - "minipass-collect": "^1.0.2", - "minipass-flush": "^1.0.5", - "minipass-pipeline": "^1.2.2", - "mkdirp": "^1.0.3", - "p-map": "^4.0.0", - "promise-inflight": "^1.0.1", - "rimraf": "^3.0.2", - "ssri": "^8.0.0", - "tar": "^6.0.2", - "unique-filename": "^1.1.1" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/terser-webpack-plugin/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/p-limit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.0.2.tgz", - "integrity": "sha512-iwqZSOoWIW+Ew4kAGUlN16J4M7OB3ysMLSZtnhmqx7njIHFPlxWBX8xo3lVTyFVq6mI/lL9qt2IsN1sHwaxJkg==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - } - }, - "node_modules/terser-webpack-plugin/node_modules/schema-utils": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", - "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", - "dev": true, - "dependencies": { - "@types/json-schema": "^7.0.6", - "ajv": "^6.12.5", - "ajv-keywords": "^3.5.2" - }, - "engines": { - "node": ">= 10.13.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", - "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", - "dev": true, - "dependencies": { - "randombytes": "^2.1.0" - } - }, - "node_modules/terser-webpack-plugin/node_modules/ssri": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.0.tgz", - "integrity": "sha512-aq/pz989nxVYwn16Tsbj1TqFpD5LLrQxHf5zaHuieFV+R0Bbr4y8qUsOA45hXT/N4/9UNXTarBjnjVmjSOVaAA==", - "dev": true, - "dependencies": { - "minipass": "^3.1.1" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser": { - "version": "5.3.8", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.3.8.tgz", - "integrity": "sha512-zVotuHoIfnYjtlurOouTazciEfL7V38QMAOhGqpXDEg6yT13cF4+fEP9b0rrCEQTn+tT46uxgFsTZzhygk+CzQ==", - "dev": true, - "dependencies": { - "commander": "^2.20.0", - "source-map": "~0.7.2", - "source-map-support": "~0.5.19" - }, - "bin": { - "terser": "bin/terser" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/terser-webpack-plugin/node_modules/terser/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/terser/node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/test-exclude": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", - "integrity": "sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==", - "dev": true, - "dependencies": { - "@istanbuljs/schema": "^0.1.2", - "glob": "^7.1.4", - "minimatch": "^3.0.4" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", - "dev": true - }, - "node_modules/throat": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-5.0.0.tgz", - "integrity": "sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", - "dev": true, - "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" - } - }, - "node_modules/timers-browserify": { - "version": "2.0.12", - "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", - "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", - "dev": true, - "dependencies": { - "setimmediate": "^1.0.4" - }, - "engines": { - "node": ">=0.6.0" - } - }, - "node_modules/tmpl": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", - "integrity": "sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==", - "dev": true - }, - "node_modules/to-arraybuffer": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz", - "integrity": "sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M=", - "dev": true - }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, - "dependencies": { - "kind-of": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-object-path/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "dependencies": { - "is-buffer": "^1.1.5" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, - "dependencies": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, - "dependencies": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/tough-cookie": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz", - "integrity": "sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==", - "dev": true, - "dependencies": { - "ip-regex": "^2.1.0", - "psl": "^1.1.28", - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/tr46": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-2.0.2.tgz", - "integrity": "sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==", - "dev": true, - "dependencies": { - "punycode": "^2.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "dev": true - }, - "node_modules/tty-browserify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.0.tgz", - "integrity": "sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY=", - "dev": true - }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } - }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true - }, - "node_modules/type-check": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", - "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", - "dev": true, - "dependencies": { - "prelude-ls": "^1.2.1" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-fest": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz", - "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "node_modules/typedarray-to-buffer": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz", - "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==", - "dev": true, - "dependencies": { - "is-typedarray": "^1.0.0" - } - }, - "node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", - "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", - "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", - "dev": true, - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^1.0.4", - "unicode-property-aliases-ecmascript": "^1.0.4" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", - "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", - "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unique-filename": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", - "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", - "dev": true, - "dependencies": { - "unique-slug": "^2.0.0" - } - }, - "node_modules/unique-slug": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", - "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4" - } - }, - "node_modules/unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, - "dependencies": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, - "dependencies": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-value/node_modules/isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, - "dependencies": { - "isarray": "1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/unset-value/node_modules/isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/upath": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", - "integrity": "sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=4", - "yarn": "*" - } - }, - "node_modules/uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "dev": true, - "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, - "node_modules/url": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", - "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", - "dev": true, - "dependencies": { - "punycode": "1.3.2", - "querystring": "0.2.0" - } - }, - "node_modules/url-polyfill": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/url-polyfill/-/url-polyfill-1.1.12.tgz", - "integrity": "sha512-mYFmBHCapZjtcNHW0MDq9967t+z4Dmg5CJ0KqysK3+ZbyoNOWQHksGCTWwDhxGXllkWlOc10Xfko6v4a3ucM6A==" - }, - "node_modules/url/node_modules/punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - }, - "node_modules/use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/util": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz", - "integrity": "sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ==", - "dev": true, - "dependencies": { - "inherits": "2.0.3" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "node_modules/util/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, - "optional": true, - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", - "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==", - "dev": true - }, - "node_modules/v8-to-istanbul": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-7.1.0.tgz", - "integrity": "sha512-uXUVqNUCLa0AH1vuVxzi+MI4RfxEOKt9pBgKwHbgH7st8Kv2P1m+jvWNnektzBh5QShF3ODgKmUFCf38LnVz1g==", - "dev": true, - "dependencies": { - "@types/istanbul-lib-coverage": "^2.0.1", - "convert-source-map": "^1.6.0", - "source-map": "^0.7.3" - }, - "engines": { - "node": ">=10.10.0" - } - }, - "node_modules/v8-to-istanbul/node_modules/source-map": { - "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", - "dev": true, - "engines": { - "node": ">= 8" - } - }, - "node_modules/validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "dev": true, - "dependencies": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, - "engines": [ - "node >=0.6.0" - ], - "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "node_modules/vm-browserify": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", - "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==", - "dev": true - }, - "node_modules/w3c-hr-time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", - "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", - "dev": true, - "dependencies": { - "browser-process-hrtime": "^1.0.0" - } - }, - "node_modules/w3c-xmlserializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz", - "integrity": "sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==", - "dev": true, - "dependencies": { - "xml-name-validator": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "dependencies": { - "makeerror": "1.0.x" - } - }, - "node_modules/watchpack": { - "version": "1.7.4", - "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.7.4.tgz", - "integrity": "sha512-aWAgTW4MoSJzZPAicljkO1hsi1oKj/RRq/OJQh2PKI2UKL04c2Bs+MBOB+BBABHTXJpf9mCwHN7ANCvYsvY2sg==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.2", - "neo-async": "^2.5.0" - }, - "optionalDependencies": { - "chokidar": "^3.4.1", - "watchpack-chokidar2": "^2.0.0" - } - }, - "node_modules/watchpack-chokidar2": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/watchpack-chokidar2/-/watchpack-chokidar2-2.0.0.tgz", - "integrity": "sha512-9TyfOyN/zLUbA288wZ8IsMZ+6cbzvsNyEzSBp6e/zkifi6xxbl8SmQ/CxQq32k8NNqrdVEVUVSEf56L4rQ/ZxA==", - "dev": true, - "optional": true, - "dependencies": { - "chokidar": "^2.1.8" - }, - "engines": { - "node": "<8.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "optional": true, - "dependencies": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/anymatch/node_modules/normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "optional": true, - "dependencies": { - "remove-trailing-separator": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/binary-extensions": { - "version": "1.13.1", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.13.1.tgz", - "integrity": "sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/chokidar": { - "version": "2.1.8", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", - "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "dev": true, - "optional": true, - "dependencies": { - "anymatch": "^2.0.0", - "async-each": "^1.0.1", - "braces": "^2.3.2", - "fsevents": "^1.2.7", - "glob-parent": "^3.1.0", - "inherits": "^2.0.3", - "is-binary-path": "^1.0.0", - "is-glob": "^4.0.0", - "normalize-path": "^3.0.0", - "path-is-absolute": "^1.0.0", - "readdirp": "^2.2.1", - "upath": "^1.1.1" - } - }, - "node_modules/watchpack-chokidar2/node_modules/fsevents": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", - "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "dev": true, - "optional": true, - "os": [ - "darwin" - ], - "dependencies": { - "nan": "^2.12.1" - }, - "engines": { - "node": ">= 4.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "dev": true, - "optional": true, - "dependencies": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/glob-parent/node_modules/is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "dev": true, - "optional": true, - "dependencies": { - "is-extglob": "^2.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/is-binary-path": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", - "dev": true, - "optional": true, - "dependencies": { - "binary-extensions": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "optional": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/watchpack-chokidar2/node_modules/readdirp": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", - "integrity": "sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==", - "dev": true, - "optional": true, - "dependencies": { - "graceful-fs": "^4.1.11", - "micromatch": "^3.1.10", - "readable-stream": "^2.0.2" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/webidl-conversions": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-6.1.0.tgz", - "integrity": "sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==", - "dev": true, - "engines": { - "node": ">=10.4" - } - }, - "node_modules/webpack": { - "version": "4.44.2", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-4.44.2.tgz", - "integrity": "sha512-6KJVGlCxYdISyurpQ0IPTklv+DULv05rs2hseIXer6D7KrUicRDLFb4IUM1S6LUAKypPM/nSiVSuv8jHu1m3/Q==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.9.0", - "@webassemblyjs/helper-module-context": "1.9.0", - "@webassemblyjs/wasm-edit": "1.9.0", - "@webassemblyjs/wasm-parser": "1.9.0", - "acorn": "^6.4.1", - "ajv": "^6.10.2", - "ajv-keywords": "^3.4.1", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^4.3.0", - "eslint-scope": "^4.0.3", - "json-parse-better-errors": "^1.0.2", - "loader-runner": "^2.4.0", - "loader-utils": "^1.2.3", - "memory-fs": "^0.4.1", - "micromatch": "^3.1.10", - "mkdirp": "^0.5.3", - "neo-async": "^2.6.1", - "node-libs-browser": "^2.2.1", - "schema-utils": "^1.0.0", - "tapable": "^1.1.3", - "terser-webpack-plugin": "^1.4.3", - "watchpack": "^1.7.4", - "webpack-sources": "^1.4.1" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=6.11.5" - } - }, - "node_modules/webpack-cli": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.1.0.tgz", - "integrity": "sha512-NdhxXMZmoik62Y05t0h1y65LjBM7BwFPq311ihXuMM3RY6dlc4KkCTyHLzTuBEc+bqq6d3xh+CWmU0xRexNJBA==", - "dev": true, - "dependencies": { - "@webpack-cli/info": "^1.0.2", - "@webpack-cli/serve": "^1.0.1", - "ansi-escapes": "^4.3.1", - "colorette": "^1.2.1", - "command-line-usage": "^6.1.0", - "commander": "^6.0.0", - "enquirer": "^2.3.4", - "execa": "^4.0.0", - "import-local": "^3.0.2", - "interpret": "^2.0.0", - "rechoir": "^0.7.0", - "v8-compile-cache": "^2.1.0", - "webpack-merge": "^4.2.2" - }, - "bin": { - "webpack-cli": "bin/cli.js" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/webpack-cli/node_modules/webpack-merge": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-4.2.2.tgz", - "integrity": "sha512-TUE1UGoTX2Cd42j3krGYqObZbOD+xF7u28WB7tfUordytSjbWTIjK/8V0amkBfTYN4/pB/GIDlJZZ657BGG19g==", - "dev": true, - "dependencies": { - "lodash": "^4.17.15" - } - }, - "node_modules/webpack-merge": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.3.0.tgz", - "integrity": "sha512-4PtsBAWnmJULIJYviiPq4BxwAykbAgGMheyEVaemj2bJI54h+p/gnlbXZEH2EM0IYC3blOE1Qm6kzKlc06N1UQ==", - "dev": true, - "dependencies": { - "clone-deep": "^4.0.1", - "wildcard": "^2.0.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/webpack-sources": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.4.3.tgz", - "integrity": "sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ==", - "dev": true, - "dependencies": { - "source-list-map": "^2.0.0", - "source-map": "~0.6.1" - } - }, - "node_modules/webpack/node_modules/find-cache-dir": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", - "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", - "dev": true, - "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/webpack/node_modules/micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "dependencies": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webpack/node_modules/terser-webpack-plugin": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-1.4.5.tgz", - "integrity": "sha512-04Rfe496lN8EYruwi6oPQkG0vo8C+HT49X687FZnpPF0qMAIHONI6HEXYPKDOE8e5HjXTyKfqRd/agHtH0kOtw==", - "dev": true, - "dependencies": { - "cacache": "^12.0.2", - "find-cache-dir": "^2.1.0", - "is-wsl": "^1.1.0", - "schema-utils": "^1.0.0", - "serialize-javascript": "^4.0.0", - "source-map": "^0.6.1", - "terser": "^4.1.2", - "webpack-sources": "^1.4.0", - "worker-farm": "^1.7.0" - }, - "engines": { - "node": ">= 6.9.0" - } - }, - "node_modules/whatwg-encoding": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", - "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", - "dev": true, - "dependencies": { - "iconv-lite": "0.4.24" - } - }, - "node_modules/whatwg-fetch": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.4.1.tgz", - "integrity": "sha512-sofZVzE1wKwO+EYPbWfiwzaKovWiZXf4coEzjGP9b2GBVgQRLQUZ2QcuPpQExGDAW5GItpEm6Tl4OU5mywnAoQ==" - }, - "node_modules/whatwg-mimetype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", - "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==", - "dev": true - }, - "node_modules/whatwg-url": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-8.4.0.tgz", - "integrity": "sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==", - "dev": true, - "dependencies": { - "lodash.sortby": "^4.7.0", - "tr46": "^2.0.2", - "webidl-conversions": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "node_modules/wildcard": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", - "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", - "dev": true - }, - "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/wordwrapjs": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.0.tgz", - "integrity": "sha512-Svqw723a3R34KvsMgpjFBYCgNOSdcW3mQFK4wIfhGQhtaFVOJmdYoXgi63ne3dTlWgatVcUc7t4HtQ/+bUVIzQ==", - "dev": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/worker-farm": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/worker-farm/-/worker-farm-1.7.0.tgz", - "integrity": "sha512-rvw3QTZc8lAxyVrqcSGVm5yP/IJ2UcB3U0graE3LCFoZ0Yn2x4EoVSqJKdB/T5M+FLcRPjz4TDacRf3OCfNUzw==", - "dev": true, - "dependencies": { - "errno": "~0.1.7" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "node_modules/write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", - "dev": true, - "dependencies": { - "mkdirp": "^0.5.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/write-file-atomic": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-3.0.3.tgz", - "integrity": "sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==", - "dev": true, - "dependencies": { - "imurmurhash": "^0.1.4", - "is-typedarray": "^1.0.0", - "signal-exit": "^3.0.2", - "typedarray-to-buffer": "^3.1.5" - } - }, - "node_modules/ws": { - "version": "7.4.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", - "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", - "dev": true, - "engines": { - "node": ">=8.3.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "node_modules/xmlchars": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", - "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", - "dev": true - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "dev": true, - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", - "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", - "dev": true - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "dev": true - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/yargs/node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - } - }, + "lockfileVersion": 1, "dependencies": { "@babel/code-frame": { "version": "7.10.4", @@ -11891,6 +1386,21 @@ } } }, + "@paypal/paypal-js": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@paypal/paypal-js/-/paypal-js-7.0.3.tgz", + "integrity": "sha512-Mpq0TGKzypi02Iw4p11eq5+dxVKcuJ+98UDrxLRbqZcr8pBRFPX+jqPFkVq6hzs3sHxwIuMPFCxnrckxxLSznw==", + "requires": { + "promise-polyfill": "^8.3.0" + }, + "dependencies": { + "promise-polyfill": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.3.0.tgz", + "integrity": "sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==" + } + } + }, "@sinonjs/commons": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.2.tgz", @@ -15626,8 +5136,7 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz", "integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==", - "dev": true, - "requires": {} + "dev": true }, "jest-regex-util": { "version": "26.0.0", @@ -18062,23 +7571,6 @@ "integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==", "dev": true }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "requires": { - "safe-buffer": "~5.1.0" - }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - } - } - }, "string-length": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/string-length/-/string-length-4.0.1.tgz", @@ -18117,6 +7609,23 @@ } } }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, "strip-ansi": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", @@ -19281,8 +8790,7 @@ "version": "7.4.3", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.3.tgz", "integrity": "sha512-hr6vCR76GsossIRsr8OLR9acVVm1jyfEWvhbNjtgPOrfvAlKzvyeg/P6r8RuDjRyrcQoPQT7K0DGEPc7Ae6jzA==", - "dev": true, - "requires": {} + "dev": true }, "xml-name-validator": { "version": "3.0.0", diff --git a/_dev/js/front/src/components/common/payment-option.component.js b/_dev/js/front/src/components/common/payment-option.component.js index 76cb926c7..f09872a22 100644 --- a/_dev/js/front/src/components/common/payment-option.component.js +++ b/_dev/js/front/src/components/common/payment-option.component.js @@ -80,7 +80,7 @@ export class PaymentOptionComponent extends BaseComponent { : this.$('funding-source.name.default'); let element = Array.prototype.slice .call(this.data.HTMLElementContainer.querySelectorAll('*')) - .find(item => item.innerHTML.trim() === label.trim()); + .find(item => item.innerText.trim() === label.trim()); if (!element) { console.error('HTMLElement label "' + label.trim() + '" not found.'); diff --git a/_dev/js/front/src/service/paypal.service.js b/_dev/js/front/src/service/paypal.service.js index 13e39362e..a87c83084 100644 --- a/_dev/js/front/src/service/paypal.service.js +++ b/_dev/js/front/src/service/paypal.service.js @@ -127,7 +127,7 @@ export class PayPalService extends BaseClass { if (fundingSource === 'paypal') { return style; - } else if(fundingSource === 'paylater') { + } else if (fundingSource === 'paylater') { return { shape: style.shape, color: style.color }; } @@ -178,7 +178,7 @@ export class PayPalService extends BaseClass { }, ...events }) - .then(hostedFields => { + .then((hostedFields) => { const numberField = document.querySelector(fieldSelectors.number); const cvvField = document.querySelector(fieldSelectors.cvv); const expirationDateField = document.querySelector( @@ -203,36 +203,36 @@ export class PayPalService extends BaseClass { return hostedFields; }) - .then(hostedFields => { - hostedFields.on('focus', event => { + .then((hostedFields) => { + hostedFields.on('focus', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsFocus', { detail: { ps_checkout: window.ps_checkout, event: event } }) ); }); - hostedFields.on('blur', event => { + hostedFields.on('blur', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsBlur', { detail: { ps_checkout: window.ps_checkout, event: event } }) ); }); - hostedFields.on('empty', event => { + hostedFields.on('empty', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsEmpty', { detail: { ps_checkout: window.ps_checkout, event: event } }) ); }); - hostedFields.on('notEmpty', event => { + hostedFields.on('notEmpty', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsNotEmpty', { detail: { ps_checkout: window.ps_checkout, event: event } }) ); }); - hostedFields.on('validityChange', event => { + hostedFields.on('validityChange', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsValidityChange', { detail: { ps_checkout: window.ps_checkout, event: event } @@ -246,7 +246,7 @@ export class PayPalService extends BaseClass { }) ); }); - hostedFields.on('cardTypeChange', event => { + hostedFields.on('cardTypeChange', (event) => { window.ps_checkout.events.dispatchEvent( new CustomEvent('hostedFieldsCardTypeChange', { detail: { ps_checkout: window.ps_checkout, event: event } @@ -304,7 +304,6 @@ export class PayPalService extends BaseClass { async getCardFields(fieldSelectors, options) { const cardFields = this.sdk.CardFields(options); - const nameField = cardFields.NameField({ placeholder: this.$('paypal.hosted-fields.placeholder.card-name') }); @@ -315,12 +314,9 @@ export class PayPalService extends BaseClass { placeholder: this.$('paypal.hosted-fields.placeholder.expiration-date') }); const cvvField = cardFields.CVVField({ - placeholder: this.$( - 'paypal.hosted-fields.placeholder.cvv' - ) + placeholder: this.$('paypal.hosted-fields.placeholder.cvv') }); - // await Promise.all( // [ // cardNameField.render(nameField), @@ -338,7 +334,7 @@ export class PayPalService extends BaseClass { await cvvField.render(fieldSelectors.cvv); await nameField.render(fieldSelectors.name); } catch (e) { - return console.error("Failed to render CardFields", e); + return console.error('Failed to render CardFields', e); } const nameLabel = document.querySelector( @@ -347,7 +343,9 @@ export class PayPalService extends BaseClass { const numberLabel = document.querySelector( `label[for="${fieldSelectors.number.id}"]` ); - const cvvLabel = document.querySelector(`label[for="${fieldSelectors.cvv.id}"]`); + const cvvLabel = document.querySelector( + `label[for="${fieldSelectors.cvv.id}"]` + ); const expirationDateLabel = document.querySelector( `label[for="${fieldSelectors.expiry.id}"]` ); @@ -355,7 +353,9 @@ export class PayPalService extends BaseClass { nameLabel.innerHTML = this.$('paypal.hosted-fields.label.card-name'); numberLabel.innerHTML = this.$('paypal.hosted-fields.label.card-number'); cvvLabel.innerHTML = this.$('paypal.hosted-fields.label.cvv'); - expirationDateLabel.innerHTML = this.$('paypal.hosted-fields.label.expiration-date'); + expirationDateLabel.innerHTML = this.$( + 'paypal.hosted-fields.label.expiration-date' + ); return cardFields; } @@ -367,16 +367,25 @@ export class PayPalService extends BaseClass { this.configPrestaShop.fundingSourcesSorted || paypalFundingSources ) .filter( - fundingSource => paypalFundingSources.indexOf(fundingSource) >= 0 + (fundingSource) => paypalFundingSources.indexOf(fundingSource) >= 0 ) - .map(fundingSource => ({ + .map((fundingSource) => ({ name: fundingSource, mark: this.sdk.Marks({ fundingSource }) })) .filter((fundingSource) => { - if (fundingSource.name === 'card' && this.configPrestaShop.hostedFieldsEnabled && !this.isCardFieldsEligible()) { - console.log(this.configPrestaShop.hostedFieldsEnabled, this.isCardFieldsEligible()); - console.error('Card Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)'); + if ( + fundingSource.name === 'card' && + this.configPrestaShop.hostedFieldsEnabled && + !this.isCardFieldsEligible() + ) { + console.log( + this.configPrestaShop.hostedFieldsEnabled, + this.isCardFieldsEligible() + ); + console.error( + 'Card Fields (CCF) eligibility is declined. Switching to PayPal branded card fields (SCF)' + ); } console.log(fundingSource.name, fundingSource.mark.isEligible()); @@ -457,11 +466,14 @@ export class PayPalService extends BaseClass { */ getPaymentFields(fundingSource, fields = {}) { console.log(this.sdk.PaymentFields); - return this.sdk.PaymentFields && this.sdk.PaymentFields({ - fundingSource: fundingSource, - style: this.getPaymentFieldsCustomizationStyle(fundingSource), - fields: fields - }); + return ( + this.sdk.PaymentFields && + this.sdk.PaymentFields({ + fundingSource: fundingSource, + style: this.getPaymentFieldsCustomizationStyle(fundingSource), + fields: fields + }) + ); } /** diff --git a/_dev/js/front/src/service/prestashop.service/prestashop-ps1_7.service.js b/_dev/js/front/src/service/prestashop.service/prestashop-ps1_7.service.js index 1f4cbb789..f00d91569 100644 --- a/_dev/js/front/src/service/prestashop.service/prestashop-ps1_7.service.js +++ b/_dev/js/front/src/service/prestashop.service/prestashop-ps1_7.service.js @@ -58,7 +58,7 @@ export class PrestashopPs1_7Service { if (document.body.id !== 'checkout') return false; const step = document.querySelector('#checkout-personal-information-step'); - return step && step.classList.contains('-current'); + return step && (step.classList.contains('-current') || step.classList.contains('step--current')); } static isIframeProductPage() { diff --git a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js new file mode 100644 index 000000000..6a0d68c1b --- /dev/null +++ b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js @@ -0,0 +1,47 @@ +/** + * Copyright since 2007 PrestaShop SA and Contributors + * PrestaShop is an International Registered Trademark & Property of PrestaShop SA + * + * NOTICE OF LICENSE + * + * This source file is subject to the Academic Free License 3.0 (AFL-3.0) + * that is bundled with this package in the file LICENSE.md. + * It is also available through the world-wide-web at this URL: + * https://opensource.org/licenses/AFL-3.0 + * If you did not receive a copy of the license and are unable to + * obtain it through the world-wide-web, please send an email + * to license@prestashop.com so we can send you a copy immediately. + * + * @author PrestaShop SA + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) + */ +export const DefaultSelectors1_7Hummingbird = { + BASE_PAYMENT_CONFIRMATION: '#payment-confirmation [type="submit"]', + + CONDITIONS_CHECKBOXES: '#conditions-to-approve input[type="checkbox"]', + + LOADER_PARENT: 'body', + + NOTIFICATION_CONDITIONS: '.accept-cgv', + NOTIFICATION_PAYMENT_CANCELLED: '#ps_checkout-canceled', + NOTIFICATION_PAYMENT_ERROR: '#ps_checkout-error', + NOTIFICATION_PAYMENT_ERROR_TEXT: '#ps_checkout-error-text', + + PAYMENT_OPTIONS: '.payment__list', + PAYMENT_OPTIONS_LOADER: '#ps_checkout-loader', + PAYMENT_OPTION_RADIOS: + '.payment__list input[type="radio"][name="payment-option"]', + + EXPRESS_CHECKOUT_CONTAINER_PRODUCT_PAGE: + '#product .product__add-to-cart .product__minimal-quantity', + EXPRESS_CHECKOUT_CONTAINER_CART_PAGE: + '#cart .cart-summary .cart-detailed__actions', + EXPRESS_CHECKOUT_CONTAINER_CHECKOUT_PAGE: + '#checkout-personal-information-step .step__content', + + PAY_LATER_OFFER_MESSAGE_CONTAINER_PRODUCT: '.product__prices', + PAY_LATER_OFFER_MESSAGE_CONTAINER_CART_SUMMARY: '.cart-summary__totals', + + PAY_LATER_BANNER_CONTAINER: '#notifications .container' +}; diff --git a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js index 77bedc006..49b42f84f 100644 --- a/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js +++ b/_dev/js/front/src/service/query-selector.service/query-selector-ps1_7.service.js @@ -17,9 +17,12 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License 3.0 (AFL-3.0) */ import { DefaultSelectors1_7 } from './default-selectors/default-selectors-ps1_7'; +import { DefaultSelectors1_7Hummingbird } from './default-selectors/default-selectors-ps1_7-hummingbird'; + +const isThemeHummingbird = document.querySelector('body>main#wrapper') !== null; const SELECTORS = { - ...DefaultSelectors1_7, + ...(isThemeHummingbird ? DefaultSelectors1_7Hummingbird : DefaultSelectors1_7), ...(window.ps_checkout.selectors || {}) }; diff --git a/config/common.yml b/config/common.yml index 5727a740b..5f8a6dc35 100644 --- a/config/common.yml +++ b/config/common.yml @@ -114,14 +114,15 @@ services: arguments: - "@ps_checkout.configuration" - ps_checkout.sdk.paypal.linkbuilder: - class: 'PrestaShop\Module\PrestashopCheckout\Builder\PayPalSdkLink\PayPalSdkLinkBuilder' + ps_checkout.sdk.paypal.configurationbuilder: + class: 'PrestaShop\Module\PrestashopCheckout\Builder\PaypalSdkConfiguration\PayPalSdkConfigurationBuilder' public: true arguments: - "@ps_checkout.paypal.configuration" - "@ps_checkout.pay_later.configuration" - "@ps_checkout.funding_source.configuration.repository" - "@ps_checkout.express_checkout.configuration" + - '@ps_checkout.context.shop' ps_checkout.repository.prestashop.account: class: 'PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository' diff --git a/ps_checkout.php b/ps_checkout.php index da4baccef..11b4485db 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -42,7 +42,6 @@ class Ps_checkout extends PaymentModule 'displayPaymentTop', 'displayPaymentByBinaries', 'actionFrontControllerSetMedia', - 'displayHeader', 'actionObjectOrderPaymentAddAfter', 'actionObjectOrderPaymentUpdateAfter', 'displayPaymentReturn', @@ -115,7 +114,7 @@ class Ps_checkout extends PaymentModule 'PS_CHECKOUT_LIVE_STEP_VIEWED' => false, 'PS_CHECKOUT_INTEGRATION_DATE' => self::INTEGRATION_DATE, 'PS_CHECKOUT_WEBHOOK_SECRET' => '', - 'PS_CHECKOUT_LIABILITY_SHIFT_REQ' => '0', + 'PS_CHECKOUT_LIABILITY_SHIFT_REQ' => '1', ]; public $confirmUninstall; @@ -1001,8 +1000,8 @@ public function hookActionFrontControllerSetMedia() return; } - /** @var \PrestaShop\Module\PrestashopCheckout\Builder\PayPalSdkLink\PayPalSdkLinkBuilder $payPalSdkLinkBuilder */ - $payPalSdkLinkBuilder = $this->getService('ps_checkout.sdk.paypal.linkbuilder'); + /** @var \PrestaShop\Module\PrestashopCheckout\Builder\PaypalSdkConfiguration\PayPalSdkConfigurationBuilder $payPalSdkConfigurationBuilder */ + $payPalSdkConfigurationBuilder = $this->getService('ps_checkout.sdk.paypal.configurationbuilder'); /** @var \PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration $expressCheckoutConfiguration */ $expressCheckoutConfiguration = $this->getService('ps_checkout.express_checkout.configuration'); @@ -1073,7 +1072,7 @@ public function hookActionFrontControllerSetMedia() $this->name . 'ExpressCheckoutUrl' => $this->context->link->getModuleLink($this->name, 'ExpressCheckout', [], true), $this->name . 'CheckoutUrl' => $this->getCheckoutPageUrl(), $this->name . 'ConfirmUrl' => $this->context->link->getPageLink('order-confirmation', true, (int) $this->context->language->id), - $this->name . 'PayPalSdkUrl' => $payPalSdkLinkBuilder->buildLink(), + $this->name . 'PayPalSdkConfig' => $payPalSdkConfigurationBuilder->buildConfiguration(), $this->name . 'PayPalClientToken' => $payPalClientToken, $this->name . 'PayPalOrderId' => $payPalOrderId, $this->name . 'FundingSource' => $cartFundingSource, @@ -1586,41 +1585,6 @@ private function getCheckoutPageUrl() ); } - public function hookDisplayHeader() - { - if (!$this->merchantIsValid()) { - return ''; - } - - $controller = Tools::getValue('controller'); - - if (empty($controller) && isset($this->context->controller->php_self)) { - $controller = $this->context->controller->php_self; - } - - /** @var \PrestaShop\Module\PrestashopCheckout\Validator\FrontControllerValidator $frontControllerValidator */ - $frontControllerValidator = $this->getService('ps_checkout.validator.front_controller'); - - if ($frontControllerValidator->shouldLoadFrontJS($controller)) { - // No need to prefetch if script will be loaded - return ''; - } - - /** @var \PrestaShop\Module\PrestashopCheckout\Builder\PayPalSdkLink\PayPalSdkLinkBuilder $payPalSdkLinkBuilder */ - $payPalSdkLinkBuilder = $this->getService('ps_checkout.sdk.paypal.linkbuilder'); - - $this->context->smarty->assign([ - 'contentToPrefetch' => [ - [ - 'link' => $payPalSdkLinkBuilder->buildLink(), - 'type' => 'script', - ], - ], - ]); - - return $this->display(__FILE__, 'views/templates/hook/header.tpl'); - } - /** * When an OrderPayment is created we should update fields payment_method and transaction_id * diff --git a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php b/src/Builder/PayPalSdkConfiguration/PayPalSdkConfigurationBuilder.php similarity index 93% rename from src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php rename to src/Builder/PayPalSdkConfiguration/PayPalSdkConfigurationBuilder.php index 3a1aa5351..b5b2dc249 100644 --- a/src/Builder/PayPalSdkLink/PayPalSdkLinkBuilder.php +++ b/src/Builder/PayPalSdkConfiguration/PayPalSdkConfigurationBuilder.php @@ -18,18 +18,19 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\Builder\PayPalSdkLink; +namespace PrestaShop\Module\PrestashopCheckout\Builder\PaypalSdkConfiguration; use PrestaShop\Module\PrestashopCheckout\Environment\PaypalEnv; use PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration; use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration; +use PrestaShop\Module\PrestashopCheckout\ShopContext; /** * Build sdk link */ -class PayPalSdkLinkBuilder +class PayPalSdkConfigurationBuilder { const BASE_LINK = 'https://www.paypal.com/sdk/js'; @@ -51,6 +52,9 @@ class PayPalSdkLinkBuilder /** @var ExpressCheckoutConfiguration */ private $expressCheckoutConfiguration; + /** @var ShopContext */ + private $shopContext; + /** @var array */ private static $cache = []; @@ -64,20 +68,20 @@ public function __construct( PayPalConfiguration $configuration, PayPalPayLaterConfiguration $payLaterConfiguration, FundingSourceConfigurationRepository $fundingSourceConfigurationRepository, - ExpressCheckoutConfiguration $expressCheckoutConfiguration + ExpressCheckoutConfiguration $expressCheckoutConfiguration, + ShopContext $shopContext ) { $this->configuration = $configuration; $this->payLaterConfiguration = $payLaterConfiguration; $this->fundingSourceConfigurationRepository = $fundingSourceConfigurationRepository; $this->expressCheckoutConfiguration = $expressCheckoutConfiguration; + $this->shopContext = $shopContext; } /** - * @todo To be refactored with Service Container and Dependency Injection - * - * @return string + * @return array */ - public function buildLink() + public function buildConfiguration() { $components = [ 'marks', @@ -97,24 +101,27 @@ public function buildLink() } $params = [ - 'client-id' => (new PaypalEnv())->getPaypalClientId(), - 'merchant-id' => $this->configuration->getMerchantId(), + 'clientId' => (new PaypalEnv())->getPaypalClientId(), + 'merchantId' => $this->configuration->getMerchantId(), 'currency' => \Context::getContext()->currency->iso_code, 'intent' => strtolower($this->configuration->getIntent()), 'commit' => 'order' === $this->getPageName() ? 'true' : 'false', 'vault' => 'false', - 'integration-date' => $this->configuration->getIntegrationDate(), + 'integrationDate' => $this->configuration->getIntegrationDate(), + 'dataPartnerAttributionId' => $this->shopContext->getBnCode(), + 'dataCspNonce' => $this->configuration->getCSPNonce(), + 'dataEnable3ds' => $this->configuration->is3dSecureEnabled(), ]; if ('SANDBOX' === $this->configuration->getPaymentMode()) { - $params['debug'] = 'true'; -// $params['buyer-country'] = $this->getCountry(); +// $params['debug'] = 'true'; + $params['buyerCountry'] = $this->getCountry(); } $fundingSourcesDisabled = $this->getFundingSourcesDisabled(); if (false === empty($fundingSourcesDisabled)) { - $params['disable-funding'] = implode(',', $fundingSourcesDisabled); + $params['disableFunding'] = implode(',', $fundingSourcesDisabled); } $eligibleAlternativePaymentMethods = $this->getEligibleAlternativePaymentMethods(); @@ -133,12 +140,12 @@ public function buildLink() } if (false === empty($eligibleAlternativePaymentMethods)) { - $params['enable-funding'] = implode(',', $eligibleAlternativePaymentMethods); + $params['enableFunding'] = implode(',', $eligibleAlternativePaymentMethods); } $params['components'] = implode(',', $components); - return self::BASE_LINK . '?' . urldecode(http_build_query($params)); + return $params; } /** diff --git a/src/Builder/PayPalSdkLink/index.php b/src/Builder/PayPalSdkConfiguration/index.php similarity index 100% rename from src/Builder/PayPalSdkLink/index.php rename to src/Builder/PayPalSdkConfiguration/index.php diff --git a/views/css/payments.css b/views/css/payments.css index 9879fae5e..6863c430f 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -31,6 +31,10 @@ #checkout-personal-information-step #ps-checkout-express-button { display: flex; flex-flow: row wrap; + + /*Fixes z-index problem on hummingbird*/ + position: relative; + z-index: 1; } .product-add-to-cart #ps-checkout-express-button.disabled{ display: none!important; @@ -357,7 +361,8 @@ label[for="ps_checkout-hosted-fields-card-cvv"] { } .js-payment-ps_checkout.disabled .ps_checkout-button[data-funding-source="card"] button:disabled { - opacity: initial; + /*TODO: This causes the button to be seen as not disabled on hummingbird. Do we keep it?*/ + /*opacity: initial;*/ } .ps_checkout.payment-method-logo-block { diff --git a/views/templates/hook/displayPaymentByBinaries.tpl b/views/templates/hook/displayPaymentByBinaries.tpl index 0cd25ca81..6a75819eb 100644 --- a/views/templates/hook/displayPaymentByBinaries.tpl +++ b/views/templates/hook/displayPaymentByBinaries.tpl @@ -18,7 +18,7 @@ *} {foreach from=$paymentOptions item="fundingSource"} -
+

{l s='You must accept the terms and conditions to be able to process your order.' mod='ps_checkout'}

diff --git a/views/templates/hook/displayPaymentReturn.tpl b/views/templates/hook/displayPaymentReturn.tpl index 182d1e906..ff0421437 100644 --- a/views/templates/hook/displayPaymentReturn.tpl +++ b/views/templates/hook/displayPaymentReturn.tpl @@ -22,7 +22,7 @@
-

+

{$translations.blockTitle|escape:'html':'UTF-8'}

diff --git a/views/templates/hook/header.tpl b/views/templates/hook/header.tpl deleted file mode 100644 index 1bd6658f5..000000000 --- a/views/templates/hook/header.tpl +++ /dev/null @@ -1,21 +0,0 @@ -{** - * Copyright since 2007 PrestaShop SA and Contributors - * PrestaShop is an International Registered Trademark & Property of PrestaShop SA - * - * NOTICE OF LICENSE - * - * This source file is subject to the Academic Free License version 3.0 - * that is bundled with this package in the file LICENSE.md. - * It is also available through the world-wide-web at this URL: - * https://opensource.org/licenses/AFL-3.0 - * If you did not receive a copy of the license and are unable to - * obtain it through the world-wide-web, please send an email - * to license@prestashop.com so we can send you a copy immediately. - * - * @author PrestaShop SA and Contributors - * @copyright Since 2007 PrestaShop SA and Contributors - * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 - *} -{foreach $contentToPrefetch as $content} - -{/foreach} From 3d2868cd654fdd1f98396ddcf2e6a358b6d2bf80 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 7 Feb 2024 17:30:27 +0200 Subject: [PATCH 097/343] Added 3DS block in order view --- config/common.yml | 1 + src/PayPal/Card3DSecure.php | 108 ++++- .../CapturePayPalOrderCommandHandler.php | 13 +- ...lOrderForCheckoutCompletedQueryHandler.php | 1 + src/Presenter/Order/OrderPresenter.php | 5 + tests/Unit/PayPal/Card3DSecureTest.php | 456 +++++++++++++++++- views/css/adminOrderView.css | 8 + views/templates/admin/ajaxPayPalOrder.tpl | 42 +- .../templates/admin/ajaxPayPalOrderLegacy.tpl | 386 ++++++++------- 9 files changed, 823 insertions(+), 197 deletions(-) diff --git a/config/common.yml b/config/common.yml index d1f5fd08b..e7eaee31d 100644 --- a/config/common.yml +++ b/config/common.yml @@ -569,6 +569,7 @@ services: public: true arguments: - "@ps_checkout.event.dispatcher" + - "@ps_checkout.cache.paypal.order" ps_checkout.command.handler.paypal.order.save_paypal_order: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\SavePayPalOrderCommandHandler' diff --git a/src/PayPal/Card3DSecure.php b/src/PayPal/Card3DSecure.php index 8347f9a5b..091eec299 100644 --- a/src/PayPal/Card3DSecure.php +++ b/src/PayPal/Card3DSecure.php @@ -79,17 +79,13 @@ class Card3DSecure */ public function continueWithAuthorization(array $order) { - if (!isset($order['payment_source'])) { - return static::NO_DECISION; - } - if (!isset($order['payment_source']['card'])) { - return static::NO_DECISION; - } - if (!isset($order['payment_source']['card']['authentication_result'])) { + $cardAuthenticationResult = $this->getAuthenticationResult($order); + + if (!$cardAuthenticationResult) { return static::NO_DECISION; } - $liabilityShift = isset($order['payment_source']['card']['authentication_result']['liability_shift']) ? $order['payment_source']['card']['authentication_result']['liability_shift'] : null; + $liabilityShift = $this->getLiabilityShift($cardAuthenticationResult); if ($liabilityShift === static::LIABILITY_SHIFT_POSSIBLE) { return static::PROCEED; @@ -99,13 +95,50 @@ public function continueWithAuthorization(array $order) return static::RETRY; } - if ($liabilityShift === static::LIABILITY_SHIFT_NO && isset($order['payment_source']['card']['authentication_result']['three_d_secure'])) { - return $this->noLiabilityShift($order['payment_source']['card']['authentication_result']['three_d_secure']); + $threeDSecure = $this->get3DSecure($cardAuthenticationResult); + + if ($liabilityShift === static::LIABILITY_SHIFT_NO && $threeDSecure) { + return $this->noLiabilityShift($cardAuthenticationResult); } return static::NO_DECISION; } + /** + * @param array $order + * + * @return bool + */ + public function is3DSecureAvailable(array $order) + { + $cardAuthenticationResult = $this->getAuthenticationResult($order); + + if (!$cardAuthenticationResult) { + return false; + } + + $threeDSecure = $this->get3DSecure($cardAuthenticationResult); + $enrollmentStatus = $this->getEnrollmentStatus($threeDSecure); + + return $enrollmentStatus === self::ENROLLMENT_STATUS_YES || $enrollmentStatus === self::ENROLLMENT_STATUS_UNAVAILABLE; + } + + /** + * @param array $order + * + * @return bool + */ + public function isLiabilityShifted(array $order) + { + $cardAuthenticationResult = $this->getAuthenticationResult($order); + $liabilityShift = $this->getLiabilityShift($cardAuthenticationResult); + $threeDSecure = $this->get3DSecure($cardAuthenticationResult); + $authenticationStatus = $this->getAuthenticationStatus($threeDSecure); + + return ($liabilityShift === self::LIABILITY_SHIFT_POSSIBLE || $liabilityShift === self::LIABILITY_SHIFT_YES) + && $authenticationStatus === self::AUTHENTICATION_RESULT_YES; + } + /** * @param array{enrollment_status: string, authentication_status: string} $cardAuthenticationResult * @@ -113,8 +146,9 @@ public function continueWithAuthorization(array $order) */ private function noLiabilityShift(array $cardAuthenticationResult) { - $enrollmentStatus = isset($cardAuthenticationResult['enrollment_status']) ? $cardAuthenticationResult['enrollment_status'] : null; - $authenticationStatus = isset($cardAuthenticationResult['authentication_status']) ? $cardAuthenticationResult['authentication_status'] : null; + $threeDSecure = $this->get3DSecure($cardAuthenticationResult); + $enrollmentStatus = $this->getEnrollmentStatus($threeDSecure); + $authenticationStatus = $this->getAuthenticationStatus($threeDSecure); if ($enrollmentStatus === static::ENROLLMENT_STATUS_BYPASS && !$authenticationStatus) { return static::PROCEED; @@ -146,4 +180,54 @@ private function noLiabilityShift(array $cardAuthenticationResult) return static::NO_DECISION; } + + /** + * @param array $order + * + * @return array|null + */ + private function getAuthenticationResult(array $order) + { + return isset($order['payment_source']['card']['authentication_result']) ? $order['payment_source']['card']['authentication_result'] : null; + } + + /** + * @param array|null $cardAuthenticationResult + * + * @return string|null + */ + private function getLiabilityShift($cardAuthenticationResult) + { + return isset($cardAuthenticationResult['liability_shift']) ? $cardAuthenticationResult['liability_shift'] : null; + } + + /** + * @param array|null $cardAuthenticationResult + * + * @return array|null + */ + private function get3DSecure($cardAuthenticationResult) + { + return isset($cardAuthenticationResult['three_d_secure']) ? $cardAuthenticationResult['three_d_secure'] : null; + } + + /** + * @param array|null $threeDSecure + * + * @return string|null + */ + public function getAuthenticationStatus($threeDSecure) + { + return isset($threeDSecure['authentication_status']) ? $threeDSecure['authentication_status'] : null; + } + + /** + * @param array|null $threeDSecure + * + * @return string|null + */ + private function getEnrollmentStatus($threeDSecure) + { + return isset($threeDSecure['enrollment_status']) ? $threeDSecure['enrollment_status'] : null; + } } diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index fef3eb10f..aafb9c3ba 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -35,6 +35,7 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; use PrestaShop\Module\PrestashopCheckout\PayPalError; use PrestaShop\Module\PrestashopCheckout\PayPalProcessorResponse; +use Psr\SimpleCache\CacheInterface; class CapturePayPalOrderCommandHandler { @@ -42,10 +43,15 @@ class CapturePayPalOrderCommandHandler * @var EventDispatcherInterface */ private $eventDispatcher; + /** + * @var CacheInterface + */ + private $orderPayPalCache; - public function __construct(EventDispatcherInterface $eventDispatcher) + public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) { $this->eventDispatcher = $eventDispatcher; + $this->orderPayPalCache = $orderPayPalCache; } public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) @@ -76,6 +82,11 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) } $orderPayPal = $response['body']; + + $payPalOrderFromCache = $this->orderPayPalCache->get($orderPayPal['id']); + + $orderPayPal = array_replace_recursive($payPalOrderFromCache, $orderPayPal); + $capturePayPal = $orderPayPal['purchase_units'][0]['payments']['captures'][0]; if ($orderPayPal['status'] === PayPalOrderStatus::COMPLETED) { diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php index f30b2208a..6c4a439cc 100644 --- a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCheckoutCompletedQueryHandler.php @@ -55,6 +55,7 @@ public function handle(GetPayPalOrderForCheckoutCompletedQuery $getPayPalOrderQu try { $orderPayPal = new PaypalOrder($getPayPalOrderQuery->getOrderPayPalId()->getValue()); + $this->orderPayPalCache->set($getPayPalOrderQuery->getOrderPayPalId()->getValue(), $orderPayPal->getOrder()); } catch (HttpTimeoutException $exception) { throw $exception; } catch (Exception $exception) { diff --git a/src/Presenter/Order/OrderPresenter.php b/src/Presenter/Order/OrderPresenter.php index 498b39525..9b0ce79e0 100644 --- a/src/Presenter/Order/OrderPresenter.php +++ b/src/Presenter/Order/OrderPresenter.php @@ -21,6 +21,7 @@ namespace PrestaShop\Module\PrestashopCheckout\Presenter\Order; use Module; +use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; use PrestaShop\Module\PrestashopCheckout\Presenter\Date\DatePresenter; use PsCheckoutCart; @@ -55,12 +56,16 @@ public function present() return []; } + $card3DSecure = new Card3DSecure(); + return array_merge( [ 'id' => $this->orderPayPal['id'], 'intent' => $this->orderPayPal['intent'], 'status' => $this->getOrderStatus(), 'transactions' => $this->getTransactions(), + 'is3DSecureAvailable' => $card3DSecure->is3DSecureAvailable($this->orderPayPal), + 'isLiabilityShifted' => $card3DSecure->isLiabilityShifted($this->orderPayPal), ], $this->getOrderTotals() ); diff --git a/tests/Unit/PayPal/Card3DSecureTest.php b/tests/Unit/PayPal/Card3DSecureTest.php index 1b54404f6..a5689c2c3 100644 --- a/tests/Unit/PayPal/Card3DSecureTest.php +++ b/tests/Unit/PayPal/Card3DSecureTest.php @@ -34,13 +34,33 @@ class Card3DSecureTest extends TestCase /** * @dataProvider orderProvider */ - public function testCard3DSecure(array $order, $expectedResult) + public function testContinueWithAuthorization(array $order, $expectedResult) { $validator = new Card3DSecure(); $actualResult = $validator->continueWithAuthorization($order); $this->assertEquals($expectedResult, $actualResult); } + /** + * @dataProvider orderIsLiabilityShiftedProvider + */ + public function testIsLiabilityShifted(array $order, $expectedResult) + { + $validator = new Card3DSecure(); + $actualResult = $validator->isLiabilityShifted($order); + $this->assertEquals($expectedResult, $actualResult); + } + + /** + * @dataProvider orderIs3DSecureAvailableProvider + */ + public function testIs3DSecureAvailable(array $order, $expectedResult) + { + $validator = new Card3DSecure(); + $actualResult = $validator->is3DSecureAvailable($order); + $this->assertEquals($expectedResult, $actualResult); + } + public function orderProvider() { return [ @@ -257,4 +277,438 @@ public function orderProvider() ], ]; } + + public function orderIsLiabilityShiftedProvider() + { + return [ + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_POSSIBLE, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_YES, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_NO, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_REJECTED, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_POSSIBLE, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_ATTEMPTED, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_UNABLE, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_UNABLE, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_CHALLENGE_REQUIRED, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_NO, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_UNAVAILABLE, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_UNAVAILABLE, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_BYPASS, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'last_digits' => '1083', + 'brand' => 'VISA', + 'type' => 'UNKNOWN', + ], + ], + ], + false, + ], + ]; + } + + public function orderIs3DSecureAvailableProvider() + { + return [ + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_POSSIBLE, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_YES, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_NO, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_REJECTED, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_POSSIBLE, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_ATTEMPTED, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_UNABLE, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_UNABLE, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + 'authentication_status' => Card3DSecure::AUTHENTICATION_RESULT_CHALLENGE_REQUIRED, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_YES, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_NO, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_UNAVAILABLE, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_UNAVAILABLE, + ], + ], + ], + ], + ], + true, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_NO, + 'three_d_secure' => [ + 'enrollment_status' => Card3DSecure::ENROLLMENT_STATUS_BYPASS, + ], + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'authentication_result' => [ + 'liability_shift' => Card3DSecure::LIABILITY_SHIFT_UNKNOWN, + ], + ], + ], + ], + false, + ], + [ + [ + 'payment_source' => [ + 'card' => [ + 'last_digits' => '1083', + 'brand' => 'VISA', + 'type' => 'UNKNOWN', + ], + ], + ], + false, + ], + ]; + } } diff --git a/views/css/adminOrderView.css b/views/css/adminOrderView.css index ce7e6e1d0..2e7db1ea3 100755 --- a/views/css/adminOrderView.css +++ b/views/css/adminOrderView.css @@ -351,3 +351,11 @@ .checkout-modal-button .btn { color: #FFFFFF; } + +#ps_checkout .tabpanel__infos .liability-explanation { + font-weight: 400; + background-color: #EEEEEE; + padding: 4px 8px; + margin-top: 5px; + display: inline-block; +} diff --git a/views/templates/admin/ajaxPayPalOrder.tpl b/views/templates/admin/ajaxPayPalOrder.tpl index 6c4e74737..8c77835eb 100755 --- a/views/templates/admin/ajaxPayPalOrder.tpl +++ b/views/templates/admin/ajaxPayPalOrder.tpl @@ -132,12 +132,48 @@
{$orderPayPalTransaction.id}
{l s='Status' mod='ps_checkout'}
- - {$orderPayPalTransaction.status.translated|escape:'html':'UTF-8'} - + + {$orderPayPalTransaction.status.translated|escape:'html':'UTF-8'} +
{l s='Amount (Tax incl.)' mod='ps_checkout'}
{$orderPayPalTransaction.amount} {$orderPayPalTransaction.currency}
+ {if $psCheckoutCart->paypal_funding === 'card'} +
{l s='3D Secure' mod='ps_checkout'}
+
+ {if $orderPayPal.is3DSecureAvailable && $orderPayPal.isLiabilityShifted} + + {l s='Success' mod='ps_checkout'} + + {elseif $orderPayPal.is3DSecureAvailable && !$orderPayPal.isLiabilityShifted} + + {l s='Failed' mod='ps_checkout'} + + {else} + + {l s='Card does not support 3D Secure' mod='ps_checkout'} + + {/if} +
+
{l s='Liability shift' mod='ps_checkout'}
+
+ {if $orderPayPal.isLiabilityShifted} + + {l s='Bank' mod='ps_checkout'} + +
+ {l s='You can safely proceed with the order.' mod='ps_checkout'} +
+ {else} + + {l s='Merchant' mod='ps_checkout'} + +
+ {l s='We advice you not to honor the order immediately, wait a few days in case of chargeback and contact the consumer to ensure authenticity of the transaction. For this type of cases we also recommend to consider Chargeback protection.' mod='ps_checkout'} +
+ {/if} +
+ {/if}
{if $orderPayPalTransaction.gross_amount || $orderPayPalTransaction.paypal_fee || $orderPayPalTransaction.net_amount} diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl index 8752e8829..4378c0b32 100755 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -60,7 +60,7 @@
{l s='Total' mod='ps_checkout'}
{$orderPayPal.total}
- {l s='Balance' mod='ps_checkout'} + {l s='Balance' mod='ps_checkout'}
{$orderPayPal.balance}
@@ -69,9 +69,9 @@
{if $isProductionEnv} - {l s='Production Environment' mod='ps_checkout'} + {l s='Production Environment' mod='ps_checkout'} {else} - {l s='Test Environment' mod='ps_checkout'} + {l s='Test Environment' mod='ps_checkout'} {/if}
@@ -79,211 +79,237 @@
- {if !empty($orderPayPal.transactions)} -
- -
+ {if !empty($orderPayPal.transactions)} +
+ +
-
-
- {assign var="counter" value=1} - {foreach $orderPayPal.transactions as $orderPayPalTransaction} - - {assign var="counter" value=$counter+1} - {/foreach} -
+ + {assign var="counter" value=$counter+1} + {/foreach} +
-
- {assign var="counter" value=1} - {foreach $orderPayPal.transactions as $orderPayPalTransaction} - {assign var="maxAmountRefundable" value=$orderPayPalTransaction.maxAmountRefundable|string_format:"%.2f"} - {assign var="orderPayPalRefundAmountIdentifier" value='orderPayPalRefundAmount'|cat:$orderPayPalTransaction.id} -
+
+ {assign var="counter" value=1} + {foreach $orderPayPal.transactions as $orderPayPalTransaction} + {assign var="maxAmountRefundable" value=$orderPayPalTransaction.maxAmountRefundable|string_format:"%.2f"} + {assign var="orderPayPalRefundAmountIdentifier" value='orderPayPalRefundAmount'|cat:$orderPayPalTransaction.id} +
+
+
+

{l s='Transaction details' mod='ps_checkout'}

+
+
{l s='Reference' mod='ps_checkout'}
+
{$orderPayPalTransaction.id}
+
{l s='Status' mod='ps_checkout'}
+
+ + {$orderPayPalTransaction.status.translated} + +
+
{l s='Amount (Tax incl.)' mod='ps_checkout'}
+
{$orderPayPalTransaction.amount} {$orderPayPalTransaction.currency}
+ {if $psCheckoutCart->paypal_funding === 'card'} +
{l s='3D Secure' mod='ps_checkout'}
+
+ {if $orderPayPal.is3DSecureAvailable && $orderPayPal.isLiabilityShifted} + {l s='Success' mod='ps_checkout'} + {elseif $orderPayPal.is3DSecureAvailable && !$orderPayPal.isLiabilityShifted} + {l s='Failed' mod='ps_checkout'} + {else} + {l s='Card does not support 3D Secure' mod='ps_checkout'} + {/if} +
+
{l s='Liability shift' mod='ps_checkout'}
+
+ {if $orderPayPal.isLiabilityShifted} + {l s='Bank' mod='ps_checkout'} +
+ {l s='You can safely proceed with the order.' mod='ps_checkout'} +
+ {else} + {l s='Merchant' mod='ps_checkout'} +
+ {l s='We advice you not to honor the order immediately, wait a few days in case of chargeback and contact the consumer to ensure authenticity of the transaction. For this type of cases we also recommend to consider Chargeback protection.' mod='ps_checkout'} +
+ {/if} +
+ {/if} +
+
+ {if $orderPayPalTransaction.gross_amount || $orderPayPalTransaction.paypal_fee || $orderPayPalTransaction.net_amount}
-
-

{l s='Transaction details' mod='ps_checkout'}

-
-
{l s='Reference' mod='ps_checkout'}
-
{$orderPayPalTransaction.id}
-
{l s='Status' mod='ps_checkout'}
-
- - {$orderPayPalTransaction.status.translated} - -
-
{l s='Amount (Tax incl.)' mod='ps_checkout'}
-
{$orderPayPalTransaction.amount} {$orderPayPalTransaction.currency}
-
-
- {if $orderPayPalTransaction.gross_amount || $orderPayPalTransaction.paypal_fee || $orderPayPalTransaction.net_amount} -
-

{l s='Transaction amounts' mod='ps_checkout'}

-
- {if $orderPayPalTransaction.gross_amount} +

{l s='Transaction amounts' mod='ps_checkout'}

+
+ {if $orderPayPalTransaction.gross_amount}
{l s='Gross amount' mod='ps_checkout'}
{$orderPayPalTransaction.gross_amount} {$orderPayPalTransaction.currency}
- {/if} - {if $orderPayPalTransaction.paypal_fee} + {/if} + {if $orderPayPalTransaction.paypal_fee}
{l s='Fees (Tax Incl.)' mod='ps_checkout'}
- {$orderPayPalTransaction.paypal_fee} {$orderPayPalTransaction.currency}
- {/if} - {if $orderPayPalTransaction.net_amount} + {/if} + {if $orderPayPalTransaction.net_amount}
{l s='Net amount' mod='ps_checkout'}
{$orderPayPalTransaction.net_amount} {$orderPayPalTransaction.currency}
- {/if} -
-
- {/if} - - {l s='See on PayPal' mod='ps_checkout'} - - {if $orderPayPalTransaction.isRefundable} - - {l s='Refund' mod='ps_checkout'} - {/if} +
-
+ {/if} +
+ {l s='See on PayPal' mod='ps_checkout'} + + {if $orderPayPalTransaction.isRefundable} + + {l s='Refund' mod='ps_checkout'} + + {/if} +
+
- {if $orderPayPalTransaction.isRefundable} - diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl index 4378c0b32..65b0a7f2e 100755 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -65,8 +65,12 @@
{$orderPayPal.balance}
{l s='Payment mode' mod='ps_checkout'}
-
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+
{$orderPaymentDisplayName|escape:'html':'UTF-8'} {$orderPaymentDisplayName|escape:'html':'UTF-8'}
+ {l s='Environment' mod='ps_checkout'} + +
+
{if $isProductionEnv} {l s='Production Environment' mod='ps_checkout'} @@ -74,9 +78,38 @@ {l s='Test Environment' mod='ps_checkout'} {/if} - -
+ + {if $psCheckoutCart->paypal_funding === 'card'} +
{l s='3D Secure' mod='ps_checkout'}
+
+ {if $orderPayPal.is3DSecureAvailable && $orderPayPal.isLiabilityShifted} + {l s='Success' mod='ps_checkout'} + {elseif $orderPayPal.is3DSecureAvailable && !$orderPayPal.isLiabilityShifted} + {l s='Failed' mod='ps_checkout'} + {else} + {l s='Card does not support 3D Secure' mod='ps_checkout'} + {/if} +
+
{l s='Liability shift' mod='ps_checkout'}
+
+ {if $orderPayPal.isLiabilityShifted} + {l s='Bank' mod='ps_checkout'} + {else} + {l s='Merchant' mod='ps_checkout'} + {/if} +
+ {/if} + {if $psCheckoutCart->paypal_funding === 'card' && !$orderPayPal.isLiabilityShifted} +
+ {l s='The bank issuer declined the liability shift. We advice you not to honor the order immediately, wait a few days in case of chargeback and contact the consumer to ensure authenticity of the transaction. For this type of cases we also recommend to consider Chargeback protection.' mod='ps_checkout'} +
+ {/if} + {if $psCheckoutCart->paypal_funding === 'card' && $orderPayPal.isLiabilityShifted} +
+ {l s='The bank issuer accepted the liability shift. You can safely honor the order.' mod='ps_checkout'} +
+ {/if}
{if !empty($orderPayPal.transactions)} @@ -138,30 +171,15 @@
{l s='Amount (Tax incl.)' mod='ps_checkout'}
{$orderPayPalTransaction.amount} {$orderPayPalTransaction.currency}
- {if $psCheckoutCart->paypal_funding === 'card'} -
{l s='3D Secure' mod='ps_checkout'}
-
- {if $orderPayPal.is3DSecureAvailable && $orderPayPal.isLiabilityShifted} - {l s='Success' mod='ps_checkout'} - {elseif $orderPayPal.is3DSecureAvailable && !$orderPayPal.isLiabilityShifted} - {l s='Failed' mod='ps_checkout'} - {else} - {l s='Card does not support 3D Secure' mod='ps_checkout'} - {/if} -
-
{l s='Liability shift' mod='ps_checkout'}
+ {if !empty($orderPayPalTransaction.seller_protection)} +
+ {l s='Seller protection' mod='ps_checkout'} + +
- {if $orderPayPal.isLiabilityShifted} - {l s='Bank' mod='ps_checkout'} -
- {l s='You can safely proceed with the order.' mod='ps_checkout'} -
- {else} - {l s='Merchant' mod='ps_checkout'} -
- {l s='We advice you not to honor the order immediately, wait a few days in case of chargeback and contact the consumer to ensure authenticity of the transaction. For this type of cases we also recommend to consider Chargeback protection.' mod='ps_checkout'} -
- {/if} + + {$orderPayPalTransaction.seller_protection.translated|escape:'html':'UTF-8'} +
{/if} From f9f9203014e95618214bce89ce50ae748c15fa80 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Feb 2024 17:42:41 +0100 Subject: [PATCH 122/343] Fix issue with BO order detail --- .../AdminAjaxPrestashopCheckoutController.php | 7 ++++- views/img/sofort.svg | 28 ++++++++++--------- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index 10a283ec7..cce1e0ae7 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -399,7 +399,12 @@ public function ajaxProcessFetchOrder() /** @var PayPalOrderProvider $paypalOrderProvider */ $paypalOrderProvider = $this->module->getService('ps_checkout.paypal.provider.order'); - $paypalOrder = $paypalOrderProvider->getById($psCheckoutCart->paypal_order); + try { + $paypalOrder = $paypalOrderProvider->getById($psCheckoutCart->paypal_order); + } catch (Exception $exception) { + $paypalOrder = []; + } + if ($paypalOrder === false) { $paypalOrder = []; } diff --git a/views/img/sofort.svg b/views/img/sofort.svg index d2933245c..cb58f5e65 100644 --- a/views/img/sofort.svg +++ b/views/img/sofort.svg @@ -1,14 +1,16 @@ - - - - - - - - - - - - - + + + + + + + + + + + + + + + From 4292cef9a56c1be61249b9283e1dca9e228ae1b9 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:08:18 +0100 Subject: [PATCH 123/343] Fix phpstan feedback --- src/Presenter/Order/OrderPresenter.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Presenter/Order/OrderPresenter.php b/src/Presenter/Order/OrderPresenter.php index 79589fe02..8592e634d 100644 --- a/src/Presenter/Order/OrderPresenter.php +++ b/src/Presenter/Order/OrderPresenter.php @@ -20,16 +20,16 @@ namespace PrestaShop\Module\PrestashopCheckout\Presenter\Order; -use Module; use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceTranslationProvider; use PrestaShop\Module\PrestashopCheckout\PayPal\Card3DSecure; use PrestaShop\Module\PrestashopCheckout\Presenter\Date\DatePresenter; +use Ps_checkout; use PsCheckoutCart; class OrderPresenter { /** - * @var Module + * @var Ps_checkout */ private $module; @@ -43,10 +43,10 @@ class OrderPresenter private $fundingSourceTranslationProvider; /** - * @param Module $module + * @param Ps_checkout $module * @param array $orderPayPal */ - public function __construct(Module $module, array $orderPayPal) + public function __construct(Ps_checkout $module, array $orderPayPal) { $this->module = $module; $this->orderPayPal = $orderPayPal; From 415722ba9651e48a22cecf243c8fb19027f5b291 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 21 Feb 2024 18:26:36 +0100 Subject: [PATCH 124/343] Fix translations --- translations/fr.php | 8 ++++---- views/templates/admin/ajaxPayPalOrder.tpl | 4 ++-- views/templates/admin/ajaxPayPalOrderLegacy.tpl | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/translations/fr.php b/translations/fr.php index e62c4b710..98b266672 100644 --- a/translations/fr.php +++ b/translations/fr.php @@ -109,8 +109,8 @@ $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_ea620d61461a00502599f2e6a064b931'] = 'Moyen de paiement'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_0ba29c6a1afacf586b03a26162c72274'] = 'Environnement'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_2cacd34854aed29bd7d0959ace917806'] = 'L\'environnement utilisé pour cette transaction: Test ou Production'; -$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_1e9511daad89204a4514d932d652480e'] = 'Production'; -$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_c85d6a8b7427e52b78fd6ea42502f457'] = 'Test'; +$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_756d97bb256b8580d4d71ee0c547804e'] = 'Production'; +$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_0cbc6611f5540bd0809a388dc95a615b'] = 'Test'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_ecfa6e92f6b49a55e336afde4c64aa14'] = '3D Secure'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_505a83f220c02df2f85c3810cd9ceb38'] = 'Succès'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorderlegacy_d7c8c85bf79bbe1b7188497c32c3b0ca'] = 'Échec'; @@ -152,8 +152,8 @@ $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_a6f74fff2223803369009919e94f2f96'] = 'Montant à recevoir sur votre compte bancaire : total de la commande moins la commission et les remboursements éventuels'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_0ba29c6a1afacf586b03a26162c72274'] = 'Environnement'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_2cacd34854aed29bd7d0959ace917806'] = 'L\'environnement utilisé pour cette transaction: Test ou Production'; -$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_1e9511daad89204a4514d932d652480e'] = 'Production'; -$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_c85d6a8b7427e52b78fd6ea42502f457'] = 'Test'; +$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_756d97bb256b8580d4d71ee0c547804e'] = 'Production'; +$_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_0cbc6611f5540bd0809a388dc95a615b'] = 'Test'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_ea620d61461a00502599f2e6a064b931'] = 'Moyen de paiement'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_ecfa6e92f6b49a55e336afde4c64aa14'] = '3D Secure'; $_MODULE['<{ps_checkout}prestashop>ajaxpaypalorder_505a83f220c02df2f85c3810cd9ceb38'] = 'Succès'; diff --git a/views/templates/admin/ajaxPayPalOrder.tpl b/views/templates/admin/ajaxPayPalOrder.tpl index 36b824644..2f545646a 100755 --- a/views/templates/admin/ajaxPayPalOrder.tpl +++ b/views/templates/admin/ajaxPayPalOrder.tpl @@ -71,9 +71,9 @@
{if $isProductionEnv} - {l s='Production Environment' mod='ps_checkout'} + {l s='Production' mod='ps_checkout'} {else} - {l s='Test Environment' mod='ps_checkout'} + {l s='Test' mod='ps_checkout'} {/if}
diff --git a/views/templates/admin/ajaxPayPalOrderLegacy.tpl b/views/templates/admin/ajaxPayPalOrderLegacy.tpl index 65b0a7f2e..a3f22dba6 100755 --- a/views/templates/admin/ajaxPayPalOrderLegacy.tpl +++ b/views/templates/admin/ajaxPayPalOrderLegacy.tpl @@ -73,9 +73,9 @@
{if $isProductionEnv} - {l s='Production Environment' mod='ps_checkout'} + {l s='Production' mod='ps_checkout'} {else} - {l s='Test Environment' mod='ps_checkout'} + {l s='Test' mod='ps_checkout'} {/if}
From 7966a84e512693a2ad2dfbc249ee51034b0ec9f5 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 22 Feb 2024 13:29:43 +0100 Subject: [PATCH 125/343] Add PrestaShop module dependencies library --- composer.json | 1 + composer.lock | 55 ++++++++++++++++++++++++- module_dependencies.json | 8 ++++ ps_checkout.php | 9 ++++ views/templates/admin/configuration.tpl | 38 ++++++++++++++++- 5 files changed, 108 insertions(+), 3 deletions(-) create mode 100644 module_dependencies.json diff --git a/composer.json b/composer.json index 8e81235e6..342eb78b5 100755 --- a/composer.json +++ b/composer.json @@ -26,6 +26,7 @@ "guzzlehttp/guzzle": "^7.4", "prestashop/decimal": "^1.3", "prestashop/module-lib-guzzle-adapter": "^1.0", + "prestashop/module-lib-mbo-installer": "^2.0", "prestashop/module-lib-service-container": "^1.0", "prestashop/prestashop-accounts-installer": "^1.0", "ramsey/uuid": "^3.8", diff --git a/composer.lock b/composer.lock index 7a2e53a27..707e750e6 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": "83e53d19b0dc27f33a825b790f2f8fd2", + "content-hash": "1ffed083e7949e1d32cb94eaf11a19ad", "packages": [ { "name": "clue/stream-filter", @@ -984,6 +984,57 @@ }, "time": "2022-09-30T10:25:35+00:00" }, + { + "name": "prestashop/module-lib-mbo-installer", + "version": "v2.0.0", + "source": { + "type": "git", + "url": "https://github.com/PrestaShopCorp/module-lib-mbo-installer.git", + "reference": "58cde161e40ba62638be2dbc5fde3e09f1733d48" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/PrestaShopCorp/module-lib-mbo-installer/zipball/58cde161e40ba62638be2dbc5fde3e09f1733d48", + "reference": "58cde161e40ba62638be2dbc5fde3e09f1733d48", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.2.5", + "prestashop/module-lib-guzzle-adapter": "^1.0" + }, + "require-dev": { + "phpstan/phpstan": "^1.9", + "phpunit/phpunit": "^9.5|^8.5|^5.7", + "prestashop/php-dev-tools": "^4.2|^3.16" + }, + "type": "library", + "autoload": { + "psr-4": { + "Prestashop\\ModuleLibMboInstaller\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "AFL-3.0" + ], + "authors": [ + { + "name": "PrestaShop SA", + "email": "contact@prestashop.com" + }, + { + "name": "Mikatux", + "email": "mickael@mayeur.eu" + } + ], + "description": "A helper to ease the download PS MBO from the Addons Marketplace", + "support": { + "issues": "https://github.com/PrestaShopCorp/module-lib-mbo-installer/issues", + "source": "https://github.com/PrestaShopCorp/module-lib-mbo-installer/tree/v2.0.0" + }, + "time": "2023-12-05T14:14:13+00:00" + }, { "name": "prestashop/module-lib-service-container", "version": "1.4.0", @@ -5695,5 +5746,5 @@ "platform-overrides": { "php": "7.2.34" }, - "plugin-api-version": "2.2.0" + "plugin-api-version": "2.6.0" } diff --git a/module_dependencies.json b/module_dependencies.json new file mode 100644 index 000000000..6002cd69f --- /dev/null +++ b/module_dependencies.json @@ -0,0 +1,8 @@ +{ + "dependencies": [ + { + "name" : "ps_accounts", + "id": "49648" + } + ] +} diff --git a/ps_checkout.php b/ps_checkout.php index 5a8c21cc1..4b367e558 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -376,6 +376,9 @@ public function uninstallTabs() public function getContent() { try { + $mboInstaller = new \Prestashop\ModuleLibMboInstaller\DependencyBuilder($this); + $requiredDependencies = $mboInstaller->handleDependencies(); + $hasRequiredDependencies = $mboInstaller->areDependenciesMet(); /** @var \PrestaShop\PsAccountsInstaller\Installer\Facade\PsAccounts $psAccountsFacade */ $psAccountsFacade = $this->getService('ps_accounts.facade'); /** @var \PrestaShop\PsAccountsInstaller\Installer\Presenter\InstallerPresenter $psAccountsPresenter */ @@ -384,6 +387,8 @@ public function getContent() $contextPsAccounts = $psAccountsPresenter->present($this->name); } catch (Exception $exception) { $contextPsAccounts = []; + $requiredDependencies = []; + $hasRequiredDependencies = false; $this->getLogger()->error( 'Failed to get PsAccounts context', [ @@ -417,6 +422,10 @@ public function getContent() } $this->context->controller->addJS($boSdkUrl, false); + $this->context->smarty->assign([ + 'requiredDependencies' => $requiredDependencies, + 'hasRequiredDependencies' => $hasRequiredDependencies, + ]); return $this->display(__FILE__, 'views/templates/admin/configuration.tpl'); } diff --git a/views/templates/admin/configuration.tpl b/views/templates/admin/configuration.tpl index 62e1cebb3..434590af1 100755 --- a/views/templates/admin/configuration.tpl +++ b/views/templates/admin/configuration.tpl @@ -17,11 +17,47 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 *} -
+{if isset($hasRequiredDependencies) && !$hasRequiredDependencies} + +
+ + +{/if} + + + + From da1949adbe22f797b28491d3937f5e1c4c6be2d3 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 22 Feb 2024 16:55:41 +0100 Subject: [PATCH 126/343] Fix 3D Secure --- src/Builder/Payload/OrderPayloadBuilder.php | 9 ++++----- src/PayPal/PayPalConfiguration.php | 5 ++--- src/PayPal/Sdk/PayPalSdkConfigurationBuilder.php | 5 ++++- upgrade/upgrade-8.3.6.0.php | 8 ++++---- 4 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index b3fc263cd..61011b1b4 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -415,15 +415,14 @@ private function buildPaymentSourceNode() 'card' => [ 'name' => $this->cart['addresses']['invoice']->firstname . ' ' . $this->cart['addresses']['invoice']->lastname, 'billing_address' => $this->getAddressPortable('invoice'), - 'attributes' => [ - 'verification' => [ - 'method' => $paypalConfiguration->getHostedFieldsContingencies(), - ], - ], ], ], ]; + if ($paypalConfiguration->is3dSecureEnabled()) { + $node['payment_source']['card']['attributes']['verification']['method'] = $paypalConfiguration->getHostedFieldsContingencies(); + } + $this->getPayload()->addAndMergeItems($node); } diff --git a/src/PayPal/PayPalConfiguration.php b/src/PayPal/PayPalConfiguration.php index 8cd165a36..3f6af00c3 100644 --- a/src/PayPal/PayPalConfiguration.php +++ b/src/PayPal/PayPalConfiguration.php @@ -33,8 +33,7 @@ class PayPalConfiguration const PS_ROUND_TYPE = 'PS_ROUND_TYPE'; const PS_PRICE_ROUND_MODE = 'PS_PRICE_ROUND_MODE'; const INTEGRATION_DATE = 'PS_CHECKOUT_INTEGRATION_DATE'; - const HOSTED_FIELDS_3DS_DISABLED = 'PS_CHECKOUT_3DS_DISABLED'; - const HOSTED_FIELDS_CONTINGENCIES = 'PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES'; + const HOSTED_FIELDS_CONTINGENCIES = 'PS_CHECKOUT_HOSTED_FIELDS_CONTINGENCIES'; const CSP_NONCE = 'PS_CHECKOUT_CSP_NONCE'; const PS_CHECKOUT_PAYPAL_CB_INLINE = 'PS_CHECKOUT_PAYPAL_CB_INLINE'; const PS_CHECKOUT_PAYPAL_BUTTON = 'PS_CHECKOUT_PAYPAL_BUTTON'; @@ -237,7 +236,7 @@ public function getCSPNonce() */ public function is3dSecureEnabled() { - return false === (bool) $this->configuration->get(static::HOSTED_FIELDS_3DS_DISABLED); + return $this->getHostedFieldsContingencies() !== 'NONE'; } /** diff --git a/src/PayPal/Sdk/PayPalSdkConfigurationBuilder.php b/src/PayPal/Sdk/PayPalSdkConfigurationBuilder.php index d5aa33d7f..6ab5624b2 100644 --- a/src/PayPal/Sdk/PayPalSdkConfigurationBuilder.php +++ b/src/PayPal/Sdk/PayPalSdkConfigurationBuilder.php @@ -109,9 +109,12 @@ public function buildConfiguration() 'integrationDate' => $this->configuration->getIntegrationDate(), 'dataPartnerAttributionId' => $this->shopContext->getBnCode(), 'dataCspNonce' => $this->configuration->getCSPNonce(), - 'dataEnable3ds' => $this->configuration->is3dSecureEnabled(), ]; + if ($this->configuration->is3dSecureEnabled()) { + $params['dataEnable3ds'] = 'true'; + } + if ('SANDBOX' === $this->configuration->getPaymentMode()) { // $params['debug'] = 'true'; $params['buyerCountry'] = $this->getCountry(); diff --git a/upgrade/upgrade-8.3.6.0.php b/upgrade/upgrade-8.3.6.0.php index 3659558ac..7fecdf330 100644 --- a/upgrade/upgrade-8.3.6.0.php +++ b/upgrade/upgrade-8.3.6.0.php @@ -44,8 +44,8 @@ function upgrade_module_8_3_6_0($module) Configuration::updateValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '1', false, null, (int) $shopId); // Update global value only if it is not already set to SCA_ALWAYS - if (Configuration::get('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', null, null, $shopId) !== 'SCA_ALWAYS') { - Configuration::updateValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED', false, null, (int) $shopId); + if (Configuration::get('PS_CHECKOUT_HOSTED_FIELDS_CONTINGENCIES', null, null, $shopId) !== 'SCA_ALWAYS') { + Configuration::updateValue('PS_CHECKOUT_HOSTED_FIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED', false, null, (int) $shopId); } } @@ -53,8 +53,8 @@ function upgrade_module_8_3_6_0($module) Configuration::updateGlobalValue('PS_CHECKOUT_LIABILITY_SHIFT_REQ', '1'); // Update global value only if it is not already set to SCA_ALWAYS - if (Configuration::getGlobalValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES') !== 'SCA_ALWAYS') { - Configuration::updateGlobalValue('PS_CHECKOUT_HOSTEDFIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED'); + if (Configuration::getGlobalValue('PS_CHECKOUT_HOSTED_FIELDS_CONTINGENCIES') !== 'SCA_ALWAYS') { + Configuration::updateGlobalValue('PS_CHECKOUT_HOSTED_FIELDS_CONTINGENCIES', 'SCA_WHEN_REQUIRED'); } // Add new configuration for displaying the logo on the product page and the cart From 83c96781ea792cf9a64896d4abba4b2786cfb7f6 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 26 Feb 2024 17:17:55 +0100 Subject: [PATCH 127/343] Refactor and fix Guzzle5 issues --- .../1_6/payment-options.component.js | 39 +- .../1_7/payment-options.component.js | 36 +- .../common/card-fields.component.js | 9 + .../express-checkout-button.component.js | 16 +- .../common/smart-button.component.js | 51 ++- config/common.yml | 120 ++++- .../AdminAjaxPrestashopCheckoutController.php | 107 ++--- controllers/front/cancel.php | 32 +- controllers/front/check.php | 71 +-- controllers/front/create.php | 100 +---- src/Api/Payment/Order.php | 7 + src/Builder/Payload/OrderPayloadBuilder.php | 4 +- .../Command/CancelCheckoutCommand.php | 128 ++++++ src/Checkout/Command/SaveCheckoutCommand.php | 158 +++++++ .../Command/SavePayPalOrderStatusCommand.php} | 24 +- .../CancelCheckoutCommandHandler.php | 68 +++ .../SaveCheckoutCommandHandler.php | 69 +++ .../SavePayPalOrderStatusCommandHandler.php} | 32 +- src/Controller/AbstractFrontController.php | 7 +- src/Dispatcher/OrderDispatcher.php | 2 +- src/Exception/PayPalException.php | 19 + src/Handler/CreatePaypalOrderHandler.php | 43 +- src/Handler/Response/ResponseApiHandler.php | 3 +- src/Http/CheckoutHttpClient.php | 184 ++++++++ ...CheckoutHttpClientConfigurationBuilder.php | 108 +++++ ...ttpClientConfigurationBuilderInterface.php | 29 ++ src/Http/HttpClientFactory.php | 34 ++ src/Http/HttpClientInterface.php | 50 +++ src/Http/PsrHttpClientAdapter.php | 63 +++ .../Command/CreatePayPalOrderCommand.php | 95 ++++ .../Command/UpdatePayPalOrderCommand.php | 112 +++++ .../CapturePayPalOrderCommandHandler.php | 48 +- .../CreatePayPalOrderCommandHandler.php | 95 ++++ .../UpdatePayPalOrderCommandHandler.php | 104 +++++ .../Order/Event/PayPalOrderCreatedEvent.php | 75 ++++ .../Order/Event/PayPalOrderUpdatedEvent.php | 100 +++++ .../PayPalOrderEventSubscriber.php | 48 +- .../Query/GetPayPalOrderForAdminViewQuery.php | 25 ++ .../GetPayPalOrderForAdminViewQueryResult.php | 25 ++ .../Query/GetPayPalOrderForCartIdQuery.php | 50 +++ .../GetPayPalOrderForCartIdQueryResult.php | 45 ++ ...GetPayPalOrderForAdminViewQueryHandler.php | 32 ++ .../GetPayPalOrderForCartIdQueryHandler.php | 66 +++ .../PayPalCaptureEventSubscriber.php | 31 -- .../Command/RefundPayPalCaptureCommand.php | 109 +++++ .../RefundPayPalCaptureCommandHandler.php | 123 ++++++ .../Event/PayPalCaptureRefundedEvent.php | 4 +- .../Refund/Event/PayPalRefundEvent.php | 82 ++++ .../PayPalRefundEventSubscriber.php | 161 +++++++ .../Exception/PayPalRefundException.php | 32 ++ .../Exception/PayPalRefundFailedException.php | 27 ++ src/PayPalError.php | 307 +++++++------ src/PaypalOrder.php | 42 +- tests/Unit/Http/CheckoutHttpClientTest.php | 417 ++++++++++++++++++ 54 files changed, 3306 insertions(+), 562 deletions(-) create mode 100644 src/Checkout/Command/CancelCheckoutCommand.php create mode 100644 src/Checkout/Command/SaveCheckoutCommand.php rename src/{PayPal/Order/Command/SavePayPalOrderCommand.php => Checkout/Command/SavePayPalOrderStatusCommand.php} (80%) create mode 100644 src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php create mode 100644 src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php rename src/{PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php => Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php} (52%) create mode 100644 src/Http/CheckoutHttpClient.php create mode 100644 src/Http/CheckoutHttpClientConfigurationBuilder.php create mode 100644 src/Http/HttpClientConfigurationBuilderInterface.php create mode 100644 src/Http/HttpClientFactory.php create mode 100644 src/Http/HttpClientInterface.php create mode 100644 src/Http/PsrHttpClientAdapter.php create mode 100644 src/PayPal/Order/Command/CreatePayPalOrderCommand.php create mode 100644 src/PayPal/Order/Command/UpdatePayPalOrderCommand.php create mode 100644 src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php create mode 100644 src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php create mode 100644 src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php create mode 100644 src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php create mode 100644 src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php create mode 100644 src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php create mode 100644 src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php create mode 100644 src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php create mode 100644 src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php create mode 100644 src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php create mode 100644 src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php rename src/PayPal/Payment/{Capture => Refund}/Event/PayPalCaptureRefundedEvent.php (85%) create mode 100644 src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php create mode 100644 src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php create mode 100644 src/PayPal/Payment/Refund/Exception/PayPalRefundException.php create mode 100644 src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php create mode 100644 tests/Unit/Http/CheckoutHttpClientTest.php diff --git a/_dev/js/front/src/components/1_6/payment-options.component.js b/_dev/js/front/src/components/1_6/payment-options.component.js index 374d2f41f..076078574 100644 --- a/_dev/js/front/src/components/1_6/payment-options.component.js +++ b/_dev/js/front/src/components/1_6/payment-options.component.js @@ -52,7 +52,8 @@ export class PaymentOptionsComponent extends BaseComponent { } renderPaymentOptionListener() { - const HTMLListenerElements = this.children.paymentOptions.map((paymentOption) => { + const HTMLListenerElements = this.children.paymentOptions.map( + (paymentOption) => { const HTMLElement = paymentOption.data.HTMLElementContainer; const [button, form] = Array.prototype.slice.call( HTMLElement.querySelectorAll('.payment_module') @@ -74,21 +75,28 @@ export class PaymentOptionsComponent extends BaseComponent { this.data.notification.hideError(); }); - if (this.config.expressCheckout.active && (('ps_checkout-' + this.payPalService.getFundingSource()) !== HTMLListenerElements[index].button.dataset.moduleName)) { - this.psCheckoutApi.postCancelOrder( - { + if ( + this.config.expressCheckout.active && + 'ps_checkout-' + this.payPalService.getFundingSource() !== + HTMLListenerElements[index].button.dataset.moduleName + ) { + this.psCheckoutApi + .postCancelOrder({ orderID: this.payPalService.getOrderId(), fundingSource: this.payPalService.getFundingSource(), - isExpressCheckout: true - } - ).then(() => { - this.config.expressCheckout.active = false; + isExpressCheckout: true, + reason: 'payment_option_changed' + }) + .then(() => { + this.config.expressCheckout.active = false; - const expressCheckoutContainer = document.querySelector('#ps_checkout-express-checkout-banner'); - if (expressCheckoutContainer) { - expressCheckoutContainer.style.display = 'none'; - } - }); + const expressCheckoutContainer = document.querySelector( + '#ps_checkout-express-checkout-banner' + ); + if (expressCheckoutContainer) { + expressCheckoutContainer.style.display = 'none'; + } + }); } HTMLListenerElements[index].button.classList.add('open'); @@ -100,7 +108,10 @@ export class PaymentOptionsComponent extends BaseComponent { if (this.config.expressCheckout.active) { HTMLListenerElements.forEach(({ button, form }) => { - if (button.dataset.moduleName === ('ps_checkout-' + this.payPalService.getFundingSource())) { + if ( + button.dataset.moduleName === + 'ps_checkout-' + this.payPalService.getFundingSource() + ) { button.classList.add('open'); button.classList.remove('closed'); form.classList.add('open'); diff --git a/_dev/js/front/src/components/1_7/payment-options.component.js b/_dev/js/front/src/components/1_7/payment-options.component.js index 31a38cc62..a4d47d0bd 100644 --- a/_dev/js/front/src/components/1_7/payment-options.component.js +++ b/_dev/js/front/src/components/1_7/payment-options.component.js @@ -40,7 +40,10 @@ export class PaymentOptionsComponent extends BaseComponent { `[data-module-name^="ps_checkout-${fundingSource.name}"]` ); - if (this.config.expressCheckout.active && this.payPalService.getFundingSource() === fundingSource.name) { + if ( + this.config.expressCheckout.active && + this.payPalService.getFundingSource() === fundingSource.name + ) { HTMLElement.click(); } @@ -63,21 +66,28 @@ export class PaymentOptionsComponent extends BaseComponent { this.data.notification.hideCancelled(); this.data.notification.hideError(); - if (this.config.expressCheckout.active && (('ps_checkout-' + this.payPalService.getFundingSource()) !== radio.dataset.moduleName)) { - this.psCheckoutApi.postCancelOrder( - { + if ( + this.config.expressCheckout.active && + 'ps_checkout-' + this.payPalService.getFundingSource() !== + radio.dataset.moduleName + ) { + this.psCheckoutApi + .postCancelOrder({ orderID: this.payPalService.getOrderId(), fundingSource: this.payPalService.getFundingSource(), - isExpressCheckout: true - } - ).then(() => { - this.config.expressCheckout.active = false; + isExpressCheckout: true, + reason: 'payment_option_changed' + }) + .then(() => { + this.config.expressCheckout.active = false; - const expressCheckoutContainer = document.querySelector('#ps_checkout-express-checkout-banner'); - if (expressCheckoutContainer) { - expressCheckoutContainer.style.display = 'none'; - } - }); + const expressCheckoutContainer = document.querySelector( + '#ps_checkout-express-checkout-banner' + ); + if (expressCheckoutContainer) { + expressCheckoutContainer.style.display = 'none'; + } + }); } }); }); diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 9c91018f3..286638e69 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -260,6 +260,15 @@ export class CardFieldsComponent extends BaseComponent { let message = error.message || this.$('checkout.form.error.label'); this.data.notification.showError(message); this.data.HTMLElementButton.removeAttribute('disabled'); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.data.name, + isExpressCheckout: this.config.expressCheckout.active, + reason: 'card_fields_error', + error: error instanceof Error ? error.message : error + }) + .catch((error) => console.error(error)); }, inputEvents: { /** diff --git a/_dev/js/front/src/components/common/express-checkout-button.component.js b/_dev/js/front/src/components/common/express-checkout-button.component.js index 8bdcf624b..583a3fbc6 100644 --- a/_dev/js/front/src/components/common/express-checkout-button.component.js +++ b/_dev/js/front/src/components/common/express-checkout-button.component.js @@ -37,7 +37,7 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { ...data, fundingSource: this.props.fundingSource, isExpressCheckout: true, - orderID: this.payPalService.getOrderId(), + orderID: this.payPalService.getOrderId() }, actions ) @@ -48,7 +48,16 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { } onError(error) { - return console.error(error); + console.error(error); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.props.fundingSource, + isExpressCheckout: true, + reason: 'express_checkout_error', + error: error instanceof Error ? error.message : error + }) + .catch((error) => console.error(error)); } onApprove(data, actions) { @@ -66,7 +75,8 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { return this.psCheckoutApi.postCancelOrder({ ...data, fundingSource: this.props.fundingSource, - isExpressCheckout: true + isExpressCheckout: true, + reason: 'express_checkout_cancelled' }); } diff --git a/_dev/js/front/src/components/common/smart-button.component.js b/_dev/js/front/src/components/common/smart-button.component.js index 56473d0af..4456eecaf 100644 --- a/_dev/js/front/src/components/common/smart-button.component.js +++ b/_dev/js/front/src/components/common/smart-button.component.js @@ -91,41 +91,53 @@ export class SmartButtonComponent extends BaseComponent { } return this.psCheckoutApi - .postCheckCartOrder({ + .postCheckCartOrder( + { ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active, - orderID: this.payPalService.getOrderId(), + orderID: this.payPalService.getOrderId() }, actions ) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); return actions.reject(); }); }, - onError: error => { + onError: (error) => { + let errorMessage = this.handleError(error); console.error(error); this.data.loader.hide(); - this.data.notification.showError(this.handleError(error)); + this.data.notification.showError(errorMessage); + + return this.psCheckoutApi + .postCancelOrder({ + fundingSource: this.data.name, + isExpressCheckout: this.config.expressCheckout.active, + reason: 'checkout_error', + error: errorMessage + }) + .catch((error) => console.error(error)); }, onApprove: (data, actions) => { this.data.loader.show(); return this.psCheckoutApi - .postValidateOrder({ + .postValidateOrder( + { ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active }, actions ) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); }); }, - onCancel: data => { + onCancel: (data) => { this.data.loader.hide(); this.data.notification.showCanceled(); @@ -133,21 +145,22 @@ export class SmartButtonComponent extends BaseComponent { .postCancelOrder({ ...data, fundingSource: this.data.name, - isExpressCheckout: this.config.expressCheckout.active + isExpressCheckout: this.config.expressCheckout.active, + reason: 'checkout_cancelled' }) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError(error.message); }); }, - createOrder: data => { + createOrder: (data) => { return this.psCheckoutApi .postCreateOrder({ ...data, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active }) - .catch(error => { + .catch((error) => { this.data.loader.hide(); this.data.notification.showError( `${error.message} ${error.name}` @@ -165,10 +178,16 @@ export class SmartButtonComponent extends BaseComponent { if (error.message) { errorMessage = error.message; - if (error.message.includes('CURRENCY_NOT_SUPPORTED_BY_PAYMENT_SOURCE')) { - errorMessage = 'Provided currency is not supported by the selected payment method.'; - } else if (error.message.includes('COUNTRY_NOT_SUPPORTED_BY_PAYMENT_SOURCE')) { - errorMessage = 'Provided country is not supported by the selected payment method.'; + if ( + error.message.includes('CURRENCY_NOT_SUPPORTED_BY_PAYMENT_SOURCE') + ) { + errorMessage = + 'Provided currency is not supported by the selected payment method.'; + } else if ( + error.message.includes('COUNTRY_NOT_SUPPORTED_BY_PAYMENT_SOURCE') + ) { + errorMessage = + 'Provided country is not supported by the selected payment method.'; } } } diff --git a/config/common.yml b/config/common.yml index da0e5ec26..891cd9802 100644 --- a/config/common.yml +++ b/config/common.yml @@ -236,10 +236,6 @@ services: arguments: - "@ps_checkout.module" - ps_checkout.funding_source.entity: - class: 'PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceEntity' - public: true - ps_checkout.repository.country: class: 'PrestaShop\Module\PrestashopCheckout\Repository\CountryRepository' public: true @@ -367,8 +363,11 @@ services: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderSummaryViewBuilder' public: true arguments: - - '@ps_checkout.translations.translations' - - '@ps_checkout.funding_source.translation' + - '@ps_checkout.repository.pscheckoutcart' + - '@ps_checkout.paypal.provider.order' + - '@ps_checkout.prestashop.router' + - '@ps_checkout.paypal.order.translations' + - '@ps_checkout.context.shop' ps_checkout.paypal.builder.view_order_summary: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderSummaryViewBuilder' @@ -456,18 +455,24 @@ services: PrestaShop\Module\PrestashopCheckout\Order\Command\CreateOrderCommand: "ps_checkout.command.handler.order.create_order" PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand: "ps_checkout.command.handler.order.update_order_status" PrestaShop\Module\PrestashopCheckout\Order\Matrice\Command\UpdateOrderMatriceCommand: "ps_checkout.command.handler.order.matrice.update_order_matrice" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.create_paypal_order" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.update_paypal_order" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.capture_paypal_order" - PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand: "ps_checkout.command.handler.paypal.order.save_paypal_order" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand: "ps_checkout.command.handler.checkout.cancel_checkout" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand: "ps_checkout.command.handler.checkout.save_checkout" + PrestaShop\Module\PrestashopCheckout\Checkout\Command\SavePayPalOrderStatusCommand: "ps_checkout.command.handler.checkout.save_paypal_order_status" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentCompletedQuery: "ps_checkout.query.handler.order.get_order_for_payment_completed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQuery: "ps_checkout.query.handler.order.get_order_for_payment_denied" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery: "ps_checkout.query.handler.order.get_order_for_payment_pending" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery: "ps_checkout.query.handler.order.get_order_for_payment_refunded" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery: "ps_checkout.query.handler.order.get_order_for_payment_reversed" PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForApprovalReversedQuery: "ps_checkout.query.handler.order.get_order_for_approval_reversed" + PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_cart_id" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetCurrentPayPalOrderStatusQuery: "ps_checkout.query.handler.paypal.order.get_current_paypal_order_status" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCheckoutCompletedQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed" PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForOrderConfirmationQuery: "ps_checkout.query.handler.paypal.order.get_paypal_order_for_order_confirmation" PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand: "ps_checkout.query.handler.checkout.update_payment_method_selected" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler' ps_checkout.event.dispatcher.factory: class: 'PrestaShop\Module\PrestashopCheckout\Event\SymfonyEventDispatcherFactory' @@ -510,6 +515,16 @@ services: - "@ps_checkout.cache.paypal.order" - "@ps_checkout.order.state.service.order_state_mapper" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber' + arguments: + - '@ps_checkout.module' + - '@ps_checkout.order.service.check_order_amount' + - '@ps_checkout.cache.paypal.capture' + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.order.state.service.order_state_mapper' + - '@ps_checkout.paypal.provider.order' + ps_checkout.event.dispatcher.symfony: class: 'Symfony\Component\EventDispatcher\EventDispatcherInterface' factory: ["@ps_checkout.event.dispatcher.factory", "create"] @@ -519,6 +534,7 @@ services: "@ps_checkout.event.subscriber.order", "@ps_checkout.event.subscriber.paypal.order", "@ps_checkout.event.subscriber.paypal.capture", + '@PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber\PayPalRefundEventSubscriber' ] ps_checkout.event.dispatcher: @@ -558,18 +574,29 @@ services: arguments: - "@ps_checkout.event.dispatcher" - ps_checkout.command.handler.paypal.order.capture_paypal_order: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler' + ps_checkout.command.handler.paypal.order.create_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CreatePayPalOrderCommandHandler' public: true arguments: + - "@ps_checkout.http.client.checkout" - "@ps_checkout.event.dispatcher" - - "@ps_checkout.cache.paypal.order" + - "@ps_checkout.context.shop" - ps_checkout.command.handler.paypal.order.save_paypal_order: - class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\SavePayPalOrderCommandHandler' + ps_checkout.command.handler.paypal.order.update_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\UpdatePayPalOrderCommandHandler' public: true arguments: - - "@ps_checkout.repository.pscheckoutcart" + - "@ps_checkout.http.client.checkout" + - "@ps_checkout.event.dispatcher" + - "@ps_checkout.context.shop" + + ps_checkout.command.handler.paypal.order.capture_paypal_order: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler\CapturePayPalOrderCommandHandler' + public: true + arguments: + - "@ps_checkout.http.client.checkout" + - "@ps_checkout.event.dispatcher" + - "@ps_checkout.cache.paypal.order" ps_checkout.query.handler.paypal.order.get_current_paypal_order_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetCurrentPayPalOrderStatusQueryHandler' @@ -613,6 +640,13 @@ services: arguments: - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.paypal.order.get_paypal_order_for_cart_id: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCartIdQueryHandler' + public: true + arguments: + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.repository.pscheckoutcart' + ps_checkout.query.handler.paypal.order.get_paypal_order_for_checkout_completed: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler\GetPayPalOrderForCheckoutCompletedQueryHandler' public: true @@ -625,12 +659,41 @@ services: arguments: - "@ps_checkout.cache.paypal.order" + ps_checkout.command.handler.checkout.save_checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\SaveCheckoutCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + + ps_checkout.command.handler.checkout.cancel_checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\CancelCheckoutCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + + ps_checkout.command.handler.checkout.save_paypal_order_status: + class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\SavePayPalOrderStatusCommandHandler' + public: true + arguments: + - "@ps_checkout.repository.pscheckoutcart" + ps_checkout.query.handler.checkout.update_payment_method_selected: class: 'PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler\UpdatePaymentMethodSelectedCommandHandler' public: true arguments: - "@ps_checkout.repository.pscheckoutcart" + PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler: + class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler' + arguments: + - '@ps_checkout.http.client.checkout' + - '@ps_checkout.paypal.configuration' + - '@ps_checkout.configuration' + - '@ps_checkout.context.prestashop' + - '@ps_checkout.event.dispatcher' + - '@ps_checkout.cache.paypal.order' + - '@ps_checkout.paypal.provider.order' + ps_checkout.paypal.capture.service.check_transition_paypal_capture_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\CheckTransitionPayPalCaptureStatusService' public: true @@ -640,3 +703,34 @@ services: public: true arguments: - "@ps_checkout.logger" + + ps_checkout.environment.payment: + class: 'PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv' + public: true + + ps_checkout.http.client.configuration: + class: 'PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClientConfigurationBuilder' + public: true + arguments: + - "@ps_checkout.environment.payment" + - "@ps_checkout.prestashop.router" + - "@ps_checkout.context.shop" + - "@ps_checkout.repository.prestashop.account" + - "@ps_checkout.context.prestashop" + + ps_checkout.http.client.factory: + class: 'PrestaShop\Module\PrestashopCheckout\Http\HttpClientFactory' + public: true + + ps_checkout.http.client: + class: 'PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface' + public: true + factory: ["@ps_checkout.http.client.factory", "create"] + arguments: + - "@ps_checkout.http.client.configuration" + + ps_checkout.http.client.checkout: + class: 'PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient' + public: true + arguments: + - "@ps_checkout.http.client" diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index cce1e0ae7..c6630e0b2 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -19,6 +19,7 @@ */ use Monolog\Logger; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Configuration\BatchConfigurationProcessor; use PrestaShop\Module\PrestashopCheckout\ExpressCheckout\ExpressCheckoutConfiguration; use PrestaShop\Module\PrestashopCheckout\FundingSource\FundingSourceConfigurationRepository; @@ -33,6 +34,9 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Mode; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundFailedException; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalPayLaterConfiguration; @@ -41,7 +45,6 @@ use PrestaShop\Module\PrestashopCheckout\Settings\RoundingSettings; use PrestaShop\Module\PrestashopCheckout\Validator\BatchConfigurationValidator; use PrestaShop\Module\PrestashopCheckout\Webhook\WebhookSecretTokenService; -use Psr\SimpleCache\CacheInterface; class AdminAjaxPrestashopCheckoutController extends ModuleAdminController { @@ -439,92 +442,60 @@ public function ajaxProcessFetchOrder() public function ajaxProcessRefundOrder() { $orderPayPalId = Tools::getValue('orderPayPalRefundOrder'); - $transactionPayPalId = Tools::getValue('orderPayPalRefundTransaction'); + $captureId = Tools::getValue('orderPayPalRefundTransaction'); $amount = Tools::getValue('orderPayPalRefundAmount'); $currency = Tools::getValue('orderPayPalRefundCurrency'); - if (empty($orderPayPalId) || false === Validate::isGenericName($orderPayPalId)) { - http_response_code(400); - $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal Order is invalid.', 'translations'), - ], - ])); - } + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); - if (empty($transactionPayPalId) || false === Validate::isGenericName($transactionPayPalId)) { - http_response_code(400); - $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal Transaction is invalid.', 'translations'), - ], - ])); - } + try { + $commandBus->handle(new RefundPayPalCaptureCommand($orderPayPalId, $captureId, $currency, $amount)); - if (empty($amount) || false === Validate::isPrice($amount) || $amount <= 0) { - http_response_code(400); $this->ajaxDie(json_encode([ - 'status' => false, - 'errors' => [ - $this->l('PayPal refund amount is invalid.', 'translations'), - ], + 'status' => true, + 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), ])); - } - - if (empty($currency) || false === in_array($currency, ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD', 'HUF', 'INR', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD'])) { - // https://developer.paypal.com/docs/api/reference/currency-codes/ - http_response_code(400); + } catch (PayPalRefundFailedException $exception) { + http_response_code($exception->getCode()); $this->ajaxDie(json_encode([ 'status' => false, 'errors' => [ - $this->l('PayPal refund currency is invalid.', 'translations'), + $this->l('Refund cannot be processed by PayPal.', 'translations'), ], ])); - } - - /** @var PayPalConfiguration $configurationPayPal */ - $configurationPayPal = $this->module->getService('ps_checkout.paypal.configuration'); - - $response = (new PrestaShop\Module\PrestashopCheckout\Api\Payment\Order($this->context->link))->refund([ - 'orderId' => $orderPayPalId, - 'captureId' => $transactionPayPalId, - 'payee' => [ - 'merchant_id' => $configurationPayPal->getMerchantId(), - ], - 'amount' => [ - 'currency_code' => $currency, - 'value' => $amount, - ], - 'note_to_payer' => 'Refund by ' - . Configuration::get( - 'PS_SHOP_NAME', - null, - null, - (int) Context::getContext()->shop->id - ), - ]); - - if (isset($response['httpCode']) && $response['httpCode'] === 200) { - /** @var CacheInterface $orderPayPalCache */ - $orderPayPalCache = $this->module->getService('ps_checkout.cache.paypal.order'); - if ($orderPayPalCache->has($orderPayPalId)) { - $orderPayPalCache->delete($orderPayPalId); + } catch (PayPalRefundException $invalidArgumentException) { + http_response_code(400); + $error = ''; + switch ($invalidArgumentException->getCode()) { + case PayPalRefundException::INVALID_ORDER_ID: + $error = $this->l('PayPal Order is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_TRANSACTION_ID: + $error = $this->l('PayPal Transaction is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_CURRENCY: + $error = $this->l('PayPal refund currency is invalid.', 'translations'); + break; + case PayPalRefundException::INVALID_AMOUNT: + $error = $this->l('PayPal refund amount is invalid.', 'translations'); + break; + default: + break; } - $this->ajaxDie(json_encode([ - 'status' => true, - 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), + 'status' => false, + 'errors' => [$error], ])); - } else { - http_response_code(isset($response['httpCode']) ? (int) $response['httpCode'] : 500); - $this->ajaxDie(json_encode([ + } catch (Exception $exception) { + $this->exitWithResponse([ + 'httpCode' => 500, 'status' => false, 'errors' => [ $this->l('Refund cannot be processed by PayPal.', 'translations'), ], - ])); + 'error' => $exception->getMessage(), + ]); } } diff --git a/controllers/front/cancel.php b/controllers/front/cancel.php index bccd1d7fe..ac4d507e7 100644 --- a/controllers/front/cancel.php +++ b/controllers/front/cancel.php @@ -18,8 +18,9 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; /** * This controller receive ajax call on customer canceled payment @@ -68,6 +69,8 @@ public function postProcess() $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : null; $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; + $reason = isset($bodyValues['reason']) ? Tools::safeOutput($bodyValues['reason']) : null; + $error = isset($bodyValues['error']) ? Tools::safeOutput($bodyValues['error']) : null; if (empty($orderId)) { $this->exitWithResponse([ @@ -76,27 +79,28 @@ public function postProcess() ]); } - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($orderId); + $commandBus->handle(new CancelCheckoutCommand( + $this->context->cart->id, + $orderId, + PsCheckoutCart::STATUS_CANCELED, + $fundingSource, + $isExpressCheckout, + $isHostedFields + )); - if (false !== $psCheckoutCart) { - $psCheckoutCart->paypal_funding = $fundingSource; - $psCheckoutCart->isExpressCheckout = $isExpressCheckout; - $psCheckoutCart->isHostedFields = $isHostedFields; - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - } - - $this->module->getLogger()->info( + $this->module->getLogger()->log( + $error ? 400 : 200, 'Customer canceled payment', [ 'PayPalOrderId' => $orderId, 'FundingSource' => $fundingSource, 'isExpressCheckout' => $isExpressCheckout, 'isHostedFields' => $isHostedFields, + 'reason' => $reason, + 'error' => $error, ] ); diff --git a/controllers/front/check.php b/controllers/front/check.php index 7f38cc04e..875b107d9 100644 --- a/controllers/front/check.php +++ b/controllers/front/check.php @@ -18,11 +18,11 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\UpdatePaymentMethodSelectedCommand; +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; -use Psr\SimpleCache\CacheInterface; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand; /** * This controller receive ajax call on customer click on a payment button @@ -67,6 +67,7 @@ public function postProcess() ]); } + $cartId = (int) $this->context->cart->id; $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : 'paypal'; $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; @@ -79,52 +80,14 @@ public function postProcess() ]); } - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); - - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByPayPalOrderId($orderId); - - if (false === $psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $this->context->cart->id; - } - - if ($fundingSource) { - $psCheckoutCart->paypal_funding = $fundingSource; - } - - $psCheckoutCart->isExpressCheckout = $isExpressCheckout; - $psCheckoutCart->isHostedFields = $isHostedFields; - $psCheckoutCartRepository->save($psCheckoutCart); - - if (false === empty($psCheckoutCart->paypal_order)) { - $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout || empty($this->context->cart->id_address_delivery), $psCheckoutCart->paypal_funding === 'card', true, $psCheckoutCart->paypal_order); - - if (false === $response['status']) { - $this->module->getLogger()->error( - 'Failed to patch PayPal Order', - [ - 'PayPalOrderId' => $orderId, - 'FundingSource' => $fundingSource, - 'isExpressCheckout' => $isExpressCheckout, - 'isHostedFields' => $isHostedFields, - 'id_cart' => (int) $this->context->cart->id, - 'response' => $response, - ] - ); - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - throw new PsCheckoutException(sprintf('Unable to patch PayPal Order - Exception %s : %s', $response['exceptionCode'], $response['exceptionMessage']), PsCheckoutException::PSCHECKOUT_UPDATE_ORDER_HANDLE_ERROR); - } - - /** @var CacheInterface $orderPayPalCache */ - $orderPayPalCache = $this->module->getService('ps_checkout.cache.paypal.order'); - $orderPayPalCache->delete($psCheckoutCart->getPaypalOrderId()); - - $this->module->getLogger()->info( - 'PayPal Order patched', + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); + $commandBus->handle(new UpdatePaymentMethodSelectedCommand($cartId, $orderId, $fundingSource, $isExpressCheckout, $isHostedFields)); + try { + $commandBus->handle(new UpdatePayPalOrderCommand($orderId, $cartId, $fundingSource, $isHostedFields, $isExpressCheckout)); + } catch (Exception $exception) { + $this->module->getLogger()->error( + 'Failed to patch PayPal Order', [ 'PayPalOrderId' => $orderId, 'FundingSource' => $fundingSource, @@ -133,6 +96,14 @@ public function postProcess() 'id_cart' => (int) $this->context->cart->id, ] ); + $commandBus->handle(new CancelCheckoutCommand( + $this->context->cart->id, + $orderId, + PsCheckoutCart::STATUS_CANCELED, + $fundingSource, + $isExpressCheckout, + $isHostedFields + )); } $this->exitWithResponse([ diff --git a/controllers/front/create.php b/controllers/front/create.php index 182b4da49..b5fadb430 100755 --- a/controllers/front/create.php +++ b/controllers/front/create.php @@ -19,11 +19,11 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Controller\AbstractFrontController; -use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; -use PrestaShop\Module\PrestashopCheckout\Handler\CreatePaypalOrderHandler; -use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; -use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQueryResult; /** * This controller receive ajax call to create a PayPal Order @@ -93,97 +93,25 @@ public function postProcess() } // END Express Checkout - if (false === Validate::isLoadedObject($this->context->cart)) { - $this->exitWithResponse([ - 'httpCode' => 400, - 'body' => 'No cart found.', - ]); - } - - /** @var PsCheckoutCartRepository $psCheckoutCartRepository */ - $psCheckoutCartRepository = $this->module->getService('ps_checkout.repository.pscheckoutcart'); - - /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $psCheckoutCartRepository->findOneByCartId((int) $this->context->cart->id); - - $isExpressCheckout = (isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']) || empty($this->context->cart->id_address_delivery); - - if (false !== $psCheckoutCart && $psCheckoutCart->isExpressCheckout() && $psCheckoutCart->isOrderAvailable()) { - $this->exitWithResponse([ - 'status' => true, - 'httpCode' => 200, - 'body' => [ - 'orderID' => $psCheckoutCart->paypal_order, - ], - 'exceptionCode' => null, - 'exceptionMessage' => null, - ]); - } - - // If we have a PayPal Order Id with a status CREATED or APPROVED or PAYER_ACTION_REQUIRED we mark it as CANCELED and create new one - // This is needed because cart gets updated so we need to update paypal order too - if ( - false !== $psCheckoutCart && $psCheckoutCart->getPaypalOrderId() - ) { - $psCheckoutCart->paypal_status = PsCheckoutCart::STATUS_CANCELED; - $psCheckoutCartRepository->save($psCheckoutCart); - $psCheckoutCart = false; - } - - $paypalOrder = new CreatePaypalOrderHandler($this->context); - $response = $paypalOrder->handle($isExpressCheckout, isset($bodyValues['fundingSource']) && $bodyValues['fundingSource'] === 'card'); + $cartId = (int) $this->context->cart->id; - if (false === $response['status']) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } - - if (empty($response['body']['id'])) { - throw new PsCheckoutException('Paypal order id is missing.', PsCheckoutException::PAYPAL_ORDER_IDENTIFIER_MISSING); - } - - $paymentSource = isset($response['body']['payment_source']) ? key($response['body']['payment_source']) : 'paypal'; - $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : $paymentSource; - $orderId = isset($bodyValues['orderID']) ? $bodyValues['orderID'] : null; - $isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; + $fundingSource = isset($bodyValues['fundingSource']) ? $bodyValues['fundingSource'] : 'paypal'; $isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; - /** @var PayPalConfiguration $configuration */ - $configuration = $this->module->getService('ps_checkout.paypal.configuration'); - - $this->module->getLogger()->info( - 'PayPal Order created', - [ - 'PayPalOrderId' => $orderId, - 'FundingSource' => $fundingSource, - 'isExpressCheckout' => $isExpressCheckout, - 'isHostedFields' => $isHostedFields, - 'id_cart' => (int) $this->context->cart->id, - 'amount' => $this->context->cart->getOrderTotal(true, Cart::BOTH), - 'environment' => $configuration->getPaymentMode(), - 'intent' => $configuration->getIntent(), - ] - ); + $isExpressCheckout = (isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']) || empty($this->context->cart->id_address_delivery); - if (false === $psCheckoutCart) { - $psCheckoutCart = new PsCheckoutCart(); - $psCheckoutCart->id_cart = (int) $this->context->cart->id; - } + /** @var CommandBusInterface $commandBus */ + $commandBus = $this->module->getService('ps_checkout.bus.command'); + $commandBus->handle(new CreatePayPalOrderCommand($cartId, $fundingSource, $isHostedFields, $isExpressCheckout)); - $psCheckoutCart->paypal_funding = $fundingSource; - $psCheckoutCart->paypal_order = $response['body']['id']; - $psCheckoutCart->paypal_status = $response['body']['status']; - $psCheckoutCart->paypal_intent = $configuration->getIntent(); - $psCheckoutCart->paypal_token = $response['body']['client_token']; - $psCheckoutCart->paypal_token_expire = (new DateTime())->modify('+3550 seconds')->format('Y-m-d H:i:s'); - $psCheckoutCart->environment = $configuration->getPaymentMode(); - $psCheckoutCart->isExpressCheckout = isset($bodyValues['isExpressCheckout']) && $bodyValues['isExpressCheckout']; - $psCheckoutCart->isHostedFields = isset($bodyValues['isHostedFields']) && $bodyValues['isHostedFields']; - $psCheckoutCartRepository->save($psCheckoutCart); + /** @var GetPayPalOrderForCartIdQueryResult $getPayPalOrderForCartIdQueryResult */ + $getPayPalOrderForCartIdQueryResult = $commandBus->handle(new GetPayPalOrderForCartIdQuery($cartId)); + $order = $getPayPalOrderForCartIdQueryResult->getOrder(); $this->exitWithResponse([ 'status' => true, 'httpCode' => 200, 'body' => [ - 'orderID' => $psCheckoutCart->paypal_order, + 'orderID' => $order['id'], ], 'exceptionCode' => null, 'exceptionMessage' => null, diff --git a/src/Api/Payment/Order.php b/src/Api/Payment/Order.php index 9b4b9db89..18f97a0d5 100644 --- a/src/Api/Payment/Order.php +++ b/src/Api/Payment/Order.php @@ -25,10 +25,13 @@ /** * Handle order requests + * + * @deprecated */ class Order extends PaymentClient { /** + * @deprecated * Create order to paypal api * * @param array $payload Cart details (json) @@ -43,6 +46,7 @@ public function create($payload) } /** + * @deprecated * Capture order funds * * @param string $orderId paypal @@ -65,6 +69,7 @@ public function capture($orderId, $merchantId, $fundingSource) } /** + * @deprecated * Get paypal order details * * @param string $orderId paypal @@ -95,6 +100,7 @@ public function authorize($orderId, $merchantId) } /** + * @deprecated * Refund an order * * @param array $payload @@ -109,6 +115,7 @@ public function refund($payload) } /** + * @deprecated * Patch paypal order * * @param array $payload diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index 61011b1b4..7d75a802c 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -109,8 +109,8 @@ public function buildFullPayload() } if ($this->isCard) { - $this->buildPaymentSourceNode(); - $this->buildSupplementaryDataNode(); + //$this->buildPaymentSourceNode(); + //$this->buildSupplementaryDataNode(); } } diff --git a/src/Checkout/Command/CancelCheckoutCommand.php b/src/Checkout/Command/CancelCheckoutCommand.php new file mode 100644 index 000000000..50c7530c5 --- /dev/null +++ b/src/Checkout/Command/CancelCheckoutCommand.php @@ -0,0 +1,128 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class CancelCheckoutCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var string + */ + private $orderPayPalStatus; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @param int $cartId + * @param string $orderPayPalId + * @param string $orderPayPalStatus + * @param string $fundingSource + * @param bool $isExpressCheckout + * @param bool $isHostedFields + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($cartId, $orderPayPalId, $orderPayPalStatus, $fundingSource, $isExpressCheckout, $isHostedFields) + { + $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->orderPayPalStatus = $orderPayPalStatus; + $this->fundingSource = $fundingSource; + $this->isExpressCheckout = $isExpressCheckout; + $this->isHostedFields = $isHostedFields; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return PayPalOrderId + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return string + */ + public function getOrderPayPalStatus() + { + return $this->orderPayPalStatus; + } +} diff --git a/src/Checkout/Command/SaveCheckoutCommand.php b/src/Checkout/Command/SaveCheckoutCommand.php new file mode 100644 index 000000000..748bdbb07 --- /dev/null +++ b/src/Checkout/Command/SaveCheckoutCommand.php @@ -0,0 +1,158 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class SaveCheckoutCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var string + */ + private $orderPayPalStatus; + + /** + * @var string + */ + private $intent; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var string + */ + private $environment; + + /** + * @param int $cartId + * @param string $orderPayPalId + * @param string $orderPayPalStatus + * @param string $intent + * @param string $fundingSource + * @param bool $isExpressCheckout + * @param bool $isHostedFields + * @param string $environment + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($cartId, $orderPayPalId, $orderPayPalStatus, $intent, $fundingSource, $isExpressCheckout, $isHostedFields, $environment) + { + $this->cartId = new CartId($cartId); + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->orderPayPalStatus = $orderPayPalStatus; + $this->intent = $intent; + $this->fundingSource = $fundingSource; + $this->isExpressCheckout = $isExpressCheckout; + $this->isHostedFields = $isHostedFields; + $this->environment = $environment; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return PayPalOrderId + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return string + */ + public function getOrderPayPalStatus() + { + return $this->orderPayPalStatus; + } + + /** + * @return string + */ + public function getIntent() + { + return $this->intent; + } + + /** + * @return string + */ + public function getEnvironment() + { + return $this->environment; + } +} diff --git a/src/PayPal/Order/Command/SavePayPalOrderCommand.php b/src/Checkout/Command/SavePayPalOrderStatusCommand.php similarity index 80% rename from src/PayPal/Order/Command/SavePayPalOrderCommand.php rename to src/Checkout/Command/SavePayPalOrderStatusCommand.php index 4533468e8..cdda30214 100644 --- a/src/PayPal/Order/Command/SavePayPalOrderCommand.php +++ b/src/Checkout/Command/SavePayPalOrderStatusCommand.php @@ -1,5 +1,4 @@ orderPayPalId = new PayPalOrderId($orderPayPalId); $this->orderPayPalStatus = $orderPayPalStatus; - $this->orderPayPal = $orderPayPal; } /** @@ -66,16 +58,8 @@ public function getOrderPayPalId() /** * @return string */ - public function getOrderPaypalStatus() + public function getOrderPayPalStatus() { return $this->orderPayPalStatus; } - - /** - * @return array - */ - public function getOrderPayPal() - { - return $this->orderPayPal; - } } diff --git a/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php b/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php new file mode 100644 index 000000000..36610d03a --- /dev/null +++ b/src/Checkout/CommandHandler/CancelCheckoutCommandHandler.php @@ -0,0 +1,68 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\CancelCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Exception\PsCheckoutSessionException; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; + +class CancelCheckoutCommandHandler +{ + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + + /** + * @param CancelCheckoutCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(CancelCheckoutCommand $command) + { + try { + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($command->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new CartNotFoundException(sprintf('Unable to retrieve PrestaShop Cart #%s', var_export($command->getCartId()->getValue(), true))); + } + + $psCheckoutCart->id_cart = $command->getCartId()->getValue(); + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_funding = $command->getFundingSource(); + $psCheckoutCart->isHostedFields = $command->isHostedFields(); + $psCheckoutCart->isExpressCheckout = $command->isExpressCheckout(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); + $this->psCheckoutCartRepository->save($psCheckoutCart); + } catch (Exception $exception) { + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($command->getCartId()->getValue(), true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); + } + } +} diff --git a/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php b/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php new file mode 100644 index 000000000..3855f1bbb --- /dev/null +++ b/src/Checkout/CommandHandler/SaveCheckoutCommandHandler.php @@ -0,0 +1,69 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Checkout\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Exception\PsCheckoutSessionException; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use PsCheckoutCart; + +class SaveCheckoutCommandHandler +{ + /** + * @var PsCheckoutCartRepository + */ + private $psCheckoutCartRepository; + + public function __construct(PsCheckoutCartRepository $psCheckoutCartRepository) + { + $this->psCheckoutCartRepository = $psCheckoutCartRepository; + } + + /** + * @param SaveCheckoutCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(SaveCheckoutCommand $command) + { + try { + /** @var PsCheckoutCart|false $psCheckoutCart */ + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByCartId($command->getCartId()->getValue()); + + if (false === $psCheckoutCart) { + $psCheckoutCart = new PsCheckoutCart(); + } + + $psCheckoutCart->id_cart = $command->getCartId()->getValue(); + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_funding = $command->getFundingSource(); + $psCheckoutCart->isHostedFields = $command->isHostedFields(); + $psCheckoutCart->isExpressCheckout = $command->isExpressCheckout(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); + $psCheckoutCart->paypal_intent = $command->getIntent(); + $psCheckoutCart->environment = $command->getEnvironment(); + $this->psCheckoutCartRepository->save($psCheckoutCart); + } catch (Exception $exception) { + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($command->getCartId()->getValue(), true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); + } + } +} diff --git a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php b/src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php similarity index 52% rename from src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php rename to src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php index f083a905f..00483385b 100644 --- a/src/PayPal/Order/CommandHandler/SavePayPalOrderCommandHandler.php +++ b/src/Checkout/CommandHandler/SavePayPalOrderStatusCommandHandler.php @@ -1,5 +1,4 @@ psCheckoutCartRepository = $psCheckoutCartRepository; } - public function handle(SavePayPalOrderCommand $savePayPalOrderCommand) + /** + * @param SavePayPalOrderStatusCommand $command + * + * @throws PsCheckoutSessionException + */ + public function handle(SavePayPalOrderStatusCommand $command) { try { /** @var PsCheckoutCart|false $psCheckoutCart */ - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($savePayPalOrderCommand->getOrderPayPalId()->getValue()); + $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($command->getOrderPayPalId()->getValue()); + + if (false === $psCheckoutCart) { + throw new CartNotFoundException(sprintf('Unable to retrieve PayPal Order %s', var_export($command->getOrderPayPalId()->getValue(), true))); + } - $psCheckoutCart->paypal_order = $savePayPalOrderCommand->getOrderPayPalId()->getValue(); - $psCheckoutCart->paypal_status = $savePayPalOrderCommand->getOrderPaypalStatus(); - $psCheckoutCart->paypal_token = null; - $psCheckoutCart->paypal_token_expire = null; + $psCheckoutCart->paypal_order = $command->getOrderPayPalId()->getValue(); + $psCheckoutCart->paypal_status = $command->getOrderPayPalStatus(); $this->psCheckoutCartRepository->save($psCheckoutCart); } catch (Exception $exception) { - throw new PayPalOrderException(sprintf('Unable to retrieve PrestaShop cart #%d', $savePayPalOrderCommand->getOrderPayPalId()->getValue()), PayPalOrderException::SESSION_EXCEPTION, $exception); + $sessionId = isset($psCheckoutCart) ? $psCheckoutCart->getIdCart() : $command->getOrderPayPalId()->getValue(); + throw new PsCheckoutSessionException(sprintf('Unable to update PrestaShop Checkout session #%s', var_export($sessionId, true)), PsCheckoutSessionException::UPDATE_FAILED, $exception); } } } diff --git a/src/Controller/AbstractFrontController.php b/src/Controller/AbstractFrontController.php index f58cb7169..6de73b31c 100644 --- a/src/Controller/AbstractFrontController.php +++ b/src/Controller/AbstractFrontController.php @@ -40,7 +40,12 @@ protected function exitWithExceptionMessage(Exception $exception) $this->exitWithResponse([ 'status' => false, 'httpCode' => 500, - 'body' => '', + 'body' => [ + 'error' => [ + 'code' => $exception->getCode(), + 'message' => $exception->getMessage(), + ], + ], 'exceptionCode' => $exception->getCode(), 'exceptionMessage' => $exception->getMessage(), ]); diff --git a/src/Dispatcher/OrderDispatcher.php b/src/Dispatcher/OrderDispatcher.php index 039ec85c9..d3a91a8ba 100644 --- a/src/Dispatcher/OrderDispatcher.php +++ b/src/Dispatcher/OrderDispatcher.php @@ -30,9 +30,9 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Exception\PayPalCaptureException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; use Ps_checkout; use Psr\Log\LoggerInterface; diff --git a/src/Exception/PayPalException.php b/src/Exception/PayPalException.php index 015b0a728..710268a8b 100644 --- a/src/Exception/PayPalException.php +++ b/src/Exception/PayPalException.php @@ -154,4 +154,23 @@ class PayPalException extends PsCheckoutException const CARD_BRAND_NOT_SUPPORTED = 129; const RESOURCE_NOT_FOUND = 130; const PAYMENT_SOURCE_CANNOT_BE_USED = 131; + const CANNOT_PROCESS_REFUNDS = 132; + const INVALID_REFUND_AMOUNT = 133; + const PUI_DUPLICATE_ORDER = 134; + const SAVE_ORDER_NOT_SUPPORTED = 135; + const CARD_CLOSED = 136; + const UNSUPPORTED_SHIPPING_TYPE = 137; + const SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT = 138; + const PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED = 139; + const PAYMENT_SOURCE_DECLINED_BY_PROCESSOR = 140; + const MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED = 141; + const MULTIPLE_ITEM_CATEGORIES = 142; + const MISSING_PICKUP_ADDRESS = 143; + const DONATION_ITEMS_NOT_SUPPORTED = 144; + const CARD_EXPIRED = 145; + const BILLING_ADDRESS_INVALID = 146; + const MALFORMED_REQUEST = 147; + const PERMISSION_DENIED_FOR_DONATION_ITEMS = 148; + const MALFORMED_REQUEST_JSON = 149; + const PAYPAL_REQUEST_ID_REQUIRED = 150; } diff --git a/src/Handler/CreatePaypalOrderHandler.php b/src/Handler/CreatePaypalOrderHandler.php index d7599d2b0..cb34dc75d 100644 --- a/src/Handler/CreatePaypalOrderHandler.php +++ b/src/Handler/CreatePaypalOrderHandler.php @@ -23,9 +23,11 @@ use Context; use Module; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; use PrestaShop\Module\PrestashopCheckout\ShopContext; use Ps_checkout; @@ -91,25 +93,36 @@ public function handle($expressCheckout = false, $isCardPayment = false, $update $payload = $builder->presentPayload()->getArray(); - // Create the paypal order or update it - if (true === $updateOrder) { - $paypalOrder = (new Order($this->context->link))->patch($payload); - } else { - $paypalOrder = (new Order($this->context->link))->create($payload); - } - - // Retry with minimal payload when full payload failed (only on 1.7) - if (substr((string) $paypalOrder['httpCode'], 0, 1) === '4' && $shopContext->isShop17()) { - $builder->buildMinimalPayload(); - $payload = $builder->presentPayload()->getArray(); + /** @var CheckoutHttpClient $checkoutHttpClient */ + $checkoutHttpClient = $module->getService('ps_checkout.http.client.checkout'); + // Create the paypal order or update it + try { if (true === $updateOrder) { - $paypalOrder = (new Order($this->context->link))->patch($payload); + $response = $checkoutHttpClient->updateOrder($payload); + } else { + $response = $checkoutHttpClient->createOrder($payload); + } + } catch (PayPalException $exception) { + $previousException = $exception->getPrevious(); + $response = method_exists($previousException, 'getResponse') ? $previousException->getResponse() : null; + // Retry with minimal payload when full payload failed (only on 1.7) + if ($response && substr((string) $response->getStatusCode(), 0, 1) === '4' && $shopContext->isShop17()) { + $builder->buildMinimalPayload(); + $payload = $builder->presentPayload()->getArray(); + + if (true === $updateOrder) { + $response = $checkoutHttpClient->updateOrder($payload); + } else { + $response = $checkoutHttpClient->createOrder($payload); + } } else { - $paypalOrder = (new Order($this->context->link))->create($payload); + throw $exception; } } - return $paypalOrder; + $responseHandler = new ResponseApiHandler(); + + return $responseHandler->handleResponse($response); } } diff --git a/src/Handler/Response/ResponseApiHandler.php b/src/Handler/Response/ResponseApiHandler.php index cff811a43..3531220a8 100644 --- a/src/Handler/Response/ResponseApiHandler.php +++ b/src/Handler/Response/ResponseApiHandler.php @@ -37,8 +37,7 @@ class ResponseApiHandler */ public function handleResponse(ResponseInterface $response) { - $response->getBody()->seek(0); // Rewind Stream to avoid empty body - $responseContents = json_decode($response->getBody()->getContents(), true); + $responseContents = json_decode($response->getBody(), true); return [ 'status' => $this->responseIsSuccessful($responseContents, $response->getStatusCode()), diff --git a/src/Http/CheckoutHttpClient.php b/src/Http/CheckoutHttpClient.php new file mode 100644 index 000000000..0d03828cf --- /dev/null +++ b/src/Http/CheckoutHttpClient.php @@ -0,0 +1,184 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use GuzzleHttp\Psr7\Request; +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\Exception\TransferException; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\PayPalError; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +class CheckoutHttpClient implements HttpClientInterface +{ + /** + * @var HttpClientInterface + */ + private $httpClient; + + public function __construct(HttpClientInterface $httpClient) + { + $this->httpClient = $httpClient; + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function createOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/create', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function updateOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/update', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function fetchOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/fetch', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function captureOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/capture', $options, json_encode($payload))); + } + + /** + * @param array $payload + * @param array $options + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function refundOrder(array $payload, array $options = []) + { + return $this->sendRequest(new Request('POST', '/payments/order/refund', $options, json_encode($payload))); + } + + /** + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + * @throws PayPalException + */ + public function sendRequest(RequestInterface $request) + { + try { + $response = $this->httpClient->sendRequest($request); + } catch (HttpException $exception) { + $response = $exception->getResponse(); + $message = $this->extractMessage(json_decode($response->getBody(), true)); + + if ($message) { + (new PayPalError($message))->throwException($exception); + } + + throw $exception; + } + + return $response; + } + + /** + * @param array $body + * + * @return string + */ + private function extractMessage(array $body) + { + if (isset($body['details'][0]['issue']) && preg_match('/^[0-9A-Z_]+$/', $body['details'][0]['issue']) === 1) { + return $body['details'][0]['issue']; + } + + if (isset($body['error']) && preg_match('/^[0-9A-Z_]+$/', $body['error']) === 1) { + return $body['error']; + } + + if (isset($body['message']) && preg_match('/^[0-9A-Z_]+$/', $body['message']) === 1) { + return $body['message']; + } + + if (isset($body['name']) && preg_match('/^[0-9A-Z_]+$/', $body['name']) === 1) { + return $body['name']; + } + + return ''; + } +} diff --git a/src/Http/CheckoutHttpClientConfigurationBuilder.php b/src/Http/CheckoutHttpClientConfigurationBuilder.php new file mode 100644 index 000000000..03f5bb8b9 --- /dev/null +++ b/src/Http/CheckoutHttpClientConfigurationBuilder.php @@ -0,0 +1,108 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; +use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; +use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; +use PrestaShop\Module\PrestashopCheckout\Routing\Router; +use PrestaShop\Module\PrestashopCheckout\ShopContext; +use Ps_checkout; + +class CheckoutHttpClientConfigurationBuilder implements HttpClientConfigurationBuilderInterface +{ + const TIMEOUT = 10; + + /** + * @var PaymentEnv + */ + private $paymentEnv; + + /** + * @var Router + */ + private $router; + + /** + * @var ShopContext + */ + private $shopContext; + + /** + * @var PsAccountRepository + */ + private $psAccountRepository; + + /** + * @var PrestaShopContext + */ + private $prestaShopContext; + + public function __construct( + PaymentEnv $paymentEnv, + Router $router, + ShopContext $shopContext, + PsAccountRepository $psAccountRepository, + PrestaShopContext $prestaShopContext + ) { + $this->paymentEnv = $paymentEnv; + $this->router = $router; + $this->shopContext = $shopContext; + $this->psAccountRepository = $psAccountRepository; + $this->prestaShopContext = $prestaShopContext; + } + + /** + * @return array + */ + public function build() + { + return [ + 'base_url' => $this->paymentEnv->getPaymentApiUrl(), + 'verify' => $this->getVerify(), + 'timeout' => static::TIMEOUT, + 'headers' => [ + 'Content-Type' => 'application/vnd.checkout.v1+json', // api version to use (psl side) + 'Accept' => 'application/json', + 'Authorization' => 'Bearer ' . $this->psAccountRepository->getIdToken(), // Token we get from PsAccounts + 'Shop-Id' => $this->psAccountRepository->getShopUuid(), // Shop UUID we get from PsAccounts + 'Hook-Url' => $this->router->getDispatchWebhookLink($this->prestaShopContext->getShopId()), + 'Bn-Code' => $this->shopContext->getBnCode(), + 'Module-Version' => Ps_checkout::VERSION, // version of the module + 'Prestashop-Version' => _PS_VERSION_, // prestashop version + ], + ]; + } + + /** + * @see https://docs.guzzlephp.org/en/5.3/clients.html#verify + * + * @return true|string + */ + protected function getVerify() + { + if (defined('_PS_CACHE_CA_CERT_FILE_') && file_exists(constant('_PS_CACHE_CA_CERT_FILE_'))) { + return constant('_PS_CACHE_CA_CERT_FILE_'); + } + + return true; + } +} diff --git a/src/Http/HttpClientConfigurationBuilderInterface.php b/src/Http/HttpClientConfigurationBuilderInterface.php new file mode 100644 index 000000000..232ab832d --- /dev/null +++ b/src/Http/HttpClientConfigurationBuilderInterface.php @@ -0,0 +1,29 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +interface HttpClientConfigurationBuilderInterface +{ + /** + * @return array + */ + public function build(); +} diff --git a/src/Http/HttpClientFactory.php b/src/Http/HttpClientFactory.php new file mode 100644 index 000000000..6c1ca840c --- /dev/null +++ b/src/Http/HttpClientFactory.php @@ -0,0 +1,34 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +class HttpClientFactory +{ + /** + * @param HttpClientConfigurationBuilderInterface $httpClientConfigurationBuilder + * + * @return HttpClientInterface + */ + public function create(HttpClientConfigurationBuilderInterface $httpClientConfigurationBuilder) + { + return new PsrHttpClientAdapter($httpClientConfigurationBuilder->build()); + } +} diff --git a/src/Http/HttpClientInterface.php b/src/Http/HttpClientInterface.php new file mode 100644 index 000000000..275432b11 --- /dev/null +++ b/src/Http/HttpClientInterface.php @@ -0,0 +1,50 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\RequestException; +use Http\Client\Exception\TransferException; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; + +/** + * Interface HttpClientInterface + * + * This interface provides a PSR-18 compliant implementation for PHP 5.6 + */ +interface HttpClientInterface +{ + /** + * Sends a PSR-7 request and returns a PSR-7 response. + * + * @param RequestInterface $request + * + * @return ResponseInterface + * + * @throws NetworkException + * @throws HttpException + * @throws RequestException + * @throws TransferException + */ + public function sendRequest(RequestInterface $request); +} diff --git a/src/Http/PsrHttpClientAdapter.php b/src/Http/PsrHttpClientAdapter.php new file mode 100644 index 000000000..fac735f2d --- /dev/null +++ b/src/Http/PsrHttpClientAdapter.php @@ -0,0 +1,63 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\Http; + +use Http\Client\Exception\HttpException; +use Http\Client\Exception\NetworkException; +use Http\Client\Exception\TransferException; +use Prestashop\ModuleLibGuzzleAdapter\ClientFactory; +use Psr\Http\Message\RequestInterface; + +class PsrHttpClientAdapter implements HttpClientInterface +{ + private $client; + + /** + * @param array $configuration + */ + public function __construct(array $configuration) + { + $this->client = (new ClientFactory())->getClient($configuration); + } + + /** + * {@inheritdoc} + */ + public function sendRequest(RequestInterface $request) + { + try { + $response = $this->client->sendRequest($request); + } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { + // Guzzle 5.3 use RingPHP for the low level connection + throw new NetworkException($exception->getMessage(), $request, $exception); + } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { + // Guzzle 5.3 use RingPHP for the low level connection + throw new TransferException($exception->getMessage(), 0, $exception); + } + + // Guzzle 5.3 does not throw exceptions on 4xx and 5xx status codes + if ($response->getStatusCode() >= 400) { + throw new HttpException($response->getReasonPhrase(), $request, $response); + } + + return $response; + } +} diff --git a/src/PayPal/Order/Command/CreatePayPalOrderCommand.php b/src/PayPal/Order/Command/CreatePayPalOrderCommand.php new file mode 100644 index 000000000..b3b8998ef --- /dev/null +++ b/src/PayPal/Order/Command/CreatePayPalOrderCommand.php @@ -0,0 +1,95 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; + +class CreatePayPalOrderCommand +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @param int $cartId + * @param string $fundingSource + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * + * @throws CartException + */ + public function __construct($cartId, $fundingSource, $isHostedFields, $isExpressCheckout) + { + $this->cartId = new CartId($cartId); + $this->fundingSource = $fundingSource; + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } +} diff --git a/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php b/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php new file mode 100644 index 000000000..ed125614c --- /dev/null +++ b/src/PayPal/Order/Command/UpdatePayPalOrderCommand.php @@ -0,0 +1,112 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +class UpdatePayPalOrderCommand +{ + /** + * @var PayPalOrderId + */ + private $orderPayPalId; + + /** + * @var CartId + */ + private $cartId; + + /** + * @var string + */ + private $fundingSource; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @param string $orderPayPalId + * @param int $cartId + * @param string $fundingSource + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * + * @throws CartException|PayPalOrderException + */ + public function __construct($orderPayPalId, $cartId, $fundingSource, $isHostedFields, $isExpressCheckout) + { + $this->orderPayPalId = new PayPalOrderId($orderPayPalId); + $this->cartId = new CartId($cartId); + $this->fundingSource = $fundingSource; + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + } + + /** + * @return PayPalOrderId + */ + public function getPayPalOrderId() + { + return $this->orderPayPalId; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } +} diff --git a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php index af28d2068..7731c10d2 100644 --- a/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CapturePayPalOrderCommandHandler.php @@ -23,9 +23,9 @@ use Configuration; use Context; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; @@ -33,7 +33,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\PayPalCaptureStatus; -use PrestaShop\Module\PrestashopCheckout\PayPalError; use PrestaShop\Module\PrestashopCheckout\PayPalProcessorResponse; use Psr\SimpleCache\CacheInterface; @@ -43,13 +42,20 @@ class CapturePayPalOrderCommandHandler * @var EventDispatcherInterface */ private $eventDispatcher; + /** * @var CacheInterface */ private $orderPayPalCache; - public function __construct(EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) + /** + * @var CheckoutHttpClient + */ + private $httpClient; + + public function __construct(CheckoutHttpClient $httpClient, EventDispatcherInterface $eventDispatcher, CacheInterface $orderPayPalCache) { + $this->httpClient = $httpClient; $this->eventDispatcher = $eventDispatcher; $this->orderPayPalCache = $orderPayPalCache; } @@ -58,34 +64,16 @@ public function handle(CapturePayPalOrderCommand $capturePayPalOrderCommand) { $context = Context::getContext(); $merchantId = Configuration::get('PS_CHECKOUT_PAYPAL_ID_MERCHANT', null, null, $context->shop->id); - $apiOrder = new Order($context->link); - $response = $apiOrder->capture( - $capturePayPalOrderCommand->getOrderId()->getValue(), - $merchantId, - $capturePayPalOrderCommand->getFundingSource() - ); - - if (false === $response['status']) { - if (isset($response['body']['details'][0]['issue'])) { - (new PayPalError($response['body']['details'][0]['issue']))->throwException(); - } - - if (isset($response['body']['name'])) { - (new PayPalError($response['body']['name']))->throwException(); - } - - if (false === empty($response['body']['message'])) { - (new PayPalError($response['body']['message']))->throwException(); - } - - if (false === empty($response['exceptionMessage']) && false === empty($response['exceptionCode'])) { - throw new PsCheckoutException($response['exceptionMessage'], (int) $response['exceptionCode']); - } - - throw new PsCheckoutException(isset($response['body']['error']) ? $response['body']['error'] : 'Unknown error', PsCheckoutException::UNKNOWN); - } - $orderPayPal = $response['body']; + $response = $this->httpClient->captureOrder([ + 'mode' => $capturePayPalOrderCommand->getFundingSource(), + 'orderId' => $capturePayPalOrderCommand->getOrderId()->getValue(), + 'payee' => [ + 'merchant_id' => $merchantId, + ], + ]); + + $orderPayPal = json_decode($response->getBody(), true); $payPalOrderFromCache = $this->orderPayPalCache->get($orderPayPal['id']); diff --git a/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php new file mode 100644 index 000000000..d3a9f4233 --- /dev/null +++ b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php @@ -0,0 +1,95 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; + +use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CreatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; +use PrestaShop\Module\PrestashopCheckout\ShopContext; + +class CreatePayPalOrderCommandHandler +{ + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var CheckoutHttpClient + */ + private $httpClient; + /** + * @var ShopContext + */ + private $shopContext; + + public function __construct( + CheckoutHttpClient $httpClient, + EventDispatcherInterface $eventDispatcher, + ShopContext $shopContext + ) { + $this->httpClient = $httpClient; + $this->eventDispatcher = $eventDispatcher; + $this->shopContext = $shopContext; + } + + /** + * @param CreatePayPalOrderCommand $command + * + * @return void + * + * @throws PayPalException + * @throws PayPalOrderException + * @throws PsCheckoutException + */ + public function handle(CreatePayPalOrderCommand $command) + { + $cartPresenter = (new CartPresenter())->present(); + $builder = new OrderPayloadBuilder($cartPresenter); + $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setExpressCheckout($command->isExpressCheckout()); + + if ($this->shopContext->isShop17()) { + // Build full payload in 1.7 + $builder->buildFullPayload(); + } else { + // if on 1.6 always build minimal payload + $builder->buildMinimalPayload(); + } + + $response = $this->httpClient->createOrder($builder->presentPayload()->getArray()); + $order = json_decode($response->getBody(), true); + $this->eventDispatcher->dispatch(new PayPalOrderCreatedEvent( + $order['id'], + $order, + $command->getCartId()->getValue(), + $command->isHostedFields(), + $command->isExpressCheckout(), + $command->getFundingSource() + )); + } +} diff --git a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php new file mode 100644 index 000000000..57eb02a1c --- /dev/null +++ b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php @@ -0,0 +1,104 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\CommandHandler; + +use Exception; +use PrestaShop\Module\PrestashopCheckout\Builder\Payload\OrderPayloadBuilder; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\UpdatePayPalOrderCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderUpdatedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\Presenter\Cart\CartPresenter; +use PrestaShop\Module\PrestashopCheckout\ShopContext; + +class UpdatePayPalOrderCommandHandler +{ + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + + /** + * @var CheckoutHttpClient + */ + private $httpClient; + + /** + * @var ShopContext + */ + private $shopContext; + + /** + * @param CheckoutHttpClient $httpClient + * @param EventDispatcherInterface $eventDispatcher + * @param ShopContext $shopContext + */ + public function __construct( + CheckoutHttpClient $httpClient, + EventDispatcherInterface $eventDispatcher, + ShopContext $shopContext + ) { + $this->httpClient = $httpClient; + $this->eventDispatcher = $eventDispatcher; + $this->shopContext = $shopContext; + } + + /** + * @param UpdatePayPalOrderCommand $command + * + * @return void + * + * @throws CartException|PayPalException|PayPalOrderException|PsCheckoutException|Exception + */ + public function handle(UpdatePayPalOrderCommand $command) + { + $cartPresenter = (new CartPresenter())->present(); + $builder = new OrderPayloadBuilder($cartPresenter, true); + $builder->setIsUpdate(true); + $builder->setPaypalOrderId($command->getPayPalOrderId()->getValue()); + $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setExpressCheckout($command->isExpressCheckout()); + + if ($this->shopContext->isShop17()) { + // Build full payload in 1.7 + $builder->buildFullPayload(); + } else { + // if on 1.6 always build minimal payload + $builder->buildMinimalPayload(); + } + + $response = $this->httpClient->updateOrder($builder->presentPayload()->getArray()); + $order = json_decode($response->getBody(), true); + + $this->eventDispatcher->dispatch(new PayPalOrderUpdatedEvent( + $order['id'], + $order, + $command->getCartId()->getValue(), + $command->isHostedFields(), + $command->isExpressCheckout(), + $command->getFundingSource() + )); + } +} diff --git a/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php b/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php index 375348587..53c1e46ee 100644 --- a/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php +++ b/src/PayPal/Order/Event/PayPalOrderCreatedEvent.php @@ -20,6 +20,81 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; + class PayPalOrderCreatedEvent extends PayPalOrderEvent { + /** + * @var CartId + */ + private $cartId; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var string + */ + private $fundingSource; + + /** + * @param string $orderPayPalId + * @param array $orderPayPal + * @param int $cartId + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * @param string $fundingSource + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($orderPayPalId, $orderPayPal, $cartId, $isHostedFields, $isExpressCheckout, $fundingSource) + { + parent::__construct($orderPayPalId, $orderPayPal); + $this->cartId = new CartId($cartId); + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + $this->fundingSource = $fundingSource; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } } diff --git a/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php b/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php new file mode 100644 index 000000000..cc2f43dbf --- /dev/null +++ b/src/PayPal/Order/Event/PayPalOrderUpdatedEvent.php @@ -0,0 +1,100 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; + +class PayPalOrderUpdatedEvent extends PayPalOrderEvent +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @var bool + */ + private $isHostedFields; + + /** + * @var bool + */ + private $isExpressCheckout; + + /** + * @var string + */ + private $fundingSource; + + /** + * @param string $orderPayPalId + * @param array $orderPayPal + * @param int $cartId + * @param bool $isHostedFields + * @param bool $isExpressCheckout + * @param string $fundingSource + * + * @throws CartException + * @throws PayPalOrderException + */ + public function __construct($orderPayPalId, $orderPayPal, $cartId, $isHostedFields, $isExpressCheckout, $fundingSource) + { + parent::__construct($orderPayPalId, $orderPayPal); + $this->cartId = new CartId($cartId); + $this->isHostedFields = $isHostedFields; + $this->isExpressCheckout = $isExpressCheckout; + $this->fundingSource = $fundingSource; + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } + + /** + * @return bool + */ + public function isHostedFields() + { + return $this->isHostedFields; + } + + /** + * @return bool + */ + public function isExpressCheckout() + { + return $this->isExpressCheckout; + } + + /** + * @return string + */ + public function getFundingSource() + { + return $this->fundingSource; + } +} diff --git a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php index 5cf29b9a9..a4d5328c7 100644 --- a/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php +++ b/src/PayPal/Order/EventSubscriber/PayPalOrderEventSubscriber.php @@ -22,6 +22,8 @@ namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\EventSubscriber; use PrestaShop\Module\PrestashopCheckout\Checkout\CheckoutChecker; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SaveCheckoutCommand; +use PrestaShop\Module\PrestashopCheckout\Checkout\Command\SavePayPalOrderStatusCommand; use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PsCheckoutException; use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; @@ -32,13 +34,14 @@ use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\CheckTransitionPayPalOrderStatusService; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\CapturePayPalOrderCommand; -use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Command\SavePayPalOrderCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovalReversedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderApprovedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCompletedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderCreatedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Event\PayPalOrderUpdatedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\PayPalOrderStatus; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; use Ps_checkout; use Psr\SimpleCache\CacheInterface; @@ -122,25 +125,27 @@ public static function getSubscribedEvents() ['setApprovalReversedOrderStatus'], ['clearCache'], ], + PayPalOrderUpdatedEvent::class => [ + ['clearCache'], + ], ]; } public function saveCreatedPayPalOrder(PayPalOrderCreatedEvent $event) { - $psCheckoutCart = $this->psCheckoutCartRepository->findOneByPayPalOrderId($event->getOrderPayPalId()->getValue()); - - if (false === $psCheckoutCart) { - throw new PsCheckoutException(sprintf('PayPal Order %s is not linked to a cart', $event->getOrderPayPalId()->getValue()), PsCheckoutException::PRESTASHOP_CART_NOT_FOUND); - } - - if (!$this->checkTransitionPayPalOrderStatusService->checkAvailableStatus($psCheckoutCart->getPaypalStatus(), PayPalOrderStatus::CREATED)) { - return; - } + /** @var PayPalConfiguration $configuration */ + $configuration = $this->module->getService('ps_checkout.paypal.configuration'); + $order = $event->getOrderPayPal(); - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SaveCheckoutCommand( + $event->getCartId()->getValue(), $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::CREATED, - $event->getOrderPayPal() + $order['status'], + $order['intent'], + $event->getFundingSource(), + $event->isExpressCheckout(), + $event->isHostedFields(), + $configuration->getPaymentMode() )); } @@ -156,10 +161,9 @@ public function saveApprovedPayPalOrder(PayPalOrderApprovedEvent $event) return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::APPROVED, - $event->getOrderPayPal() + PayPalOrderStatus::APPROVED )); } @@ -175,10 +179,9 @@ public function saveCompletedPayPalOrder(PayPalOrderCompletedEvent $event) return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::COMPLETED, - $event->getOrderPayPal() + PayPalOrderStatus::COMPLETED )); } @@ -194,10 +197,9 @@ public function saveApprovalReversedPayPalOrder(PayPalOrderApprovalReversedEvent return; } - $this->commandBus->handle(new SavePayPalOrderCommand( + $this->commandBus->handle(new SavePayPalOrderStatusCommand( $event->getOrderPayPalId()->getValue(), - PayPalOrderStatus::REVERSED, - $event->getOrderPayPal() + PayPalOrderStatus::REVERSED )); } @@ -265,7 +267,7 @@ public function updateCache(PayPalOrderEvent $event) $this->orderPayPalCache->set($event->getOrderPayPalId()->getValue(), $newOrderPayPal); } - public function clearCache(PayPalOrderApprovalReversedEvent $event) + public function clearCache(PayPalOrderEvent $event) { $this->orderPayPalCache->delete($event->getOrderPayPalId()->getValue()); } diff --git a/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php new file mode 100644 index 000000000..345fd6ac2 --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQuery.php @@ -0,0 +1,25 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForAdminViewQuery +{ +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php new file mode 100644 index 000000000..9ee9a9a3b --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForAdminViewQueryResult.php @@ -0,0 +1,25 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForAdminViewQueryResult +{ +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php new file mode 100644 index 000000000..c9232288d --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQuery.php @@ -0,0 +1,50 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +use PrestaShop\Module\PrestashopCheckout\Cart\Exception\CartException; +use PrestaShop\Module\PrestashopCheckout\Cart\ValueObject\CartId; + +class GetPayPalOrderForCartIdQuery +{ + /** + * @var CartId + */ + private $cartId; + + /** + * @param int $cartId + * + * @throws CartException + */ + public function __construct($cartId) + { + $this->cartId = new CartId($cartId); + } + + /** + * @return CartId + */ + public function getCartId() + { + return $this->cartId; + } +} diff --git a/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php new file mode 100644 index 000000000..ea663dbf4 --- /dev/null +++ b/src/PayPal/Order/Query/GetPayPalOrderForCartIdQueryResult.php @@ -0,0 +1,45 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query; + +class GetPayPalOrderForCartIdQueryResult +{ + /** + * @var array + */ + private $order; + + /** + * @param array{id: string, status: string, intent: string, payment_source: array, purchase_units: array, payer: array, create_time: string, links: array} $order + */ + public function __construct(array $order) + { + $this->order = $order; + } + + /** + * @return array{id: string, status: string, intent: string, payment_source: array, purchase_units: array, payer: array, create_time: string, links: array} + */ + public function getOrder() + { + return $this->order; + } +} diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php new file mode 100644 index 000000000..c41d2f7a4 --- /dev/null +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForAdminViewQueryHandler.php @@ -0,0 +1,32 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForAdminViewQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForAdminViewQueryResult; + +class GetPayPalOrderForAdminViewQueryHandler +{ + public function handle(GetPayPalOrderForAdminViewQuery $query) + { + return new GetPayPalOrderForAdminViewQueryResult(); + } +} diff --git a/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php new file mode 100644 index 000000000..256e36a68 --- /dev/null +++ b/src/PayPal/Order/QueryHandler/GetPayPalOrderForCartIdQueryHandler.php @@ -0,0 +1,66 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Order\QueryHandler; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQuery; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Query\GetPayPalOrderForCartIdQueryResult; +use PrestaShop\Module\PrestashopCheckout\Repository\PsCheckoutCartRepository; +use Psr\SimpleCache\CacheInterface; + +class GetPayPalOrderForCartIdQueryHandler +{ + /** + * @var CacheInterface + */ + private $orderPayPalCache; + /** + * @var PsCheckoutCartRepository + */ + private $checkoutCartRepository; + + public function __construct(CacheInterface $orderPayPalCache, PsCheckoutCartRepository $checkoutCartRepository) + { + $this->orderPayPalCache = $orderPayPalCache; + $this->checkoutCartRepository = $checkoutCartRepository; + } + + /** + * @param GetPayPalOrderForCartIdQuery $getPayPalOrderQuery + * + * @return GetPayPalOrderForCartIdQueryResult + * + * @throws PayPalOrderException + */ + public function handle(GetPayPalOrderForCartIdQuery $getPayPalOrderQuery) + { + $psCheckoutCart = $this->checkoutCartRepository->findOneByCartId($getPayPalOrderQuery->getCartId()->getValue()); + + /** @var array $order */ + $order = $this->orderPayPalCache->get($psCheckoutCart->getPaypalOrderId()); + + if (empty($order)) { + throw new PayPalOrderException('PayPal order not found', PayPalOrderException::CANNOT_RETRIEVE_ORDER); + } + + return new GetPayPalOrderForCartIdQueryResult($order); + } +} diff --git a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php index c4979cc41..eefa235de 100644 --- a/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php +++ b/src/PayPal/Payment/Capture/EventSubscriber/PayPalCaptureEventSubscriber.php @@ -31,8 +31,6 @@ use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentDeniedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentPendingQueryResult; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; -use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQuery; use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentReversedQueryResult; use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; @@ -42,7 +40,6 @@ use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureDeclinedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCapturePendingEvent; -use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureRefundedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event\PayPalCaptureReversedEvent; use Ps_checkout; use Psr\SimpleCache\CacheInterface; @@ -116,10 +113,6 @@ public static function getSubscribedEvents() ['setPaymentPendingOrderStatus'], ['updateCache'], ], - PayPalCaptureRefundedEvent::class => [ - ['setPaymentRefundedOrderStatus'], - ['updateCache'], - ], PayPalCaptureReversedEvent::class => [ ['setPaymentReversedOrderStatus'], ['updateCache'], @@ -216,30 +209,6 @@ public function setPaymentDeclinedOrderStatus(PayPalCaptureDeclinedEvent $event) $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_ERROR))); } - public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) - { - try { - /** @var GetOrderForPaymentRefundedQueryResult $order */ - $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); - } catch (OrderNotFoundException $exception) { - return; - } - - if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { - return; - } - - $capture = $event->getCapture(); - // In case there no OrderSlip for this refund, we use the refund amount from payload - $totalRefunded = $order->getTotalRefund() ? $order->getTotalRefund() : $capture['amount']['value']; - - if ($this->checkOrderAmount->checkAmount($order->getTotalAmount(), $totalRefunded) === CheckOrderAmount::ORDER_NOT_FULL_PAID) { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED))); - } else { - $this->commandBus->handle(new UpdateOrderStatusCommand($order->getOrderId()->getValue(), $this->orderStateMapper->getIdByKey(OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED))); - } - } - public function setPaymentReversedOrderStatus(PayPalCaptureReversedEvent $event) { try { diff --git a/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php b/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php new file mode 100644 index 000000000..8e2ec5452 --- /dev/null +++ b/src/PayPal/Payment/Refund/Command/RefundPayPalCaptureCommand.php @@ -0,0 +1,109 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command; + +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundException; +use Validate; + +class RefundPayPalCaptureCommand +{ + /** + * @var string + */ + private $orderPayPalId; + /** + * @var string + */ + private $captureId; + /** + * @var string + */ + private $currencyCode; + /** + * @var string + */ + private $amount; + + /** + * @param string $orderPayPalId + * @param string $captureId + * @param string $currencyCode + * @param mixed $amount + * + * @throws PayPalRefundException + */ + public function __construct($orderPayPalId, $captureId, $currencyCode, $amount) + { + if (empty($orderPayPalId) || !Validate::isGenericName($orderPayPalId)) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_ORDER_ID); + } + + if (empty($captureId) || !Validate::isGenericName($captureId)) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_TRANSACTION_ID); + } + + // https://developer.paypal.com0/docs/api/reference/currency-codes/ + if (empty($currencyCode) || !in_array($currencyCode, ['AUD', 'BRL', 'CAD', 'CZK', 'DKK', 'EUR', 'HKD', 'HUF', 'INR', 'ILS', 'JPY', 'MYR', 'MXN', 'TWD', 'NZD', 'NOK', 'PHP', 'PLN', 'GBP', 'RUB', 'SGD', 'SEK', 'CHF', 'THB', 'USD'])) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_CURRENCY); + } + + if (empty($amount) || !Validate::isPrice($amount) || $amount <= 0) { + throw new PayPalRefundException('', PayPalRefundException::INVALID_AMOUNT); + } + + $this->orderPayPalId = $orderPayPalId; + $this->captureId = $captureId; + $this->currencyCode = $currencyCode; + $this->amount = $amount; + } + + /** + * @return string + */ + public function getOrderPayPalId() + { + return $this->orderPayPalId; + } + + /** + * @return string + */ + public function getCaptureId() + { + return $this->captureId; + } + + /** + * @return string + */ + public function getCurrencyCode() + { + return $this->currencyCode; + } + + /** + * @return mixed + */ + public function getAmount() + { + return $this->amount; + } +} diff --git a/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php new file mode 100644 index 000000000..291984583 --- /dev/null +++ b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php @@ -0,0 +1,123 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler; + +use PrestaShop\Module\PrestashopCheckout\Configuration\PrestaShopConfiguration; +use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; +use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundFailedException; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; +use Psr\SimpleCache\CacheInterface; + +class RefundPayPalCaptureCommandHandler +{ + /** + * @var CheckoutHttpClient + */ + private $checkoutHttpClient; + /** + * @var PayPalConfiguration + */ + private $payPalConfiguration; + /** + * @var PrestaShopConfiguration + */ + private $prestaShopConfiguration; + /** + * @var PrestaShopContext + */ + private $prestaShopContext; + /** + * @var EventDispatcherInterface + */ + private $eventDispatcher; + /** + * @var CacheInterface + */ + private $orderPayPalCache; + + public function __construct( + CheckoutHttpClient $checkoutHttpClient, + PayPalConfiguration $payPalConfiguration, + PrestaShopConfiguration $prestaShopConfiguration, + PrestaShopContext $prestaShopContext, + EventDispatcherInterface $eventDispatcher, + CacheInterface $orderPayPalCache + ) { + $this->checkoutHttpClient = $checkoutHttpClient; + $this->payPalConfiguration = $payPalConfiguration; + $this->prestaShopConfiguration = $prestaShopConfiguration; + $this->prestaShopContext = $prestaShopContext; + $this->eventDispatcher = $eventDispatcher; + $this->orderPayPalCache = $orderPayPalCache; + } + + /** + * @param RefundPayPalCaptureCommand $command + * + * @throws PayPalException + * @throws PayPalRefundFailedException + * @throws PayPalOrderException + */ + public function handle(RefundPayPalCaptureCommand $command) + { + $response = $this->checkoutHttpClient->refundOrder([ + 'orderId' => $command->getOrderPayPalId(), + 'captureId' => $command->getCaptureId(), + 'payee' => [ + 'merchant_id' => $this->payPalConfiguration->getMerchantId(), + ], + 'amount' => [ + 'currency_code' => $command->getCurrencyCode(), + 'value' => $command->getAmount(), + ], + 'note_to_payer' => 'Refund by ' + . $this->prestaShopConfiguration->get( + 'PS_SHOP_NAME', + ['id_shop' => $this->prestaShopContext->getShopId()] + ), + ]); + $responseHandler = new ResponseApiHandler(); + $response = $responseHandler->handleResponse($response); + + if (isset($response['httpCode']) && $response['httpCode'] === 200) { + if ($this->orderPayPalCache->has($command->getOrderPayPalId())) { + $this->orderPayPalCache->delete($command->getOrderPayPalId()); + } + } else { + throw new PayPalRefundFailedException('', isset($response['httpCode']) ? $response['httpCode'] : 500); + } + + $this->eventDispatcher->dispatch( + new PayPalCaptureRefundedEvent( + $response['body']['id'], + $command->getOrderPayPalId(), + $response['body'] + ) + ); + } +} diff --git a/src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php b/src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php similarity index 85% rename from src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php rename to src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php index a281444a8..c7e010544 100644 --- a/src/PayPal/Payment/Capture/Event/PayPalCaptureRefundedEvent.php +++ b/src/PayPal/Payment/Refund/Event/PayPalCaptureRefundedEvent.php @@ -18,8 +18,8 @@ * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 */ -namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\Event; +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event; -class PayPalCaptureRefundedEvent extends PayPalCaptureEvent +class PayPalCaptureRefundedEvent extends PayPalRefundEvent { } diff --git a/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php b/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php new file mode 100644 index 000000000..5f67c1954 --- /dev/null +++ b/src/PayPal/Payment/Refund/Event/PayPalRefundEvent.php @@ -0,0 +1,82 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event; + +use PrestaShop\Module\PrestashopCheckout\Event\Event; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; +use PrestaShop\Module\PrestashopCheckout\PayPal\Order\ValueObject\PayPalOrderId; + +abstract class PayPalRefundEvent extends Event +{ + /** + * @var string + */ + private $refundId; + + /** + * @var PayPalOrderId + */ + private $paypalOrderId; + + /** + * @var array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} + */ + private $refund; + + /** + * @param string $refundId + * @param string $paypalOrderId + * @param array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} $refund + * + * @throws PayPalOrderException + */ + public function __construct($refundId, $paypalOrderId, array $refund) + { + $this->refundId = $refundId; + $this->paypalOrderId = new PayPalOrderId($paypalOrderId); + $this->refund = $refund; + } + + /** + * @return string + */ + public function getPayPalRefundId() + { + return $this->refundId; + } + + /** + * @return PayPalOrderId + */ + public function getPayPalOrderId() + { + return $this->paypalOrderId; + } + + /** + * @return array{id: string, status: string, amount: array, create_time: string, update_time: string, custom_id: string, note_to_payer: string, payer: array, seller_payable_breakdown: array, links: array} + */ + public function getRefund() + { + return $this->refund; + } +} diff --git a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php new file mode 100644 index 000000000..2c9fdeeec --- /dev/null +++ b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php @@ -0,0 +1,161 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\EventSubscriber; + +use PrestaShop\Module\PrestashopCheckout\CommandBus\CommandBusInterface; +use PrestaShop\Module\PrestashopCheckout\Order\Command\UpdateOrderStatusCommand; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderNotFoundException; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQuery; +use PrestaShop\Module\PrestashopCheckout\Order\Query\GetOrderForPaymentRefundedQueryResult; +use PrestaShop\Module\PrestashopCheckout\Order\Service\CheckOrderAmount; +use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateConfigurationKeys; +use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalRefundEvent; +use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalOrderProvider; +use Ps_checkout; +use Psr\SimpleCache\CacheInterface; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; + +class PayPalRefundEventSubscriber implements EventSubscriberInterface +{ + /** + * @var Ps_checkout + */ + private $module; + + /** + * @var CheckOrderAmount + */ + private $checkOrderAmount; + + /** + * @var CommandBusInterface + */ + private $commandBus; + + /** + * @var CacheInterface + */ + private $capturePayPalCache; + + /** + * @var CacheInterface + */ + private $orderPayPalCache; + + /** + * @var OrderStateMapper + */ + private $orderStateMapper; + /** + * @var PayPalOrderProvider + */ + private $orderProvider; + + public function __construct( + Ps_checkout $module, + CheckOrderAmount $checkOrderAmount, + CacheInterface $capturePayPalCache, + CacheInterface $orderPayPalCache, + OrderStateMapper $orderStateMapper, + PayPalOrderProvider $orderProvider + ) { + $this->module = $module; + $this->checkOrderAmount = $checkOrderAmount; + $this->commandBus = $this->module->getService('ps_checkout.bus.command'); + $this->capturePayPalCache = $capturePayPalCache; + $this->orderPayPalCache = $orderPayPalCache; + $this->orderStateMapper = $orderStateMapper; + $this->orderProvider = $orderProvider; + } + + /** + * {@inheritdoc} + */ + public static function getSubscribedEvents() + { + return [ + PayPalCaptureRefundedEvent::class => [ + ['setPaymentRefundedOrderStatus'], + ['updateCache'], + ], + ]; + } + + public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) + { + try { + /** @var GetOrderForPaymentRefundedQueryResult $order */ + $order = $this->commandBus->handle(new GetOrderForPaymentRefundedQuery($event->getPayPalOrderId()->getValue())); + } catch (OrderNotFoundException $exception) { + return; + } + + if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { + return; + } + + $orderPayPal = $this->orderProvider->getById($event->getPayPalOrderId()->getValue()); + + if (empty($orderPayPal['purchase_units'][0]['payments']['refunds'])) { + return; + } + + $totalRefunded = array_reduce($orderPayPal['purchase_units'][0]['payments']['refunds'], function ($totalRefunded, $refund) { + return $totalRefunded + (float) $refund['amount']['value']; + }); + + $orderFullyRefunded = (float) $order->getTotalAmount() <= (float) $totalRefunded; + + $this->commandBus->handle( + new UpdateOrderStatusCommand( + $order->getOrderId()->getValue(), + $this->orderStateMapper->getIdByKey($orderFullyRefunded ? OrderStateConfigurationKeys::PS_CHECKOUT_STATE_REFUNDED : OrderStateConfigurationKeys::PS_CHECKOUT_STATE_PARTIALLY_REFUNDED) + ) + ); + } + + public function updateCache(PayPalRefundEvent $event) + { + if ($this->orderPayPalCache->has($event->getPayPalOrderId()->getValue())) { + $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); + } +// $this->capturePayPalCache->set($event->getPayPalCaptureId()->getValue(), $event->getCapture()); +// +// $needToClearOrderPayPalCache = true; +// $orderPayPalCache = $this->orderPayPalCache->get($event->getPayPalOrderId()->getValue()); +// +// if ($orderPayPalCache && isset($orderPayPalCache['purchase_units'][0]['payments']['captures'])) { +// foreach ($orderPayPalCache['purchase_units'][0]['payments']['captures'] as $key => $capture) { +// if ($capture['id'] === $event->getPayPalCaptureId()->getValue()) { +// $needToClearOrderPayPalCache = false; +// $orderPayPalCache['purchase_units'][0]['payments']['captures'][$key] = $event->getCapture(); +// $this->orderPayPalCache->set($event->getPayPalOrderId()->getValue(), $orderPayPalCache); +// } +// } +// } +// +// if ($needToClearOrderPayPalCache) { +// $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); +// } + } +} diff --git a/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php b/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php new file mode 100644 index 000000000..ee2efdede --- /dev/null +++ b/src/PayPal/Payment/Refund/Exception/PayPalRefundException.php @@ -0,0 +1,32 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception; + +use Exception; + +class PayPalRefundException extends Exception +{ + const INVALID_ORDER_ID = 1; + const INVALID_TRANSACTION_ID = 2; + const INVALID_CURRENCY = 3; + const INVALID_AMOUNT = 4; + const INVALID_REFUND_ID = 4; +} diff --git a/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php b/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php new file mode 100644 index 000000000..c037a864f --- /dev/null +++ b/src/PayPal/Payment/Refund/Exception/PayPalRefundFailedException.php @@ -0,0 +1,27 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception; + +use Exception; + +class PayPalRefundFailedException extends Exception +{ +} diff --git a/src/PayPalError.php b/src/PayPalError.php index 7d30d09c1..643346fa2 100644 --- a/src/PayPalError.php +++ b/src/PayPalError.php @@ -20,6 +20,7 @@ namespace PrestaShop\Module\PrestashopCheckout; +use Http\Client\Exception\HttpException; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; class PayPalError @@ -38,275 +39,315 @@ public function __construct($message) } /** + * @param HttpException|null $previous + * * @throws PayPalException */ - public function throwException() + public function throwException(HttpException $previous = null) { switch ($this->message) { case 'ACTION_DOES_NOT_MATCH_INTENT': - throw new PayPalException('Order was created with an intent to CAPTURE, to complete the transaction, call capture payment for order or create an order with an intent of AUTHORIZE.', PayPalException::ACTION_DOES_NOT_MATCH_INTENT); + throw new PayPalException('Order was created with an intent to CAPTURE, to complete the transaction, call capture payment for order or create an order with an intent of AUTHORIZE.', PayPalException::ACTION_DOES_NOT_MATCH_INTENT, $previous); case 'AGREEMENT_ALREADY_CANCELLED': - throw new PayPalException('The requested agreement is already cancelled, the specified agreement ID cannot be used for this transaction.', PayPalException::AGREEMENT_ALREADY_CANCELLED); + throw new PayPalException('The requested agreement is already cancelled, the specified agreement ID cannot be used for this transaction.', PayPalException::AGREEMENT_ALREADY_CANCELLED, $previous); case 'AMOUNT_CANNOT_BE_SPECIFIED': - throw new PayPalException('An authorization amount can only be specified if an order was saved. Save the order and try again.', PayPalException::AMOUNT_CANNOT_BE_SPECIFIED); + throw new PayPalException('An authorization amount can only be specified if an order was saved. Save the order and try again.', PayPalException::AMOUNT_CANNOT_BE_SPECIFIED, $previous); case 'AMOUNT_MISMATCH': - throw new PayPalException('The amount specified does not match the breakdown : amount must equal item_total + tax_total + shipping + handling + insurance - shipping_discount - discount.', PayPalException::AMOUNT_MISMATCH); + throw new PayPalException('The amount specified does not match the breakdown : amount must equal item_total + tax_total + shipping + handling + insurance - shipping_discount - discount.', PayPalException::AMOUNT_MISMATCH, $previous); case 'AMOUNT_NOT_PATCHABLE': - throw new PayPalException('The amount cannot be updated as the payer has chosen and approved a specific financing offer for a given amount. Create an order with the updated order amount and have the payer approve the new payment terms.', PayPalException::AMOUNT_NOT_PATCHABLE); + throw new PayPalException('The amount cannot be updated as the payer has chosen and approved a specific financing offer for a given amount. Create an order with the updated order amount and have the payer approve the new payment terms.', PayPalException::AMOUNT_NOT_PATCHABLE, $previous); case 'AUTH_CAPTURE_NOT_ENABLED': - throw new PayPalException('The authorization and capture feature is not enabled for the merchant. Make sure that the recipient of the funds is a verified business account.', PayPalException::AUTH_CAPTURE_NOT_ENABLED); + throw new PayPalException('The authorization and capture feature is not enabled for the merchant. Make sure that the recipient of the funds is a verified business account.', PayPalException::AUTH_CAPTURE_NOT_ENABLED, $previous); case 'AUTHENTICATION_FAILURE': - throw new PayPalException('The account validations failed for the user.', PayPalException::AUTHENTICATION_FAILURE); + throw new PayPalException('The account validations failed for the user.', PayPalException::AUTHENTICATION_FAILURE, $previous); case 'AUTHORIZATION_AMOUNT_EXCEEDED': - throw new PayPalException('The currency of the authorization must match the currency of the order that the payer created and approved. Check the currency_code and try the request again.', PayPalException::AUTHORIZATION_AMOUNT_EXCEEDED); + throw new PayPalException('The currency of the authorization must match the currency of the order that the payer created and approved. Check the currency_code and try the request again.', PayPalException::AUTHORIZATION_AMOUNT_EXCEEDED, $previous); case 'BILLING_AGREEMENT_NOT_FOUND': - throw new PayPalException('The requested Billing Agreement token was not found. Verify the token and try the request again.', PayPalException::BILLING_AGREEMENT_NOT_FOUND); + throw new PayPalException('The requested Billing Agreement token was not found. Verify the token and try the request again.', PayPalException::BILLING_AGREEMENT_NOT_FOUND, $previous); case 'CANNOT_BE_NEGATIVE': - throw new PayPalException('Must be greater than or equal to zero. Try the request again with a different value.', PayPalException::CANNOT_BE_NEGATIVE); + throw new PayPalException('Must be greater than or equal to zero. Try the request again with a different value.', PayPalException::CANNOT_BE_NEGATIVE, $previous); case 'CANNOT_BE_ZERO_OR_NEGATIVE': - throw new PayPalException('Must be greater than zero. Try the request again with a different value.', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE); + throw new PayPalException('Must be greater than zero. Try the request again with a different value.', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE, $previous); case 'CARD_TYPE_NOT_SUPPORTED': - throw new PayPalException('Processing of this card type is not supported. Use another card type.', PayPalException::CARD_TYPE_NOT_SUPPORTED); + throw new PayPalException('Processing of this card type is not supported. Use another card type.', PayPalException::CARD_TYPE_NOT_SUPPORTED, $previous); case 'INVALID_SECURITY_CODE_LENGTH': - throw new PayPalException('The security_code length is invalid for the specified card type.', PayPalException::INVALID_SECURITY_CODE_LENGTH); + throw new PayPalException('The security_code length is invalid for the specified card type.', PayPalException::INVALID_SECURITY_CODE_LENGTH, $previous); case 'CITY_REQUIRED': - throw new PayPalException('The specified country requires a city (address.admin_area_2). Specify a city and try the request again.', PayPalException::CITY_REQUIRED); + throw new PayPalException('The specified country requires a city (address.admin_area_2). Specify a city and try the request again.', PayPalException::CITY_REQUIRED, $previous); case 'COMPLIANCE_VIOLATION': - throw new PayPalException('Transaction cannot be processed due to a possible compliance violation. To get more information about the transaction, call Customer Support.', PayPalException::COMPLIANCE_VIOLATION); + throw new PayPalException('Transaction cannot be processed due to a possible compliance violation. To get more information about the transaction, call Customer Support.', PayPalException::COMPLIANCE_VIOLATION, $previous); case 'CONSENT_NEEDED': - throw new PayPalException('Authorization failed due to insufficient permissions. To continue with this transaction, the payer must provide consent.', PayPalException::CONSENT_NEEDED); + throw new PayPalException('Authorization failed due to insufficient permissions. To continue with this transaction, the payer must provide consent.', PayPalException::CONSENT_NEEDED, $previous); case 'CURRENCY_NOT_SUPPORTED_FOR_COUNTRY': - throw new PayPalException('Currency code not supported for direct card payments in this country.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_COUNTRY); + throw new PayPalException('Currency code not supported for direct card payments in this country.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_COUNTRY, $previous); case 'CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE': - throw new PayPalException('The currency code is not supported for direct card payments for this card type.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE); + throw new PayPalException('The currency code is not supported for direct card payments for this card type.', PayPalException::CURRENCY_NOT_SUPPORTED_FOR_CARD_TYPE, $previous); case 'DECIMAL_PRECISION': - throw new PayPalException('The value of the field should not be more than two decimal places. Verify the number of decimal places and try the request again.', PayPalException::DECIMAL_PRECISION); + throw new PayPalException('The value of the field should not be more than two decimal places. Verify the number of decimal places and try the request again.', PayPalException::DECIMAL_PRECISION, $previous); case 'DOMESTIC_TRANSACTION_REQUIRED': - throw new PayPalException('This transaction requires the payee and payer to be resident in the same country. To create this payment, a domestic transaction is required.', PayPalException::DOMESTIC_TRANSACTION_REQUIRED); + throw new PayPalException('This transaction requires the payee and payer to be resident in the same country. To create this payment, a domestic transaction is required.', PayPalException::DOMESTIC_TRANSACTION_REQUIRED, $previous); case 'DUPLICATE_INVOICE_ID': - throw new PayPalException('Duplicate Invoice ID detected. To avoid a duplicate transaction, verify that the invoice ID is unique for each transaction.', PayPalException::DUPLICATE_INVOICE_ID); + throw new PayPalException('Duplicate Invoice ID detected. To avoid a duplicate transaction, verify that the invoice ID is unique for each transaction.', PayPalException::DUPLICATE_INVOICE_ID, $previous); case 'DUPLICATE_REQUEST_ID': - throw new PayPalException('The value of PayPal-Request-Id header has already been used. Specify a different value and try the request again.', PayPalException::DUPLICATE_REQUEST_ID); + throw new PayPalException('The value of PayPal-Request-Id header has already been used. Specify a different value and try the request again.', PayPalException::DUPLICATE_REQUEST_ID, $previous); case 'FIELD_NOT_PATCHABLE': - throw new PayPalException('Field cannot be patched. You cannot update this field.', PayPalException::FIELD_NOT_PATCHABLE); + throw new PayPalException('Field cannot be patched. You cannot update this field.', PayPalException::FIELD_NOT_PATCHABLE, $previous); case 'INSTRUMENT_DECLINED': - throw new PayPalException('The funding instrument presented was either declined by the processor or bank. The specified funding instrument cannot be used for this payment.', PayPalException::INSTRUMENT_DECLINED); + throw new PayPalException('The funding instrument presented was either declined by the processor or bank. The specified funding instrument cannot be used for this payment.', PayPalException::INSTRUMENT_DECLINED, $previous); case 'INTERNAL_SERVER_ERROR': - throw new PayPalException('An internal server error has occurred. Retry the request later.', PayPalException::INTERNAL_SERVER_ERROR); + throw new PayPalException('An internal server error has occurred. Retry the request later.', PayPalException::INTERNAL_SERVER_ERROR, $previous); case 'INTERNAL_SERVICE_ERROR': - throw new PayPalException('An internal service error has occurred.', PayPalException::INTERNAL_SERVICE_ERROR); + throw new PayPalException('An internal service error has occurred.', PayPalException::INTERNAL_SERVICE_ERROR, $previous); case 'INVALID_ACCOUNT_STATUS': - throw new PayPalException('Account validations failed for the user. To continue with this transaction, the payer must provide consent.', PayPalException::INVALID_ACCOUNT_STATUS); + throw new PayPalException('Account validations failed for the user. To continue with this transaction, the payer must provide consent.', PayPalException::INVALID_ACCOUNT_STATUS, $previous); case 'INVALID_ARRAY_MAX_ITEMS': - throw new PayPalException('The number of items in an array parameter is too large.', PayPalException::INVALID_ARRAY_MAX_ITEMS); + throw new PayPalException('The number of items in an array parameter is too large.', PayPalException::INVALID_ARRAY_MAX_ITEMS, $previous); case 'INVALID_ARRAY_MIN_ITEMS': - throw new PayPalException('The number of items in an array parameter is too small.', PayPalException::INVALID_ARRAY_MIN_ITEMS); + throw new PayPalException('The number of items in an array parameter is too small.', PayPalException::INVALID_ARRAY_MIN_ITEMS, $previous); case 'INVALID_COUNTRY_CODE': - throw new PayPalException('Country code is invalid.', PayPalException::INVALID_COUNTRY_CODE); + throw new PayPalException('Country code is invalid.', PayPalException::INVALID_COUNTRY_CODE, $previous); case 'INVALID_CURRENCY_CODE': - throw new PayPalException('Currency code is invalid or is not currently supported.', PayPalException::INVALID_CURRENCY_CODE); + throw new PayPalException('Currency code is invalid or is not currently supported.', PayPalException::INVALID_CURRENCY_CODE, $previous); case 'INVALID_JSON_POINTER_FORMAT': - throw new PayPalException('Path should be a valid JavaScript Object Notation (JSON) Pointer that references a location within the request where the operation is performed. The path is not valid.', PayPalException::INVALID_JSON_POINTER_FORMAT); + throw new PayPalException('Path should be a valid JavaScript Object Notation (JSON) Pointer that references a location within the request where the operation is performed. The path is not valid.', PayPalException::INVALID_JSON_POINTER_FORMAT, $previous); case 'INVALID_PARAMETER_SYNTAX': - throw new PayPalException('The value of a field does not conform to the expected format. Verify that the pattern is supported and try the request again.', PayPalException::INVALID_PARAMETER_SYNTAX); + throw new PayPalException('The value of a field does not conform to the expected format. Verify that the pattern is supported and try the request again.', PayPalException::INVALID_PARAMETER_SYNTAX, $previous); case 'INVALID_PARAMETER_VALUE': - throw new PayPalException('The value of a field is invalid. Verify the parameter value and try the request again.', PayPalException::INVALID_PARAMETER_VALUE); + throw new PayPalException('The value of a field is invalid. Verify the parameter value and try the request again.', PayPalException::INVALID_PARAMETER_VALUE, $previous); case 'INVALID_PARAMETER': - throw new PayPalException('Cannot be specified as part of the request. Check that the API supports this parameter and try the request again.', PayPalException::INVALID_PARAMETER); + throw new PayPalException('Cannot be specified as part of the request. Check that the API supports this parameter and try the request again.', PayPalException::INVALID_PARAMETER, $previous); case 'INVALID_PATCH_OPERATION': - throw new PayPalException('Request is not well-formed, syntactically incorrect, or violates schema. The operation cannot be honored. You cannot add a property that is already present. Instead, use replace. You cannot remove a property that is not present. Instead, use add. You cannot replace a property that is not present. Instead, use add.', PayPalException::INVALID_PATCH_OPERATION); + throw new PayPalException('Request is not well-formed, syntactically incorrect, or violates schema. The operation cannot be honored. You cannot add a property that is already present. Instead, use replace. You cannot remove a property that is not present. Instead, use add. You cannot replace a property that is not present. Instead, use add.', PayPalException::INVALID_PATCH_OPERATION, $previous); case 'INVALID_PAYER_ID': - throw new PayPalException('The payer ID is not valid. Verify the payer ID and try the request again.', PayPalException::INVALID_PAYER_ID); + throw new PayPalException('The payer ID is not valid. Verify the payer ID and try the request again.', PayPalException::INVALID_PAYER_ID, $previous); case 'INVALID_RESOURCE_ID': - throw new PayPalException('Specified resource ID does not exist. Verify the resource ID and try the request again.', PayPalException::INVALID_RESOURCE_ID); + throw new PayPalException('Specified resource ID does not exist. Verify the resource ID and try the request again.', PayPalException::INVALID_RESOURCE_ID, $previous); case 'INVALID_STRING_LENGTH': - throw new PayPalException('The value of a field is either too short or too long. Verify the minimum and maximum values and try the request again.', PayPalException::INVALID_STRING_LENGTH); + throw new PayPalException('The value of a field is either too short or too long. Verify the minimum and maximum values and try the request again.', PayPalException::INVALID_STRING_LENGTH, $previous); case 'ITEM_TOTAL_MISMATCH': - throw new PayPalException('Verify the corresponding values and try the request again. The item total should equal the sum of (unit_amount * quantity) across all items for a purchase_unit.', PayPalException::ITEM_TOTAL_MISMATCH); + throw new PayPalException('Verify the corresponding values and try the request again. The item total should equal the sum of (unit_amount * quantity) across all items for a purchase_unit.', PayPalException::ITEM_TOTAL_MISMATCH, $previous); case 'ITEM_TOTAL_REQUIRED': - throw new PayPalException('If item details are specified (items.unit_amount and items.quantity) corresponding amount.breakdown.item_total is required. The amount.breakdown.item_total value was not found.', PayPalException::ITEM_TOTAL_REQUIRED); + throw new PayPalException('If item details are specified (items.unit_amount and items.quantity) corresponding amount.breakdown.item_total is required. The amount.breakdown.item_total value was not found.', PayPalException::ITEM_TOTAL_REQUIRED, $previous); case 'MAX_AUTHORIZATION_COUNT_EXCEEDED': - throw new PayPalException('The maximum number of authorizations that are allowed for the order was reached. To increase your limit, contact Customer Support.', PayPalException::MAX_AUTHORIZATION_COUNT_EXCEEDED); + throw new PayPalException('The maximum number of authorizations that are allowed for the order was reached. To increase your limit, contact Customer Support.', PayPalException::MAX_AUTHORIZATION_COUNT_EXCEEDED, $previous); case 'MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED': - throw new PayPalException('You have exceeded the maximum number of payment attempts. To review the maximum number of payment attempts allowed and retry this transaction, call Customer Support.', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED); + throw new PayPalException('You have exceeded the maximum number of payment attempts. To review the maximum number of payment attempts allowed and retry this transaction, call Customer Support.', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED, $previous); case 'MAX_VALUE_EXCEEDED': - throw new PayPalException('Should be less than or equal to 9999999.99 ; try the request again with a different value.', PayPalException::MAX_VALUE_EXCEEDED); + throw new PayPalException('Should be less than or equal to 9999999.99 ; try the request again with a different value.', PayPalException::MAX_VALUE_EXCEEDED, $previous); case 'MISSING_REQUIRED_PARAMETER': - throw new PayPalException('A required field or parameter is missing. Verify that you have specified all required parameters and try the request again.', PayPalException::MISSING_REQUIRED_PARAMETER); + throw new PayPalException('A required field or parameter is missing. Verify that you have specified all required parameters and try the request again.', PayPalException::MISSING_REQUIRED_PARAMETER, $previous); case 'MISSING_SHIPPING_ADDRESS': - throw new PayPalException('The shipping address is required when shipping_preference=SET_PROVIDED_ADDRESS. Verify that you have provided the shipping address and try the request again.', PayPalException::MISSING_SHIPPING_ADDRESS); + throw new PayPalException('The shipping address is required when shipping_preference=SET_PROVIDED_ADDRESS. Verify that you have provided the shipping address and try the request again.', PayPalException::MISSING_SHIPPING_ADDRESS, $previous); case 'MULTI_CURRENCY_ORDER': - throw new PayPalException('Multiple differing values of currency_code are not supported. The entire order request must have the same currency code.', PayPalException::MULTI_CURRENCY_ORDER); + throw new PayPalException('Multiple differing values of currency_code are not supported. The entire order request must have the same currency code.', PayPalException::MULTI_CURRENCY_ORDER, $previous); case 'MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED': - throw new PayPalException('Multiple shipping addresses are not supported. Try the request again with the same shipping_address.', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED); + throw new PayPalException('Multiple shipping addresses are not supported. Try the request again with the same shipping_address.', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED, $previous); case 'MULTIPLE_SHIPPING_OPTION_SELECTED': - throw new PayPalException('Only one shipping.option can be set to selected = true.', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED); + throw new PayPalException('Only one shipping.option can be set to selected = true.', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED, $previous); case 'INVALID_PICKUP_ADDRESS': - throw new PayPalException('Invalid shipping address. If the \'shipping_option.type\' is set as \'PICKUP\' then the \'shipping_detail.name.full_name\' should start with \'S2S\' meaning Ship To Store. Example: \'S2S My Store\'.', PayPalException::INVALID_PICKUP_ADDRESS); + throw new PayPalException('Invalid shipping address. If the \'shipping_option.type\' is set as \'PICKUP\' then the \'shipping_detail.name.full_name\' should start with \'S2S\' meaning Ship To Store. Example: \'S2S My Store\'.', PayPalException::INVALID_PICKUP_ADDRESS, $previous); case 'NOT_AUTHORIZED': - throw new PayPalException('Authorization failed due to insufficient permissions. To check that your application has sufficient permissions, log in to the PayPal Developer Portal.', PayPalException::NOT_AUTHORIZED); + throw new PayPalException('Authorization failed due to insufficient permissions. To check that your application has sufficient permissions, log in to the PayPal Developer Portal.', PayPalException::NOT_AUTHORIZED, $previous); case 'NOT_ENABLED_FOR_CARD_PROCESSING': - throw new PayPalException('The request fails. The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING); + throw new PayPalException('The request fails. The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING, $previous); case 'NOT_PATCHABLE': - throw new PayPalException('Cannot be patched. You cannot update this field.', PayPalException::NOT_PATCHABLE); + throw new PayPalException('Cannot be patched. You cannot update this field.', PayPalException::NOT_PATCHABLE, $previous); case 'NOT_SUPPORTED': - throw new PayPalException('This field is not currently supported. Specify only supported parameters and try the request again.', PayPalException::NOT_SUPPORTED); + throw new PayPalException('This field is not currently supported. Specify only supported parameters and try the request again.', PayPalException::NOT_SUPPORTED, $previous); case 'ORDER_ALREADY_AUTHORIZED': - throw new PayPalException('Order already authorized. If intent=AUTHORIZE only one authorization per order is allowed. The order was already authorized and you can create only one authorization for an order.', PayPalException::ORDER_ALREADY_AUTHORIZED); + throw new PayPalException('Order already authorized. If intent=AUTHORIZE only one authorization per order is allowed. The order was already authorized and you can create only one authorization for an order.', PayPalException::ORDER_ALREADY_AUTHORIZED, $previous); case 'ORDER_ALREADY_CAPTURED': - throw new PayPalException('Order already captured. If intent=CAPTURE only one capture per order is allowed. The order was already captured and you can capture only one payment for an order.', PayPalException::ORDER_ALREADY_CAPTURED); + throw new PayPalException('Order already captured. If intent=CAPTURE only one capture per order is allowed. The order was already captured and you can capture only one payment for an order.', PayPalException::ORDER_ALREADY_CAPTURED, $previous); case 'ORDER_ALREADY_COMPLETED': - throw new PayPalException('The order cannot be patched after it is completed.', PayPalException::ORDER_ALREADY_COMPLETED); + throw new PayPalException('The order cannot be patched after it is completed.', PayPalException::ORDER_ALREADY_COMPLETED, $previous); case 'ORDER_CANNOT_BE_SAVED': - throw new PayPalException('The option to save an order is only available if the intent is AUTHORIZE and the processing_instruction is ORDER_SAVED_EXPLICITLY. Change the intent to AUTHORIZE and the processing_instruction to ORDER_SAVED_EXPLICITLY and try the request again.', PayPalException::ORDER_CANNOT_BE_SAVED); + throw new PayPalException('The option to save an order is only available if the intent is AUTHORIZE and the processing_instruction is ORDER_SAVED_EXPLICITLY. Change the intent to AUTHORIZE and the processing_instruction to ORDER_SAVED_EXPLICITLY and try the request again.', PayPalException::ORDER_CANNOT_BE_SAVED, $previous); case 'ORDER_COMPLETED_OR_VOIDED': - throw new PayPalException('Order is voided or completed and hence cannot be authorized.', PayPalException::ORDER_COMPLETED_OR_VOIDED); + throw new PayPalException('Order is voided or completed and hence cannot be authorized.', PayPalException::ORDER_COMPLETED_OR_VOIDED, $previous); case 'ORDER_EXPIRED': - throw new PayPalException('Order is expired and hence cannot be authorized. Please contact Customer Support if you need to increase your order validity period.', PayPalException::ORDER_EXPIRED); + throw new PayPalException('Order is expired and hence cannot be authorized. Please contact Customer Support if you need to increase your order validity period.', PayPalException::ORDER_EXPIRED, $previous); case 'ORDER_NOT_APPROVED': - throw new PayPalException('Payer has not yet approved the Order for payment. The payer has not yet approved payment for the order. Redirect the payer to the rel:approve URL that was returned in the HATEOAS links in the create order response or provide a valid payment_source in the request.', PayPalException::ORDER_NOT_APPROVED); + throw new PayPalException('Payer has not yet approved the Order for payment. The payer has not yet approved payment for the order. Redirect the payer to the rel:approve URL that was returned in the HATEOAS links in the create order response or provide a valid payment_source in the request.', PayPalException::ORDER_NOT_APPROVED, $previous); case 'ORDER_NOT_SAVED': - throw new PayPalException('Please save the order or alternately, If you do not intend to save the order, PATCH the order to update the value of processing_instruction to NO_INSTRUCTION.', PayPalException::ORDER_NOT_SAVED); + throw new PayPalException('Please save the order or alternately, If you do not intend to save the order, PATCH the order to update the value of processing_instruction to NO_INSTRUCTION.', PayPalException::ORDER_NOT_SAVED, $previous); case 'ORDER_PREVIOUSLY_VOIDED': - throw new PayPalException('This order has been previously voided and cannot be voided again. Verify the order id and try again.', PayPalException::ORDER_PREVIOUSLY_VOIDED); + throw new PayPalException('This order has been previously voided and cannot be voided again. Verify the order id and try again.', PayPalException::ORDER_PREVIOUSLY_VOIDED, $previous); case 'PARAMETER_VALUE_NOT_SUPPORTED': - throw new PayPalException('The value specified for this field is not currently supported. The specified parameter value is not supported.', PayPalException::PARAMETER_VALUE_NOT_SUPPORTED); + throw new PayPalException('The value specified for this field is not currently supported. The specified parameter value is not supported.', PayPalException::PARAMETER_VALUE_NOT_SUPPORTED, $previous); case 'PATCH_PATH_REQUIRED': - throw new PayPalException('Specify a path for the field for which the operation needs to be performed. To complete the operation for this field, specify a path for the field.', PayPalException::PATCH_PATH_REQUIRED); + throw new PayPalException('Specify a path for the field for which the operation needs to be performed. To complete the operation for this field, specify a path for the field.', PayPalException::PATCH_PATH_REQUIRED, $previous); case 'PATCH_VALUE_REQUIRED': - throw new PayPalException('Please specify a value to for the field that is being patched.', PayPalException::PATCH_VALUE_REQUIRED); + throw new PayPalException('Please specify a value to for the field that is being patched.', PayPalException::PATCH_VALUE_REQUIRED, $previous); case 'PAYEE_ACCOUNT_INVALID': - throw new PayPalException('Payee account specified is invalid. Please check the payee.email_address or payee.merchant_id specified and try again. Ensure that either payee.merchant_id or payee.email_address is specified. Specify either payee.merchant_id or payee.email_address.', PayPalException::PAYEE_ACCOUNT_INVALID); + throw new PayPalException('Payee account specified is invalid. Please check the payee.email_address or payee.merchant_id specified and try again. Ensure that either payee.merchant_id or payee.email_address is specified. Specify either payee.merchant_id or payee.email_address.', PayPalException::PAYEE_ACCOUNT_INVALID, $previous); case 'PAYEE_ACCOUNT_LOCKED_OR_CLOSED': - throw new PayPalException('Payee account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED); + throw new PayPalException('Payee account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED, $previous); case 'PAYEE_ACCOUNT_RESTRICTED': - throw new PayPalException('The merchant account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_RESTRICTED); + throw new PayPalException('The merchant account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYEE_ACCOUNT_RESTRICTED, $previous); case 'PAYEE_BLOCKED_TRANSACTION': - throw new PayPalException('The fraud settings for this seller are such that this payment cannot be executed. Verify the fraud settings. Then, retry the transaction.', PayPalException::PAYEE_BLOCKED_TRANSACTION); + throw new PayPalException('The fraud settings for this seller are such that this payment cannot be executed. Verify the fraud settings. Then, retry the transaction.', PayPalException::PAYEE_BLOCKED_TRANSACTION, $previous); case 'PAYER_ACCOUNT_LOCKED_OR_CLOSED': - throw new PayPalException('Payer account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED); + throw new PayPalException('Payer account is locked or closed. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED, $previous); case 'PAYER_ACCOUNT_RESTRICTED': - throw new PayPalException('Payer account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_RESTRICTED); + throw new PayPalException('Payer account is restricted. To get more information about the status of the account, call Customer Support.', PayPalException::PAYER_ACCOUNT_RESTRICTED, $previous); case 'PAYER_CANNOT_PAY': - throw new PayPalException('Payer cannot pay for this transaction. Please contact the payer to find other ways to pay for this transaction.', PayPalException::PAYER_CANNOT_PAY); + throw new PayPalException('Payer cannot pay for this transaction. Please contact the payer to find other ways to pay for this transaction.', PayPalException::PAYER_CANNOT_PAY, $previous); case 'PAYER_CONSENT_REQUIRED': - throw new PayPalException('The payer has not provided appropriate consent to proceed with this transaction. To proceed with the transaction, you must get payer consent.', PayPalException::PAYER_CONSENT_REQUIRED); + throw new PayPalException('The payer has not provided appropriate consent to proceed with this transaction. To proceed with the transaction, you must get payer consent.', PayPalException::PAYER_CONSENT_REQUIRED, $previous); case 'PAYER_COUNTRY_NOT_SUPPORTED': - throw new PayPalException('Payer Country is not supported. The Payer country is not supported. Redirect the payer to select another funding source.', PayPalException::PAYER_COUNTRY_NOT_SUPPORTED); + throw new PayPalException('Payer Country is not supported. The Payer country is not supported. Redirect the payer to select another funding source.', PayPalException::PAYER_COUNTRY_NOT_SUPPORTED, $previous); case 'PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING': - throw new PayPalException('The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING); + throw new PayPalException('The API Caller account is not setup to be able to process card payments. Please contact PayPal customer support.', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING, $previous); case 'PAYMENT_INSTRUCTION_REQUIRED': - throw new PayPalException('You must provide the payment instruction when you capture an authorized payment for intent=AUTHORIZE. For details, see Capture authorization. For intent=CAPTURE, send the payment instruction when you create the order.', PayPalException::PAYMENT_INSTRUCTION_REQUIRED); + throw new PayPalException('You must provide the payment instruction when you capture an authorized payment for intent=AUTHORIZE. For details, see Capture authorization. For intent=CAPTURE, send the payment instruction when you create the order.', PayPalException::PAYMENT_INSTRUCTION_REQUIRED, $previous); case 'PERMISSION_DENIED': - throw new PayPalException('You do not have permission to access or perform operations on this resource. If you make API calls on behalf of a merchant or payee, ensure that you have been granted appropriate permissions to continue with this request.', PayPalException::PERMISSION_DENIED); + throw new PayPalException('You do not have permission to access or perform operations on this resource. If you make API calls on behalf of a merchant or payee, ensure that you have been granted appropriate permissions to continue with this request.', PayPalException::PERMISSION_DENIED, $previous); case 'POSTAL_CODE_REQUIRED': - throw new PayPalException('The specified country requires a postal code. Specify a postal code and try the request again.', PayPalException::POSTAL_CODE_REQUIRED); + throw new PayPalException('The specified country requires a postal code. Specify a postal code and try the request again.', PayPalException::POSTAL_CODE_REQUIRED, $previous); case 'PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH': - throw new PayPalException('The amount provided in the preferred shipping option should match the amount provided in amount breakdown.', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH); + throw new PayPalException('The amount provided in the preferred shipping option should match the amount provided in amount breakdown.', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH, $previous); case 'REDIRECT_PAYER_FOR_ALTERNATE_FUNDING': - throw new PayPalException('Transaction failed. Redirect the payer to select another funding source.', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING); + throw new PayPalException('Transaction failed. Redirect the payer to select another funding source.', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING, $previous); case 'REFERENCE_ID_NOT_FOUND': - throw new PayPalException('Filter expression value is incorrect. Check the value of the reference_id and try the request again.', PayPalException::REFERENCE_ID_NOT_FOUND); + throw new PayPalException('Filter expression value is incorrect. Check the value of the reference_id and try the request again.', PayPalException::REFERENCE_ID_NOT_FOUND, $previous); case 'REFERENCE_ID_REQUIRED': - throw new PayPalException('\'reference_id\' is required for each \'purchase_unit\' if multiple \'purchase_unit\' are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::REFERENCE_ID_REQUIRED); + throw new PayPalException('\'reference_id\' is required for each \'purchase_unit\' if multiple \'purchase_unit\' are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::REFERENCE_ID_REQUIRED, $previous); case 'DUPLICATE_REFERENCE_ID': - throw new PayPalException('reference_id must be unique if multiple purchase_unit are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::DUPLICATE_REFERENCE_ID); + throw new PayPalException('reference_id must be unique if multiple purchase_unit are provided. Provide a unique value for reference_id for each purchase_unit and try the request again.', PayPalException::DUPLICATE_REFERENCE_ID, $previous); case 'SHIPPING_ADDRESS_INVALID': - throw new PayPalException('Provided shipping address is invalid.', PayPalException::SHIPPING_ADDRESS_INVALID); + throw new PayPalException('Provided shipping address is invalid.', PayPalException::SHIPPING_ADDRESS_INVALID, $previous); case 'SHIPPING_OPTION_NOT_SELECTED': - throw new PayPalException('At least one of the shipping.option values must be selected = true.', PayPalException::SHIPPING_OPTION_NOT_SELECTED); + throw new PayPalException('At least one of the shipping.option values must be selected = true.', PayPalException::SHIPPING_OPTION_NOT_SELECTED, $previous); case 'SHIPPING_OPTIONS_NOT_SUPPORTED': - throw new PayPalException('Shipping options are not supported when application_context.shipping_preference is set as NO_SHIPPING or SET_PROVIDED_ADDRESS.', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED); + throw new PayPalException('Shipping options are not supported when application_context.shipping_preference is set as NO_SHIPPING or SET_PROVIDED_ADDRESS.', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED, $previous); case 'TAX_TOTAL_MISMATCH': - throw new PayPalException('Should equal sum of (tax * quantity) across all items for a given purchase_unit. The tax total must equal the sum of (tax * quantity) across all items for a purchase_unit.', PayPalException::TAX_TOTAL_MISMATCH); + throw new PayPalException('Should equal sum of (tax * quantity) across all items for a given purchase_unit. The tax total must equal the sum of (tax * quantity) across all items for a purchase_unit.', PayPalException::TAX_TOTAL_MISMATCH, $previous); case 'TAX_TOTAL_REQUIRED': - throw new PayPalException('If item details are specified (items.tax_total and items.quantity), the corresponding amount.breakdown.tax_total is required. The amount.breakdown.tax_total is a required field.', PayPalException::TAX_TOTAL_REQUIRED); + throw new PayPalException('If item details are specified (items.tax_total and items.quantity), the corresponding amount.breakdown.tax_total is required. The amount.breakdown.tax_total is a required field.', PayPalException::TAX_TOTAL_REQUIRED, $previous); case 'TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT': - throw new PayPalException('The transaction amount exceeds monthly maximum limit. To review the monthly transaction limits and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT); + throw new PayPalException('The transaction amount exceeds monthly maximum limit. To review the monthly transaction limits and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_AMOUNT_EXCEEDS_MONTHLY_MAX_LIMIT, $previous); case 'TRANSACTION_BLOCKED_BY_PAYEE': - throw new PayPalException('The transaction was blocked by the payee’s Fraud Protection settings.', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE); + throw new PayPalException('The transaction was blocked by the payee’s Fraud Protection settings.', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE, $previous); case 'TRANSACTION_LIMIT_EXCEEDED': - throw new PayPalException('Total payment amount exceeded transaction limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_LIMIT_EXCEEDED); + throw new PayPalException('Total payment amount exceeded transaction limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_LIMIT_EXCEEDED, $previous); case 'TRANSACTION_RECEIVING_LIMIT_EXCEEDED': - throw new PayPalException('The transaction exceeds the payee\'s receiving limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED); + throw new PayPalException('The transaction exceeds the payee\'s receiving limit. To review the transaction limit and retry this transaction, call Customer Support.', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED, $previous); case 'TRANSACTION_REFUSED': - throw new PayPalException('The transaction was refused. Verify the transaction and try the request again.', PayPalException::TRANSACTION_REFUSED); + throw new PayPalException('The transaction was refused. Verify the transaction and try the request again.', PayPalException::TRANSACTION_REFUSED, $previous); case 'UNSUPPORTED_INTENT': - throw new PayPalException('intent=AUTHORIZE is not supported for multiple purchase units. Only intent=CAPTURE is supported.', PayPalException::UNSUPPORTED_INTENT); + throw new PayPalException('intent=AUTHORIZE is not supported for multiple purchase units. Only intent=CAPTURE is supported.', PayPalException::UNSUPPORTED_INTENT, $previous); case 'UNSUPPORTED_PATCH_PARAMETER_VALUE': - throw new PayPalException('The value specified for this field is not currently supported. Try the request again with a different value.', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE); + throw new PayPalException('The value specified for this field is not currently supported. Try the request again with a different value.', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE, $previous); case 'UNSUPPORTED_PAYMENT_INSTRUCTION': - throw new PayPalException('Only supported when the intent=CAPTURE. If intent is AUTHORIZE, you must provide a payment_instruction when you capture payment for the authorization.', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION); + throw new PayPalException('Only supported when the intent=CAPTURE. If intent is AUTHORIZE, you must provide a payment_instruction when you capture payment for the authorization.', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION, $previous); case 'PAYEE_ACCOUNT_NOT_SUPPORTED': - throw new PayPalException('Payee does not have an account with PayPal. Your current setup requires the \'payee\' to have a verified account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED); + throw new PayPalException('Payee does not have an account with PayPal. Your current setup requires the \'payee\' to have a verified account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED, $previous); case 'PAYEE_ACCOUNT_NOT_VERIFIED': - throw new PayPalException('Payee has not verified their account with PayPal. Your current setup requires the \'payee\' to have an account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED); + throw new PayPalException('Payee has not verified their account with PayPal. Your current setup requires the \'payee\' to have an account with PayPal before you can process transactions on their behalf.', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED, $previous); case 'PAYEE_NOT_CONSENTED': - throw new PayPalException('Payee does not have appropriate consent to allow the API caller to process this type of transaction on their behalf. Your current setup requires the \'payee\' to provide a consent before this transaction can be processed successfully.', PayPalException::PAYEE_NOT_CONSENTED); + throw new PayPalException('Payee does not have appropriate consent to allow the API caller to process this type of transaction on their behalf. Your current setup requires the \'payee\' to provide a consent before this transaction can be processed successfully.', PayPalException::PAYEE_NOT_CONSENTED, $previous); case 'AUTH_CAPTURE_CURRENCY_MISMATCH': - throw new PayPalException('Currency of capture must be the same as currency of authorization. Verify the currency of the capture and try the request again.', PayPalException::AUTH_CAPTURE_CURRENCY_MISMATCH); + throw new PayPalException('Currency of capture must be the same as currency of authorization. Verify the currency of the capture and try the request again.', PayPalException::AUTH_CAPTURE_CURRENCY_MISMATCH, $previous); case 'AUTHORIZATION_ALREADY_CAPTURED': - throw new PayPalException('Authorization has already been captured. If final_capture is set to to true, additional captures are not possible against the authorization.', PayPalException::AUTHORIZATION_ALREADY_CAPTURED); + throw new PayPalException('Authorization has already been captured. If final_capture is set to to true, additional captures are not possible against the authorization.', PayPalException::AUTHORIZATION_ALREADY_CAPTURED, $previous); case 'AUTHORIZATION_DENIED': - throw new PayPalException('A denied authorization cannot be captured. You cannot capture a denied authorization.', PayPalException::AUTHORIZATION_DENIED); + throw new PayPalException('A denied authorization cannot be captured. You cannot capture a denied authorization.', PayPalException::AUTHORIZATION_DENIED, $previous); case 'AUTHORIZATION_EXPIRED': - throw new PayPalException('An expired authorization cannot be captured. You cannot capture an expired authorization.', PayPalException::AUTHORIZATION_EXPIRED); + throw new PayPalException('An expired authorization cannot be captured. You cannot capture an expired authorization.', PayPalException::AUTHORIZATION_EXPIRED, $previous); case 'AUTHORIZATION_VOIDED': - throw new PayPalException('A voided authorization cannot be captured or reauthorized. You cannot capture or reauthorize a voided authorization.', PayPalException::AUTHORIZATION_VOIDED); + throw new PayPalException('A voided authorization cannot be captured or reauthorized. You cannot capture or reauthorize a voided authorization.', PayPalException::AUTHORIZATION_VOIDED, $previous); case 'CANNOT_BE_VOIDED': - throw new PayPalException('A reauthorization cannot be voided. Please void the original parent authorization. You cannot void a reauthorized payment. You must void the original parent authorized payment.', PayPalException::CANNOT_BE_VOIDED); + throw new PayPalException('A reauthorization cannot be voided. Please void the original parent authorization. You cannot void a reauthorized payment. You must void the original parent authorized payment.', PayPalException::CANNOT_BE_VOIDED, $previous); case 'REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK': - throw new PayPalException('Refunds not allowed on this capture due to a chargeback on the card or bank. Please contact the payee to resolve the chargeback.', PayPalException::REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK); + throw new PayPalException('Refunds not allowed on this capture due to a chargeback on the card or bank. Please contact the payee to resolve the chargeback.', PayPalException::REFUND_NOT_PERMITTED_DUE_TO_CHARGEBACK, $previous); case 'CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED': - throw new PayPalException('Refund for an amount less than the remaining transaction amount cannot be processed at this time because of an open dispute on the capture. Please visit the PayPal Resolution Center to view the details.', PayPalException::CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED); + throw new PayPalException('Refund for an amount less than the remaining transaction amount cannot be processed at this time because of an open dispute on the capture. Please visit the PayPal Resolution Center to view the details.', PayPalException::CAPTURE_DISPUTED_PARTIAL_REFUND_NOT_ALLOWED, $previous); case 'CAPTURE_FULLY_REFUNDED': - throw new PayPalException('The capture has already been fully refunded. You cannot capture additional refunds against this capture.', PayPalException::CAPTURE_FULLY_REFUNDED); + throw new PayPalException('The capture has already been fully refunded. You cannot capture additional refunds against this capture.', PayPalException::CAPTURE_FULLY_REFUNDED, $previous); case 'DECIMALS_NOT_SUPPORTED': - throw new PayPalException('Currency does not support decimals.', PayPalException::DECIMALS_NOT_SUPPORTED); + throw new PayPalException('Currency does not support decimals.', PayPalException::DECIMALS_NOT_SUPPORTED, $previous); case 'INVALID_PAYEE_ACCOUNT': - throw new PayPalException('Payee account is invalid. Verify the payee account information and try the request again.', PayPalException::INVALID_PAYEE_ACCOUNT); + throw new PayPalException('Payee account is invalid. Verify the payee account information and try the request again.', PayPalException::INVALID_PAYEE_ACCOUNT, $previous); case 'INVALID_PLATFORM_FEES_AMOUNT': - throw new PayPalException('The platform_fees amount cannot be greater than the capture amount. Verify the platform_fees amount and try the request again.', PayPalException::INVALID_PLATFORM_FEES_AMOUNT); + throw new PayPalException('The platform_fees amount cannot be greater than the capture amount. Verify the platform_fees amount and try the request again.', PayPalException::INVALID_PLATFORM_FEES_AMOUNT, $previous); case 'INVALID_STRING_MAX_LENGTH': - throw new PayPalException('The value of a field is too long. The parameter string is too long.', PayPalException::INVALID_STRING_MAX_LENGTH); + throw new PayPalException('The value of a field is too long. The parameter string is too long.', PayPalException::INVALID_STRING_MAX_LENGTH, $previous); case 'MAX_CAPTURE_AMOUNT_EXCEEDED': - throw new PayPalException('Capture amount exceeds allowable limit. Please contact customer service or your account manager to request the change to your overage limit. The default overage limit is 115%, which allows the sum of all captures to be up to 115% of the authorization amount. Specify a different amount and try the request again. Alternately, contact Customer Support to increase your limits.', PayPalException::MAX_CAPTURE_AMOUNT_EXCEEDED); + throw new PayPalException('Capture amount exceeds allowable limit. Please contact customer service or your account manager to request the change to your overage limit. The default overage limit is 115%, which allows the sum of all captures to be up to 115% of the authorization amount. Specify a different amount and try the request again. Alternately, contact Customer Support to increase your limits.', PayPalException::MAX_CAPTURE_AMOUNT_EXCEEDED, $previous); case 'MAX_CAPTURE_COUNT_EXCEEDED': - throw new PayPalException('Maximum number of allowable captures has been reached. No additional captures are possible for this authorization. Please contact customer service or your account manager to change the number of captures that be made for a given authorization. You cannot make additional captures.', PayPalException::MAX_CAPTURE_COUNT_EXCEEDED); + throw new PayPalException('Maximum number of allowable captures has been reached. No additional captures are possible for this authorization. Please contact customer service or your account manager to change the number of captures that be made for a given authorization. You cannot make additional captures.', PayPalException::MAX_CAPTURE_COUNT_EXCEEDED, $previous); case 'MAX_NUMBER_OF_REFUNDS_EXCEEDED': - throw new PayPalException('You have exceeded the number of refunds that can be processed per capture. Please contact customer support or your account manager to review the number of refunds that can be processed per capture.', PayPalException::MAX_NUMBER_OF_REFUNDS_EXCEEDED); + throw new PayPalException('You have exceeded the number of refunds that can be processed per capture. Please contact customer support or your account manager to review the number of refunds that can be processed per capture.', PayPalException::MAX_NUMBER_OF_REFUNDS_EXCEEDED, $previous); case 'PARTIAL_REFUND_NOT_ALLOWED': - throw new PayPalException('You cannot do a refund for an amount less than the original capture amount. Specify an amount equal to the capture amount or omit the amount object from the request. Then, try the request again.', PayPalException::PARTIAL_REFUND_NOT_ALLOWED); + throw new PayPalException('You cannot do a refund for an amount less than the original capture amount. Specify an amount equal to the capture amount or omit the amount object from the request. Then, try the request again.', PayPalException::PARTIAL_REFUND_NOT_ALLOWED, $previous); case 'PENDING_CAPTURE': - throw new PayPalException('Cannot initiate a refund as the capture is pending. Capture is typically pending when the payer has funded the transaction by using an e-check or bank account.', PayPalException::PENDING_CAPTURE); + throw new PayPalException('Cannot initiate a refund as the capture is pending. Capture is typically pending when the payer has funded the transaction by using an e-check or bank account.', PayPalException::PENDING_CAPTURE, $previous); case 'PERMISSION_NOT_GRANTED': - throw new PayPalException('Payee of the authorization has not granted permission to perform capture on the authorization. To make API calls on behalf of a merchant, ensure that you have sufficient permissions to capture the authorization.', PayPalException::PERMISSION_NOT_GRANTED); + throw new PayPalException('Payee of the authorization has not granted permission to perform capture on the authorization. To make API calls on behalf of a merchant, ensure that you have sufficient permissions to capture the authorization.', PayPalException::PERMISSION_NOT_GRANTED, $previous); case 'PREVIOUSLY_CAPTURED': - throw new PayPalException('Authorization has been previously captured and hence cannot be voided. This authorized payment was already captured. You cannot capture it again.', PayPalException::PREVIOUSLY_CAPTURED); + throw new PayPalException('Authorization has been previously captured and hence cannot be voided. This authorized payment was already captured. You cannot capture it again.', PayPalException::PREVIOUSLY_CAPTURED, $previous); case 'PREVIOUSLY_VOIDED': - throw new PayPalException('Authorization has been previously voided and hence cannot be voided again. This authorized payment was already voided. You cannot void it again.', PayPalException::PREVIOUSLY_VOIDED); + throw new PayPalException('Authorization has been previously voided and hence cannot be voided again. This authorized payment was already voided. You cannot void it again.', PayPalException::PREVIOUSLY_VOIDED, $previous); case 'REFUND_AMOUNT_EXCEEDED': - throw new PayPalException('The refund amount must be less than or equal to the capture amount that has not yet been refunded. Verify the refund amount and try the request again.', PayPalException::REFUND_AMOUNT_EXCEEDED); + throw new PayPalException('The refund amount must be less than or equal to the capture amount that has not yet been refunded. Verify the refund amount and try the request again.', PayPalException::REFUND_AMOUNT_EXCEEDED, $previous); case 'REFUND_CAPTURE_CURRENCY_MISMATCH': - throw new PayPalException('Refund must be in the same currency as the capture. Verify the currency of the refund and try the request again.', PayPalException::REFUND_CAPTURE_CURRENCY_MISMATCH); + throw new PayPalException('Refund must be in the same currency as the capture. Verify the currency of the refund and try the request again.', PayPalException::REFUND_CAPTURE_CURRENCY_MISMATCH, $previous); case 'REFUND_FAILED_INSUFFICIENT_FUNDS': - throw new PayPalException('Capture could not be refunded due to insufficient funds. Verify that either you have sufficient funds in your PayPal account or the bank account that is linked to your PayPal account is verified and has sufficient funds.', PayPalException::REFUND_FAILED_INSUFFICIENT_FUNDS); + throw new PayPalException('Capture could not be refunded due to insufficient funds. Verify that either you have sufficient funds in your PayPal account or the bank account that is linked to your PayPal account is verified and has sufficient funds.', PayPalException::REFUND_FAILED_INSUFFICIENT_FUNDS, $previous); case 'REFUND_NOT_ALLOWED': - throw new PayPalException('Full refund refused - partial refund has already been done on this payment. You cannot refund this capture.', PayPalException::REFUND_NOT_ALLOWED); + throw new PayPalException('Full refund refused - partial refund has already been done on this payment. You cannot refund this capture.', PayPalException::REFUND_NOT_ALLOWED, $previous); case 'REFUND_TIME_LIMIT_EXCEEDED': - throw new PayPalException('You are over the time limit to perform a refund on this capture. The refund cannot be issued at this time.', PayPalException::REFUND_TIME_LIMIT_EXCEEDED); + throw new PayPalException('You are over the time limit to perform a refund on this capture. The refund cannot be issued at this time.', PayPalException::REFUND_TIME_LIMIT_EXCEEDED, $previous); case 'NO_EXTERNAL_FUNDING_DETAILS_FOUND': - throw new PayPalException('External funding details not found.', PayPalException::NO_EXTERNAL_FUNDING_DETAILS_FOUND); + throw new PayPalException('External funding details not found.', PayPalException::NO_EXTERNAL_FUNDING_DETAILS_FOUND, $previous); case 'PAYMENT_DENIED': - throw new PayPalException('Payment denied.', PayPalException::PAYMENT_DENIED); + throw new PayPalException('Payment denied.', PayPalException::PAYMENT_DENIED, $previous); case 'CARD_BRAND_NOT_SUPPORTED': - throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED); + throw new PayPalException('Processing of this card brand is not supported. Use another type of card.', PayPalException::CARD_BRAND_NOT_SUPPORTED, $previous); case 'RESOURCE_NOT_FOUND': - throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND); + throw new PayPalException('The specified resource does not exist.', PayPalException::RESOURCE_NOT_FOUND, $previous); case 'PAYMENT_SOURCE_CANNOT_BE_USED': - throw new PayPalException('The provided payment source cannot be used to pay for the order. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED); + throw new PayPalException('The provided payment source cannot be used to pay for the order. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED, $previous); + case 'PAYPAL_REQUEST_ID_REQUIRED': + throw new PayPalException('A PayPal-Request-Id is required if you are trying to process payment for an Order. Please specify a PayPal-Request-Id or Create the Order without a payment_source specified.', PayPalException::PAYPAL_REQUEST_ID_REQUIRED, $previous); + case 'MALFORMED_REQUEST_JSON': + throw new PayPalException('The request JSON is not well formed.', PayPalException::MALFORMED_REQUEST_JSON, $previous); + case 'PERMISSION_DENIED_FOR_DONATION_ITEMS': + throw new PayPalException('The API caller or payee have not been granted appropriate permissions to send items.category as DONATION. Speak to your account manager if you want to process these type of items.', PayPalException::PERMISSION_DENIED_FOR_DONATION_ITEMS, $previous); + case 'MALFORMED_REQUEST': + throw new PayPalException('You\'ve sent a request that our server could not understand.', PayPalException::MALFORMED_REQUEST, $previous); + case 'BILLING_ADDRESS_INVALID': + throw new PayPalException('Provided billing address is invalid.', PayPalException::BILLING_ADDRESS_INVALID, $previous); + case 'CARD_EXPIRED': + throw new PayPalException('The payment card provided is expired.', PayPalException::CARD_EXPIRED, $previous); + case 'DONATION_ITEMS_NOT_SUPPORTED': + throw new PayPalException('If "purchase_unit" has "DONATION" as the "items.category" then the Order can at most have one purchase_unit. Multiple purchase_units are not supported if either of them have at least one items with category as "DONATION".', PayPalException::DONATION_ITEMS_NOT_SUPPORTED, $previous); + case 'MISSING_PICKUP_ADDRESS': + throw new PayPalException('A pickup address (shipping.address) is required for the provided shipping.type. Possible error location: /purchase_units/0/shipping/type', PayPalException::MISSING_PICKUP_ADDRESS, $previous); + case 'MULTIPLE_ITEM_CATEGORIES': + throw new PayPalException('For a given purchase unit, the items.category could be either PHYSICAL_GOODS and\/or DIGITAL_GOODS or just DONATION. items.category as DONATION cannot be combined with items with either PHYSICAL_GOODS or DIGITAL_GOODS.', PayPalException::MULTIPLE_ITEM_CATEGORIES, $previous); + case 'MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED': + throw new PayPalException('Different shipping.type are not supported across purchase units.', PayPalException::MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED, $previous); + case 'PAYMENT_SOURCE_DECLINED_BY_PROCESSOR': + throw new PayPalException('The provided payment source is declined by the processor. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_DECLINED_BY_PROCESSOR, $previous); + case 'PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED': + throw new PayPalException('The provided payment source is declined by the processor. Please try again with a different payment source by creating a new order.', PayPalException::PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED, $previous); + case 'SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT': + throw new PayPalException('The API Caller account is not setup to be able to support a shipping.type=PICKUP_IN_PERSON. This feature is only supported for PayPal Commerce Platform for Platforms and Marketplaces.', PayPalException::SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT, $previous); + case 'UNSUPPORTED_SHIPPING_TYPE': + throw new PayPalException('The provided shipping.type is only supported for application_context.shipping_preference=SET_PROVIDED_ADDRESS or NO_SHIPPING. Possible error location: /purchase_units/0/shipping/type.', PayPalException::UNSUPPORTED_SHIPPING_TYPE, $previous); + case 'CARD_CLOSED': + throw new PayPalException('The card is closed with the issuer.', PayPalException::CARD_CLOSED, $previous); + case 'SAVE_ORDER_NOT_SUPPORTED': + throw new PayPalException('The API caller account is setup in a way that does not allow it to be used for saving the order. This functionality is not available for PayPal Commerce Platform for Platforms & Marketplaces.', PayPalException::SAVE_ORDER_NOT_SUPPORTED, $previous); + case 'PUI_DUPLICATE_ORDER': + throw new PayPalException('A Pay Upon Invoice (Rechnungskauf) order with the same payload has already been successfully processed in the last few seconds. To process a new order, please try again in a few seconds.', PayPalException::PUI_DUPLICATE_ORDER, $previous); + case 'CANNOT_PROCESS_REFUNDS': + throw new PayPalException('We can\'t process any refund at this moment due to technical reasons. Please try again later.', PayPalException::CANNOT_PROCESS_REFUNDS, $previous); + case 'INVALID_REFUND_AMOUNT': + throw new PayPalException('The refund amount is invalid. Please check the refund amount and try again.', PayPalException::INVALID_REFUND_AMOUNT, $previous); default: - throw new PayPalException($this->message, PayPalException::UNKNOWN); + throw new PayPalException($this->message, PayPalException::UNKNOWN, $previous); } } } diff --git a/src/PaypalOrder.php b/src/PaypalOrder.php index 831f6c80d..4cc0e6462 100644 --- a/src/PaypalOrder.php +++ b/src/PaypalOrder.php @@ -20,7 +20,11 @@ namespace PrestaShop\Module\PrestashopCheckout; -use PrestaShop\Module\PrestashopCheckout\Api\Payment\Order; +use Module; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use Ps_checkout; /** * Allow to instantiate a paypal order @@ -47,20 +51,32 @@ public function __construct($id) */ private function loadOrder($id) { - $response = (new Order(\Context::getContext()->link))->fetch($id); + /** @var Ps_checkout $module */ + $module = Module::getInstanceByName('ps_checkout'); - if (false === $response['status'] && ((isset($response['body']['message']) && $response['body']['message'] === 'INVALID_RESOURCE_ID') || $response['exceptionCode'] === 404)) { - \Db::getInstance()->update( - \PsCheckoutCart::$definition['table'], - [ - 'paypal_status' => \PsCheckoutCart::STATUS_CANCELED, - ], - 'paypal_order = "' . pSQL($id) . '"' - ); - } + /** @var CheckoutHttpClient $checkoutHttpClient */ + $checkoutHttpClient = $module->getService('ps_checkout.http.client.checkout'); + + try { + $response = $checkoutHttpClient->fetchOrder([ + 'orderId' => $id, + ]); + $responseHandler = new ResponseApiHandler(); + $response = $responseHandler->handleResponse($response); - if (true === $response['status'] && !empty($response['body'])) { - $this->setOrder($response['body']); + if (true === $response['status'] && !empty($response['body'])) { + $this->setOrder($response['body']); + } + } catch (PayPalException $exception) { + if ($exception->getCode() === PayPalException::INVALID_RESOURCE_ID) { + \Db::getInstance()->update( + \PsCheckoutCart::$definition['table'], + [ + 'paypal_status' => \PsCheckoutCart::STATUS_CANCELED, + ], + 'paypal_order = "' . pSQL($id) . '"' + ); + } } } diff --git a/tests/Unit/Http/CheckoutHttpClientTest.php b/tests/Unit/Http/CheckoutHttpClientTest.php new file mode 100644 index 000000000..3944e6b34 --- /dev/null +++ b/tests/Unit/Http/CheckoutHttpClientTest.php @@ -0,0 +1,417 @@ + + * @copyright Since 2007 PrestaShop SA and Contributors + * @license https://opensource.org/licenses/AFL-3.0 Academic Free License version 3.0 + */ + +namespace Tests\Unit\Http; + +use Http\Client\Exception\HttpException; +use PHPUnit\Framework\TestCase; +use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; +use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; +use PrestaShop\Module\PrestashopCheckout\Http\HttpClientInterface; +use Psr\Http\Message\RequestInterface; +use Psr\Http\Message\ResponseInterface; +use Psr\Http\Message\StreamInterface; + +class CheckoutHttpClientTest extends TestCase +{ + /** + * @dataProvider invalidRequestErrorsProvider + * + * @throws PayPalException + */ + public function testInvalidRequestErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(400, $errorName, $errorCode); + } + + /** + * @dataProvider notAuthorizedErrorsProvider + * + * @throws PayPalException + */ + public function testNotAuthorizedErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(401, $errorName, $errorCode); + } + + /** + * @dataProvider unprocessableEntityErrorsProvider + * + * @throws PayPalException + */ + public function testUnprocessableEntityErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(422, $errorName, $errorCode); + } + + /** + * @dataProvider notFoundErrorsProvider + * + * @throws PayPalException + */ + public function testNotFoundErrorsCreateOrder($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(404, $errorName, $errorCode); + } + + /** + * @dataProvider invalidRequestErrorsProvider + * + * @throws PayPalException + */ + public function testInvalidRequestErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(400, $errorName, $errorCode, true); + } + + /** + * @dataProvider notAuthorizedErrorsProvider + * + * @throws PayPalException + */ + public function testNotAuthorizedErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(401, $errorName, $errorCode, true); + } + + /** + * @dataProvider unprocessableEntityErrorsProvider + * + * @throws PayPalException + */ + public function testUnprocessableEntityErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(422, $errorName, $errorCode, true); + } + + /** + * @dataProvider notFoundErrorsProvider + * + * @throws PayPalException + */ + public function testNotFoundErrorsCreateOrderLegacy($errorName, $errorCode) + { + $this->handleTestErrorsCreateOrder(404, $errorName, $errorCode, true); + } + + /** + * @param int $statusCode + * @param string $errorName + * @param int $errorCode + * @param bool $legacy + * + * @throws PayPalException + */ + private function handleTestErrorsCreateOrder($statusCode, $errorName, $errorCode, $legacy = false) + { + $error = $this->getErrorResponse($statusCode, $errorName, $legacy); + $requestMock = $this->createMock(RequestInterface::class); + $streamMock = $this->createMock(StreamInterface::class); + $streamMock->method('__toString')->willReturn(json_encode($error)); + $responseMock = $this->createMock(ResponseInterface::class); + $responseMock->method('getStatusCode')->willReturn($statusCode); + $responseMock->method('getBody')->willReturn($streamMock); + $httpClient = $this->createMock(HttpClientInterface::class); + $httpClient->method('sendRequest')->willThrowException(new HttpException('An error occurred', $requestMock, $responseMock)); + $this->expectExceptionCode($errorCode); + $this->expectException(PayPalException::class); + $checkoutHttpClient = new CheckoutHttpClient($httpClient); + $checkoutHttpClient->createOrder([]); + } + + private function getInvalidRequestError($issueError) + { + return [ + 'name' => 'INVALID_REQUEST', + 'message' => 'Request is not well-formed, syntactically incorrect, or violates schema.', + 'debug_id' => 'b6b9a374802ea', + 'details' => [ + [ + 'field' => '', + 'value' => '', + 'location' => 'body', + 'issue' => $issueError, + 'description' => '', + ], + ], + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-INVALID_PARAMETER_VALUE', + 'rel' => 'information_link', + 'encType' => 'application/json', + ], + ], + ]; + } + + /** + * @param int $statusCode + * @param string $errorName + * @param bool $legacy + * + * @return array + */ + private function getErrorResponse($statusCode, $errorName, $legacy) + { + if ($legacy) { + switch ($statusCode) { + case 400: + return $this->getInvalidRequestErrorLegacy($errorName); + case 401: + return $this->getNotAuthorizedErrorLegacy($errorName); + case 404: + return $this->getNotFoundErrorLegacy($errorName); + case 422: + return $this->getUnprocessableEntityErrorLegacy($errorName); + } + } + + switch ($statusCode) { + case 400: + return $this->getInvalidRequestError($errorName); + case 401: + return $this->getNotAuthorizedError($errorName); + case 404: + return $this->getNotFoundError($errorName); + case 422: + return $this->getUnprocessableEntityError($errorName); + } + + return []; + } + + private function getInvalidRequestErrorLegacy($issueError) + { + return [ + 'statusCode' => 400, + 'error' => 'Bad Request', + 'message' => $issueError, + ]; + } + + private function getNotAuthorizedError($issueError) + { + return [ + 'name' => $issueError, + 'message' => 'Authentication failed due to invalid authentication credentials or a missing Authorization header.', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/overview/#error', + 'rel' => 'information_link', + ], + ], + ]; + } + + private function getNotAuthorizedErrorLegacy($issueError) + { + return [ + 'statusCode' => 401, + 'error' => 'Unprocessable Entity', + 'message' => $issueError, + ]; + } + + private function getUnprocessableEntityError($issueError) + { + return [ + 'name' => 'UNPROCESSABLE_ENTITY', + 'details' => [ + [ + 'field' => '', + 'value' => '', + 'issue' => $issueError, + 'description' => '', + ], + ], + 'message' => 'The requested action could not be performed, semantically incorrect, or failed business validation.', + 'debug_id' => 'c9a75b43fc807', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-MAX_VALUE_EXCEEDED', + 'rel' => 'information_link', + 'method' => 'GET', + ], + ], + ]; + } + + private function getUnprocessableEntityErrorLegacy($issueError) + { + return [ + 'statusCode' => 422, + 'error' => 'Unprocessable Entity', + 'message' => $issueError, + ]; + } + + /** + * @param string $errorName + * + * @return array + */ + private function getNotFoundError($errorName) + { + return [ + 'name' => $errorName, + 'message' => 'The specified resource does not exist.', + 'debug_id' => 'b6b9a374802ea', + 'links' => [ + [ + 'href' => 'https://developer.paypal.com/docs/api/orders/v2/#error-' . $errorName, + 'rel' => 'information_link', + 'encType' => 'application/json', + ], + ], + ]; + } + + private function getNotFoundErrorLegacy($issueError) + { + return [ + 'statusCode' => 404, + 'error' => 'Not found', + 'message' => $issueError, + ]; + } + + public function notFoundErrorsProvider() + { + return [ + ['INVALID_RESOURCE_ID', PayPalException::INVALID_RESOURCE_ID], + ]; + } + + public function invalidRequestErrorsProvider() + { + return [ + ['INVALID_ARRAY_MAX_ITEMS', PayPalException::INVALID_ARRAY_MAX_ITEMS], + ['INVALID_ARRAY_MIN_ITEMS', PayPalException::INVALID_ARRAY_MIN_ITEMS], + ['INVALID_COUNTRY_CODE', PayPalException::INVALID_COUNTRY_CODE], + ['INVALID_PARAMETER_SYNTAX', PayPalException::INVALID_PARAMETER_SYNTAX], + ['INVALID_STRING_LENGTH', PayPalException::INVALID_STRING_LENGTH], + ['INVALID_PARAMETER_VALUE', PayPalException::INVALID_PARAMETER_VALUE], + ['MISSING_REQUIRED_PARAMETER', PayPalException::MISSING_REQUIRED_PARAMETER], + ['NOT_SUPPORTED', PayPalException::NOT_SUPPORTED], + ['PAYPAL_REQUEST_ID_REQUIRED', PayPalException::PAYPAL_REQUEST_ID_REQUIRED], + ['MALFORMED_REQUEST_JSON', PayPalException::MALFORMED_REQUEST_JSON], + ['FIELD_NOT_PATCHABLE', PayPalException::FIELD_NOT_PATCHABLE], + ['AMOUNT_NOT_PATCHABLE', PayPalException::AMOUNT_NOT_PATCHABLE], + ['INVALID_PATCH_OPERATION', PayPalException::INVALID_PATCH_OPERATION], + ]; + } + + public function notAuthorizedErrorsProvider() + { + return [ + ['PERMISSION_DENIED', PayPalException::PERMISSION_DENIED], + ['PERMISSION_DENIED_FOR_DONATION_ITEMS', PayPalException::PERMISSION_DENIED_FOR_DONATION_ITEMS], + ['MALFORMED_REQUEST', PayPalException::MALFORMED_REQUEST], + ['PAYEE_ACCOUNT_NOT_SUPPORTED', PayPalException::PAYEE_ACCOUNT_NOT_SUPPORTED], + ['PAYEE_ACCOUNT_NOT_VERIFIED', PayPalException::PAYEE_ACCOUNT_NOT_VERIFIED], + ['PAYEE_NOT_CONSENTED', PayPalException::PAYEE_NOT_CONSENTED], + ['CONSENT_NEEDED', PayPalException::CONSENT_NEEDED], + ['INVALID_ACCOUNT_STATUS', PayPalException::INVALID_ACCOUNT_STATUS], + ]; + } + + public function unprocessableEntityErrorsProvider() + { + return [ + ['AMOUNT_MISMATCH', PayPalException::AMOUNT_MISMATCH], + ['BILLING_ADDRESS_INVALID', PayPalException::BILLING_ADDRESS_INVALID], + ['CANNOT_BE_NEGATIVE', PayPalException::CANNOT_BE_NEGATIVE], + ['CANNOT_BE_ZERO_OR_NEGATIVE', PayPalException::CANNOT_BE_ZERO_OR_NEGATIVE], + ['CARD_EXPIRED', PayPalException::CARD_EXPIRED], + ['CITY_REQUIRED', PayPalException::CITY_REQUIRED], + ['DECIMAL_PRECISION', PayPalException::DECIMAL_PRECISION], + ['DONATION_ITEMS_NOT_SUPPORTED', PayPalException::DONATION_ITEMS_NOT_SUPPORTED], + ['DUPLICATE_REFERENCE_ID', PayPalException::DUPLICATE_REFERENCE_ID], + ['INVALID_CURRENCY_CODE', PayPalException::INVALID_CURRENCY_CODE], + ['INVALID_PAYER_ID', PayPalException::INVALID_PAYER_ID], + ['ITEM_TOTAL_MISMATCH', PayPalException::ITEM_TOTAL_MISMATCH], + ['ITEM_TOTAL_REQUIRED', PayPalException::ITEM_TOTAL_REQUIRED], + ['MAX_VALUE_EXCEEDED', PayPalException::MAX_VALUE_EXCEEDED], + ['MISSING_PICKUP_ADDRESS', PayPalException::MISSING_PICKUP_ADDRESS], + ['MULTIPLE_ITEM_CATEGORIES', PayPalException::MULTIPLE_ITEM_CATEGORIES], + ['MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED', PayPalException::MULTIPLE_SHIPPING_ADDRESS_NOT_SUPPORTED], + ['MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED', PayPalException::MULTIPLE_SHIPPING_TYPE_NOT_SUPPORTED], + ['PAYEE_ACCOUNT_INVALID', PayPalException::PAYEE_ACCOUNT_INVALID], + ['PAYEE_ACCOUNT_LOCKED_OR_CLOSED', PayPalException::PAYEE_ACCOUNT_LOCKED_OR_CLOSED], + ['PAYEE_ACCOUNT_RESTRICTED', PayPalException::PAYEE_ACCOUNT_RESTRICTED], + ['REFERENCE_ID_REQUIRED', PayPalException::REFERENCE_ID_REQUIRED], + ['PAYMENT_SOURCE_CANNOT_BE_USED', PayPalException::PAYMENT_SOURCE_CANNOT_BE_USED], + ['PAYMENT_SOURCE_DECLINED_BY_PROCESSOR', PayPalException::PAYMENT_SOURCE_DECLINED_BY_PROCESSOR], + ['PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED', PayPalException::PAYMENT_SOURCE_INFO_CANNOT_BE_VERIFIED], + ['POSTAL_CODE_REQUIRED', PayPalException::POSTAL_CODE_REQUIRED], + ['SHIPPING_ADDRESS_INVALID', PayPalException::SHIPPING_ADDRESS_INVALID], + ['TAX_TOTAL_MISMATCH', PayPalException::TAX_TOTAL_MISMATCH], + ['TAX_TOTAL_REQUIRED', PayPalException::TAX_TOTAL_REQUIRED], + ['UNSUPPORTED_INTENT', PayPalException::UNSUPPORTED_INTENT], + ['UNSUPPORTED_PAYMENT_INSTRUCTION', PayPalException::UNSUPPORTED_PAYMENT_INSTRUCTION], + ['SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT', PayPalException::SHIPPING_TYPE_NOT_SUPPORTED_FOR_CLIENT], + ['UNSUPPORTED_SHIPPING_TYPE', PayPalException::UNSUPPORTED_SHIPPING_TYPE], + ['SHIPPING_OPTION_NOT_SELECTED', PayPalException::SHIPPING_OPTION_NOT_SELECTED], + ['SHIPPING_OPTIONS_NOT_SUPPORTED', PayPalException::SHIPPING_OPTIONS_NOT_SUPPORTED], + ['MULTIPLE_SHIPPING_OPTION_SELECTED', PayPalException::MULTIPLE_SHIPPING_OPTION_SELECTED], + ['PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH', PayPalException::PREFERRED_SHIPPING_OPTION_AMOUNT_MISMATCH], + ['CARD_CLOSED', PayPalException::CARD_CLOSED], + ['ORDER_CANNOT_BE_SAVED', PayPalException::ORDER_CANNOT_BE_SAVED], + ['SAVE_ORDER_NOT_SUPPORTED', PayPalException::SAVE_ORDER_NOT_SUPPORTED], + ['PUI_DUPLICATE_ORDER', PayPalException::PUI_DUPLICATE_ORDER], + ['INVALID_JSON_POINTER_FORMAT', PayPalException::INVALID_JSON_POINTER_FORMAT], + ['INVALID_PARAMETER', PayPalException::INVALID_PARAMETER], + ['NOT_PATCHABLE', PayPalException::NOT_PATCHABLE], + ['UNSUPPORTED_PATCH_PARAMETER_VALUE', PayPalException::UNSUPPORTED_PATCH_PARAMETER_VALUE], + ['PATCH_VALUE_REQUIRED', PayPalException::PATCH_VALUE_REQUIRED], + ['PATCH_PATH_REQUIRED', PayPalException::PATCH_PATH_REQUIRED], + ['REFERENCE_ID_NOT_FOUND', PayPalException::REFERENCE_ID_NOT_FOUND], + ['MULTI_CURRENCY_ORDER', PayPalException::MULTI_CURRENCY_ORDER], + ['ORDER_ALREADY_COMPLETED', PayPalException::ORDER_ALREADY_COMPLETED], + ['AGREEMENT_ALREADY_CANCELLED', PayPalException::AGREEMENT_ALREADY_CANCELLED], + ['BILLING_AGREEMENT_NOT_FOUND', PayPalException::BILLING_AGREEMENT_NOT_FOUND], + ['COMPLIANCE_VIOLATION', PayPalException::COMPLIANCE_VIOLATION], + ['DOMESTIC_TRANSACTION_REQUIRED', PayPalException::DOMESTIC_TRANSACTION_REQUIRED], + ['DUPLICATE_INVOICE_ID', PayPalException::DUPLICATE_INVOICE_ID], + ['INSTRUMENT_DECLINED', PayPalException::INSTRUMENT_DECLINED], + ['ORDER_NOT_APPROVED', PayPalException::ORDER_NOT_APPROVED], + ['MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED', PayPalException::MAX_NUMBER_OF_PAYMENT_ATTEMPTS_EXCEEDED], + ['PAYEE_BLOCKED_TRANSACTION', PayPalException::PAYEE_BLOCKED_TRANSACTION], + ['PAYER_ACCOUNT_LOCKED_OR_CLOSED', PayPalException::PAYER_ACCOUNT_LOCKED_OR_CLOSED], + ['PAYER_ACCOUNT_RESTRICTED', PayPalException::PAYER_ACCOUNT_RESTRICTED], + ['PAYER_CANNOT_PAY', PayPalException::PAYER_CANNOT_PAY], + ['TRANSACTION_LIMIT_EXCEEDED', PayPalException::TRANSACTION_LIMIT_EXCEEDED], + ['TRANSACTION_RECEIVING_LIMIT_EXCEEDED', PayPalException::TRANSACTION_RECEIVING_LIMIT_EXCEEDED], + ['TRANSACTION_REFUSED', PayPalException::TRANSACTION_REFUSED], + ['REDIRECT_PAYER_FOR_ALTERNATE_FUNDING', PayPalException::REDIRECT_PAYER_FOR_ALTERNATE_FUNDING], + ['ORDER_ALREADY_CAPTURED', PayPalException::ORDER_ALREADY_CAPTURED], + ['TRANSACTION_BLOCKED_BY_PAYEE', PayPalException::TRANSACTION_BLOCKED_BY_PAYEE], + ['ORDER_ALREADY_CAPTURED', PayPalException::ORDER_ALREADY_CAPTURED], + ['AUTH_CAPTURE_NOT_ENABLED', PayPalException::AUTH_CAPTURE_NOT_ENABLED], + ['NOT_ENABLED_FOR_CARD_PROCESSING', PayPalException::NOT_ENABLED_FOR_CARD_PROCESSING], + ['PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING', PayPalException::PAYEE_NOT_ENABLED_FOR_CARD_PROCESSING], + ['INVALID_PICKUP_ADDRESS', PayPalException::INVALID_PICKUP_ADDRESS], + ['SHIPPING_ADDRESS_INVALID', PayPalException::SHIPPING_ADDRESS_INVALID], + ['CANNOT_PROCESS_REFUNDS', PayPalException::CANNOT_PROCESS_REFUNDS], + ['INVALID_REFUND_AMOUNT', PayPalException::INVALID_REFUND_AMOUNT], + ['PAYMENT_DENIED', PayPalException::PAYMENT_DENIED], + ]; + } +} From 53f93eb01addfb696075d973096b685ecc76194e Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 28 Feb 2024 18:11:15 +0100 Subject: [PATCH 128/343] Send card supplementary data --- src/Builder/Payload/OrderPayloadBuilder.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index 7d75a802c..61011b1b4 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -109,8 +109,8 @@ public function buildFullPayload() } if ($this->isCard) { - //$this->buildPaymentSourceNode(); - //$this->buildSupplementaryDataNode(); + $this->buildPaymentSourceNode(); + $this->buildSupplementaryDataNode(); } } From 4266855b964cf74c02fc4b9b6a6198ce09b5f55d Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 28 Feb 2024 19:02:36 +0100 Subject: [PATCH 129/343] Restore http logger --- config/common.yml | 2 + ...CheckoutHttpClientConfigurationBuilder.php | 52 ++++++++++++++++++- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/config/common.yml b/config/common.yml index 891cd9802..7123a9afd 100644 --- a/config/common.yml +++ b/config/common.yml @@ -717,6 +717,8 @@ services: - "@ps_checkout.context.shop" - "@ps_checkout.repository.prestashop.account" - "@ps_checkout.context.prestashop" + - "@ps_checkout.logger.configuration" + - "@ps_checkout.logger" ps_checkout.http.client.factory: class: 'PrestaShop\Module\PrestashopCheckout\Http\HttpClientFactory' diff --git a/src/Http/CheckoutHttpClientConfigurationBuilder.php b/src/Http/CheckoutHttpClientConfigurationBuilder.php index 03f5bb8b9..e5bd032fc 100644 --- a/src/Http/CheckoutHttpClientConfigurationBuilder.php +++ b/src/Http/CheckoutHttpClientConfigurationBuilder.php @@ -20,12 +20,19 @@ namespace PrestaShop\Module\PrestashopCheckout\Http; +use GuzzleHttp\Event\Emitter; +use GuzzleHttp\HandlerStack; +use GuzzleHttp\Subscriber\Log\Formatter; +use GuzzleHttp\Subscriber\Log\LogSubscriber; +use GuzzleLogMiddleware\LogMiddleware; use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; use PrestaShop\Module\PrestashopCheckout\Environment\PaymentEnv; +use PrestaShop\Module\PrestashopCheckout\Logger\LoggerConfiguration; use PrestaShop\Module\PrestashopCheckout\Repository\PsAccountRepository; use PrestaShop\Module\PrestashopCheckout\Routing\Router; use PrestaShop\Module\PrestashopCheckout\ShopContext; use Ps_checkout; +use Psr\Log\LoggerInterface; class CheckoutHttpClientConfigurationBuilder implements HttpClientConfigurationBuilderInterface { @@ -56,18 +63,32 @@ class CheckoutHttpClientConfigurationBuilder implements HttpClientConfigurationB */ private $prestaShopContext; + /** + * @var LoggerConfiguration + */ + private $loggerConfiguration; + + /** + * @var LoggerInterface + */ + private $logger; + public function __construct( PaymentEnv $paymentEnv, Router $router, ShopContext $shopContext, PsAccountRepository $psAccountRepository, - PrestaShopContext $prestaShopContext + PrestaShopContext $prestaShopContext, + LoggerConfiguration $loggerConfiguration, + LoggerInterface $logger ) { $this->paymentEnv = $paymentEnv; $this->router = $router; $this->shopContext = $shopContext; $this->psAccountRepository = $psAccountRepository; $this->prestaShopContext = $prestaShopContext; + $this->loggerConfiguration = $loggerConfiguration; + $this->logger = $logger; } /** @@ -75,7 +96,7 @@ public function __construct( */ public function build() { - return [ + $configuration = [ 'base_url' => $this->paymentEnv->getPaymentApiUrl(), 'verify' => $this->getVerify(), 'timeout' => static::TIMEOUT, @@ -90,6 +111,33 @@ public function build() 'Prestashop-Version' => _PS_VERSION_, // prestashop version ], ]; + + if ( + $this->loggerConfiguration->isHttpEnabled() + && defined('\GuzzleHttp\ClientInterface::MAJOR_VERSION') + && class_exists(HandlerStack::class) + && class_exists(LogMiddleware::class) + ) { + $handlerStack = HandlerStack::create(); + $handlerStack->push(new LogMiddleware($this->logger)); + $configuration['handler'] = $handlerStack; + } elseif ( + $this->loggerConfiguration->isHttpEnabled() + && defined('\GuzzleHttp\ClientInterface::VERSION') + && class_exists(Emitter::class) + && class_exists(LogSubscriber::class) + && class_exists(Formatter::class) + ) { + $emitter = new Emitter(); + $emitter->attach(new LogSubscriber( + $this->logger, + Formatter::DEBUG + )); + + $configuration['emitter'] = $emitter; + } + + return $configuration; } /** From 20733faa1c166489175e80749ab4fda4027a9ba6 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:17:28 +0100 Subject: [PATCH 130/343] Add user-friendly message for popup closed --- _dev/js/front/src/components/common/smart-button.component.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/_dev/js/front/src/components/common/smart-button.component.js b/_dev/js/front/src/components/common/smart-button.component.js index 4456eecaf..695c8a4e9 100644 --- a/_dev/js/front/src/components/common/smart-button.component.js +++ b/_dev/js/front/src/components/common/smart-button.component.js @@ -188,6 +188,9 @@ export class SmartButtonComponent extends BaseComponent { ) { errorMessage = 'Provided country is not supported by the selected payment method.'; + } else if (error.message.includes('Detected popup close')) { + errorMessage = + 'The payment failed because the payment window has been closed before the end of the payment process.'; } } } From e493da036e8b46be48144f4e886743271aaafada Mon Sep 17 00:00:00 2001 From: Laurynas Date: Thu, 29 Feb 2024 16:40:27 +0200 Subject: [PATCH 131/343] Added a fix for order cancel for card-fields and changed refund status event emitting --- .../1_6/express-button-cart.component.js | 18 ++-- .../1_6/express-button-checkout.component.js | 11 +-- .../1_6/express-button-product.component.js | 32 +++---- .../1_6/pay-later-button-cart.component.js | 14 ++- .../pay-later-button-checkout.component.js | 17 ++-- .../1_6/pay-later-button-product.component.js | 36 ++++---- .../1_7/express-button-cart.component.js | 12 +-- .../1_7/express-button-checkout.component.js | 12 +-- .../1_7/express-button-product.component.js | 46 +++++----- .../1_7/pay-later-button-cart.component.js | 14 ++- .../pay-later-button-checkout.component.js | 14 ++- .../1_7/pay-later-button-product.component.js | 50 ++++++----- .../common/card-fields.component.js | 3 + .../express-checkout-button.component.js | 89 +++++++++++++++---- .../common/smart-button.component.js | 6 ++ config/common.yml | 3 +- .../AdminAjaxPrestashopCheckoutController.php | 41 ++++++--- .../UpdateOrderStatusCommandHandler.php | 4 +- .../RefundPayPalCaptureCommandHandler.php | 25 +----- .../PayPalRefundEventSubscriber.php | 22 +---- 20 files changed, 238 insertions(+), 231 deletions(-) diff --git a/_dev/js/front/src/components/1_6/express-button-cart.component.js b/_dev/js/front/src/components/1_6/express-button-cart.component.js index 98d627e00..f555e8459 100644 --- a/_dev/js/front/src/components/1_6/express-button-cart.component.js +++ b/_dev/js/front/src/components/1_6/express-button-cart.component.js @@ -28,7 +28,8 @@ export class ExpressButtonCartComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCart(); } renderComponent() { @@ -50,14 +51,7 @@ export class ExpressButtonCartComponent extends BaseComponent { this.app, { fundingSource: 'paypal', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps_checkout-express-button-cart', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paypal', - isExpressCheckout: true - }) + querySelector: '#ps_checkout-express-button-cart' } ).render(); } @@ -67,9 +61,9 @@ export class ExpressButtonCartComponent extends BaseComponent { this.renderComponent(); this.prestashopService.onUpdatedShoppingCartExtra(() => { - if (null === document.querySelector('#ps_checkout-express-button-cart')) { - this.renderComponent(); - } + if (null === document.querySelector('#ps_checkout-express-button-cart')) { + this.renderComponent(); + } }); return this; diff --git a/_dev/js/front/src/components/1_6/express-button-checkout.component.js b/_dev/js/front/src/components/1_6/express-button-checkout.component.js index 4ef5a6c74..6296b816b 100644 --- a/_dev/js/front/src/components/1_6/express-button-checkout.component.js +++ b/_dev/js/front/src/components/1_6/express-button-checkout.component.js @@ -28,7 +28,8 @@ export class ExpressButtonCheckoutComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); } renderTitle() { @@ -53,13 +54,7 @@ export class ExpressButtonCheckoutComponent extends BaseComponent { this.app, { fundingSource: 'paypal', - querySelector: '#ps_checkout-express-button-checkout', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paypal', - isExpressCheckout: true - }) + querySelector: '#ps_checkout-express-button-checkout' } ).render(); diff --git a/_dev/js/front/src/components/1_6/express-button-product.component.js b/_dev/js/front/src/components/1_6/express-button-product.component.js index 5a3a09d42..7d7c164a0 100644 --- a/_dev/js/front/src/components/1_6/express-button-product.component.js +++ b/_dev/js/front/src/components/1_6/express-button-product.component.js @@ -27,7 +27,8 @@ export class ExpressButtonProductComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerProduct(); } render() { @@ -42,28 +43,23 @@ export class ExpressButtonProductComponent extends BaseComponent { buttonContainer.append(this.checkoutExpressButton); + const { + id_product, + id_product_attribute, + id_customization, + quantity_wanted + } = this.prestashopService.getProductDetails(); + this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent( this.app, { fundingSource: 'paypal', - // TODO: Move this to constant when ExpressCheckoutButton component is created querySelector: '#ps-checkout-express-button', - createOrder: () => { - const { - id_product, - id_product_attribute, - id_customization, - quantity_wanted - } = this.prestashopService.getProductDetails(); - - return this.psCheckoutApi.postCreateOrder({ - id_product, - id_product_attribute, - id_customization, - quantity_wanted, - fundingSource: 'paypal', - isExpressCheckout: true - }); + data: { + id_product, + id_product_attribute, + id_customization, + quantity_wanted } } ).render(); diff --git a/_dev/js/front/src/components/1_6/pay-later-button-cart.component.js b/_dev/js/front/src/components/1_6/pay-later-button-cart.component.js index 8929c8f6c..bf3c724a5 100644 --- a/_dev/js/front/src/components/1_6/pay-later-button-cart.component.js +++ b/_dev/js/front/src/components/1_6/pay-later-button-cart.component.js @@ -24,11 +24,14 @@ export class PayLaterButtonCartComponent extends BaseComponent { querySelectorService: 'QuerySelectorService', prestashopService: 'PrestashopService', psCheckoutApi: 'PsCheckoutApi', + payPalService: 'PayPalService', $: '$' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.data.orderId = this.payPalService.getOrderId(); } renderComponent() { @@ -52,14 +55,7 @@ export class PayLaterButtonCartComponent extends BaseComponent { this.app, { fundingSource: 'paylater', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps_checkout-express-button-cart', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paylater', - isExpressCheckout: true - }) + querySelector: '#ps_checkout-express-button-cart' } ).render(); } diff --git a/_dev/js/front/src/components/1_6/pay-later-button-checkout.component.js b/_dev/js/front/src/components/1_6/pay-later-button-checkout.component.js index bbe9989aa..0989d7a73 100644 --- a/_dev/js/front/src/components/1_6/pay-later-button-checkout.component.js +++ b/_dev/js/front/src/components/1_6/pay-later-button-checkout.component.js @@ -24,11 +24,14 @@ export class PayLaterButtonCheckoutComponent extends BaseComponent { querySelectorService: 'QuerySelectorService', prestashopService: 'PrestashopService', psCheckoutApi: 'PsCheckoutApi', + payPalService: 'PayPalService', $: '$' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.data.orderId = this.payPalService.getOrderId(); } renderTitle() { @@ -55,19 +58,13 @@ export class PayLaterButtonCheckoutComponent extends BaseComponent { this.app, { fundingSource: 'paylater', - querySelector: '#ps_checkout-express-button-checkout', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paylater', - isExpressCheckout: true - }) + querySelector: '#ps_checkout-express-button-checkout' } ).render(); if ( - this.prestashopService.isNativeOnePageCheckoutPage() - && !document.getElementById('ps_checkout-express-button-checkout') + this.prestashopService.isNativeOnePageCheckoutPage() && + !document.getElementById('ps_checkout-express-button-checkout') ) { const separatorText = document.createElement('div'); separatorText.classList.add('ps_checkout-express-separator'); diff --git a/_dev/js/front/src/components/1_6/pay-later-button-product.component.js b/_dev/js/front/src/components/1_6/pay-later-button-product.component.js index f7c5ceb5c..eb07a9093 100644 --- a/_dev/js/front/src/components/1_6/pay-later-button-product.component.js +++ b/_dev/js/front/src/components/1_6/pay-later-button-product.component.js @@ -23,11 +23,14 @@ export class PayLaterButtonProductComponent extends BaseComponent { static Inject = { querySelectorService: 'QuerySelectorService', psCheckoutApi: 'PsCheckoutApi', - prestashopService: 'PrestashopService' + prestashopService: 'PrestashopService', + payPalService: 'PayPalService' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.data.orderId = this.payPalService.getOrderId(); } render() { @@ -44,28 +47,23 @@ export class PayLaterButtonProductComponent extends BaseComponent { buttonContainer.append(this.checkoutExpressButton); } + const { + id_product, + id_product_attribute, + id_customization, + quantity_wanted + } = this.prestashopService.getProductDetails(); + this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent( this.app, { fundingSource: 'paylater', - // TODO: Move this to constant when ExpressCheckoutButton component is created querySelector: '#ps-checkout-express-button', - createOrder: () => { - const { - id_product, - id_product_attribute, - id_customization, - quantity_wanted - } = this.prestashopService.getProductDetails(); - - return this.psCheckoutApi.postCreateOrder({ - id_product, - id_product_attribute, - id_customization, - quantity_wanted, - fundingSource: 'paylater', - isExpressCheckout: true - }); + data: { + id_product, + id_product_attribute, + id_customization, + quantity_wanted } } ).render(); diff --git a/_dev/js/front/src/components/1_7/express-button-cart.component.js b/_dev/js/front/src/components/1_7/express-button-cart.component.js index 96e14af6f..66dafe5d2 100644 --- a/_dev/js/front/src/components/1_7/express-button-cart.component.js +++ b/_dev/js/front/src/components/1_7/express-button-cart.component.js @@ -27,7 +27,8 @@ export class ExpressButtonCartComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCart(); } render() { @@ -47,14 +48,7 @@ export class ExpressButtonCartComponent extends BaseComponent { this.app, { fundingSource: 'paypal', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps-checkout-express-button', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paypal', - isExpressCheckout: true - }) + querySelector: '#ps-checkout-express-button' } ).render(); return this; diff --git a/_dev/js/front/src/components/1_7/express-button-checkout.component.js b/_dev/js/front/src/components/1_7/express-button-checkout.component.js index ab9ccc1cf..12ebd9384 100644 --- a/_dev/js/front/src/components/1_7/express-button-checkout.component.js +++ b/_dev/js/front/src/components/1_7/express-button-checkout.component.js @@ -27,7 +27,8 @@ export class ExpressButtonCheckoutComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); } renderTitle() { @@ -60,14 +61,7 @@ export class ExpressButtonCheckoutComponent extends BaseComponent { this.app, { fundingSource: 'paypal', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps-checkout-express-button', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paypal', - isExpressCheckout: true - }) + querySelector: '#ps-checkout-express-button' } ).render(); return this; diff --git a/_dev/js/front/src/components/1_7/express-button-product.component.js b/_dev/js/front/src/components/1_7/express-button-product.component.js index 9fedc1eb1..aba07b87a 100644 --- a/_dev/js/front/src/components/1_7/express-button-product.component.js +++ b/_dev/js/front/src/components/1_7/express-button-product.component.js @@ -29,14 +29,16 @@ export class ExpressButtonProductComponent extends BaseComponent { }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerProduct(); } render() { this.checkoutExpressButton = document.createElement('div'); this.checkoutExpressButton.id = BUTTON_CONTAINER_SELECTOR; - const productQuantityHTMLElement = this.buttonReferenceContainer.nextElementSibling; + const productQuantityHTMLElement = + this.buttonReferenceContainer.nextElementSibling; this.buttonReferenceContainer.parentNode.insertBefore( this.checkoutExpressButton, @@ -49,28 +51,23 @@ export class ExpressButtonProductComponent extends BaseComponent { this.updateButtonContainerVisibility(); }); + const { + id_product, + id_product_attribute, + id_customization, + quantity_wanted + } = this.prestashopService.getProductDetails(); + this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent( this.app, { fundingSource: 'paypal', - // TODO: Move this to constant when ExpressCheckoutButton component is created querySelector: `#${BUTTON_CONTAINER_SELECTOR}`, - createOrder: () => { - const { - id_product, - id_product_attribute, - id_customization, - quantity_wanted - } = this.prestashopService.getProductDetails(); - - return this.psCheckoutApi.postCreateOrder({ - id_product, - id_product_attribute, - id_customization, - quantity_wanted, - fundingSource: 'paypal', - isExpressCheckout: true - }); + data: { + id_product, + id_product_attribute, + id_customization, + quantity_wanted } } ).render(); @@ -78,12 +75,15 @@ export class ExpressButtonProductComponent extends BaseComponent { return this; } - updateButtonContainerVisibility() - { + updateButtonContainerVisibility() { if (this.prestashopService.isAddToCartButtonDisabled()) { - document.getElementById(BUTTON_CONTAINER_SELECTOR).classList.add('disabled'); + document + .getElementById(BUTTON_CONTAINER_SELECTOR) + .classList.add('disabled'); } else { - document.getElementById(BUTTON_CONTAINER_SELECTOR).classList.remove('disabled'); + document + .getElementById(BUTTON_CONTAINER_SELECTOR) + .classList.remove('disabled'); } } } diff --git a/_dev/js/front/src/components/1_7/pay-later-button-cart.component.js b/_dev/js/front/src/components/1_7/pay-later-button-cart.component.js index 0a468f2fc..b9ee83180 100644 --- a/_dev/js/front/src/components/1_7/pay-later-button-cart.component.js +++ b/_dev/js/front/src/components/1_7/pay-later-button-cart.component.js @@ -23,11 +23,14 @@ export class PayLaterButtonCartComponent extends BaseComponent { static Inject = { querySelectorService: 'QuerySelectorService', psCheckoutApi: 'PsCheckoutApi', + payPalService: 'PayPalService', $: '$' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCart(); + this.data.orderId = this.payPalService.getOrderId(); } render() { @@ -49,14 +52,7 @@ export class PayLaterButtonCartComponent extends BaseComponent { this.app, { fundingSource: 'paylater', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps-checkout-express-button', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paylater', - isExpressCheckout: true - }) + querySelector: '#ps-checkout-express-button' } ).render(); return this; diff --git a/_dev/js/front/src/components/1_7/pay-later-button-checkout.component.js b/_dev/js/front/src/components/1_7/pay-later-button-checkout.component.js index a6b7dab25..aaa245d3c 100644 --- a/_dev/js/front/src/components/1_7/pay-later-button-checkout.component.js +++ b/_dev/js/front/src/components/1_7/pay-later-button-checkout.component.js @@ -23,11 +23,14 @@ export class PayLaterButtonCheckoutComponent extends BaseComponent { static Inject = { querySelectorService: 'QuerySelectorService', psCheckoutApi: 'PsCheckoutApi', + payPalService: 'PayPalService', $: '$' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerCheckout(); + this.data.orderId = this.payPalService.getOrderId(); } renderTitle() { @@ -62,14 +65,7 @@ export class PayLaterButtonCheckoutComponent extends BaseComponent { this.app, { fundingSource: 'paylater', - // TODO: Move this to constant when ExpressCheckoutButton component is created - querySelector: '#ps-checkout-express-button', - createOrder: (data) => - this.psCheckoutApi.postCreateOrder({ - ...data, - fundingSource: 'paylater', - isExpressCheckout: true - }) + querySelector: '#ps-checkout-express-button' } ).render(); return this; diff --git a/_dev/js/front/src/components/1_7/pay-later-button-product.component.js b/_dev/js/front/src/components/1_7/pay-later-button-product.component.js index 2605b7431..49b3956e1 100644 --- a/_dev/js/front/src/components/1_7/pay-later-button-product.component.js +++ b/_dev/js/front/src/components/1_7/pay-later-button-product.component.js @@ -25,11 +25,14 @@ export class PayLaterButtonProductComponent extends BaseComponent { static Inject = { querySelectorService: 'QuerySelectorService', psCheckoutApi: 'PsCheckoutApi', - prestashopService: 'PrestashopService' + prestashopService: 'PrestashopService', + payPalService: 'PayPalService' }; created() { - this.buttonReferenceContainer = this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.buttonReferenceContainer = + this.querySelectorService.getExpressCheckoutButtonContainerProduct(); + this.data.orderId = this.payPalService.getOrderId(); } render() { @@ -37,7 +40,8 @@ export class PayLaterButtonProductComponent extends BaseComponent { this.checkoutExpressButton = document.createElement('div'); this.checkoutExpressButton.id = BUTTON_CONTAINER_SELECTOR; - const productQuantityHTMLElement = this.buttonReferenceContainer.nextElementSibling; + const productQuantityHTMLElement = + this.buttonReferenceContainer.nextElementSibling; this.buttonReferenceContainer.parentNode.insertBefore( this.checkoutExpressButton, @@ -51,28 +55,23 @@ export class PayLaterButtonProductComponent extends BaseComponent { this.updateButtonContainerVisibility(); }); + const { + id_product, + id_product_attribute, + id_customization, + quantity_wanted + } = this.prestashopService.getProductDetails(); + this.children.expressCheckoutButton = new ExpressCheckoutButtonComponent( this.app, { fundingSource: 'paylater', - // TODO: Move this to constant when ExpressCheckoutButton component is created querySelector: `#${BUTTON_CONTAINER_SELECTOR}`, - createOrder: () => { - const { - id_product, - id_product_attribute, - id_customization, - quantity_wanted - } = this.prestashopService.getProductDetails(); - - return this.psCheckoutApi.postCreateOrder({ - id_product, - id_product_attribute, - id_customization, - quantity_wanted, - fundingSource: 'paylater', - isExpressCheckout: true - }); + data: { + id_product, + id_product_attribute, + id_customization, + quantity_wanted } } ).render(); @@ -80,12 +79,15 @@ export class PayLaterButtonProductComponent extends BaseComponent { return this; } - updateButtonContainerVisibility() - { + updateButtonContainerVisibility() { if (this.prestashopService.isAddToCartButtonDisabled()) { - document.getElementById(BUTTON_CONTAINER_SELECTOR).classList.add('disabled'); + document + .getElementById(BUTTON_CONTAINER_SELECTOR) + .classList.add('disabled'); } else { - document.getElementById(BUTTON_CONTAINER_SELECTOR).classList.remove('disabled'); + document + .getElementById(BUTTON_CONTAINER_SELECTOR) + .classList.remove('disabled'); } } } diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 286638e69..07cbbdf03 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -56,6 +56,7 @@ export class CardFieldsComponent extends BaseComponent { created() { this.data.name = this.props.fundingSource.name; this.data.validity = false; + this.data.orderId = null; /** * @property {PaypalCardFieldsEvent} data.cardFieldsState */ @@ -228,6 +229,7 @@ export class CardFieldsComponent extends BaseComponent { // vault: storeCardInVault }) .then((data) => { + this.data.orderId = data; return data; }) .catch((error) => { @@ -263,6 +265,7 @@ export class CardFieldsComponent extends BaseComponent { return this.psCheckoutApi .postCancelOrder({ + orderID: this.data.orderId, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active, reason: 'card_fields_error', diff --git a/_dev/js/front/src/components/common/express-checkout-button.component.js b/_dev/js/front/src/components/common/express-checkout-button.component.js index 583a3fbc6..4cd49629a 100644 --- a/_dev/js/front/src/components/common/express-checkout-button.component.js +++ b/_dev/js/front/src/components/common/express-checkout-button.component.js @@ -25,37 +25,43 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { $: '$' }; + created() { + this.data.orderId = this.payPalService.getOrderId(); + } + onInit(data, actions) { return actions.enable(); } onClick(data, actions) { - return ( - this.psCheckoutApi - .postCheckCartOrder( - { - ...data, - fundingSource: this.props.fundingSource, - isExpressCheckout: true, - orderID: this.payPalService.getOrderId() - }, - actions - ) - // TODO: Error notification - .catch(() => actions.reject()) - ); - // TODO: [PAYSHIP-605] Error handling + return this.psCheckoutApi + .postCheckCartOrder( + { + ...data, + fundingSource: this.props.fundingSource, + isExpressCheckout: true, + orderID: this.data.orderId + }, + actions + ) + .catch((error) => { + actions.reject(); + throw error; + }); } onError(error) { + const errorText = error?.message ? error.message : error; + this.notifyError(errorText); console.error(error); return this.psCheckoutApi .postCancelOrder({ + orderID: this.data.orderId, fundingSource: this.props.fundingSource, isExpressCheckout: true, reason: 'express_checkout_error', - error: error instanceof Error ? error.message : error + error: errorText }) .catch((error) => console.error(error)); } @@ -74,6 +80,7 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { onCancel(data) { return this.psCheckoutApi.postCancelOrder({ ...data, + orderID: this.data.orderId, fundingSource: this.props.fundingSource, isExpressCheckout: true, reason: 'express_checkout_cancelled' @@ -81,9 +88,55 @@ export class ExpressCheckoutButtonComponent extends BaseComponent { } createOrder(data) { - if (this.props.createOrder) { - return this.props.createOrder(data); + const extraData = this.props?.data ? this.props.data : {}; + + return this.psCheckoutApi + .postCreateOrder({ + ...data, + ...extraData + }) + .then((data) => { + this.data.orderId = data; + return data; + }) + .catch((error) => { + throw error; + }); + } + + notifyError(message) { + const expressCheckoutContainer = document.querySelector( + this.props.querySelector + ); + const notificationContainerIdentifier = + 'ps_checkout-product-notification-container'; + let notificationContainerElement = document.getElementById( + notificationContainerIdentifier + ); + + if (!notificationContainerElement) { + notificationContainerElement = document.createElement('div'); + notificationContainerElement.id = notificationContainerIdentifier; + expressCheckoutContainer.prepend(notificationContainerElement); + } + + const notificationIdentifier = 'ps_checkout-product-notification-container'; + const currentNotificationElement = document.querySelector( + '#' + notificationContainerIdentifier + ' .' + notificationIdentifier + ); + + if (currentNotificationElement) { + return (currentNotificationElement.textContent = message); } + + const notificationElement = document.createElement('div'); + notificationElement.classList.add( + 'alert', + 'alert-danger', + notificationIdentifier + ); + notificationElement.textContent = message; + notificationContainerElement.appendChild(notificationElement); } renderPayPalButton() { diff --git a/_dev/js/front/src/components/common/smart-button.component.js b/_dev/js/front/src/components/common/smart-button.component.js index 695c8a4e9..146ea21de 100644 --- a/_dev/js/front/src/components/common/smart-button.component.js +++ b/_dev/js/front/src/components/common/smart-button.component.js @@ -37,6 +37,7 @@ export class SmartButtonComponent extends BaseComponent { created() { this.data.name = this.props.fundingSource.name; + this.data.orderId = null; this.data.HTMLElement = this.props.HTMLElement; @@ -114,6 +115,7 @@ export class SmartButtonComponent extends BaseComponent { return this.psCheckoutApi .postCancelOrder({ + orderID: this.data.orderId, fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active, reason: 'checkout_error', @@ -160,6 +162,10 @@ export class SmartButtonComponent extends BaseComponent { fundingSource: this.data.name, isExpressCheckout: this.config.expressCheckout.active }) + .then((data) => { + this.data.orderId = data; + return data; + }) .catch((error) => { this.data.loader.hide(); this.data.notification.showError( diff --git a/config/common.yml b/config/common.yml index 7123a9afd..f94a67ac5 100644 --- a/config/common.yml +++ b/config/common.yml @@ -685,14 +685,13 @@ services: PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\CommandHandler\RefundPayPalCaptureCommandHandler' + public: true arguments: - '@ps_checkout.http.client.checkout' - '@ps_checkout.paypal.configuration' - '@ps_checkout.configuration' - '@ps_checkout.context.prestashop' - '@ps_checkout.event.dispatcher' - - '@ps_checkout.cache.paypal.order' - - '@ps_checkout.paypal.provider.order' ps_checkout.paypal.capture.service.check_transition_paypal_capture_status: class: 'PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Capture\CheckTransitionPayPalCaptureStatusService' diff --git a/controllers/admin/AdminAjaxPrestashopCheckoutController.php b/controllers/admin/AdminAjaxPrestashopCheckoutController.php index c6630e0b2..79c0a487a 100755 --- a/controllers/admin/AdminAjaxPrestashopCheckoutController.php +++ b/controllers/admin/AdminAjaxPrestashopCheckoutController.php @@ -30,6 +30,7 @@ use PrestaShop\Module\PrestashopCheckout\Logger\LoggerFileReader; use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\LiveStep; use PrestaShop\Module\PrestashopCheckout\OnBoarding\Step\ValueBanner; +use PrestaShop\Module\PrestashopCheckout\Order\Exception\OrderException; use PrestaShop\Module\PrestashopCheckout\Order\State\Exception\OrderStateException; use PrestaShop\Module\PrestashopCheckout\Order\State\OrderStateInstaller; use PrestaShop\Module\PrestashopCheckout\Order\State\Service\OrderStateMapper; @@ -451,21 +452,15 @@ public function ajaxProcessRefundOrder() try { $commandBus->handle(new RefundPayPalCaptureCommand($orderPayPalId, $captureId, $currency, $amount)); - - $this->ajaxDie(json_encode([ - 'status' => true, - 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), - ])); } catch (PayPalRefundFailedException $exception) { - http_response_code($exception->getCode()); - $this->ajaxDie(json_encode([ + $this->exitWithResponse([ + 'httpCode' => $exception->getCode(), 'status' => false, 'errors' => [ $this->l('Refund cannot be processed by PayPal.', 'translations'), ], - ])); + ]); } catch (PayPalRefundException $invalidArgumentException) { - http_response_code(400); $error = ''; switch ($invalidArgumentException->getCode()) { case PayPalRefundException::INVALID_ORDER_ID: @@ -483,10 +478,28 @@ public function ajaxProcessRefundOrder() default: break; } - $this->ajaxDie(json_encode([ + $this->exitWithResponse([ + 'httpCode' => 400, 'status' => false, 'errors' => [$error], - ])); + ]); + } catch (OrderException $exception) { + if ($exception->getCode() === OrderException::FAILED_UPDATE_ORDER_STATUS) { + $this->exitWithResponse([ + 'httpCode' => 200, + 'status' => true, + 'content' => $this->l('Refund has been processed by PayPal, but order status change or email sending failed.', 'translations'), + ]); + } elseif ($exception->getCode() !== OrderException::ORDER_HAS_ALREADY_THIS_STATUS) { + $this->exitWithResponse([ + 'httpCode' => 500, + 'status' => false, + 'errors' => [ + $exception->getMessage(), + ], + 'error' => $exception->getMessage(), + ]); + } } catch (Exception $exception) { $this->exitWithResponse([ 'httpCode' => 500, @@ -497,6 +510,12 @@ public function ajaxProcessRefundOrder() 'error' => $exception->getMessage(), ]); } + + $this->exitWithResponse([ + 'httpCode' => 200, + 'status' => true, + 'content' => $this->l('Refund has been processed by PayPal.', 'translations'), + ]); } /** diff --git a/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php b/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php index 741d9b6bd..199d1735a 100644 --- a/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php +++ b/src/Order/CommandHandler/UpdateOrderStatusCommandHandler.php @@ -73,11 +73,11 @@ public function handle(UpdateOrderStatusCommand $command) // Save all changes $historyAdded = $history->addWithemail(true); } catch (Exception $exception) { - throw new OrderException(sprintf('Failed to update status or sent email when changing OrderState #%d of Order #%d.', $command->getNewOrderStatusId()->getValue(), $command->getOrderId()->getValue()), OrderException::FAILED_UPDATE_ORDER_STATUS, $exception); + throw new OrderException(sprintf('Failed to update status or send email when changing OrderState #%d of Order #%d.', $command->getNewOrderStatusId()->getValue(), $command->getOrderId()->getValue()), OrderException::FAILED_UPDATE_ORDER_STATUS, $exception); } if (!$historyAdded) { - throw new OrderException(sprintf('Failed to update status or sent email when changing OrderState #%d of Order #%d.', $command->getNewOrderStatusId()->getValue(), $command->getOrderId()->getValue()), OrderException::FAILED_UPDATE_ORDER_STATUS); + throw new OrderException(sprintf('Failed to update status or send email when changing OrderState #%d of Order #%d.', $command->getNewOrderStatusId()->getValue(), $command->getOrderId()->getValue()), OrderException::FAILED_UPDATE_ORDER_STATUS); } $this->eventDispatcher->dispatch(new OrderStatusUpdatedEvent($orderStateId)); diff --git a/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php index 291984583..82a7c9ee6 100644 --- a/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php +++ b/src/PayPal/Payment/Refund/CommandHandler/RefundPayPalCaptureCommandHandler.php @@ -24,14 +24,12 @@ use PrestaShop\Module\PrestashopCheckout\Context\PrestaShopContext; use PrestaShop\Module\PrestashopCheckout\Event\EventDispatcherInterface; use PrestaShop\Module\PrestashopCheckout\Exception\PayPalException; -use PrestaShop\Module\PrestashopCheckout\Handler\Response\ResponseApiHandler; use PrestaShop\Module\PrestashopCheckout\Http\CheckoutHttpClient; use PrestaShop\Module\PrestashopCheckout\PayPal\Order\Exception\PayPalOrderException; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Command\RefundPayPalCaptureCommand; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Event\PayPalCaptureRefundedEvent; use PrestaShop\Module\PrestashopCheckout\PayPal\Payment\Refund\Exception\PayPalRefundFailedException; use PrestaShop\Module\PrestashopCheckout\PayPal\PayPalConfiguration; -use Psr\SimpleCache\CacheInterface; class RefundPayPalCaptureCommandHandler { @@ -55,25 +53,19 @@ class RefundPayPalCaptureCommandHandler * @var EventDispatcherInterface */ private $eventDispatcher; - /** - * @var CacheInterface - */ - private $orderPayPalCache; public function __construct( CheckoutHttpClient $checkoutHttpClient, PayPalConfiguration $payPalConfiguration, PrestaShopConfiguration $prestaShopConfiguration, PrestaShopContext $prestaShopContext, - EventDispatcherInterface $eventDispatcher, - CacheInterface $orderPayPalCache + EventDispatcherInterface $eventDispatcher ) { $this->checkoutHttpClient = $checkoutHttpClient; $this->payPalConfiguration = $payPalConfiguration; $this->prestaShopConfiguration = $prestaShopConfiguration; $this->prestaShopContext = $prestaShopContext; $this->eventDispatcher = $eventDispatcher; - $this->orderPayPalCache = $orderPayPalCache; } /** @@ -101,22 +93,13 @@ public function handle(RefundPayPalCaptureCommand $command) ['id_shop' => $this->prestaShopContext->getShopId()] ), ]); - $responseHandler = new ResponseApiHandler(); - $response = $responseHandler->handleResponse($response); - - if (isset($response['httpCode']) && $response['httpCode'] === 200) { - if ($this->orderPayPalCache->has($command->getOrderPayPalId())) { - $this->orderPayPalCache->delete($command->getOrderPayPalId()); - } - } else { - throw new PayPalRefundFailedException('', isset($response['httpCode']) ? $response['httpCode'] : 500); - } + $refund = json_decode($response->getBody()->getContents(), true); $this->eventDispatcher->dispatch( new PayPalCaptureRefundedEvent( - $response['body']['id'], + $refund['id'], $command->getOrderPayPalId(), - $response['body'] + $refund ) ); } diff --git a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php index 2c9fdeeec..19699c7d7 100644 --- a/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php +++ b/src/PayPal/Payment/Refund/EventSubscriber/PayPalRefundEventSubscriber.php @@ -110,6 +110,10 @@ public function setPaymentRefundedOrderStatus(PayPalCaptureRefundedEvent $event) return; } + if ($this->orderPayPalCache->has($event->getPayPalOrderId()->getValue())) { + $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); + } + if (!$order->hasBeenPaid() || $order->hasBeenTotallyRefund()) { return; } @@ -139,23 +143,5 @@ public function updateCache(PayPalRefundEvent $event) if ($this->orderPayPalCache->has($event->getPayPalOrderId()->getValue())) { $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); } -// $this->capturePayPalCache->set($event->getPayPalCaptureId()->getValue(), $event->getCapture()); -// -// $needToClearOrderPayPalCache = true; -// $orderPayPalCache = $this->orderPayPalCache->get($event->getPayPalOrderId()->getValue()); -// -// if ($orderPayPalCache && isset($orderPayPalCache['purchase_units'][0]['payments']['captures'])) { -// foreach ($orderPayPalCache['purchase_units'][0]['payments']['captures'] as $key => $capture) { -// if ($capture['id'] === $event->getPayPalCaptureId()->getValue()) { -// $needToClearOrderPayPalCache = false; -// $orderPayPalCache['purchase_units'][0]['payments']['captures'][$key] = $event->getCapture(); -// $this->orderPayPalCache->set($event->getPayPalOrderId()->getValue(), $orderPayPalCache); -// } -// } -// } -// -// if ($needToClearOrderPayPalCache) { -// $this->orderPayPalCache->delete($event->getPayPalOrderId()->getValue()); -// } } } From 04f1d10da1b103e0f3c49e752ffb396d748a9cbf Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Fri, 1 Mar 2024 14:37:08 +0100 Subject: [PATCH 132/343] Update readme.md --- README.md | 49 ++++++++++++------------------------------------- 1 file changed, 12 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 0aa0823cb..5ed7e4b7d 100755 --- a/README.md +++ b/README.md @@ -1,9 +1,6 @@ -

PrestaShop Checkout

- # PrestaShop Checkout ![PHP tests](https://github.com/PrestaShopCorp/ps_checkout/workflows/PHP%20tests/badge.svg) -![NodeJS tests](https://github.com/PrestaShopCorp/ps_checkout/workflows/NodeJS%20tests/badge.svg) ![JS tests](https://github.com/PrestaShopCorp/ps_checkout/workflows/JS%20tests/badge.svg) ![Build & Release draft](https://github.com/PrestaShopCorp/ps_checkout/workflows/Build%20&%20Release%20draft/badge.svg) ![GitHub release (latest SemVer)](https://img.shields.io/github/v/release/prestashopcorp/ps_checkout) @@ -15,48 +12,27 @@ PrestaShop official payment module in partnership with PayPal. ## Reporting issues -In order to contact the team, please use the link available in the +In order to contact the team, please use the [link][contact-us] available in the back-office once logged to your PrestaShop account. -## Building the module - -### Direct download +## Direct download If you want to get a zip ready to install on your shop. You can directly download it by clicking [here][direct-download]. -### Production - -1. Clone this repo `git clone git@github.com:PrestaShop/ps_checkout.git` -2. `make build-prod-zip` - -The zip will be generated in the root directory of the module. - -### Development - -1. Clone this repo -2. `make docker-build` -3. `make watch-front` - -I also recommend you to install the [vuejs-devtools][vuejs-devtools]. - -#### Switch on sanbox (Advanced) - -PayPal offers a sandbox mode in which an order can be created without -involving actual money. - -To enable it, reach the module configuration page, then replace `#...` at the end of the URL with `#/experimental`. - -This route allow you to acces to some experimental features (like paypal sandbox). Don't use them in a production environment until these features are officially released. - ## Contributing PrestaShop modules are open source extensions to the PrestaShop e-commerce solution. Everyone is welcome and even encouraged to contribute with their own improvements. ### Requirements +There 3 main branches on the repository: +- `prestashop/8.x` is the branch for PrestaShop v8.x +- `prestashop/1.7.x` is the branch for PrestaShop v1.7.x +- `prestashop/1.6.1.x` is the branch for PrestaShop v1.6.1.x + Contributors **must** follow the following rules: -* **Make your Pull Request on the "dev" branch**, NOT the "master" branch. +* Use the main branch `prestashop/8.x` * Do not update the module's version number. * Follow [the coding standards][1]. @@ -66,11 +42,11 @@ Contributors wishing to edit a module's files should follow the following proces 1. Create your GitHub account, if you do not have one already. 2. Fork this project to your GitHub account. -3. Clone your fork to your local machine in the ```/modules``` directory of your PrestaShop installation. +3. Clone your fork to your local machine in the `/modules` directory of your PrestaShop installation. 4. Create a branch in your local clone of the module for your changes. 5. Change the files in your branch. Be sure to follow the [coding standards][1]! 6. Push your changed branch to your fork in your GitHub account. -7. Create a pull request for your changes **on the _'dev'_ branch** of the module's project. Be sure to follow the [contribution guidelines][2] in your pull request. If you need help to make a pull request, read the [GitHub help page about creating pull requests][3]. +7. Create a pull request for your changes on the `prestashop/8.x` branch of the module's project. Be sure to follow the [contribution guidelines][2] in your pull request. If you need help to make a pull request, read the [GitHub help page about creating pull requests][3]. 8. Wait for one of the core developers either to include your change in the codebase, or to comment on possible improvements you should make to your code. That's it: you have contributed to this open source project! Congratulations! @@ -79,9 +55,8 @@ That's it: you have contributed to this open source project! Congratulations! This module is released under the [Academic Free License 3.0][AFL-3.0] -[vuejs]: https://vuejs.org/ -[vuejs-devtools]: https://github.com/vuejs/vue-devtools -[direct-download]: https://github.com/PrestaShop/ps_checkout/releases/latest/download/ps_checkout.zip +[contact-us]: https://help-center.prestashop.com/contact?psx=ps_checkout +[direct-download]: https://github.com/PrestaShopCorp/ps_checkout/releases [1]: https://devdocs.prestashop.com/1.7/development/coding-standards/ [2]: https://devdocs.prestashop.com/1.7/contribute/contribution-guidelines/ [3]: https://help.github.com/articles/using-pull-requests From ae9af0c514d6e9dcfdc246702f9796b219526589 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 4 Mar 2024 09:57:44 +0100 Subject: [PATCH 133/343] Fix phpstan --- .github/workflows/php.yml | 5 + composer.json | 3 +- composer.lock | 441 ++++++++++++++++++------------ src/Http/PsrHttpClientAdapter.php | 8 +- 4 files changed, 276 insertions(+), 181 deletions(-) diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index c781a322d..8e90cf09e 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -92,6 +92,11 @@ jobs: - name: Checkout uses: actions/checkout@v2 + - name: Setup PHP 7.4 + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + - name: Cache vendor folder uses: actions/cache@v1 with: diff --git a/composer.json b/composer.json index 342eb78b5..8082a38dc 100755 --- a/composer.json +++ b/composer.json @@ -36,7 +36,8 @@ }, "require-dev": { "phpunit/phpunit": "~5.7", - "prestashop/php-dev-tools": "~3.0" + "prestashop/php-dev-tools": "~3.0", + "monolog/monolog": "^1.27.1" }, "autoload": { "psr-4": { diff --git a/composer.lock b/composer.lock index 707e750e6..de9931dc0 100644 --- a/composer.lock +++ b/composer.lock @@ -4,27 +4,27 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "1ffed083e7949e1d32cb94eaf11a19ad", + "content-hash": "38c3ff6e8b0ad69217d568aacbee4d1e", "packages": [ { "name": "clue/stream-filter", - "version": "v1.6.0", + "version": "v1.7.0", "source": { "type": "git", "url": "https://github.com/clue/stream-filter.git", - "reference": "d6169430c7731d8509da7aecd0af756a5747b78e" + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/clue/stream-filter/zipball/d6169430c7731d8509da7aecd0af756a5747b78e", - "reference": "d6169430c7731d8509da7aecd0af756a5747b78e", + "url": "https://api.github.com/repos/clue/stream-filter/zipball/049509fef80032cb3f051595029ab75b49a3c2f7", + "reference": "049509fef80032cb3f051595029ab75b49a3c2f7", "shasum": "" }, "require": { "php": ">=5.3" }, "require-dev": { - "phpunit/phpunit": "^9.3 || ^5.7 || ^4.8.36" + "phpunit/phpunit": "^9.6 || ^5.7 || ^4.8.36" }, "type": "library", "autoload": { @@ -46,7 +46,7 @@ } ], "description": "A simple and modern approach to stream filtering in PHP", - "homepage": "https://github.com/clue/php-stream-filter", + "homepage": "https://github.com/clue/stream-filter", "keywords": [ "bucket brigade", "callback", @@ -58,7 +58,7 @@ ], "support": { "issues": "https://github.com/clue/stream-filter/issues", - "source": "https://github.com/clue/stream-filter/tree/v1.6.0" + "source": "https://github.com/clue/stream-filter/tree/v1.7.0" }, "funding": [ { @@ -70,20 +70,20 @@ "type": "github" } ], - "time": "2022-02-21T13:15:14+00:00" + "time": "2023-12-20T15:40:13+00:00" }, { "name": "giggsey/libphonenumber-for-php", - "version": "8.13.14", + "version": "8.13.31", "source": { "type": "git", "url": "https://github.com/giggsey/libphonenumber-for-php.git", - "reference": "31b94ef2aa349b76bb725f375e9cfa2e398a1620" + "reference": "a4934bddda4672d12f21728e08bd575913bdc310" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/31b94ef2aa349b76bb725f375e9cfa2e398a1620", - "reference": "31b94ef2aa349b76bb725f375e9cfa2e398a1620", + "url": "https://api.github.com/repos/giggsey/libphonenumber-for-php/zipball/a4934bddda4672d12f21728e08bd575913bdc310", + "reference": "a4934bddda4672d12f21728e08bd575913bdc310", "shasum": "" }, "require": { @@ -142,20 +142,20 @@ "issues": "https://github.com/giggsey/libphonenumber-for-php/issues", "source": "https://github.com/giggsey/libphonenumber-for-php" }, - "time": "2023-06-13T08:08:40+00:00" + "time": "2024-02-26T08:31:45+00:00" }, { "name": "giggsey/locale", - "version": "2.4", + "version": "2.5", "source": { "type": "git", "url": "https://github.com/giggsey/Locale.git", - "reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb" + "reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/giggsey/Locale/zipball/a6b33dfc9e8949b7e28133c4628b29cd9f1850bb", - "reference": "a6b33dfc9e8949b7e28133c4628b29cd9f1850bb", + "url": "https://api.github.com/repos/giggsey/Locale/zipball/e6d4540109a01dd2bc7334cdc842d6a6a67cf239", + "reference": "e6d4540109a01dd2bc7334cdc842d6a6a67cf239", "shasum": "" }, "require": { @@ -194,9 +194,9 @@ "description": "Locale functions required by libphonenumber-for-php", "support": { "issues": "https://github.com/giggsey/Locale/issues", - "source": "https://github.com/giggsey/Locale/tree/2.4" + "source": "https://github.com/giggsey/Locale/tree/2.5" }, - "time": "2023-04-13T07:40:58+00:00" + "time": "2023-11-01T17:19:48+00:00" }, { "name": "gmponos/guzzle_logger", @@ -272,22 +272,22 @@ }, { "name": "guzzlehttp/guzzle", - "version": "7.7.0", + "version": "7.8.1", "source": { "type": "git", "url": "https://github.com/guzzle/guzzle.git", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5" + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/guzzle/zipball/fb7566caccf22d74d1ab270de3551f72a58399f5", - "reference": "fb7566caccf22d74d1ab270de3551f72a58399f5", + "url": "https://api.github.com/repos/guzzle/guzzle/zipball/41042bc7ab002487b876a0683fc8dce04ddce104", + "reference": "41042bc7ab002487b876a0683fc8dce04ddce104", "shasum": "" }, "require": { "ext-json": "*", - "guzzlehttp/promises": "^1.5.3 || ^2.0", - "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "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" @@ -296,11 +296,11 @@ "psr/http-client-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "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.29 || ^9.5.23", + "phpunit/phpunit": "^8.5.36 || ^9.6.15", "psr/log": "^1.1 || ^2.0 || ^3.0" }, "suggest": { @@ -378,7 +378,7 @@ ], "support": { "issues": "https://github.com/guzzle/guzzle/issues", - "source": "https://github.com/guzzle/guzzle/tree/7.7.0" + "source": "https://github.com/guzzle/guzzle/tree/7.8.1" }, "funding": [ { @@ -394,28 +394,28 @@ "type": "tidelift" } ], - "time": "2023-05-21T14:04:53+00:00" + "time": "2023-12-03T20:35:24+00:00" }, { "name": "guzzlehttp/promises", - "version": "2.0.0", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + "reference": "bbff78d96034045e58e13dedd6ad91b5d1253223" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "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.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "bamarni/composer-bin-plugin": "^1.8.2", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "type": "library", "extra": { @@ -461,7 +461,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.0" + "source": "https://github.com/guzzle/promises/tree/2.0.2" }, "funding": [ { @@ -477,20 +477,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T13:50:22+00:00" + "time": "2023-12-03T20:19:20+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.2", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", "shasum": "" }, "require": { @@ -504,9 +504,9 @@ "psr/http-message-implementation": "1.0" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", + "bamarni/composer-bin-plugin": "^1.8.2", "http-interop/http-factory-tests": "^0.9", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "phpunit/phpunit": "^8.5.36 || ^9.6.15" }, "suggest": { "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" @@ -577,7 +577,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.2" }, "funding": [ { @@ -593,7 +593,7 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-12-03T20:05:35+00:00" }, { "name": "paragonie/random_compat", @@ -773,31 +773,26 @@ }, { "name": "php-http/promise", - "version": "1.1.0", + "version": "1.3.0", "source": { "type": "git", "url": "https://github.com/php-http/promise.git", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88" + "reference": "2916a606d3b390f4e9e8e2b8dd68581508be0f07" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-http/promise/zipball/4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", - "reference": "4c4c1f9b7289a2ec57cde7f1e9762a5789506f88", + "url": "https://api.github.com/repos/php-http/promise/zipball/2916a606d3b390f4e9e8e2b8dd68581508be0f07", + "reference": "2916a606d3b390f4e9e8e2b8dd68581508be0f07", "shasum": "" }, "require": { "php": "^7.1 || ^8.0" }, "require-dev": { - "friends-of-phpspec/phpspec-code-coverage": "^4.3.2", - "phpspec/phpspec": "^5.1.2 || ^6.2" + "friends-of-phpspec/phpspec-code-coverage": "^4.3.2 || ^6.3", + "phpspec/phpspec": "^5.1.2 || ^6.2 || ^7.4" }, "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.1-dev" - } - }, "autoload": { "psr-4": { "Http\\Promise\\": "src/" @@ -824,9 +819,9 @@ ], "support": { "issues": "https://github.com/php-http/promise/issues", - "source": "https://github.com/php-http/promise/tree/1.1.0" + "source": "https://github.com/php-http/promise/tree/1.3.0" }, - "time": "2020-07-07T09:29:14+00:00" + "time": "2024-01-04T18:49:48+00:00" }, { "name": "prestashop/decimal", @@ -1228,16 +1223,16 @@ }, { "name": "psr/http-client", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/php-fig/http-client.git", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31" + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-fig/http-client/zipball/0955afe48220520692d2d09f7ab7e0f93ffd6a31", - "reference": "0955afe48220520692d2d09f7ab7e0f93ffd6a31", + "url": "https://api.github.com/repos/php-fig/http-client/zipball/bb5906edc1c324c9a05aa0873d40117941e5fa90", + "reference": "bb5906edc1c324c9a05aa0873d40117941e5fa90", "shasum": "" }, "require": { @@ -1274,9 +1269,9 @@ "psr-18" ], "support": { - "source": "https://github.com/php-fig/http-client/tree/1.0.2" + "source": "https://github.com/php-fig/http-client" }, - "time": "2023-04-10T20:12:12+00:00" + "time": "2023-09-23T14:17:50+00:00" }, { "name": "psr/http-factory", @@ -2231,16 +2226,16 @@ }, { "name": "symfony/polyfill-ctype", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/5bbc823adecdae860bb64756d639ecfec17b050a", - "reference": "5bbc823adecdae860bb64756d639ecfec17b050a", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -2254,9 +2249,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2293,7 +2285,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -2309,20 +2301,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/8ad114f6b39e2c98a8b0e3bd907732c207c2b534", - "reference": "8ad114f6b39e2c98a8b0e3bd907732c207c2b534", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -2336,9 +2328,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2376,7 +2365,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -2392,7 +2381,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php70", @@ -2464,16 +2453,16 @@ }, { "name": "symfony/polyfill-php73", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php73.git", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9" + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/9e8ecb5f92152187c4799efd3c96b78ccab18ff9", - "reference": "9e8ecb5f92152187c4799efd3c96b78ccab18ff9", + "url": "https://api.github.com/repos/symfony/polyfill-php73/zipball/21bd091060673a1177ae842c0ef8fe30893114d2", + "reference": "21bd091060673a1177ae842c0ef8fe30893114d2", "shasum": "" }, "require": { @@ -2481,9 +2470,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2523,7 +2509,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php73/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php73/tree/v1.29.0" }, "funding": [ { @@ -2539,20 +2525,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", - "reference": "7a6ff3f1959bb01aefccb463a0f2cd3d3d2fd936", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -2560,9 +2546,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -2606,7 +2589,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -2622,7 +2605,7 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/service-contracts", @@ -2709,16 +2692,16 @@ }, { "name": "symfony/var-exporter", - "version": "v5.4.21", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/var-exporter.git", - "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4" + "reference": "abb0a151b62d6b07e816487e20040464af96cae7" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-exporter/zipball/be74908a6942fdd331554b3cec27ff41b45ccad4", - "reference": "be74908a6942fdd331554b3cec27ff41b45ccad4", + "url": "https://api.github.com/repos/symfony/var-exporter/zipball/abb0a151b62d6b07e816487e20040464af96cae7", + "reference": "abb0a151b62d6b07e816487e20040464af96cae7", "shasum": "" }, "require": { @@ -2762,7 +2745,7 @@ "serialize" ], "support": { - "source": "https://github.com/symfony/var-exporter/tree/v5.4.21" + "source": "https://github.com/symfony/var-exporter/tree/v5.4.35" }, "funding": [ { @@ -2778,7 +2761,7 @@ "type": "tidelift" } ], - "time": "2023-02-21T19:46:44+00:00" + "time": "2024-01-23T13:51:25+00:00" }, { "name": "symfony/yaml", @@ -2984,16 +2967,16 @@ }, { "name": "composer/semver", - "version": "3.3.2", + "version": "3.4.0", "source": { "type": "git", "url": "https://github.com/composer/semver.git", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9" + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/composer/semver/zipball/3953f23262f2bff1919fc82183ad9acb13ff62c9", - "reference": "3953f23262f2bff1919fc82183ad9acb13ff62c9", + "url": "https://api.github.com/repos/composer/semver/zipball/35e8d0af4486141bc745f23a29cc2091eb624a32", + "reference": "35e8d0af4486141bc745f23a29cc2091eb624a32", "shasum": "" }, "require": { @@ -3043,9 +3026,9 @@ "versioning" ], "support": { - "irc": "irc://irc.freenode.org/composer", + "irc": "ircs://irc.libera.chat:6697/composer", "issues": "https://github.com/composer/semver/issues", - "source": "https://github.com/composer/semver/tree/3.3.2" + "source": "https://github.com/composer/semver/tree/3.4.0" }, "funding": [ { @@ -3061,7 +3044,7 @@ "type": "tidelift" } ], - "time": "2022-04-01T19:23:25+00:00" + "time": "2023-08-31T09:50:34+00:00" }, { "name": "composer/xdebug-handler", @@ -3207,16 +3190,16 @@ }, { "name": "doctrine/deprecations", - "version": "v1.1.1", + "version": "1.1.3", "source": { "type": "git", "url": "https://github.com/doctrine/deprecations.git", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3" + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/deprecations/zipball/612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", - "reference": "612a3ee5ab0d5dd97b7cf3874a6efe24325efac3", + "url": "https://api.github.com/repos/doctrine/deprecations/zipball/dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", + "reference": "dfbaa3c2d2e9a9df1118213f3b8b0c597bb99fab", "shasum": "" }, "require": { @@ -3248,9 +3231,9 @@ "homepage": "https://www.doctrine-project.org/", "support": { "issues": "https://github.com/doctrine/deprecations/issues", - "source": "https://github.com/doctrine/deprecations/tree/v1.1.1" + "source": "https://github.com/doctrine/deprecations/tree/1.1.3" }, - "time": "2023-06-03T09:27:29+00:00" + "time": "2024-01-30T19:34:25+00:00" }, { "name": "doctrine/instantiator", @@ -3324,16 +3307,16 @@ }, { "name": "doctrine/lexer", - "version": "2.1.0", + "version": "2.1.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124" + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", - "reference": "39ab8fcf5a51ce4b85ca97c7a7d033eb12831124", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", + "reference": "861c870e8b75f7c8f69c146c7f89cc1c0f1b49b6", "shasum": "" }, "require": { @@ -3341,11 +3324,11 @@ "php": "^7.1 || ^8.0" }, "require-dev": { - "doctrine/coding-standard": "^9 || ^10", + "doctrine/coding-standard": "^9 || ^12", "phpstan/phpstan": "^1.3", - "phpunit/phpunit": "^7.5 || ^8.5 || ^9.5", + "phpunit/phpunit": "^7.5 || ^8.5 || ^9.6", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^4.11 || ^5.0" + "vimeo/psalm": "^4.11 || ^5.21" }, "type": "library", "autoload": { @@ -3382,7 +3365,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/2.1.0" + "source": "https://github.com/doctrine/lexer/tree/2.1.1" }, "funding": [ { @@ -3398,7 +3381,7 @@ "type": "tidelift" } ], - "time": "2022-12-14T08:49:07+00:00" + "time": "2024-02-05T11:35:39+00:00" }, { "name": "friendsofphp/php-cs-fixer", @@ -3509,6 +3492,92 @@ ], "time": "2021-11-15T17:17:55+00:00" }, + { + "name": "monolog/monolog", + "version": "1.27.1", + "source": { + "type": "git", + "url": "https://github.com/Seldaek/monolog.git", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Seldaek/monolog/zipball/904713c5929655dc9b97288b69cfeedad610c9a1", + "reference": "904713c5929655dc9b97288b69cfeedad610c9a1", + "shasum": "" + }, + "require": { + "php": ">=5.3.0", + "psr/log": "~1.0" + }, + "provide": { + "psr/log-implementation": "1.0.0" + }, + "require-dev": { + "aws/aws-sdk-php": "^2.4.9 || ^3.0", + "doctrine/couchdb": "~1.0@dev", + "graylog2/gelf-php": "~1.0", + "php-amqplib/php-amqplib": "~2.4", + "php-console/php-console": "^3.1.3", + "phpstan/phpstan": "^0.12.59", + "phpunit/phpunit": "~4.5", + "ruflin/elastica": ">=0.90 <3.0", + "sentry/sentry": "^0.13", + "swiftmailer/swiftmailer": "^5.3|^6.0" + }, + "suggest": { + "aws/aws-sdk-php": "Allow sending log messages to AWS services like DynamoDB", + "doctrine/couchdb": "Allow sending log messages to a CouchDB server", + "ext-amqp": "Allow sending log messages to an AMQP server (1.0+ required)", + "ext-mongo": "Allow sending log messages to a MongoDB server", + "graylog2/gelf-php": "Allow sending log messages to a GrayLog2 server", + "mongodb/mongodb": "Allow sending log messages to a MongoDB server via PHP Driver", + "php-amqplib/php-amqplib": "Allow sending log messages to an AMQP server using php-amqplib", + "php-console/php-console": "Allow sending log messages to Google Chrome", + "rollbar/rollbar": "Allow sending log messages to Rollbar", + "ruflin/elastica": "Allow sending log messages to an Elastic Search server", + "sentry/sentry": "Allow sending log messages to a Sentry server" + }, + "type": "library", + "autoload": { + "psr-4": { + "Monolog\\": "src/Monolog" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Jordi Boggiano", + "email": "j.boggiano@seld.be", + "homepage": "http://seld.be" + } + ], + "description": "Sends your logs to files, sockets, inboxes, databases and various web services", + "homepage": "http://github.com/Seldaek/monolog", + "keywords": [ + "log", + "logging", + "psr-3" + ], + "support": { + "issues": "https://github.com/Seldaek/monolog/issues", + "source": "https://github.com/Seldaek/monolog/tree/1.27.1" + }, + "funding": [ + { + "url": "https://github.com/Seldaek", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/monolog/monolog", + "type": "tidelift" + } + ], + "time": "2022-06-09T08:53:42+00:00" + }, { "name": "myclabs/deep-copy", "version": "1.11.1", @@ -4473,16 +4542,16 @@ }, { "name": "sebastian/code-unit-reverse-lookup", - "version": "1.0.2", + "version": "1.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/code-unit-reverse-lookup.git", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619" + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/1de8cd5c010cb153fcd68b8d0f64606f523f7619", - "reference": "1de8cd5c010cb153fcd68b8d0f64606f523f7619", + "url": "https://api.github.com/repos/sebastianbergmann/code-unit-reverse-lookup/zipball/92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", + "reference": "92a1a52e86d34cde6caa54f1b5ffa9fda18e5d54", "shasum": "" }, "require": { @@ -4516,7 +4585,7 @@ "homepage": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/", "support": { "issues": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/issues", - "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.2" + "source": "https://github.com/sebastianbergmann/code-unit-reverse-lookup/tree/1.0.3" }, "funding": [ { @@ -4524,7 +4593,7 @@ "type": "github" } ], - "time": "2020-11-30T08:15:22+00:00" + "time": "2024-03-01T13:45:45+00:00" }, { "name": "sebastian/comparator", @@ -5032,16 +5101,16 @@ }, { "name": "squizlabs/php_codesniffer", - "version": "3.7.2", + "version": "3.9.0", "source": { "type": "git", - "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" + "url": "https://github.com/PHPCSStandards/PHP_CodeSniffer.git", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", - "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", + "url": "https://api.github.com/repos/PHPCSStandards/PHP_CodeSniffer/zipball/d63cee4890a8afaf86a22e51ad4d97c91dd4579b", + "reference": "d63cee4890a8afaf86a22e51ad4d97c91dd4579b", "shasum": "" }, "require": { @@ -5051,11 +5120,11 @@ "php": ">=5.4.0" }, "require-dev": { - "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0" + "phpunit/phpunit": "^4.0 || ^5.0 || ^6.0 || ^7.0 || ^8.0 || ^9.3.4" }, "bin": [ - "bin/phpcs", - "bin/phpcbf" + "bin/phpcbf", + "bin/phpcs" ], "type": "library", "extra": { @@ -5070,22 +5139,45 @@ "authors": [ { "name": "Greg Sherwood", - "role": "lead" + "role": "Former lead" + }, + { + "name": "Juliette Reinders Folmer", + "role": "Current lead" + }, + { + "name": "Contributors", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer/graphs/contributors" } ], "description": "PHP_CodeSniffer tokenizes PHP, JavaScript and CSS files and detects violations of a defined set of coding standards.", - "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", + "homepage": "https://github.com/PHPCSStandards/PHP_CodeSniffer", "keywords": [ "phpcs", "standards", "static analysis" ], "support": { - "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", - "source": "https://github.com/squizlabs/PHP_CodeSniffer", - "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" + "issues": "https://github.com/PHPCSStandards/PHP_CodeSniffer/issues", + "security": "https://github.com/PHPCSStandards/PHP_CodeSniffer/security/policy", + "source": "https://github.com/PHPCSStandards/PHP_CodeSniffer", + "wiki": "https://github.com/PHPCSStandards/PHP_CodeSniffer/wiki" }, - "time": "2023-02-22T23:07:41+00:00" + "funding": [ + { + "url": "https://github.com/PHPCSStandards", + "type": "github" + }, + { + "url": "https://github.com/jrfnl", + "type": "github" + }, + { + "url": "https://opencollective.com/php_codesniffer", + "type": "open_collective" + } + ], + "time": "2024-02-16T15:06:51+00:00" }, { "name": "symfony/console", @@ -5326,16 +5418,16 @@ }, { "name": "symfony/event-dispatcher-contracts", - "version": "v1.1.13", + "version": "v1.10.0", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher-contracts.git", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e" + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/1d5cd762abaa6b2a4169d3e77610193a7157129e", - "reference": "1d5cd762abaa6b2a4169d3e77610193a7157129e", + "url": "https://api.github.com/repos/symfony/event-dispatcher-contracts/zipball/761c8b8387cfe5f8026594a75fdf0a4e83ba6974", + "reference": "761c8b8387cfe5f8026594a75fdf0a4e83ba6974", "shasum": "" }, "require": { @@ -5385,7 +5477,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.1.13" + "source": "https://github.com/symfony/event-dispatcher-contracts/tree/v1.10.0" }, "funding": [ { @@ -5401,7 +5493,7 @@ "type": "tidelift" } ], - "time": "2022-01-02T09:41:36+00:00" + "time": "2022-07-20T09:59:04+00:00" }, { "name": "symfony/finder", @@ -5535,16 +5627,16 @@ }, { "name": "symfony/polyfill-php72", - "version": "v1.27.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97" + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/869329b1e9894268a8a61dabb69153029b7a8c97", - "reference": "869329b1e9894268a8a61dabb69153029b7a8c97", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", "shasum": "" }, "require": { @@ -5552,9 +5644,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.27-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5591,7 +5680,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.27.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" }, "funding": [ { @@ -5607,20 +5696,20 @@ "type": "tidelift" } ], - "time": "2022-11-03T14:55:06+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v5.4.24", + "version": "v5.4.36", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64" + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/e3c46cc5689c8782944274bb30702106ecbe3b64", - "reference": "e3c46cc5689c8782944274bb30702106ecbe3b64", + "url": "https://api.github.com/repos/symfony/process/zipball/4fdf34004f149cc20b2f51d7d119aa500caad975", + "reference": "4fdf34004f149cc20b2f51d7d119aa500caad975", "shasum": "" }, "require": { @@ -5653,7 +5742,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v5.4.24" + "source": "https://github.com/symfony/process/tree/v5.4.36" }, "funding": [ { @@ -5669,20 +5758,20 @@ "type": "tidelift" } ], - "time": "2023-05-17T11:26:05+00:00" + "time": "2024-02-12T15:49:53+00:00" }, { "name": "symfony/stopwatch", - "version": "v5.4.21", + "version": "v5.4.35", "source": { "type": "git", "url": "https://github.com/symfony/stopwatch.git", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee" + "reference": "887762aa99ff16f65dc8b48aafead415f942d407" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/stopwatch/zipball/f83692cd869a6f2391691d40a01e8acb89e76fee", - "reference": "f83692cd869a6f2391691d40a01e8acb89e76fee", + "url": "https://api.github.com/repos/symfony/stopwatch/zipball/887762aa99ff16f65dc8b48aafead415f942d407", + "reference": "887762aa99ff16f65dc8b48aafead415f942d407", "shasum": "" }, "require": { @@ -5715,7 +5804,7 @@ "description": "Provides a way to profile code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/stopwatch/tree/v5.4.21" + "source": "https://github.com/symfony/stopwatch/tree/v5.4.35" }, "funding": [ { @@ -5731,7 +5820,7 @@ "type": "tidelift" } ], - "time": "2023-02-14T08:03:56+00:00" + "time": "2024-01-23T13:51:25+00:00" } ], "aliases": [], diff --git a/src/Http/PsrHttpClientAdapter.php b/src/Http/PsrHttpClientAdapter.php index fac735f2d..513f4b5e7 100644 --- a/src/Http/PsrHttpClientAdapter.php +++ b/src/Http/PsrHttpClientAdapter.php @@ -45,12 +45,12 @@ public function sendRequest(RequestInterface $request) { try { $response = $this->client->sendRequest($request); - } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { + } catch (\GuzzleHttp\Ring\Exception\ConnectException $exception) { // @phpstan-ignore-line // Guzzle 5.3 use RingPHP for the low level connection - throw new NetworkException($exception->getMessage(), $request, $exception); - } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { + throw new NetworkException($exception->getMessage(), $request, $exception); // @phpstan-ignore-line + } catch (\GuzzleHttp\Ring\Exception\RingException $exception) { // @phpstan-ignore-line // Guzzle 5.3 use RingPHP for the low level connection - throw new TransferException($exception->getMessage(), 0, $exception); + throw new TransferException($exception->getMessage(), 0, $exception); // @phpstan-ignore-line } // Guzzle 5.3 does not throw exceptions on 4xx and 5xx status codes From 725c3204021f1d85a7466d3b2836490826ab35f1 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Mon, 4 Mar 2024 14:07:01 +0100 Subject: [PATCH 134/343] Fix hummingbird --- .../default-selectors-ps1_7-hummingbird.js | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js index 6a0d68c1b..22dc2e21e 100644 --- a/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js +++ b/_dev/js/front/src/service/query-selector.service/default-selectors/default-selectors-ps1_7-hummingbird.js @@ -43,5 +43,21 @@ export const DefaultSelectors1_7Hummingbird = { PAY_LATER_OFFER_MESSAGE_CONTAINER_PRODUCT: '.product__prices', PAY_LATER_OFFER_MESSAGE_CONTAINER_CART_SUMMARY: '.cart-summary__totals', - PAY_LATER_BANNER_CONTAINER: '#notifications .container' + PAY_LATER_BANNER_CONTAINER: '#notifications .container', + + CARD_FIELDS: { + FORM: '#ps_checkout-card-fields-form', + NAME: '#ps_checkout-card-fields-name', + NUMBER: '#ps_checkout-card-fields-number', + EXPIRY: '#ps_checkout-card-fields-expiry', + CVV: '#ps_checkout-card-fields-cvv', + NAME_ERROR: '#ps_checkout-card-fields-name-error', + NUMBER_ERROR: '#ps_checkout-card-fields-number-error', + VENDOR_ERROR: '#ps_checkout-card-fields-vendor-error', + EXPIRY_ERROR: '#ps_checkout-card-fields-expiry-error', + CVV_ERROR: '#ps_checkout-card-fields-cvv-error', + }, + + PAYMENT_METHOD_LOGO_PRODUCT_CONTAINER: '#product .product-add-to-cart', + PAYMENT_METHOD_LOGO_CART_CONTAINER: '#cart .cart-summary .cart-detailed-actions' }; From 994563c9a7ee38dfc066f29e0467046fdf0649d0 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Mon, 4 Mar 2024 15:47:12 +0200 Subject: [PATCH 135/343] Added wrapper for cvv field and tooltip --- .../src/components/common/card-fields.component.js | 2 +- views/css/payments.css | 8 ++++++++ views/css/payments16.css | 8 ++++++++ views/templates/hook/partials/cardFields.tpl | 12 +++++++----- 4 files changed, 24 insertions(+), 6 deletions(-) diff --git a/_dev/js/front/src/components/common/card-fields.component.js b/_dev/js/front/src/components/common/card-fields.component.js index 07cbbdf03..db87a7d34 100644 --- a/_dev/js/front/src/components/common/card-fields.component.js +++ b/_dev/js/front/src/components/common/card-fields.component.js @@ -201,7 +201,7 @@ export class CardFieldsComponent extends BaseComponent { color: 'black' }, body: { - padding: '0px' + padding: '0 0 5px 0' } }, ...(this.configPayPal.hostedFieldsCustomization || {}), diff --git a/views/css/payments.css b/views/css/payments.css index d2076bfc4..97db35383 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -503,3 +503,11 @@ label[for="ps_checkout-hosted-fields-card-cvv"] { #cart #ps_checkout-payment-method-logo-block-container { text-align: center; } + +#ps_checkout-card-fields-form .ps_checkout-card-fields-cvv-label-wrapper { + display: table; +} + +#ps_checkout-card-fields-form .ps_checkout-card-fields-cvv-label-wrapper>label { + display: table-cell; +} diff --git a/views/css/payments16.css b/views/css/payments16.css index 152579c2d..e306f4037 100644 --- a/views/css/payments16.css +++ b/views/css/payments16.css @@ -473,3 +473,11 @@ #ps_checkout-canceled img, #ps_checkout-error img{ margin-right: 10px; } + +#ps_checkout-card-fields-form .ps_checkout-card-fields-cvv-label-wrapper { + display: table; +} + +#ps_checkout-card-fields-form .ps_checkout-card-fields-cvv-label-wrapper>label { + display: table-cell; +} diff --git a/views/templates/hook/partials/cardFields.tpl b/views/templates/hook/partials/cardFields.tpl index 0443c5718..69bd482fe 100644 --- a/views/templates/hook/partials/cardFields.tpl +++ b/views/templates/hook/partials/cardFields.tpl @@ -48,12 +48,14 @@
- -
-
i - +
diff --git a/views/templates/hook/partials/vaultTokenForm.tpl b/views/templates/hook/partials/vaultTokenForm.tpl index d22e96559..42f20f95e 100644 --- a/views/templates/hook/partials/vaultTokenForm.tpl +++ b/views/templates/hook/partials/vaultTokenForm.tpl @@ -28,7 +28,7 @@ *}
- {l s='Delete' mod='ps_checkout'} +
{if !$isFavorite} From daf28438eaf381e41d73548605c8ebc8ae2ea96d Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 10 Apr 2024 13:38:33 +0300 Subject: [PATCH 211/343] Added loader to token delete and fixed token ordering --- .../src/components/common/loader.component.js | 11 +++++++++-- .../components/common/payment-token.component.js | 14 ++++++++++++-- ps_checkout.php | 1 + src/Database/TableManager.php | 6 ++++-- src/Repository/PaymentTokenRepository.php | 12 ++++++------ upgrade/upgrade-8.4.0.0.php | 6 ++++-- 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/_dev/js/front/src/components/common/loader.component.js b/_dev/js/front/src/components/common/loader.component.js index 89f6f891b..38a8bb256 100644 --- a/_dev/js/front/src/components/common/loader.component.js +++ b/_dev/js/front/src/components/common/loader.component.js @@ -26,6 +26,8 @@ export class LoaderComponent extends BaseComponent { }; created() { + this.data.text = this.props.text || this.$('loader-component.label.body'); + this.data.header = this.props.header || this.$('loader-component.label.header'); this.data.parent = this.querySelectorService.getLoaderParent(); } @@ -38,7 +40,7 @@ export class LoaderComponent extends BaseComponent { this.text = document.createElement('h1'); this.text.classList.add('ps-checkout', 'text'); - this.text.innerHTML = this.$('loader-component.label.header'); + this.text.innerHTML = this.data.header; this.loader = document.createElement('img'); this.loader.classList.add('ps-checkout', 'loader'); @@ -47,7 +49,7 @@ export class LoaderComponent extends BaseComponent { this.subtext = document.createElement('div'); this.subtext.classList.add('ps-checkout', 'subtext'); - this.text.innerHTML = this.$('loader-component.label.body'); + this.text.innerHTML = this.data.text; this.popup.append(this.text); this.popup.append(this.loader); @@ -68,4 +70,9 @@ export class LoaderComponent extends BaseComponent { this.overlay.classList.remove('visible'); document.body.style.overflow = ''; } + + destroy() { + this.overlay.classList.add('visible'); + this.overlay.remove(); + } } diff --git a/_dev/js/front/src/components/common/payment-token.component.js b/_dev/js/front/src/components/common/payment-token.component.js index 839cf6e76..c680f8436 100644 --- a/_dev/js/front/src/components/common/payment-token.component.js +++ b/_dev/js/front/src/components/common/payment-token.component.js @@ -18,6 +18,7 @@ */ import { BaseComponent } from '../../core/dependency-injection/base.component'; import {ModalComponent} from "./modal.component"; +import {LoaderComponent} from "./loader.component"; /** * @typedef PaymentTokenComponentProps @@ -56,7 +57,6 @@ export class PaymentTokenComponent extends BaseComponent { this.data.disabled = false; this.data.modal = null; - console.log(this.data.HTMLElementContainer, this.data.HTMLElementRadio, this.data.HTMLElementForm); } showModal() { @@ -89,12 +89,22 @@ export class PaymentTokenComponent extends BaseComponent { onDeleteConfirm() { const vaultId = this.getVaultFormData().vaultId; + const loader = new LoaderComponent( + this.app, + {text: this.$('checkout.payment.loader.processing-request')} + ).render(); + + loader.show(); this.psCheckoutApi.postDeleteVaultedToken({vaultId}).then(() => { this.data.disabled = true; this.data.HTMLElementRadio.checked = false; this.data.HTMLElementContainer.remove(); this.data.HTMLElementForm.remove(); - }).catch((error) => this.handleError(error)); + loader.destroy(); + }).catch((error) => { + loader.destroy(); + this.handleError(error); + }); } getDeleteButton() { diff --git a/ps_checkout.php b/ps_checkout.php index 43a609ede..2294c1391 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -1080,6 +1080,7 @@ public function hookActionFrontControllerSetMedia() 'checkout.payment.token.delete.modal.header' => $this->l('Delete this payment method?'), 'checkout.payment.token.delete.modal.content' => $this->l('The following payment method will be deleted from your account:'), 'checkout.payment.token.delete.modal.confirm-button' => $this->l('Delete payment method'), + 'checkout.payment.loader.processing-request' => $this->l('Please wait, we are processing your request'), ], ]); diff --git a/src/Database/TableManager.php b/src/Database/TableManager.php index 6b2181f32..dee65c965 100644 --- a/src/Database/TableManager.php +++ b/src/Database/TableManager.php @@ -160,13 +160,15 @@ public function createTable() ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; ') && $this->db->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'pscheckout_payment_token` ( - `id` varchar(50) NOT NULL, + `id` INT UNSIGNED AUTO_INCREMENT, + `token_id` varchar(50) NOT NULL, `paypal_customer_id` varchar(50) NOT NULL, `payment_source` varchar(50) NOT NULL, `data` text NOT NULL, `merchant_id` varchar(50) NOT NULL, `status` varchar(50) NOT NULL, - PRIMARY KEY (`id`, `paypal_customer_id`) + `is_favorite` tinyint(1) unsigned DEFAULT 0 NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; '); diff --git a/src/Repository/PaymentTokenRepository.php b/src/Repository/PaymentTokenRepository.php index d18fbecdf..baefcbf3d 100644 --- a/src/Repository/PaymentTokenRepository.php +++ b/src/Repository/PaymentTokenRepository.php @@ -53,7 +53,7 @@ public function save(PaymentToken $paymentToken) return $this->db->insert( PaymentToken::TABLE, [ - 'id' => pSQL($paymentToken->getId()->getValue()), + 'token_id' => pSQL($paymentToken->getId()->getValue()), 'paypal_customer_id' => pSQL($paymentToken->getPayPalCustomerId()->getValue()), 'payment_source' => pSQL($paymentToken->getPaymentSource()), 'data' => pSQL(json_encode($paymentToken->getData())), @@ -80,7 +80,7 @@ public function save(PaymentToken $paymentToken) public function deleteById(PaymentTokenId $paymentTokenId) { try { - return $this->db->delete(PaymentToken::TABLE, sprintf('`id` = "%s"', pSQL($paymentTokenId->getValue()))); + return $this->db->delete(PaymentToken::TABLE, sprintf('`token_id` = "%s"', pSQL($paymentTokenId->getValue()))); } catch (Exception $exception) { throw new PsCheckoutException('Error while deleting PayPal Payment Token', 0, $exception); } @@ -102,7 +102,7 @@ public function findByPrestaShopCustomerId($psCustomerId, $onlyVaulted = false) $query->leftJoin('pscheckout_customer', 'c', 't.`paypal_customer_id` = c.`paypal_customer_id`'); $query->where(sprintf('c.`id_customer` = %d', (int) $psCustomerId)); $query->orderBy('t.`is_favorite` DESC'); - $query->orderBy('t.`id` ASC'); + $query->orderBy('t.`id` DESC'); if ($onlyVaulted) { $query->where('t.`status` = "VAULTED"'); @@ -165,7 +165,7 @@ public function findById(PaymentTokenId $id) $query = new DbQuery(); $query->select('*'); $query->from(PaymentToken::TABLE); - $query->where(sprintf('`id` = "%s"', pSQL($id->getValue()))); + $query->where(sprintf('`token_id` = "%s"', pSQL($id->getValue()))); $result = $this->db->getRow($query); } catch (Exception $exception) { throw new PsCheckoutException('Error while fetching PayPal Payment Token', 0, $exception); @@ -194,7 +194,7 @@ public function setTokenFavorite(PaymentTokenId $paymentTokenId) try { $this->db->update(PaymentToken::TABLE, ['is_favorite' => 0], sprintf('`paypal_customer_id` = "%s"', pSQL($customerId))); - $this->db->update(PaymentToken::TABLE, ['is_favorite' => 1], sprintf('`id` = "%s"', pSQL($paymentTokenId->getValue()))); + $this->db->update(PaymentToken::TABLE, ['is_favorite' => 1], sprintf('`token_id` = "%s"', pSQL($paymentTokenId->getValue()))); } catch (Exception $exception) { throw new PsCheckoutException('Error while setting PayPal Payment Token as favorite', 0, $exception); } @@ -238,7 +238,7 @@ public function getCount($customerId = null) private function buildPaymentTokenObject(array $data) { return new PaymentToken( - $data['id'], + $data['token_id'], $data['paypal_customer_id'], $data['payment_source'], json_decode($data['data'], true), diff --git a/upgrade/upgrade-8.4.0.0.php b/upgrade/upgrade-8.4.0.0.php index b7a6e0f9c..2aef74926 100644 --- a/upgrade/upgrade-8.4.0.0.php +++ b/upgrade/upgrade-8.4.0.0.php @@ -101,13 +101,15 @@ function upgrade_module_8_4_0_0($module) '); $db->execute(' CREATE TABLE IF NOT EXISTS `' . _DB_PREFIX_ . 'pscheckout_payment_token` ( - `id` varchar(50) NOT NULL, + `id` INT UNSIGNED AUTO_INCREMENT, + `token_id` varchar(50) NOT NULL, `paypal_customer_id` varchar(50) NOT NULL, `payment_source` varchar(50) NOT NULL, `data` text NOT NULL, `merchant_id` varchar(50) NOT NULL, `status` varchar(50) NOT NULL, - PRIMARY KEY (`id`, `paypal_customer_id`) + `is_favorite` tinyint(1) unsigned DEFAULT 0 NOT NULL, + PRIMARY KEY (`id`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; '); $db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` CHANGE `paypal_status` `paypal_status` VARCHAR(30) NULL; '); From 0f240328a880ceddd5d63eb156492b2a6c82d004 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 10 Apr 2024 15:28:20 +0200 Subject: [PATCH 212/343] Do not send invalid email address --- src/Builder/Payload/OrderPayloadBuilder.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/Builder/Payload/OrderPayloadBuilder.php b/src/Builder/Payload/OrderPayloadBuilder.php index cbdcbe431..d886b8cb8 100644 --- a/src/Builder/Payload/OrderPayloadBuilder.php +++ b/src/Builder/Payload/OrderPayloadBuilder.php @@ -222,10 +222,13 @@ public function buildPayerNode() 'given_name' => (string) $this->cart['addresses']['invoice']->firstname, 'surname' => (string) $this->cart['addresses']['invoice']->lastname, ], - 'email_address' => (string) $this->cart['customer']->email, 'address' => $this->getAddressPortable('invoice'), ]; + if (\Validate::isEmail($this->cart['customer']->email)) { + $node['payer']['email_address'] = (string) $this->cart['customer']->email; + } + // Add optional birthdate if provided if (!empty($this->cart['customer']->birthday) && $this->cart['customer']->birthday !== '0000-00-00') { $node['payer']['birth_date'] = (string) $this->cart['customer']->birthday; From 3147cdf1d41df71cfd4f333419c4073323d5b608 Mon Sep 17 00:00:00 2001 From: Matt75 <5262628+Matt75@users.noreply.github.com> Date: Wed, 10 Apr 2024 16:33:44 +0200 Subject: [PATCH 213/343] Set card payment source only for card-fields --- .../Order/CommandHandler/CreatePayPalOrderCommandHandler.php | 2 +- .../Order/CommandHandler/UpdatePayPalOrderCommandHandler.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php index d3a9f4233..4cf0cddfb 100644 --- a/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/CreatePayPalOrderCommandHandler.php @@ -70,7 +70,7 @@ public function handle(CreatePayPalOrderCommand $command) { $cartPresenter = (new CartPresenter())->present(); $builder = new OrderPayloadBuilder($cartPresenter); - $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setIsCard($command->getFundingSource() === 'card' && $command->isHostedFields()); $builder->setExpressCheckout($command->isExpressCheckout()); if ($this->shopContext->isShop17()) { diff --git a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php index 57eb02a1c..e59ad0d5c 100644 --- a/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php +++ b/src/PayPal/Order/CommandHandler/UpdatePayPalOrderCommandHandler.php @@ -78,7 +78,7 @@ public function handle(UpdatePayPalOrderCommand $command) $builder = new OrderPayloadBuilder($cartPresenter, true); $builder->setIsUpdate(true); $builder->setPaypalOrderId($command->getPayPalOrderId()->getValue()); - $builder->setIsCard($command->getFundingSource() === 'card'); + $builder->setIsCard($command->getFundingSource() === 'card' && $command->isHostedFields()); $builder->setExpressCheckout($command->isExpressCheckout()); if ($this->shopContext->isShop17()) { From 497dce4cd20d755f24c2979ed177224aed51cb6c Mon Sep 17 00:00:00 2001 From: Laurynas Date: Wed, 10 Apr 2024 18:03:21 +0300 Subject: [PATCH 214/343] added unique key for payment token --- src/Database/TableManager.php | 3 ++- upgrade/upgrade-8.4.0.0.php | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/Database/TableManager.php b/src/Database/TableManager.php index dee65c965..61b204c93 100644 --- a/src/Database/TableManager.php +++ b/src/Database/TableManager.php @@ -168,7 +168,8 @@ public function createTable() `merchant_id` varchar(50) NOT NULL, `status` varchar(50) NOT NULL, `is_favorite` tinyint(1) unsigned DEFAULT 0 NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE KEY `token_id_merchant_id_paypal_customer_id` (`token_id`, `merchant_id`, `paypal_customer_id`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; '); diff --git a/upgrade/upgrade-8.4.0.0.php b/upgrade/upgrade-8.4.0.0.php index 2aef74926..95d8c0e70 100644 --- a/upgrade/upgrade-8.4.0.0.php +++ b/upgrade/upgrade-8.4.0.0.php @@ -109,7 +109,8 @@ function upgrade_module_8_4_0_0($module) `merchant_id` varchar(50) NOT NULL, `status` varchar(50) NOT NULL, `is_favorite` tinyint(1) unsigned DEFAULT 0 NOT NULL, - PRIMARY KEY (`id`) + PRIMARY KEY (`id`), + UNIQUE KEY `token_id_merchant_id_paypal_customer_id` (`token_id`, `merchant_id`, `paypal_customer_id`) ) ENGINE=' . _MYSQL_ENGINE_ . ' DEFAULT CHARSET=UTF8; '); $db->execute('ALTER TABLE `' . _DB_PREFIX_ . 'pscheckout_cart` CHANGE `paypal_status` `paypal_status` VARCHAR(30) NULL; '); From 17fe80aa8bc1e61705bb0f41f2f25c181b78673d Mon Sep 17 00:00:00 2001 From: Laurynas Date: Fri, 12 Apr 2024 10:37:51 +0300 Subject: [PATCH 215/343] Fixed lock icon smarty var and added favorite checkbox uncheck on vault checkbox uncheck --- ps_checkout.php | 3 ++- views/templates/hook/partials/vaultPaymentFields.tpl | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/ps_checkout.php b/ps_checkout.php index 2294c1391..484d6c164 100755 --- a/ps_checkout.php +++ b/ps_checkout.php @@ -610,6 +610,8 @@ public function hookPaymentOptions(array $params) $vaultingEnabled = $configurationPayPal->isVaultingEnabled() && $this->context->customer->isLogged(); + $this->context->smarty->assign('lockIcon', Media::getMediaPath(_PS_MODULE_DIR_ . $this->name . '/views/img/icons/lock_fill.svg')); + foreach ($fundingSourceProvider->getSavedTokens($cart->id_customer) as $fundingSource) { $paymentOption = new PrestaShop\PrestaShop\Core\Payment\PaymentOption(); $paymentOption->setModuleName($this->name . '-' . $fundingSource->name); @@ -622,7 +624,6 @@ public function hookPaymentOptions(array $params) 'isFavorite' => $fundingSource->isFavorite, 'label' => $fundingSource->label, 'vaultId' => explode('-', $fundingSource->name)[1], - 'lockIcon' => Media::getMediaPath(_PS_MODULE_DIR_ . $this->name . '/views/img/icons/lock_fill.svg'), ]); $paymentOption->setForm($this->context->smarty->fetch('module:ps_checkout/views/templates/hook/partials/vaultTokenForm.tpl')); diff --git a/views/templates/hook/partials/vaultPaymentFields.tpl b/views/templates/hook/partials/vaultPaymentFields.tpl index 8b4411029..5790cc4fc 100644 --- a/views/templates/hook/partials/vaultPaymentFields.tpl +++ b/views/templates/hook/partials/vaultPaymentFields.tpl @@ -48,6 +48,9 @@ const vaultCheckbox{$paymentIdentifier} = document.getElementById('ps_checkout-vault-payment-{$paymentIdentifier}'); const favoriteCheckbox{$paymentIdentifier} = document.getElementById('ps_checkout-favorite-payment-{$paymentIdentifier}'); vaultCheckbox{$paymentIdentifier}.addEventListener('change', (event) => { + if (!event.target.checked) { + favoriteCheckbox{$paymentIdentifier}.checked = false; + } favoriteCheckbox{$paymentIdentifier}.toggleAttribute('disabled', !event.target.checked); }); From e5887a59739afe074ed82cb0bfea8ec9dfbd00c0 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Fri, 12 Apr 2024 11:21:38 +0300 Subject: [PATCH 216/343] Made hert icon only appear on favorite payment method --- .../js/front/src/components/common/payment-token.component.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/_dev/js/front/src/components/common/payment-token.component.js b/_dev/js/front/src/components/common/payment-token.component.js index c680f8436..1db1066e6 100644 --- a/_dev/js/front/src/components/common/payment-token.component.js +++ b/_dev/js/front/src/components/common/payment-token.component.js @@ -195,11 +195,11 @@ export class PaymentTokenComponent extends BaseComponent { } renderFavoriteImg() { - if (this.data.HTMLElementLabel) { + if (this.data.HTMLElementLabel && !document.getElementById(`ps_checkout-favorite-payment-${this.data.name}`)) { const img = document.createElement('img'); img.classList.add('ps-checkout', 'icon-favorite'); img.style.display = 'inline-block'; - img.src = this.config.iconPath + (document.getElementById(`ps_checkout-favorite-payment-${this.data.name}`) ? 'favorite.svg' : 'favorite_fill.svg'); + img.src = this.config.iconPath + 'favorite_fill.svg'; this.data.HTMLElementLabel.append(img); } From 1e2ba921c19ce76ea5ed1cb198013f80658b0b46 Mon Sep 17 00:00:00 2001 From: Laurynas Date: Fri, 12 Apr 2024 16:08:23 +0300 Subject: [PATCH 217/343] Added description specifying that the payment method is vaulted --- src/FundingSource/FundingSourcePresenter.php | 11 ++++------- .../FundingSourceTranslationProvider.php | 9 ++++----- views/css/payments.css | 1 + views/templates/hook/partials/vaultTokenForm.tpl | 13 +++++++++++-- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/FundingSource/FundingSourcePresenter.php b/src/FundingSource/FundingSourcePresenter.php index 5a8feb696..addad1d1d 100644 --- a/src/FundingSource/FundingSourcePresenter.php +++ b/src/FundingSource/FundingSourcePresenter.php @@ -78,18 +78,15 @@ public function present($entity, $isAdmin) */ public function presentPaymentToken(PaymentToken $paymentToken) { + $paymentSource = $paymentToken->getData()['payment_source'][$paymentToken->getPaymentSource()]; + if ($paymentToken->getPaymentSource() === 'card') { - $cardDetails = $paymentToken->getData()['payment_source']['card']; $fundingSourceName = $this->translation->getVaultedPaymentMethodName( - true, - (isset($cardDetails['brand']) ? $cardDetails['brand'] : '') . (isset($cardDetails['last_digits']) ? ' *' . $cardDetails['last_digits'] : '') + (isset($paymentSource['brand']) ? $paymentSource['brand'] : '') . (isset($paymentSource['last_digits']) ? ' *' . $paymentSource['last_digits'] : '') ); } else { $fundingSourceName = $this->translation->getVaultedPaymentMethodName( - false, - isset($paymentToken->getData()['payment_source'][$paymentToken->getPaymentSource()]['email_address']) ? - $paymentToken->getData()['payment_source'][$paymentToken->getPaymentSource()]['email_address'] : - '' + isset($paymentSource['email_address']) ? $paymentSource['email_address'] : '' ); } diff --git a/src/FundingSource/FundingSourceTranslationProvider.php b/src/FundingSource/FundingSourceTranslationProvider.php index 1bf45526e..e1deeafb9 100644 --- a/src/FundingSource/FundingSourceTranslationProvider.php +++ b/src/FundingSource/FundingSourceTranslationProvider.php @@ -64,8 +64,7 @@ public function __construct(Module $module) 'maxima' => 'Maxima', 'mercadopago' => 'Mercado Pago', 'sepa' => 'SEPA', - 'token-card' => $module->l('Saved payment method %s', 'fundingsourcetranslationprovider'), - 'token-account' => $module->l('Saved payment method PayPal %s', 'fundingsourcetranslationprovider'), + 'token' => $module->l('Pay with %s', 'fundingsourcetranslationprovider'), ]; $payByTranslation = $module->l('Pay by %s', 'fundingsourcetranslationprovider'); @@ -101,13 +100,13 @@ public function getPaymentMethodName($fundingSource) } /** - * @param bool $isCard + * @param string $identifier * * @return string */ - public function getVaultedPaymentMethodName($isCard, $data) + public function getVaultedPaymentMethodName($identifier) { - return str_replace('%s', $data, $this->fundingSourceNames['token-' . ($isCard ? 'card' : 'account')]); + return str_replace('%s', $identifier, $this->fundingSourceNames['token']); } /** diff --git a/views/css/payments.css b/views/css/payments.css index 9347fa53c..5fd041b55 100755 --- a/views/css/payments.css +++ b/views/css/payments.css @@ -586,6 +586,7 @@ form button.ps_checkout-vault-token-delete { display: inline-block; background-color: transparent; border: none; + padding: 0; } #ps_checkout-card-fields-form .ps_checkout-card-fields-cvv-label-wrapper { diff --git a/views/templates/hook/partials/vaultTokenForm.tpl b/views/templates/hook/partials/vaultTokenForm.tpl index 42f20f95e..6ec9c046a 100644 --- a/views/templates/hook/partials/vaultTokenForm.tpl +++ b/views/templates/hook/partials/vaultTokenForm.tpl @@ -27,10 +27,19 @@ * Script tags will be removed and some HTML5 element can cause an Exception due to DOMDocument class *} -
+
+

+ {if $isFavorite} + {l s='This payment method has been saved to your account and defined as favorite for future purchases.' mod='ps_checkout'} + {else} + {l s='This payment method has been saved to your account.' mod='ps_checkout'} + {/if} +

+
+
-
+
{if !$isFavorite}