From 84bdea35e2582141ec69d756ba2d6a2631db4d21 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 11:36:17 +0300 Subject: [PATCH 001/357] Use reusable workflow for building package --- .distignore | 28 +++++++++++++++++++ .github/workflows/package.yml | 51 +++++------------------------------ 2 files changed, 34 insertions(+), 45 deletions(-) create mode 100644 .distignore diff --git a/.distignore b/.distignore new file mode 100644 index 000000000..ddc50d5b8 --- /dev/null +++ b/.distignore @@ -0,0 +1,28 @@ +.distignore +.editorconfig +.gitattributes +.gitignore +.ddev +.git* +.phpstorm* +.idea +*.env* +.psalm* +tests +phpcs.xml* +psalm*.xml* +phpunit.xml* +.phpunit.result.cache +resources +*.lock +webpack.config.js +wp-cli.yml +ngrok.yml +composer.json +package.json +package-lock.json +patchwork.json +.babelrc +README.md +wordpress_org_assets +.DS_Store diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 70f6fe58e..9ea7fcf45 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -7,50 +7,11 @@ on: description: 'Package version' required: false type: string - filePrefix: - description: 'File prefix' - required: false - type: string jobs: - package: - runs-on: ubuntu-latest - - env: - PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} - FILENAME: woocommerce-paypal-payments - - name: Build package - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: 7.2 - - - name: Fix plugin version input # Add the version number if only suffix entered - run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV - if: env.PACKAGE_VERSION && !contains(env.PACKAGE_VERSION, '.') - - - name: Set plugin version header - run: 'sed -Ei "s/Version: .*/Version: ${PACKAGE_VERSION}/g" woocommerce-paypal-payments.php' - if: env.PACKAGE_VERSION - - - name: Build - run: yarn build - - - name: Unzip # GH currently always zips, so if we upload a zip we get a zip inside a zip - run: unzip woocommerce-paypal-payments.zip -d dist - - - name: Set file name - env: - FILE_PREFIX: ${{ github.event.inputs.filePrefix }} - run: echo "FILENAME=$FILE_PREFIX-$FILENAME" >> $GITHUB_ENV - if: github.event.inputs.filePrefix - - - name: Upload - uses: actions/upload-artifact@v3 - with: - name: ${{ env.FILENAME }} - path: dist/ + create_archive: + uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main + with: + PHP_VERSION: 7.2 + PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php + PLUGIN_VERSION: ${{ inputs.packageVersion }} From a2e547c8cd51c730b8504ec6c1639b9d583828f3 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 15:09:16 +0300 Subject: [PATCH 002/357] Add assets compiler --- .distignore | 3 + .gitignore | 1 + assets-compiler.json | 4 + composer.json | 11 ++ composer.lock | 180 ++++++++++++------ modules/ppcp-blocks/assets-compiler.json | 5 + modules/ppcp-button/assets-compiler.json | 5 + modules/ppcp-compat/assets-compiler.json | 5 + modules/ppcp-onboarding/assets-compiler.json | 5 + .../ppcp-order-tracking/assets-compiler.json | 5 + .../ppcp-subscription/assets-compiler.json | 5 + modules/ppcp-uninstall/assets-compiler.json | 5 + modules/ppcp-vaulting/assets-compiler.json | 5 + modules/ppcp-wc-gateway/assets-compiler.json | 5 + modules/ppcp-webhooks/assets-compiler.json | 5 + 15 files changed, 192 insertions(+), 57 deletions(-) create mode 100644 assets-compiler.json create mode 100644 modules/ppcp-blocks/assets-compiler.json create mode 100644 modules/ppcp-button/assets-compiler.json create mode 100644 modules/ppcp-compat/assets-compiler.json create mode 100644 modules/ppcp-onboarding/assets-compiler.json create mode 100644 modules/ppcp-order-tracking/assets-compiler.json create mode 100644 modules/ppcp-subscription/assets-compiler.json create mode 100644 modules/ppcp-uninstall/assets-compiler.json create mode 100644 modules/ppcp-vaulting/assets-compiler.json create mode 100644 modules/ppcp-wc-gateway/assets-compiler.json create mode 100644 modules/ppcp-webhooks/assets-compiler.json diff --git a/.distignore b/.distignore index ddc50d5b8..6f044625a 100644 --- a/.distignore +++ b/.distignore @@ -14,6 +14,7 @@ psalm*.xml* phpunit.xml* .phpunit.result.cache resources +node_modules *.lock webpack.config.js wp-cli.yml @@ -21,6 +22,8 @@ ngrok.yml composer.json package.json package-lock.json +.composer_compiled_assets +assets-compiler.json patchwork.json .babelrc README.md diff --git a/.gitignore b/.gitignore index ff5c7fa98..7a516c31e 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,4 @@ modules/ppcp-wc-gateway/assets/css auth.json .DS_Store tests/.DS_Store +.composer_compiled_assets diff --git a/assets-compiler.json b/assets-compiler.json new file mode 100644 index 000000000..9badf65f8 --- /dev/null +++ b/assets-compiler.json @@ -0,0 +1,4 @@ +{ + "dependencies": "install", + "commands": "yarn" +} diff --git a/composer.json b/composer.json index 4a94eb54b..d8096a337 100644 --- a/composer.json +++ b/composer.json @@ -14,6 +14,7 @@ "symfony/polyfill-php80": "^1.19" }, "require-dev": { + "inpsyde/composer-assets-compiler": "^2.5", "psr/container": "^1.0", "dhii/module-interface": "^0.2 || ^0.3", "container-interop/service-provider": "^0.4.0", @@ -68,6 +69,15 @@ "vendor/bin/phpcbf" ] }, + "composer-asset-compiler": { + "auto-discover": true, + "packages": { + "inpsyde/*": { + "dependencies": "install", + "script": "build" + } + } + }, "mozart": { "dep_namespace": "WooCommerce\\PayPalCommerce\\Vendor\\", "dep_directory": "/lib/packages/", @@ -83,6 +93,7 @@ }, "config": { "allow-plugins": { + "inpsyde/composer-assets-compiler": true, "dealerdirect/phpcodesniffer-composer-installer": true, "wikimedia/composer-merge-plugin": true } diff --git a/composer.lock b/composer.lock index 295f011ee..9cb383547 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": "97559eb2b9d8b6f1529d44abd2604f99", + "content-hash": "282f8541c4ce8de1e5c061e197e395fa", "packages": [ { "name": "container-interop/service-provider", @@ -521,16 +521,16 @@ }, { "name": "wikimedia/composer-merge-plugin", - "version": "v2.0.1", + "version": "v2.1.0", "source": { "type": "git", "url": "https://github.com/wikimedia/composer-merge-plugin.git", - "reference": "8ca2ed8ab97c8ebce6b39d9943e9909bb4f18912" + "reference": "a03d426c8e9fb2c9c569d9deeb31a083292788bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/8ca2ed8ab97c8ebce6b39d9943e9909bb4f18912", - "reference": "8ca2ed8ab97c8ebce6b39d9943e9909bb4f18912", + "url": "https://api.github.com/repos/wikimedia/composer-merge-plugin/zipball/a03d426c8e9fb2c9c569d9deeb31a083292788bc", + "reference": "a03d426c8e9fb2c9c569d9deeb31a083292788bc", "shasum": "" }, "require": { @@ -539,9 +539,12 @@ }, "require-dev": { "composer/composer": "^1.1||^2.0", - "php-parallel-lint/php-parallel-lint": "~1.1.0", + "ext-json": "*", + "mediawiki/mediawiki-phan-config": "0.11.1", + "php-parallel-lint/php-parallel-lint": "~1.3.1", + "phpspec/prophecy": "~1.15.0", "phpunit/phpunit": "^8.5||^9.0", - "squizlabs/php_codesniffer": "~3.5.4" + "squizlabs/php_codesniffer": "~3.7.1" }, "type": "composer-plugin", "extra": { @@ -568,9 +571,9 @@ "description": "Composer plugin to merge multiple composer.json files", "support": { "issues": "https://github.com/wikimedia/composer-merge-plugin/issues", - "source": "https://github.com/wikimedia/composer-merge-plugin/tree/v2.0.1" + "source": "https://github.com/wikimedia/composer-merge-plugin/tree/v2.1.0" }, - "time": "2021-02-24T05:28:06+00:00" + "time": "2023-04-15T19:07:00+00:00" }, { "name": "wp-oop/wordpress-interface", @@ -1712,6 +1715,71 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "inpsyde/composer-assets-compiler", + "version": "2.5.2", + "source": { + "type": "git", + "url": "https://github.com/inpsyde/composer-asset-compiler.git", + "reference": "26a4062c2171ac09492e684eb3e4462ec69cf3a6" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inpsyde/composer-asset-compiler/zipball/26a4062c2171ac09492e684eb3e4462ec69cf3a6", + "reference": "26a4062c2171ac09492e684eb3e4462ec69cf3a6", + "shasum": "" + }, + "require": { + "composer-plugin-api": "^1 || ^2", + "ext-json": "*", + "php": ">= 7.2" + }, + "require-dev": { + "composer/composer": "^2.1.9", + "inpsyde/php-coding-standards": "^1@dev", + "mikey179/vfsstream": "^1.6.10", + "mockery/mockery": "^1.3.5", + "phpunit/phpunit": "^8.5.21", + "vimeo/psalm": ">=4.11.2@stable" + }, + "type": "composer-plugin", + "extra": { + "class": "Inpsyde\\AssetsCompiler\\Composer\\Plugin", + "branch-alias": { + "dev-master": "2.x-dev", + "dev-v1.x": "1.x-dev", + "dev-v2.x": "2.x-dev", + "dev-v3.x": "3.x-dev" + } + }, + "autoload": { + "psr-4": { + "Inpsyde\\AssetsCompiler\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Inpsyde GmbH", + "email": "hello@inpsyde.com", + "homepage": "https://inpsyde.com/", + "role": "Company" + }, + { + "name": "Giuseppe Mazzapica", + "email": "g.mazzapica@inpsyde.com" + } + ], + "description": "Composer plugin that installs and compile frontend dependencies for installed Composer packages based on configuration.", + "support": { + "issues": "https://github.com/inpsyde/composer-asset-compiler/issues", + "source": "https://github.com/inpsyde/composer-asset-compiler/tree/2.5.2" + }, + "time": "2022-03-07T13:50:29+00:00" + }, { "name": "mockery/mockery", "version": "1.3.6", @@ -1842,16 +1910,16 @@ }, { "name": "netresearch/jsonmapper", - "version": "v4.1.0", + "version": "v4.2.0", "source": { "type": "git", "url": "https://github.com/cweiske/jsonmapper.git", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f" + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", - "reference": "cfa81ea1d35294d64adb9c68aa4cb9e92400e53f", + "url": "https://api.github.com/repos/cweiske/jsonmapper/zipball/f60565f8c0566a31acf06884cdaa591867ecc956", + "reference": "f60565f8c0566a31acf06884cdaa591867ecc956", "shasum": "" }, "require": { @@ -1887,22 +1955,22 @@ "support": { "email": "cweiske@cweiske.de", "issues": "https://github.com/cweiske/jsonmapper/issues", - "source": "https://github.com/cweiske/jsonmapper/tree/v4.1.0" + "source": "https://github.com/cweiske/jsonmapper/tree/v4.2.0" }, - "time": "2022-12-08T20:46:14+00:00" + "time": "2023-04-09T17:37:40+00:00" }, { "name": "nikic/php-parser", - "version": "v4.15.4", + "version": "v4.17.1", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290" + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/6bb5176bc4af8bcb7d926f88718db9b96a2d4290", - "reference": "6bb5176bc4af8bcb7d926f88718db9b96a2d4290", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", + "reference": "a6303e50c90c355c7eeee2c4a8b27fe8dc8fef1d", "shasum": "" }, "require": { @@ -1943,9 +2011,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.4" + "source": "https://github.com/nikic/PHP-Parser/tree/v4.17.1" }, - "time": "2023-03-05T19:49:14+00:00" + "time": "2023-08-13T19:53:39+00:00" }, { "name": "openlss/lib-array2xml", @@ -2157,27 +2225,25 @@ }, { "name": "php-stubs/wordpress-stubs", - "version": "v5.9.5", + "version": "v5.9.6", "source": { "type": "git", "url": "https://github.com/php-stubs/wordpress-stubs.git", - "reference": "13ecf204a7e6d215a7c0d23e2aa27940fe617717" + "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/13ecf204a7e6d215a7c0d23e2aa27940fe617717", - "reference": "13ecf204a7e6d215a7c0d23e2aa27940fe617717", + "url": "https://api.github.com/repos/php-stubs/wordpress-stubs/zipball/6a18d938d0aef39d091505a4a35b025fb6c10098", + "reference": "6a18d938d0aef39d091505a4a35b025fb6c10098", "shasum": "" }, - "replace": { - "giacocorsiglia/wordpress-stubs": "*" - }, "require-dev": { "nikic/php-parser": "< 4.12.0", "php": "~7.3 || ~8.0", - "php-stubs/generator": "^0.8.1", + "php-stubs/generator": "^0.8.3", "phpdocumentor/reflection-docblock": "^5.3", - "phpstan/phpstan": "^1.2" + "phpstan/phpstan": "^1.10.12", + "phpunit/phpunit": "^9.5" }, "suggest": { "paragonie/sodium_compat": "Pure PHP implementation of libsodium", @@ -2198,9 +2264,9 @@ ], "support": { "issues": "https://github.com/php-stubs/wordpress-stubs/issues", - "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.9.5" + "source": "https://github.com/php-stubs/wordpress-stubs/tree/v5.9.6" }, - "time": "2022-11-09T05:32:14+00:00" + "time": "2023-05-18T04:34:27+00:00" }, { "name": "phpcompatibility/php-compatibility", @@ -3136,16 +3202,16 @@ }, { "name": "sebastian/diff", - "version": "3.0.3", + "version": "3.0.4", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211" + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/14f72dd46eaf2f2293cbe79c93cc0bc43161a211", - "reference": "14f72dd46eaf2f2293cbe79c93cc0bc43161a211", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/6296a0c086dd0117c1b78b059374d7fcbe7545ae", + "reference": "6296a0c086dd0117c1b78b059374d7fcbe7545ae", "shasum": "" }, "require": { @@ -3190,7 +3256,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/3.0.3" + "source": "https://github.com/sebastianbergmann/diff/tree/3.0.4" }, "funding": [ { @@ -3198,7 +3264,7 @@ "type": "github" } ], - "time": "2020-11-30T07:59:04+00:00" + "time": "2023-05-07T05:30:20+00:00" }, { "name": "sebastian/environment", @@ -3342,16 +3408,16 @@ }, { "name": "sebastian/global-state", - "version": "3.0.2", + "version": "3.0.3", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921" + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/de036ec91d55d2a9e0db2ba975b512cdb1c23921", - "reference": "de036ec91d55d2a9e0db2ba975b512cdb1c23921", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/66783ce213de415b451b904bfef9dda0cf9aeae0", + "reference": "66783ce213de415b451b904bfef9dda0cf9aeae0", "shasum": "" }, "require": { @@ -3394,7 +3460,7 @@ ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", - "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.2" + "source": "https://github.com/sebastianbergmann/global-state/tree/3.0.3" }, "funding": [ { @@ -3402,7 +3468,7 @@ "type": "github" } ], - "time": "2022-02-10T06:55:38+00:00" + "time": "2023-08-02T09:23:32+00:00" }, { "name": "sebastian/object-enumerator", @@ -3793,16 +3859,16 @@ }, { "name": "symfony/console", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9" + "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/c77433ddc6cdc689caf48065d9ea22ca0853fbd9", - "reference": "c77433ddc6cdc689caf48065d9ea22ca0853fbd9", + "url": "https://api.github.com/repos/symfony/console/zipball/b504a3d266ad2bb632f196c0936ef2af5ff6e273", + "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273", "shasum": "" }, "require": { @@ -3867,12 +3933,12 @@ "homepage": "https://symfony.com", "keywords": [ "cli", - "command line", + "command-line", "console", "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v5.4.21" + "source": "https://github.com/symfony/console/tree/v5.4.26" }, "funding": [ { @@ -3888,7 +3954,7 @@ "type": "tidelift" } ], - "time": "2023-02-25T16:59:41+00:00" + "time": "2023-07-19T20:11:33+00:00" }, { "name": "symfony/deprecation-contracts", @@ -4451,16 +4517,16 @@ }, { "name": "symfony/string", - "version": "v5.4.21", + "version": "v5.4.26", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f" + "reference": "1181fe9270e373537475e826873b5867b863883c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/edac10d167b78b1d90f46a80320d632de0bd9f2f", - "reference": "edac10d167b78b1d90f46a80320d632de0bd9f2f", + "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c", + "reference": "1181fe9270e373537475e826873b5867b863883c", "shasum": "" }, "require": { @@ -4517,7 +4583,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v5.4.21" + "source": "https://github.com/symfony/string/tree/v5.4.26" }, "funding": [ { @@ -4533,7 +4599,7 @@ "type": "tidelift" } ], - "time": "2023-02-22T08:00:55+00:00" + "time": "2023-06-28T12:46:07+00:00" }, { "name": "theseer/tokenizer", diff --git a/modules/ppcp-blocks/assets-compiler.json b/modules/ppcp-blocks/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-blocks/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-button/assets-compiler.json b/modules/ppcp-button/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-button/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-compat/assets-compiler.json b/modules/ppcp-compat/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-compat/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-onboarding/assets-compiler.json b/modules/ppcp-onboarding/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-onboarding/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-order-tracking/assets-compiler.json b/modules/ppcp-order-tracking/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-order-tracking/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-subscription/assets-compiler.json b/modules/ppcp-subscription/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-subscription/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-uninstall/assets-compiler.json b/modules/ppcp-uninstall/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-uninstall/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-vaulting/assets-compiler.json b/modules/ppcp-vaulting/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-vaulting/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-wc-gateway/assets-compiler.json b/modules/ppcp-wc-gateway/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-wc-gateway/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} diff --git a/modules/ppcp-webhooks/assets-compiler.json b/modules/ppcp-webhooks/assets-compiler.json new file mode 100644 index 000000000..1eb956051 --- /dev/null +++ b/modules/ppcp-webhooks/assets-compiler.json @@ -0,0 +1,5 @@ +{ + "dependencies": "install", + "script": "build", + "commands": "yarn" +} From 4848513754a43065eaf69766a88892db3cde8706 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 15:49:39 +0300 Subject: [PATCH 003/357] Fix psalm --- modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php | 5 +---- modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php | 1 - 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php index d8cc06dca..f9bd95076 100644 --- a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php +++ b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php @@ -201,10 +201,7 @@ class WebhookEndpoint { $status_code = (int) wp_remote_retrieve_response_code( $response ); if ( 204 !== $status_code ) { - $json = null; - if ( is_array( $response ) ) { - $json = json_decode( $response['body'] ); - } + $json = json_decode( $response['body'] ); throw new PayPalApiException( $json, $status_code diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php index ede513aa2..ac9545986 100644 --- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php +++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php @@ -101,7 +101,6 @@ class PaymentCaptureRefunded implements RequestHandler { ) ); if ( is_wp_error( $refund ) ) { - assert( $refund instanceof WP_Error ); $message = sprintf( 'Order %1$s could not be refunded. %2$s', (string) $wc_order->get_id(), From 8c8c4e5efe6ad0a52b1b35ba8f2a3a5b2d643515 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 16:37:18 +0300 Subject: [PATCH 004/357] Add numeric version if needed --- .github/workflows/package.yml | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/.github/workflows/package.yml b/.github/workflows/package.yml index 9ea7fcf45..9c45ab7a7 100644 --- a/.github/workflows/package.yml +++ b/.github/workflows/package.yml @@ -9,9 +9,24 @@ on: type: string jobs: + check_version: + runs-on: ubuntu-latest + env: + PACKAGE_VERSION: ${{ github.event.inputs.packageVersion }} + outputs: + version: ${{ env.PACKAGE_VERSION }} + name: Check version + steps: + - uses: actions/checkout@v2 + - name: Fix plugin version input # Add the version number if only suffix entered + id: fix_version + run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV + if: env.PACKAGE_VERSION && !contains(env.PACKAGE_VERSION, '.') + create_archive: + needs: check_version uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main with: PHP_VERSION: 7.2 PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php - PLUGIN_VERSION: ${{ inputs.packageVersion }} + PLUGIN_VERSION: ${{ needs.check_version.outputs.version }} From 87c9beba574a7de7350ae755240bc2f8d20deffb Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 17:19:55 +0300 Subject: [PATCH 005/357] Replace old local archive command --- README.md | 3 ++- package.json | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index d640960c9..791c360cd 100644 --- a/README.md +++ b/README.md @@ -33,7 +33,8 @@ follow these steps: 1. Clone the repository and `cd` into it. 2. Make sure you have the version in the plugin root file updated. 3. Update the PayPal JavaScript SDK integration date by using the current date for the `PAYPAL_INTEGRATION_DATE` constant. -4. The following command should get you a ZIP file ready to be used on a WordPress site: +4. Install WP-CLI archive command: `wp package install wp-cli/dist-archive-command`. +5. The following command should get you a ZIP file ready to be used on a WordPress site: ``` $ yarn run build diff --git a/package.json b/package.json index 97fbd5df5..b22e4cd63 100644 --- a/package.json +++ b/package.json @@ -62,9 +62,7 @@ "prebuild": "rm -rf ./vendor && find . -name 'node_modules' -type d -maxdepth 3 -exec rm -rf {} +", "build": "composer install --no-dev && yarn install && yarn run archive", "prearchive": "rm -rf $npm_package_name.zip", - "archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*", - "postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name", - "archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\" + "archive": "wp dist-archive . ./$npm_package_name.zip --plugin-dirname=$npm_package_name" }, "config": { "wp_org_slug": "woocommerce-paypal-payments" From a7546fb55371f14d421e51b073d923efa9a57829 Mon Sep 17 00:00:00 2001 From: Alex P Date: Fri, 25 Aug 2023 17:20:12 +0300 Subject: [PATCH 006/357] Add to .distignore --- .distignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.distignore b/.distignore index 6f044625a..afd3a57f0 100644 --- a/.distignore +++ b/.distignore @@ -29,3 +29,5 @@ patchwork.json README.md wordpress_org_assets .DS_Store +auth.json +*.log From 3be8563870963442b0455a353e02c459d51ebd19 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 23 Nov 2023 17:43:56 +0000 Subject: [PATCH 007/357] Refactor app container to modularity. --- bootstrap.php | 74 +- composer.json | 6 +- composer.lock | 70 +- .../Container/ContainerConfigurator.php | 153 ++++ .../Container/PackageProxyContainer.php | 102 +++ .../Container/ReadOnlyContainer.php | 138 ++++ .../Modularity/Module/ExecutableModule.php | 21 + .../Modularity/Module/ExtendingModule.php | 24 + .../Modularity/Module/FactoryModule.php | 18 + .../Inpsyde/Modularity/Module/Module.php | 20 + .../Module/ModuleClassNameIdTrait.php | 23 + .../Modularity/Module/ServiceModule.php | 21 + lib/packages/Inpsyde/Modularity/Package.php | 727 ++++++++++++++++++ .../Modularity/Properties/BaseProperties.php | 217 ++++++ .../Properties/LibraryProperties.php | 209 +++++ .../Properties/PluginProperties.php | 176 +++++ .../Modularity/Properties/Properties.php | 139 ++++ .../Modularity/Properties/ThemeProperties.php | 131 ++++ .../Container/ContainerExceptionInterface.php | 3 - .../Psr/Container/ContainerInterface.php | 9 +- .../Container/NotFoundExceptionInterface.php | 3 - src/DhiiToModularityModule.php | 128 +++ 22 files changed, 2367 insertions(+), 45 deletions(-) create mode 100644 lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php create mode 100644 lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php create mode 100644 lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/FactoryModule.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/Module.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/ModuleClassNameIdTrait.php create mode 100644 lib/packages/Inpsyde/Modularity/Module/ServiceModule.php create mode 100644 lib/packages/Inpsyde/Modularity/Package.php create mode 100644 lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php create mode 100644 lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php create mode 100644 lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php create mode 100644 lib/packages/Inpsyde/Modularity/Properties/Properties.php create mode 100644 lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php create mode 100644 src/DhiiToModularityModule.php diff --git a/bootstrap.php b/bootstrap.php index fe4552110..fe9a0799b 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -11,8 +11,11 @@ use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeContainer; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\DelegatingContainer; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ProxyContainer; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; +use WooCommerce\PayPalCommerce\DhiiToModularityModule; return function ( string $root_dir, @@ -34,38 +37,47 @@ return function ( */ $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); - $providers = array_map( - function ( ModuleInterface $module ): ServiceProviderInterface { - return $module->setup(); - }, - $modules - ); + // Initialize plugin. + $properties = PluginProperties::new(__FILE__); + $bootstrap = Package::new($properties); - $provider = new CompositeCachingServiceProvider( $providers ); - $proxy_container = new ProxyContainer(); - // TODO: caching does not work currently, - // may want to consider fixing it later (pass proxy as parent to DelegatingContainer) - // for now not fixed since we were using this behavior for long time and fixing it now may break things. - $container = new DelegatingContainer( $provider ); - /** - * Skip iterable vs array check. - * - * @psalm-suppress PossiblyInvalidArgument - */ - $app_container = new CachingContainer( - new CompositeContainer( - array_merge( - $additional_containers, - array( $container ) - ) - ) - ); - $proxy_container->setInnerContainer( $app_container ); + $bootstrap->addModule( new DhiiToModularityModule( $modules ) ); + $bootstrap->boot(); - foreach ( $modules as $module ) { - /* @var $module ModuleInterface module */ - $module->run( $app_container ); - } + return $bootstrap->container(); - return $app_container; +// $providers = array_map( +// function ( ModuleInterface $module ): ServiceProviderInterface { +// return $module->setup(); +// }, +// $modules +// ); + +// $provider = new CompositeCachingServiceProvider( $providers ); +// $proxy_container = new ProxyContainer(); +// // TODO: caching does not work currently, +// // may want to consider fixing it later (pass proxy as parent to DelegatingContainer) +// // for now not fixed since we were using this behavior for long time and fixing it now may break things. +// $container = new DelegatingContainer( $provider ); +// /** +// * Skip iterable vs array check. +// * +// * @psalm-suppress PossiblyInvalidArgument +// */ +// $app_container = new CachingContainer( +// new CompositeContainer( +// array_merge( +// $additional_containers, +// array( $container ) +// ) +// ) +// ); +// $proxy_container->setInnerContainer( $app_container ); +// +// foreach ( $modules as $module ) { +// /* @var $module ModuleInterface module */ +// $module->run( $app_container ); +// } +// +// return $app_container; }; diff --git a/composer.json b/composer.json index bb2ccbf7c..96c9459ef 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "wikimedia/composer-merge-plugin": "^2.0", "wp-oop/wordpress-interface": "^0.1.0-alpha1", "dhii/versions": "^0.1.0-alpha1", - "symfony/polyfill-php80": "^1.19" + "symfony/polyfill-php80": "^1.19", + "inpsyde/modularity": "^1.7" }, "require-dev": { "psr/container": "^1.0", @@ -77,7 +78,8 @@ "packages": [ "psr/container", "dhii/containers", - "dhii/module-interface" + "dhii/module-interface", + "inpsyde/modularity" ], "delete_vendor_directories": true } diff --git a/composer.lock b/composer.lock index 450c96e96..36ce94f4e 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": "97559eb2b9d8b6f1529d44abd2604f99", + "content-hash": "214504278540f7c3d05f10d1e67920b1", "packages": [ { "name": "container-interop/service-provider", @@ -294,6 +294,74 @@ }, "time": "2021-12-08T16:54:50+00:00" }, + { + "name": "inpsyde/modularity", + "version": "1.7.2", + "source": { + "type": "git", + "url": "https://github.com/inpsyde/modularity.git", + "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inpsyde/modularity/zipball/43f4b3a7c8fbb585a44fb7a619e2546b338a8934", + "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.2", + "psr/container": "^1.1.0 || ^2" + }, + "require-dev": { + "brain/monkey": "^2.6.1", + "inpsyde/php-coding-standards": "^1", + "johnpbloch/wordpress-core": ">=5.8", + "mikey179/vfsstream": "^v1.6.10", + "php-stubs/wordpress-stubs": ">=5.8@stable", + "phpunit/phpunit": "^8.5.21 || ^9.6.7", + "vimeo/psalm": "^4.13.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Inpsyde\\Modularity\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Inpsyde GmbH", + "email": "hello@inpsyde.com", + "homepage": "https://inpsyde.com/", + "role": "Company" + }, + { + "name": "Christian Leucht", + "email": "c.leucht@inpsyde.com", + "role": "Developer" + }, + { + "name": "Giuseppe Mazzapica", + "email": "g.mazzapica@inpsyde.com", + "role": "Developer" + } + ], + "description": "Modular PSR-11 implementation for WordPress plugins, themes or libraries.", + "support": { + "issues": "https://github.com/inpsyde/modularity/issues", + "source": "https://github.com/inpsyde/modularity/tree/1.7.2" + }, + "time": "2023-11-16T11:19:47+00:00" + }, { "name": "psr/container", "version": "1.1.1", diff --git a/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php new file mode 100644 index 000000000..429869912 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/ContainerConfigurator.php @@ -0,0 +1,153 @@ + + */ + private $services = []; + + /** + * @var array + */ + private $factoryIds = []; + + /** + * @var array> + */ + private $extensions = []; + + /** + * @var ContainerInterface[] + */ + private $containers = []; + + /** + * @var null|ContainerInterface + */ + private $compiledContainer; + + /** + * ContainerConfigurator constructor. + * + * @param ContainerInterface[] $containers + */ + public function __construct(array $containers = []) + { + array_map([$this, 'addContainer'], $containers); + } + + /** + * Allowing to add child containers. + * + * @param ContainerInterface $container + */ + public function addContainer(ContainerInterface $container): void + { + $this->containers[] = $container; + } + + /** + * @param string $id + * @param callable(ContainerInterface $container):mixed $factory + */ + public function addFactory(string $id, callable $factory): void + { + $this->addService($id, $factory); + // We're using a hash table to detect later + // via isset() if a Service as a Factory. + $this->factoryIds[$id] = true; + } + + /** + * @param string $id + * @param callable(ContainerInterface $container):mixed $service + * + * @return void + */ + public function addService(string $id, callable $service): void + { + /* + * We are being intentionally permissive here, + * allowing a simple workflow for *intentional* overrides + * while accepting the (small?) risk of *accidental* overrides + * that could be hard to notice and debug. + * + * Clear a factory flag in case it was a factory. + * If needs be, it will get re-added after this function completes. + */ + unset($this->factoryIds[$id]); + + $this->services[$id] = $service; + } + + /** + * @param string $id + * + * @return bool + */ + public function hasService(string $id): bool + { + if (array_key_exists($id, $this->services)) { + return true; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + return true; + } + } + + return false; + } + + /** + * @param string $id + * @param callable(mixed $service, ContainerInterface $container):mixed $extender + * + * @return void + */ + public function addExtension(string $id, callable $extender): void + { + if (!isset($this->extensions[$id])) { + $this->extensions[$id] = []; + } + + $this->extensions[$id][] = $extender; + } + + /** + * @param string $id + * + * @return bool + */ + public function hasExtension(string $id): bool + { + return isset($this->extensions[$id]); + } + + /** + * Returns a read only version of this Container. + * + * @return ContainerInterface + */ + public function createReadOnlyContainer(): ContainerInterface + { + if (!$this->compiledContainer) { + $this->compiledContainer = new ReadOnlyContainer( + $this->services, + $this->factoryIds, + $this->extensions, + $this->containers + ); + } + + return $this->compiledContainer; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php new file mode 100644 index 000000000..2c8d79d5d --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/PackageProxyContainer.php @@ -0,0 +1,102 @@ +package = $package; + } + + /** + * @param string $id + * @return mixed + * + * @throws \Exception + */ + public function get(string $id) + { + $this->assertPackageBooted($id); + + return $this->container->get($id); + } + + /** + * @param string $id + * @return bool + * + * @throws \Exception + */ + public function has(string $id): bool + { + return $this->tryContainer() && $this->container->has($id); + } + + /** + * @return bool + * + * @throws \Exception + * @psalm-assert-if-true ContainerInterface $this->container + */ + private function tryContainer(): bool + { + if ($this->container) { + return true; + } + + /** TODO: We need a better way to deal with status checking besides equality */ + if ( + $this->package->statusIs(Package::STATUS_READY) + || $this->package->statusIs(Package::STATUS_BOOTED) + ) { + $this->container = $this->package->container(); + } + + return (bool)$this->container; + } + + /** + * @param string $id + * @return void + * + * @throws \Exception + * + * @psalm-assert ContainerInterface $this->container + */ + private function assertPackageBooted(string $id): void + { + if ($this->tryContainer()) { + return; + } + + $name = $this->package->name(); + $status = $this->package->statusIs(Package::STATUS_FAILED) + ? 'is errored' + : 'is not ready yet'; + + throw new class ("Error retrieving service {$id} because package {$name} {$status}.") + extends \Exception + implements ContainerExceptionInterface { + }; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php new file mode 100644 index 000000000..763ae23ac --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php @@ -0,0 +1,138 @@ + + */ + private $services; + + /** + * @var array + */ + private $factoryIds; + + /** + * @var array> + */ + private $extensions; + + /** + * Resolved factories. + * + * @var array + */ + private $resolvedServices = []; + + /** + * @var ContainerInterface[] + */ + private $containers; + + /** + * ReadOnlyContainer constructor. + * + * @param array $services + * @param array $factoryIds + * @param array> $extensions + * @param ContainerInterface[] $containers + */ + public function __construct( + array $services, + array $factoryIds, + array $extensions, + array $containers + ) { + $this->services = $services; + $this->factoryIds = $factoryIds; + $this->extensions = $extensions; + $this->containers = $containers; + } + + /** + * @param string $id + * + * @return mixed + */ + public function get(string $id) + { + if (array_key_exists($id, $this->resolvedServices)) { + return $this->resolvedServices[$id]; + } + + if (array_key_exists($id, $this->services)) { + $service = $this->services[$id]($this); + $resolved = $this->resolveExtensions($id, $service); + + if (!isset($this->factoryIds[$id])) { + $this->resolvedServices[$id] = $resolved; + unset($this->services[$id]); + } + + return $resolved; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + $service = $container->get($id); + + return $this->resolveExtensions($id, $service); + } + } + + throw new class ("Service with ID {$id} not found.") + extends \Exception + implements NotFoundExceptionInterface { + }; + } + + /** + * @param string $id + * + * @return bool + */ + public function has(string $id): bool + { + if (array_key_exists($id, $this->services)) { + return true; + } + + if (array_key_exists($id, $this->resolvedServices)) { + return true; + } + + foreach ($this->containers as $container) { + if ($container->has($id)) { + return true; + } + } + + return false; + } + + /** + * @param string $id + * @param mixed $service + * + * @return mixed + */ + private function resolveExtensions(string $id, $service) + { + if (!isset($this->extensions[$id])) { + return $service; + } + + foreach ($this->extensions[$id] as $extender) { + $service = $extender($service, $this); + } + + return $service; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php new file mode 100644 index 000000000..2f7771fb5 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php @@ -0,0 +1,21 @@ + + */ + public function extensions(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php new file mode 100644 index 000000000..6e686c0c2 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/FactoryModule.php @@ -0,0 +1,18 @@ + + */ + public function factories(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Module/Module.php b/lib/packages/Inpsyde/Modularity/Module/Module.php new file mode 100644 index 000000000..49dd0e5e3 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Module/Module.php @@ -0,0 +1,20 @@ + + */ + public function services(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Package.php b/lib/packages/Inpsyde/Modularity/Package.php new file mode 100644 index 000000000..f55eaeccb --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Package.php @@ -0,0 +1,727 @@ + + * $package = Package::new(); + * $package->boot(); + * + * $container = $package->container(); + * $container->has(Package::PROPERTIES); + * $container->get(Package::PROPERTIES); + * + * + * @var string + */ + public const PROPERTIES = 'properties'; + + /** + * Custom action to be used to add Modules to the package. + * It might also be used to access package properties. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_INIT), + * $callback + * ); + * + */ + public const ACTION_INIT = 'init'; + + /** + * Custom action which is triggered after the application + * is booted to access container and properties. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_READY), + * $callback + * ); + * + */ + public const ACTION_READY = 'ready'; + + /** + * Custom action which is triggered when a failure happens during the building stage. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_FAILED_BUILD), + * $callback + * ); + * + */ + public const ACTION_FAILED_BUILD = 'failed-build'; + + /** + * Custom action which is triggered when a failure happens during the booting stage. + * + * @example + * + * $package = Package::new(); + * + * add_action( + * $package->hookName(Package::ACTION_FAILED_BOOT), + * $callback + * ); + * + */ + public const ACTION_FAILED_BOOT = 'failed-boot'; + + /** + * Custom action which is triggered when a package is connected. + */ + public const ACTION_PACKAGE_CONNECTED = 'package-connected'; + + /** + * Custom action which is triggered when a package cannot be connected. + */ + public const ACTION_FAILED_CONNECTION = 'failed-connection'; + + /** + * Module states can be used to get information about your module. + * + * @example + * + * $package = Package::new(); + * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // false + * $package->boot(new SomeModule()); + * $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // true + * + */ + public const MODULE_ADDED = 'added'; + public const MODULE_NOT_ADDED = 'not-added'; + public const MODULE_REGISTERED = 'registered'; + public const MODULE_REGISTERED_FACTORIES = 'registered-factories'; + public const MODULE_EXTENDED = 'extended'; + public const MODULE_EXECUTED = 'executed'; + public const MODULE_EXECUTION_FAILED = 'executed-failed'; + public const MODULES_ALL = '*'; + + /** + * Custom states for the class. + * + * @example + * + * $package = Package::new(); + * $package->statusIs(Package::IDLE); // true + * $package->boot(); + * $package->statusIs(Package::BOOTED); // true + * + */ + public const STATUS_IDLE = 2; + public const STATUS_INITIALIZED = 4; + public const STATUS_MODULES_ADDED = 5; + public const STATUS_READY = 7; + public const STATUS_BOOTED = 8; + public const STATUS_FAILED = -8; + + /** + * Current state of the application. + * + * @see Package::STATUS_* + * + * @var int + */ + private $status = self::STATUS_IDLE; + + /** + * Contains the progress of all modules. + * + * @see Package::moduleProgress() + * + * @var array> + */ + private $moduleStatus = [self::MODULES_ALL => []]; + + /** + * Hashmap of where keys are names of connected packages, and values are boolean, true + * if connection was successful. + * + * @see Package::connect() + * + * @var array + */ + private $connectedPackages = []; + + /** + * @var list + */ + private $executables = []; + + /** + * @var Properties + */ + private $properties; + + /** + * @var ContainerConfigurator + */ + private $containerConfigurator; + + /** + * @var bool + */ + private $built = false; + + /** + * @var bool + */ + private $hasContainer = false; + + /** + * @var \Throwable|null + */ + private $lastError = null; + + /** + * @param Properties $properties + * @param ContainerInterface[] $containers + * + * @return Package + */ + public static function new(Properties $properties, ContainerInterface ...$containers): Package + { + return new self($properties, ...$containers); + } + + /** + * @param Properties $properties + * @param ContainerInterface[] $containers + */ + private function __construct(Properties $properties, ContainerInterface ...$containers) + { + $this->properties = $properties; + + $this->containerConfigurator = new ContainerConfigurator($containers); + $this->containerConfigurator->addService( + self::PROPERTIES, + static function () use ($properties) { + return $properties; + } + ); + } + + /** + * @param Module $module + * + * @return static + * @throws \Exception + */ + public function addModule(Module $module): Package + { + try { + $this->assertStatus(self::STATUS_IDLE, sprintf('add module %s', $module->id())); + + $registeredServices = $this->addModuleServices( + $module, + self::MODULE_REGISTERED + ); + $registeredFactories = $this->addModuleServices( + $module, + self::MODULE_REGISTERED_FACTORIES + ); + $extended = $this->addModuleServices( + $module, + self::MODULE_EXTENDED + ); + $isExecutable = $module instanceof ExecutableModule; + + // ExecutableModules are collected and executed on Package::boot() + // when the Container is being compiled. + if ($isExecutable) { + /** @var ExecutableModule $module */ + $this->executables[] = $module; + } + + $added = $registeredServices || $registeredFactories || $extended || $isExecutable; + $status = $added ? self::MODULE_ADDED : self::MODULE_NOT_ADDED; + $this->moduleProgress($module->id(), $status); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + } + + return $this; + } + + /** + * @param Package $package + * @return bool + * @throws \Exception + */ + public function connect(Package $package): bool + { + try { + if ($package === $this) { + return false; + } + + $packageName = $package->name(); + $errorData = ['package' => $packageName, 'status' => $this->status]; + $errorMessage = "Failed connecting package {$packageName}"; + + // Don't connect, if already connected + if (array_key_exists($packageName, $this->connectedPackages)) { + $error = "{$errorMessage} because it was already connected."; + do_action( + $this->hookName(self::ACTION_FAILED_CONNECTION), + $packageName, + new \WP_Error('already_connected', $error, $errorData) + ); + + throw new \Exception($error, 0, $this->lastError); + } + + // Don't connect, if already booted or boot failed + $failed = $this->statusIs(self::STATUS_FAILED); + if ($failed || $this->statusIs(self::STATUS_BOOTED)) { + $status = $failed ? 'errored' : 'booted'; + $error = "{$errorMessage} to a {$status} package."; + do_action( + $this->hookName(self::ACTION_FAILED_CONNECTION), + $packageName, + new \WP_Error("no_connect_on_{$status}", $error, $errorData) + ); + + throw new \Exception($error, 0, $this->lastError); + } + + $this->connectedPackages[$packageName] = true; + + // We put connected package's properties in this package's container, so that in modules + // "run" method we can access them if we need to. + $this->containerConfigurator->addService( + sprintf('%s.%s', $package->name(), self::PROPERTIES), + static function () use ($package): Properties { + return $package->properties(); + } + ); + + // If the other package is booted, we can obtain a container, otherwise + // we build a proxy container + $container = $package->statusIs(self::STATUS_BOOTED) + ? $package->container() + : new PackageProxyContainer($package); + + $this->containerConfigurator->addContainer($container); + + do_action( + $this->hookName(self::ACTION_PACKAGE_CONNECTED), + $packageName, + $this->status, + $container instanceof PackageProxyContainer + ); + + return true; + } catch (\Throwable $throwable) { + if (isset($packageName)) { + $this->connectedPackages[$packageName] = false; + } + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + + return false; + } + } + + /** + * @return static + */ + public function build(): Package + { + try { + // Don't allow building the application multiple times. + $this->assertStatus(self::STATUS_IDLE, 'build package'); + + do_action( + $this->hookName(self::ACTION_INIT), + $this + ); + // Changing the status here ensures we can not call this method again, and also we can not + // add new modules, because both this and `addModule()` methods check for idle status. + // For backward compatibility, adding new modules via `boot()` will still be possible, even + // if deprecated, at the condition that the container was not yet accessed at that point. + $this->progress(self::STATUS_INITIALIZED); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BUILD); + } finally { + $this->built = true; + } + + return $this; + } + + /** + * @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules. + * @return bool + * + * @throws \Throwable + */ + public function boot(Module ...$defaultModules): bool + { + try { + // Call build() if not called yet, and ensure any new module passed here is added + // as well, throwing if the container was already built. + $this->doBuild(...$defaultModules); + + // Don't allow booting the application multiple times. + $this->assertStatus(self::STATUS_MODULES_ADDED, 'boot application', '<'); + $this->assertStatus(self::STATUS_FAILED, 'boot application', '!='); + + $this->progress(self::STATUS_MODULES_ADDED); + + $this->doExecute(); + + $this->progress(self::STATUS_READY); + + do_action( + $this->hookName(self::ACTION_READY), + $this + ); + } catch (\Throwable $throwable) { + $this->handleFailure($throwable, self::ACTION_FAILED_BOOT); + + return false; + } + + $this->progress(self::STATUS_BOOTED); + + return true; + } + + /** + * @param Module ...$defaultModules + * @return void + */ + private function doBuild(Module ...$defaultModules): void + { + if ($defaultModules) { + $this->deprecatedArgument( + sprintf( + 'Passing default modules to %1$s::boot() is deprecated since version 1.7.0.' + . ' Please add modules via %1$s::addModule().', + __CLASS__ + ), + __METHOD__, + '1.7.0' + ); + } + + if (!$this->built) { + array_map([$this, 'addModule'], $defaultModules); + $this->build(); + + return; + } + + if ( + !$defaultModules + || ($this->status >= self::STATUS_MODULES_ADDED) + || ($this->statusIs(self::STATUS_FAILED)) + ) { + // if we don't have default modules, there's nothing to do, and if the status is beyond + // "modules added" or is failed, we do nothing as well and let `boot()` throw. + return; + } + + $backup = $this->status; + + try { + // simulate idle status to prevent `addModule()` from throwing + // only if we don't have a container yet + $this->hasContainer or $this->status = self::STATUS_IDLE; + + foreach ($defaultModules as $defaultModule) { + // If a module was added by `build()` or `addModule()` we can skip it, a + // deprecation was trigger to make it noticeable without breakage + if (!$this->moduleIs($defaultModule->id(), self::MODULE_ADDED)) { + $this->addModule($defaultModule); + } + } + } finally { + $this->status = $backup; + } + } + + /** + * @param Module $module + * @param string $status + * @return bool + */ + private function addModuleServices(Module $module, string $status): bool + { + $services = null; + $addCallback = null; + switch ($status) { + case self::MODULE_REGISTERED: + $services = $module instanceof ServiceModule ? $module->services() : null; + $addCallback = [$this->containerConfigurator, 'addService']; + break; + case self::MODULE_REGISTERED_FACTORIES: + $services = $module instanceof FactoryModule ? $module->factories() : null; + $addCallback = [$this->containerConfigurator, 'addFactory']; + break; + case self::MODULE_EXTENDED: + $services = $module instanceof ExtendingModule ? $module->extensions() : null; + $addCallback = [$this->containerConfigurator, 'addExtension']; + break; + } + + if (!$services) { + return false; + } + + $ids = []; + array_walk( + $services, + static function (callable $service, string $id) use ($addCallback, &$ids) { + /** @var callable(string, callable) $addCallback */ + $addCallback($id, $service); + /** @var list $ids */ + $ids[] = $id; + } + ); + /** @var list $ids */ + $this->moduleProgress($module->id(), $status, $ids); + + return true; + } + + /** + * @return void + * + * @throws \Throwable + */ + private function doExecute(): void + { + foreach ($this->executables as $executable) { + $success = $executable->run($this->container()); + $this->moduleProgress( + $executable->id(), + $success + ? self::MODULE_EXECUTED + : self::MODULE_EXECUTION_FAILED + ); + } + } + + /** + * @param string $moduleId + * @param string $status + * @param list|null $serviceIds + * + * @return void + */ + private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null) + { + isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = []; + $this->moduleStatus[$status][] = $moduleId; + + if (!$serviceIds || !$this->properties->isDebug()) { + $this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}"; + + return; + } + + $description = sprintf('%s %s (%s)', $moduleId, $status, implode(', ', $serviceIds)); + $this->moduleStatus[self::MODULES_ALL][] = $description; + } + + /** + * @return array> + */ + public function modulesStatus(): array + { + return $this->moduleStatus; + } + + /** + * @return array + */ + public function connectedPackages(): array + { + return $this->connectedPackages; + } + + /** + * @param string $packageName + * @return bool + */ + public function isPackageConnected(string $packageName): bool + { + return $this->connectedPackages[$packageName] ?? false; + } + + /** + * @param string $moduleId + * @param string $status + * + * @return bool + */ + public function moduleIs(string $moduleId, string $status): bool + { + return in_array($moduleId, $this->moduleStatus[$status] ?? [], true); + } + + /** + * Return the filter name to be used to extend modules of the plugin. + * + * If the plugin is single file `my-plugin.php` in plugins folder the filter name will be: + * `inpsyde.modularity.my-plugin`. + * + * If the plugin is in a sub-folder e.g. `my-plugin/index.php` the filter name will be: + * `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant. + * + * @param string $suffix + * + * @return string + * @see Package::name() + * + */ + public function hookName(string $suffix = ''): string + { + $filter = self::HOOK_PREFIX . $this->properties->baseName(); + + if ($suffix) { + $filter .= '.' . $suffix; + } + + return $filter; + } + + /** + * @return Properties + */ + public function properties(): Properties + { + return $this->properties; + } + + /** + * @return ContainerInterface + * + * @throws \Exception + */ + public function container(): ContainerInterface + { + $this->assertStatus(self::STATUS_INITIALIZED, 'obtain the container instance', '>='); + $this->hasContainer = true; + + return $this->containerConfigurator->createReadOnlyContainer(); + } + + /** + * @return string + */ + public function name(): string + { + return $this->properties->baseName(); + } + + /** + * @param int $status + */ + private function progress(int $status): void + { + $this->status = $status; + } + + /** + * @param int $status + * + * @return bool + */ + public function statusIs(int $status): bool + { + return $this->status === $status; + } + + /** + * @param \Throwable $throwable + * @param Package::ACTION_FAILED_* $action + * @return void + * @throws \Throwable + */ + private function handleFailure(\Throwable $throwable, string $action): void + { + $this->progress(self::STATUS_FAILED); + $hook = $this->hookName($action); + did_action($hook) or do_action($hook, $throwable); + + if ($this->properties->isDebug()) { + throw $throwable; + } + + $this->lastError = $throwable; + } + + /** + * @param int $status + * @param string $action + * @param string $operator + * + * @throws \Exception + * @psalm-suppress ArgumentTypeCoercion + */ + private function assertStatus(int $status, string $action, string $operator = '=='): void + { + if (!version_compare((string) $this->status, (string) $status, $operator)) { + throw new \Exception( + sprintf("Can't %s at this point of application.", $action), + 0, + $this->lastError + ); + } + } + + /** + * Similar to WP's `_deprecated_argument()`, but executes regardless of WP_DEBUG and without + * translated message (so without attempting loading translation files). + * + * @param string $message + * @param string $function + * @param string $version + * + * @return void + */ + private function deprecatedArgument(string $message, string $function, string $version): void + { + do_action('deprecated_argument_run', $function, $message, $version); + + if (apply_filters('deprecated_argument_trigger_error', true)) { + trigger_error($message, \E_USER_DEPRECATED); + } + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php new file mode 100644 index 000000000..47bb4db74 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php @@ -0,0 +1,217 @@ +sanitizeBaseName($baseName); + $basePath = (string) trailingslashit($basePath); + if ($baseUrl) { + $baseUrl = (string) trailingslashit($baseUrl); + } + + $this->baseName = $baseName; + $this->basePath = $basePath; + $this->baseUrl = $baseUrl; + $this->properties = array_replace(Properties::DEFAULT_PROPERTIES, $properties); + } + + /** + * @param string $name + * + * @return string + */ + protected function sanitizeBaseName(string $name): string + { + substr_count($name, '/') and $name = dirname($name); + + return strtolower(pathinfo($name, PATHINFO_FILENAME)); + } + + /** + * @return string + */ + public function baseName(): string + { + return $this->baseName; + } + + /** + * @return string + */ + public function basePath(): string + { + return $this->basePath; + } + + /** + * @return string|null + */ + public function baseUrl(): ?string + { + return $this->baseUrl; + } + + /** + * @return string + */ + public function author(): string + { + return (string) $this->get(self::PROP_AUTHOR); + } + + /** + * @return string + */ + public function authorUri(): string + { + return (string) $this->get(self::PROP_AUTHOR_URI); + } + + /** + * @return string + */ + public function description(): string + { + return (string) $this->get(self::PROP_DESCRIPTION); + } + + /** + * @return string + */ + public function textDomain(): string + { + return (string) $this->get(self::PROP_TEXTDOMAIN); + } + + /** + * @return string + */ + public function domainPath(): string + { + return (string) $this->get(self::PROP_DOMAIN_PATH); + } + + /** + * @return string + */ + public function name(): string + { + return (string) $this->get(self::PROP_NAME); + } + + /** + * @return string + */ + public function uri(): string + { + return (string) $this->get(self::PROP_URI); + } + + /** + * @return string + */ + public function version(): string + { + return (string) $this->get(self::PROP_VERSION); + } + + /** + * @return string|null + */ + public function requiresWp(): ?string + { + $value = $this->get(self::PROP_REQUIRES_WP); + + return $value && is_string($value) ? $value : null; + } + + /** + * @return string|null + */ + public function requiresPhp(): ?string + { + $value = $this->get(self::PROP_REQUIRES_PHP); + + return $value && is_string($value) ? $value : null; + } + + /** + * @return array + */ + public function tags(): array + { + return (array) $this->get(self::PROP_TAGS); + } + + /** + * @param string $key + * @param null $default + * @return mixed + */ + public function get(string $key, $default = null) + { + return $this->properties[$key] ?? $default; + } + + /** + * @param string $key + * @return bool + */ + public function has(string $key): bool + { + return isset($this->properties[$key]); + } + + /** + * @return bool + * @see Properties::isDebug() + */ + public function isDebug(): bool + { + if ($this->isDebug === null) { + $this->isDebug = defined('WP_DEBUG') && WP_DEBUG; + } + + return $this->isDebug; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php new file mode 100644 index 000000000..5c06577e1 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php @@ -0,0 +1,209 @@ + 0) { + $properties[self::PROP_AUTHOR] = implode(', ', $names); + } + + // Custom settings which can be stored in composer.json "extra.modularity" + $extra = $composerJsonData['extra']['modularity'] ?? []; + foreach (self::EXTRA_KEYS as $key) { + $properties[$key] = $extra[$key] ?? ''; + } + + // PHP requirement in composer.json "require" or "require-dev" + $properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData); + + // composer.json might have "version" in root + $version = $composerJsonData['version'] ?? null; + if ($version && is_string($version)) { + $properties[self::PROP_VERSION] = $version; + } + + [$baseName, $name] = static::buildNames($composerJsonData); + $basePath = dirname($composerJsonFile); + if (empty($properties[self::PROP_NAME])) { + $properties[self::PROP_NAME] = $name; + } + + return new self( + $baseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * @param array $composerJsonData + * + * @return array{string, string} + */ + private static function buildNames(array $composerJsonData): array + { + $composerName = (string) ($composerJsonData['name'] ?? ''); + $packageNamePieces = explode('/', $composerName, 2); + $basename = implode('-', $packageNamePieces); + // "inpsyde/foo-bar-baz" => "Inpsyde Foo Bar Baz" + $name = mb_convert_case( + str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)), + MB_CASE_TITLE + ); + + return [$basename, $name]; + } + + /** + * Check PHP version in require, require-dev. + * + * Attempt to parse requirements to find the _minimum_ accepted version (consistent with WP). + * Composer requirements are parsed in a way that, for example: + * `>=7.2` returns `7.2` + * `^7.3` returns `7.3` + * `5.6 || >= 7.1` returns `5.6` + * `>= 7.1 < 8` returns `7.1` + * + * @param array $composerData + * @param string $key + * + * @return string|null + */ + private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string + { + $nextKey = ($key === 'require') + ? 'require-dev' + : null; + $base = (array) ($composerData[$key] ?? []); + $requirement = $base['php'] ?? null; + $version = ($requirement && is_string($requirement)) + ? trim($requirement) + : null; + if (!$version) { + return $nextKey + ? static::extractPhpVersion($composerData, $nextKey) + : null; + } + + static $matcher; + $matcher or $matcher = static function (string $version): ?string { + $version = trim($version); + if (!$version) { + return null; + } + + // versions range like `>= 7.2.4 < 8` + if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // aliases like `dev-src#abcde as 7.4` + if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + // Basic requirements like 7.2, >=7.2, ^7.2, ~7.2 + if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) { + return trim($matches[1], " \t\n\r\0\x0B."); + } + + return null; + }; + + // support for simpler requirements like `7.3`, `>=7.4` or alternative like `5.6 || >=7` + + $alternatives = explode('||', $version); + $found = null; + foreach ($alternatives as $alternative) { + /** @var callable(string):?string $matcher */ + $itemFound = $matcher($alternative); + if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) { + $found = $itemFound; + } + } + + if ($found) { + return $found; + } + + return $nextKey + ? static::extractPhpVersion($composerData, $nextKey) + : null; + } + + /** + * @param string $url + * + * @return static + * + * @throws \Exception + */ + public function withBaseUrl(string $url): LibraryProperties + { + if ($this->baseUrl !== null) { + throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__)); + } + + $this->baseUrl = trailingslashit($url); + + return $this; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php new file mode 100644 index 000000000..f3d3f5485 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php @@ -0,0 +1,176 @@ + 'Author', + self::PROP_AUTHOR_URI => 'AuthorURI', + self::PROP_DESCRIPTION => 'Description', + self::PROP_DOMAIN_PATH => 'DomainPath', + self::PROP_NAME => 'Name', + self::PROP_TEXTDOMAIN => 'TextDomain', + self::PROP_URI => 'PluginURI', + self::PROP_VERSION => 'Version', + self::PROP_REQUIRES_WP => 'RequiresWP', + self::PROP_REQUIRES_PHP => 'RequiresPHP', + + // additional headers + self::PROP_NETWORK => 'Network', + ]; + + /** + * @var string + */ + private $pluginMainFile; + + /** + * @var string + */ + private $pluginBaseName; + + /** + * @var bool|null + */ + protected $isMu; + + /** + * @var bool|null + */ + protected $isActive; + + /** + * @var bool|null + */ + protected $isNetworkActive; + + /** + * @param string $pluginMainFile + * + * @return PluginProperties + */ + public static function new(string $pluginMainFile): PluginProperties + { + return new self($pluginMainFile); + } + + /** + * PluginProperties constructor. + * + * @param string $pluginMainFile + */ + protected function __construct(string $pluginMainFile) + { + if (!function_exists('get_plugin_data')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $pluginData = get_plugin_data($pluginMainFile); + $properties = Properties::DEFAULT_PROPERTIES; + + // Map pluginData to internal structure. + foreach (self::HEADERS as $key => $pluginDataKey) { + $properties[$key] = $pluginData[$pluginDataKey] ?? ''; + unset($pluginData[$pluginDataKey]); + } + $properties = array_merge($properties, $pluginData); + + $this->pluginMainFile = wp_normalize_path($pluginMainFile); + + $this->pluginBaseName = plugin_basename($pluginMainFile); + $basePath = plugin_dir_path($pluginMainFile); + $baseUrl = plugins_url('/', $pluginMainFile); + + parent::__construct( + $this->pluginBaseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * @return string + */ + public function pluginMainFile(): string + { + return $this->pluginMainFile; + } + + /** + * @return bool + * + * @psalm-suppress PossiblyFalseArgument + */ + public function network(): bool + { + return (bool) $this->get(self::PROP_NETWORK, false); + } + + /** + * @return bool + */ + public function isActive(): bool + { + if ($this->isActive === null) { + if (!function_exists('is_plugin_active')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $this->isActive = is_plugin_active($this->pluginBaseName); + } + + return $this->isActive; + } + + /** + * @return bool + */ + public function isNetworkActive(): bool + { + if ($this->isNetworkActive === null) { + if (!function_exists('is_plugin_active_for_network')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + $this->isNetworkActive = is_plugin_active_for_network($this->pluginBaseName); + } + + return $this->isNetworkActive; + } + + /** + * @return bool + */ + public function isMuPlugin(): bool + { + if ($this->isMu === null) { + /** + * @psalm-suppress UndefinedConstant + * @psalm-suppress MixedArgument + */ + $muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR); + $this->isMu = strpos($this->pluginMainFile, $muPluginDir) === 0; + } + + return $this->isMu; + } +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/Properties.php b/lib/packages/Inpsyde/Modularity/Properties/Properties.php new file mode 100644 index 000000000..8663d6883 --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/Properties.php @@ -0,0 +1,139 @@ + '', + self::PROP_AUTHOR_URI => '', + self::PROP_DESCRIPTION => '', + self::PROP_DOMAIN_PATH => '', + self::PROP_NAME => '', + self::PROP_TEXTDOMAIN => '', + self::PROP_URI => '', + self::PROP_VERSION => '', + self::PROP_REQUIRES_WP => null, + self::PROP_REQUIRES_PHP => null, + self::PROP_TAGS => [], + ]; + + /** + * @param string $key + * @param null $default + * + * @return mixed + */ + public function get(string $key, $default = null); + + /** + * @param string $key + * + * @return bool + */ + public function has(string $key): bool; + + /** + * @return bool + */ + public function isDebug(): bool; + + /** + * @return string + */ + public function baseName(): string; + + /** + * @return string + */ + public function basePath(): string; + + /** + * @return string|null + */ + public function baseUrl(): ?string; + + /** + * @return string + */ + public function author(): string; + + /** + * @return string + */ + public function authorUri(): string; + + /** + * @return string + */ + public function description(): string; + + /** + * @return string + */ + public function textDomain(): string; + + /** + * @return string + */ + public function domainPath(): string; + + /** + * The name of the plugin, theme or library. + * + * @return string + */ + public function name(): string; + + /** + * The home page of the plugin, theme or library. + * @return string + */ + public function uri(): string; + + /** + * @return string + */ + public function version(): string; + + /** + * Optional. Specify the minimum required WordPress version. + * + * @return string|null + */ + public function requiresWp(): ?string; + + /** + * Optional. Specify the minimum required PHP version. + * + * @return string + */ + public function requiresPhp(): ?string; + + /** + * Optional. Currently, only available for Theme and Library. + * Plugins do not have support for "tags"/"keywords" in header. + * + * @link https://developer.wordpress.org/reference/classes/wp_theme/#properties + * @link https://getcomposer.org/doc/04-schema.md#keywords + * + * @return array + */ + public function tags(): array; +} diff --git a/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php new file mode 100644 index 000000000..24a464caa --- /dev/null +++ b/lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php @@ -0,0 +1,131 @@ + 'Author', + self::PROP_AUTHOR_URI => 'AuthorURI', + self::PROP_DESCRIPTION => 'Description', + self::PROP_DOMAIN_PATH => 'DomainPath', + self::PROP_NAME => 'Name', + self::PROP_TEXTDOMAIN => 'TextDomain', + self::PROP_URI => 'ThemeURI', + self::PROP_VERSION => 'Version', + self::PROP_REQUIRES_WP => 'RequiresWP', + self::PROP_REQUIRES_PHP => 'RequiresPHP', + + // additional headers + self::PROP_STATUS => 'Status', + self::PROP_TAGS => 'Tags', + self::PROP_TEMPLATE => 'Template', + ]; + + /** + * @param string $themeDirectory + * + * @return ThemeProperties + */ + public static function new(string $themeDirectory): ThemeProperties + { + return new self($themeDirectory); + } + + /** + * ThemeProperties constructor. + * + * @param string $themeDirectory + */ + protected function __construct(string $themeDirectory) + { + if (!function_exists('wp_get_theme')) { + require_once ABSPATH . 'wp-includes/theme.php'; + } + + $theme = wp_get_theme($themeDirectory); + $properties = Properties::DEFAULT_PROPERTIES; + + foreach (self::HEADERS as $key => $themeKey) { + /** @psalm-suppress DocblockTypeContradiction */ + $properties[$key] = $theme->get($themeKey) ?? ''; + } + + $baseName = $theme->get_stylesheet(); + $basePath = $theme->get_stylesheet_directory(); + $baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri()); + + parent::__construct( + $baseName, + $basePath, + $baseUrl, + $properties + ); + } + + /** + * If the theme is published. + * + * @return string + */ + public function status(): string + { + return (string) $this->get(self::PROP_STATUS); + } + + public function template(): string + { + return (string) $this->get(self::PROP_TEMPLATE); + } + + /** + * @return bool + */ + public function isChildTheme(): bool + { + return (bool) $this->template(); + } + + /** + * @return bool + */ + public function isCurrentTheme(): bool + { + return get_stylesheet() === $this->baseName(); + } + + /** + * @return ThemeProperties|null + */ + public function parentThemeProperties(): ?ThemeProperties + { + $template = $this->template(); + if (!$template) { + return null; + } + + $parent = wp_get_theme($template, get_theme_root($template)); + + return static::new($parent->get_template_directory()); + } +} diff --git a/lib/packages/Psr/Container/ContainerExceptionInterface.php b/lib/packages/Psr/Container/ContainerExceptionInterface.php index e36fa7885..78ee05e4a 100644 --- a/lib/packages/Psr/Container/ContainerExceptionInterface.php +++ b/lib/packages/Psr/Container/ContainerExceptionInterface.php @@ -1,7 +1,4 @@ modules = $modules; + } + + private function setup(): void { + if ( $this->is_initialized ) { + return; + } + + $this->services = array(); + $this->extensions = array(); + + foreach ( $this->modules as $module ) { + $service_provider = $module->setup(); + + $this->services = array_merge( + $this->services, + $service_provider->getFactories() + ); + + foreach ( $service_provider->getExtensions() as $key => $extension ) { + if ( ! isset( $this->extensions[ $key ] ) ) { + $this->extensions[ $key ] = array(); + } + $this->extensions[ $key ][] = $extension; + } + } + + $this->is_initialized = true; + } + + /** + * Returns the services. + * + * @return array|callable[] + */ + public function services(): array { + $this->setup(); + return $this->services; + } + + /** + * Returns the extensions. + * + * @return array|callable[] + */ + public function extensions(): array { + $this->setup(); + + $map = array_map( function ( $extension_group ) { + return function ( $previous, ContainerInterface $container ) use ( $extension_group ) { + $value = $previous; + foreach ( $extension_group as $extension ) { + $value = $extension( $container, $value ); + } + return $value; + }; + }, $this->extensions ); + + return $map; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $container ): bool { + foreach ( $this->modules as $module ) { + $module->run( $container ); + } + return true; + } + +} From 63690fa696fc034b1993a2e5202cef1a4d658d87 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 24 Nov 2023 18:07:56 +0000 Subject: [PATCH 008/357] Fix lint --- bootstrap.php | 46 ++-------------------------- src/DhiiToModularityModule.php | 56 +++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 65 deletions(-) diff --git a/bootstrap.php b/bootstrap.php index fe9a0799b..4ab5049aa 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -5,15 +5,8 @@ * @package WooCommerce\PayPalCommerce */ -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CachingContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeCachingServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\DelegatingContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ProxyContainer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\DhiiToModularityModule; @@ -38,46 +31,11 @@ return function ( $modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules ); // Initialize plugin. - $properties = PluginProperties::new(__FILE__); - $bootstrap = Package::new($properties); + $properties = PluginProperties::new( __FILE__ ); + $bootstrap = Package::new( $properties ); $bootstrap->addModule( new DhiiToModularityModule( $modules ) ); $bootstrap->boot(); return $bootstrap->container(); - -// $providers = array_map( -// function ( ModuleInterface $module ): ServiceProviderInterface { -// return $module->setup(); -// }, -// $modules -// ); - -// $provider = new CompositeCachingServiceProvider( $providers ); -// $proxy_container = new ProxyContainer(); -// // TODO: caching does not work currently, -// // may want to consider fixing it later (pass proxy as parent to DelegatingContainer) -// // for now not fixed since we were using this behavior for long time and fixing it now may break things. -// $container = new DelegatingContainer( $provider ); -// /** -// * Skip iterable vs array check. -// * -// * @psalm-suppress PossiblyInvalidArgument -// */ -// $app_container = new CachingContainer( -// new CompositeContainer( -// array_merge( -// $additional_containers, -// array( $container ) -// ) -// ) -// ); -// $proxy_container->setInnerContainer( $app_container ); -// -// foreach ( $modules as $module ) { -// /* @var $module ModuleInterface module */ -// $module->run( $app_container ); -// } -// -// return $app_container; }; diff --git a/src/DhiiToModularityModule.php b/src/DhiiToModularityModule.php index f584857a8..83baeccd8 100644 --- a/src/DhiiToModularityModule.php +++ b/src/DhiiToModularityModule.php @@ -1,6 +1,6 @@ modules = $modules; } + /** + * Performs module-specific setup and initializes this object based on module service providers. + * + * @return void + * @throws Vendor\Dhii\Modular\Module\Exception\ModuleExceptionInterface Throws in case of module setup failure. + */ private function setup(): void { if ( $this->is_initialized ) { return; } - $this->services = array(); + $this->services = array(); $this->extensions = array(); foreach ( $this->modules as $module ) { @@ -85,9 +93,7 @@ class DhiiToModularityModule implements ServiceModule, ExtendingModule, Executab } /** - * Returns the services. - * - * @return array|callable[] + * {@inheritDoc} */ public function services(): array { $this->setup(); @@ -95,24 +101,32 @@ class DhiiToModularityModule implements ServiceModule, ExtendingModule, Executab } /** - * Returns the extensions. - * - * @return array|callable[] + * {@inheritDoc} */ public function extensions(): array { $this->setup(); - $map = array_map( function ( $extension_group ) { - return function ( $previous, ContainerInterface $container ) use ( $extension_group ) { - $value = $previous; - foreach ( $extension_group as $extension ) { - $value = $extension( $container, $value ); - } - return $value; - }; - }, $this->extensions ); - - return $map; + return array_map( + function ( $extension_group ) { + /** + * Maps Dhii extensions to modularity. + * + * @param mixed $previous The previous value. + * @return ContainerInterface $container The container instance. + * + * @psalm-suppress MissingClosureParamType + * @psalm-suppress MissingClosureReturnType + */ + return function ( $previous, ContainerInterface $container ) use ( $extension_group ) { + $value = $previous; + foreach ( $extension_group as $extension ) { + $value = $extension( $container, $value ); + } + return $value; + }; + }, + $this->extensions + ); } /** From 1a21ef0d21418b74460dbaedeab45d61782410d5 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 24 Nov 2023 18:24:30 +0000 Subject: [PATCH 009/357] Fix composer warning --- composer.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.lock b/composer.lock index 36ce94f4e..fd07c70cd 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": "214504278540f7c3d05f10d1e67920b1", + "content-hash": "5c45654af6d2f3addad4a089950e515f", "packages": [ { "name": "container-interop/service-provider", From 838449251fb67077c232d07e7136df55a2e0863e Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Thu, 14 Dec 2023 13:55:19 +0000 Subject: [PATCH 010/357] Fix tests. --- tests/PHPUnit/bootstrap.php | 1 + tests/inc/wp_functions.php | 41 +++++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 tests/inc/wp_functions.php diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php index 75145ac11..8910b0241 100644 --- a/tests/PHPUnit/bootstrap.php +++ b/tests/PHPUnit/bootstrap.php @@ -4,6 +4,7 @@ declare(strict_types=1); define('TESTS_ROOT_DIR', dirname(__DIR__)); define('ROOT_DIR', dirname(TESTS_ROOT_DIR)); +require_once TESTS_ROOT_DIR . '/inc/wp_functions.php'; require_once ROOT_DIR . '/vendor/autoload.php'; require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway.php'; require_once TESTS_ROOT_DIR . '/stubs/WC_Payment_Gateway_CC.php'; diff --git a/tests/inc/wp_functions.php b/tests/inc/wp_functions.php new file mode 100644 index 000000000..035e35885 --- /dev/null +++ b/tests/inc/wp_functions.php @@ -0,0 +1,41 @@ + Date: Fri, 15 Dec 2023 14:16:53 +0000 Subject: [PATCH 011/357] Refactor some modules to native modularity support. --- bootstrap.php | 13 +++++++ modules/ppcp-admin-notices/module.php | 4 +- .../ppcp-admin-notices/src/AdminNotices.php | 34 ++++++++--------- modules/ppcp-api-client/module.php | 4 +- modules/ppcp-api-client/src/ApiModule.php | 34 ++++++++--------- modules/ppcp-applepay/module.php | 4 +- modules/ppcp-applepay/src/ApplepayModule.php | 35 +++++++++--------- modules/ppcp-blocks/module.php | 4 +- modules/ppcp-blocks/src/BlocksModule.php | 37 ++++++++++--------- modules/ppcp-button/module.php | 4 +- modules/ppcp-button/src/ButtonModule.php | 37 +++++++++---------- 11 files changed, 107 insertions(+), 103 deletions(-) diff --git a/bootstrap.php b/bootstrap.php index 4ab5049aa..b4f61914c 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -34,6 +34,19 @@ return function ( $properties = PluginProperties::new( __FILE__ ); $bootstrap = Package::new( $properties ); + + + foreach ($modules as $key => $module) { + if ( + $module instanceof \WooCommerce\PayPalCommerce\AdminNotices\AdminNotices || + $module instanceof \WooCommerce\PayPalCommerce\Button\ButtonModule + ) { + $bootstrap->addModule( $module ); + unset($modules[$key]); + } + } + + $bootstrap->addModule( new DhiiToModularityModule( $modules ) ); $bootstrap->boot(); diff --git a/modules/ppcp-admin-notices/module.php b/modules/ppcp-admin-notices/module.php index 94db126b1..cb59f2988 100644 --- a/modules/ppcp-admin-notices/module.php +++ b/modules/ppcp-admin-notices/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\AdminNotices; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): AdminNotices { return new AdminNotices(); }; diff --git a/modules/ppcp-admin-notices/src/AdminNotices.php b/modules/ppcp-admin-notices/src/AdminNotices.php index 73fe24b71..fd3166d50 100644 --- a/modules/ppcp-admin-notices/src/AdminNotices.php +++ b/modules/ppcp-admin-notices/src/AdminNotices.php @@ -9,30 +9,36 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\AdminNotices; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class AdminNotices */ -class AdminNotices implements ModuleInterface { +class AdminNotices implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'admin_notices', function() use ( $c ) { @@ -40,13 +46,7 @@ class AdminNotices implements ModuleInterface { $renderer->render(); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-api-client/module.php b/modules/ppcp-api-client/module.php index d01a4253f..401dab48a 100644 --- a/modules/ppcp-api-client/module.php +++ b/modules/ppcp-api-client/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): ApiModule { return new ApiModule(); }; diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 42bc1f117..f4db40f11 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -12,31 +12,37 @@ namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; /** * Class ApiModule */ -class ApiModule implements ModuleInterface { +class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_after_calculate_totals', function ( \WC_Cart $cart ) { @@ -94,13 +100,7 @@ class ApiModule implements ModuleInterface { 10, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-applepay/module.php b/modules/ppcp-applepay/module.php index b8a05dcc9..1ab30942c 100644 --- a/modules/ppcp-applepay/module.php +++ b/modules/ppcp-applepay/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): ApplepayModule { return new ApplepayModule(); }; diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 86aaff278..74aa3f0ea 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -17,30 +17,37 @@ use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice; use WooCommerce\PayPalCommerce\Onboarding\Environment; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class ApplepayModule */ -class ApplepayModule implements ModuleInterface { +class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $module = $this; // Clears product status when appropriate. @@ -116,14 +123,8 @@ class ApplepayModule implements ModuleInterface { 100, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-blocks/module.php b/modules/ppcp-blocks/module.php index cfb4f43f9..042ab6f56 100644 --- a/modules/ppcp-blocks/module.php +++ b/modules/ppcp-blocks/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Blocks; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): BlocksModule { return new BlocksModule(); }; diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php index 1766806b7..afe4eb430 100644 --- a/modules/ppcp-blocks/src/BlocksModule.php +++ b/modules/ppcp-blocks/src/BlocksModule.php @@ -12,29 +12,36 @@ namespace WooCommerce\PayPalCommerce\Blocks; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class BlocksModule */ -class BlocksModule implements ModuleInterface { +class BlocksModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' ) || ! function_exists( 'woocommerce_store_api_register_payment_requirements' ) @@ -54,7 +61,7 @@ class BlocksModule implements ModuleInterface { } ); - return; + return true; } add_action( @@ -88,13 +95,7 @@ class BlocksModule implements ModuleInterface { $endpoint->handle_request(); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-button/module.php b/modules/ppcp-button/module.php index b2077096e..68f04fe13 100644 --- a/modules/ppcp-button/module.php +++ b/modules/ppcp-button/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): ButtonModule { return new ButtonModule(); }; diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php index 67dec01f3..cd2c8d381 100644 --- a/modules/ppcp-button/src/ButtonModule.php +++ b/modules/ppcp-button/src/ButtonModule.php @@ -14,8 +14,6 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint; @@ -23,29 +21,36 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint; use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint; use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class ButtonModule */ -class ButtonModule implements ModuleInterface { - +class ButtonModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'wp', @@ -91,6 +96,8 @@ class ButtonModule implements ModuleInterface { ); $this->register_ajax_endpoints( $c ); + + return true; } /** @@ -211,12 +218,4 @@ class ButtonModule implements ModuleInterface { } ); } - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } } From e5404848059e97dbeb8fc1e2f222e97533dc95fb Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 19 Dec 2023 10:13:45 +0000 Subject: [PATCH 012/357] Fix extensions. --- bootstrap.php | 5 +++-- modules/ppcp-applepay/extensions.php | 2 +- modules/ppcp-blocks/extensions.php | 10 +++++----- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/bootstrap.php b/bootstrap.php index b4f61914c..3d15509c5 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -34,11 +34,12 @@ return function ( $properties = PluginProperties::new( __FILE__ ); $bootstrap = Package::new( $properties ); - - foreach ($modules as $key => $module) { if ( $module instanceof \WooCommerce\PayPalCommerce\AdminNotices\AdminNotices || + $module instanceof \WooCommerce\PayPalCommerce\ApiClient\ApiModule || + $module instanceof \WooCommerce\PayPalCommerce\Applepay\ApplepayModule || + $module instanceof \WooCommerce\PayPalCommerce\Blocks\BlocksModule || $module instanceof \WooCommerce\PayPalCommerce\Button\ButtonModule ) { $bootstrap->addModule( $module ); diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index 694a7b8a9..89828849f 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { // Eligibility check. if ( ! $container->has( 'applepay.eligible' ) || ! $container->get( 'applepay.eligible' ) ) { diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php index 31f90576d..b4f643969 100644 --- a/modules/ppcp-blocks/extensions.php +++ b/modules/ppcp-blocks/extensions.php @@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'wcgateway.button.locations' => function ( ContainerInterface $container, array $locations ): array { + 'wcgateway.button.locations' => function ( array $locations, ContainerInterface $container ): array { return array_merge( $locations, array( @@ -22,13 +22,13 @@ return array( ) ); }, - 'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array { + 'wcgateway.settings.pay-later.messaging-locations' => function ( array $locations, ContainerInterface $container ): array { unset( $locations['checkout-block-express'] ); unset( $locations['cart-block'] ); return $locations; }, - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); $index = array_search( $key, $keys, true ); @@ -59,7 +59,7 @@ Skipping the final confirmation on the checkout page may impact the buyer experi ); }, - 'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array { + 'button.pay-now-contexts' => function ( array $contexts, ContainerInterface $container ): array { if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) { $contexts[] = 'checkout-block'; $contexts[] = 'cart-block'; @@ -68,7 +68,7 @@ Skipping the final confirmation on the checkout page may impact the buyer experi return $contexts; }, - 'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool { + 'button.handle-shipping-in-paypal' => function ( bool $previous, ContainerInterface $container ): bool { return ! $container->get( 'blocks.settings.final_review_enabled' ); }, ); From 429232f60f37c01f19bdedf95f739bae48fd5f29 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Tue, 19 Dec 2023 17:26:09 +0000 Subject: [PATCH 013/357] Refactor remaining modules to Modularity. Remove Dhii dependencies. --- bootstrap.php | 16 +- modules/ppcp-card-fields/module.php | 4 +- .../ppcp-card-fields/src/CardFieldsModule.php | 30 ++-- modules/ppcp-compat/module.php | 4 +- modules/ppcp-compat/src/CompatModule.php | 39 +++-- modules/ppcp-googlepay/extensions.php | 2 +- modules/ppcp-googlepay/module.php | 4 +- .../ppcp-googlepay/src/GooglepayModule.php | 35 ++--- modules/ppcp-onboarding/module.php | 4 +- .../ppcp-onboarding/src/OnboardingModule.php | 34 ++--- modules/ppcp-order-tracking/module.php | 4 +- .../src/OrderTrackingModule.php | 29 ++-- .../ppcp-paypal-subscriptions/extensions.php | 2 - modules/ppcp-paypal-subscriptions/module.php | 4 +- .../src/PayPalSubscriptionsModule.php | 28 ++-- .../ppcp-save-payment-methods/extensions.php | 2 - modules/ppcp-save-payment-methods/module.php | 4 +- .../src/SavePaymentMethodsModule.php | 31 ++-- .../ppcp-saved-payment-checker/extensions.php | 2 +- modules/ppcp-saved-payment-checker/module.php | 4 +- .../src/SavedPaymentCheckerModule.php | 28 ++-- modules/ppcp-session/module.php | 4 +- modules/ppcp-session/src/SessionModule.php | 35 ++--- modules/ppcp-status-report/module.php | 4 +- .../src/StatusReportModule.php | 33 ++-- modules/ppcp-uninstall/extensions.php | 2 +- modules/ppcp-uninstall/module.php | 4 +- .../ppcp-uninstall/src/UninstallModule.php | 28 ++-- modules/ppcp-vaulting/module.php | 4 +- modules/ppcp-vaulting/src/VaultingModule.php | 34 +++-- modules/ppcp-wc-gateway/extensions.php | 16 +- modules/ppcp-wc-gateway/module.php | 4 +- .../ppcp-wc-gateway/src/WCGatewayModule.php | 37 +++-- modules/ppcp-wc-subscriptions/module.php | 4 +- .../src/WcSubscriptionsModule.php | 36 +++-- modules/ppcp-webhooks/extensions.php | 3 +- modules/ppcp-webhooks/module.php | 4 +- modules/ppcp-webhooks/src/WebhookModule.php | 34 ++--- modules/woocommerce-logging/module.php | 4 +- .../src/WooCommerceLoggingModule.php | 28 ++-- src/DhiiToModularityModule.php | 142 ------------------ src/PluginModule.php | 27 ++-- tests/PHPUnit/ModularTestCase.php | 17 ++- .../Processor/OrderProcessorTest.php | 4 +- .../WcSubscriptions/RenewalHandlerTest.php | 4 +- 45 files changed, 341 insertions(+), 481 deletions(-) delete mode 100644 src/DhiiToModularityModule.php diff --git a/bootstrap.php b/bootstrap.php index 3d15509c5..1972d92d3 100644 --- a/bootstrap.php +++ b/bootstrap.php @@ -8,7 +8,6 @@ use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package; use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; -use WooCommerce\PayPalCommerce\DhiiToModularityModule; return function ( string $root_dir, @@ -34,21 +33,10 @@ return function ( $properties = PluginProperties::new( __FILE__ ); $bootstrap = Package::new( $properties ); - foreach ($modules as $key => $module) { - if ( - $module instanceof \WooCommerce\PayPalCommerce\AdminNotices\AdminNotices || - $module instanceof \WooCommerce\PayPalCommerce\ApiClient\ApiModule || - $module instanceof \WooCommerce\PayPalCommerce\Applepay\ApplepayModule || - $module instanceof \WooCommerce\PayPalCommerce\Blocks\BlocksModule || - $module instanceof \WooCommerce\PayPalCommerce\Button\ButtonModule - ) { - $bootstrap->addModule( $module ); - unset($modules[$key]); - } + foreach ( $modules as $module ) { + $bootstrap->addModule( $module ); } - - $bootstrap->addModule( new DhiiToModularityModule( $modules ) ); $bootstrap->boot(); return $bootstrap->container(); diff --git a/modules/ppcp-card-fields/module.php b/modules/ppcp-card-fields/module.php index 088900ab9..94cc0782f 100644 --- a/modules/ppcp-card-fields/module.php +++ b/modules/ppcp-card-fields/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\CardFields; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): CardFieldsModule { return new CardFieldsModule(); }; diff --git a/modules/ppcp-card-fields/src/CardFieldsModule.php b/modules/ppcp-card-fields/src/CardFieldsModule.php index 8524f1f6d..ddcdf962d 100644 --- a/modules/ppcp-card-fields/src/CardFieldsModule.php +++ b/modules/ppcp-card-fields/src/CardFieldsModule.php @@ -9,9 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\CardFields; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -19,24 +20,29 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class CardFieldsModule */ -class CardFieldsModule implements ModuleInterface { +class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! $c->get( 'card-fields.eligible' ) ) { - return; + return true; } /** @@ -117,5 +123,7 @@ class CardFieldsModule implements ModuleInterface { return $data; } ); + + return true; } } diff --git a/modules/ppcp-compat/module.php b/modules/ppcp-compat/module.php index 2f9ccf4b0..50c3e58d2 100644 --- a/modules/ppcp-compat/module.php +++ b/modules/ppcp-compat/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): CompatModule { return new CompatModule(); }; diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 44cfd1988..3f1d29c89 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -9,9 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -20,17 +21,21 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class CompatModule */ -class CompatModule implements ModuleInterface { +class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** - * Setup the compatibility module. - * - * @return ServiceProviderInterface + * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -38,7 +43,7 @@ class CompatModule implements ModuleInterface { * * @throws NotFoundException */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { $this->initialize_ppec_compat_layer( $c ); $this->fix_site_ground_optimizer_compatibility( $c ); @@ -54,14 +59,8 @@ class CompatModule implements ModuleInterface { $this->migrate_smart_button_settings( $c ); $this->fix_page_builders(); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index 1583e5c6e..68b400c98 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { // Eligibility check. if ( ! $container->has( 'googlepay.eligible' ) || ! $container->get( 'googlepay.eligible' ) ) { diff --git a/modules/ppcp-googlepay/module.php b/modules/ppcp-googlepay/module.php index 2e146435b..cfa949499 100644 --- a/modules/ppcp-googlepay/module.php +++ b/modules/ppcp-googlepay/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): GooglepayModule { return new GooglepayModule(); }; diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index e478881ba..fcc87ada8 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -15,30 +15,37 @@ use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Googlepay\Helper\ApmProductStatus; use WooCommerce\PayPalCommerce\Googlepay\Helper\AvailabilityNotice; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class GooglepayModule */ -class GooglepayModule implements ModuleInterface { +class GooglepayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { // Clears product status when appropriate. add_action( @@ -159,13 +166,7 @@ class GooglepayModule implements ModuleInterface { }, 1 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-onboarding/module.php b/modules/ppcp-onboarding/module.php index 55d1b4df3..dc255e3c2 100644 --- a/modules/ppcp-onboarding/module.php +++ b/modules/ppcp-onboarding/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): OnboardingModule { return new OnboardingModule(); }; diff --git a/modules/ppcp-onboarding/src/OnboardingModule.php b/modules/ppcp-onboarding/src/OnboardingModule.php index 578cda42c..40e137832 100644 --- a/modules/ppcp-onboarding/src/OnboardingModule.php +++ b/modules/ppcp-onboarding/src/OnboardingModule.php @@ -10,33 +10,39 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Onboarding; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets; use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class OnboardingModule */ -class OnboardingModule implements ModuleInterface { +class OnboardingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $asset_loader = $c->get( 'onboarding.assets' ); /** @@ -107,13 +113,7 @@ class OnboardingModule implements ModuleInterface { // Initialize REST routes at the appropriate time. $rest_controller = $c->get( 'onboarding.rest' ); add_action( 'rest_api_init', array( $rest_controller, 'register_routes' ) ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-order-tracking/module.php b/modules/ppcp-order-tracking/module.php index a82bbb033..7816e8d4a 100644 --- a/modules/ppcp-order-tracking/module.php +++ b/modules/ppcp-order-tracking/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\OrderTracking; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): OrderTrackingModule { return new OrderTrackingModule(); }; diff --git a/modules/ppcp-order-tracking/src/OrderTrackingModule.php b/modules/ppcp-order-tracking/src/OrderTrackingModule.php index 5d5905cef..bc3bdf960 100644 --- a/modules/ppcp-order-tracking/src/OrderTrackingModule.php +++ b/modules/ppcp-order-tracking/src/OrderTrackingModule.php @@ -10,9 +10,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\OrderTracking; use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets; @@ -22,8 +23,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** * Class OrderTrackingModule */ -class OrderTrackingModule implements ModuleInterface { - +class OrderTrackingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use TrackingAvailabilityTrait; public const PPCP_TRACKING_INFO_META_NAME = '_ppcp_paypal_tracking_info_meta_name'; @@ -31,11 +32,15 @@ class OrderTrackingModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -44,7 +49,7 @@ class OrderTrackingModule implements ModuleInterface { * @param ContainerInterface $c A services container instance. * @throws NotFoundException */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { $endpoint = $c->get( 'order-tracking.endpoint.controller' ); assert( $endpoint instanceof OrderTrackingEndpoint ); @@ -109,5 +114,7 @@ class OrderTrackingModule implements ModuleInterface { 10, 2 ); + + return true; } } diff --git a/modules/ppcp-paypal-subscriptions/extensions.php b/modules/ppcp-paypal-subscriptions/extensions.php index fc91cd245..a02c96045 100644 --- a/modules/ppcp-paypal-subscriptions/extensions.php +++ b/modules/ppcp-paypal-subscriptions/extensions.php @@ -9,6 +9,4 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; - return array(); diff --git a/modules/ppcp-paypal-subscriptions/module.php b/modules/ppcp-paypal-subscriptions/module.php index ee5f79da8..f09b27206 100644 --- a/modules/ppcp-paypal-subscriptions/module.php +++ b/modules/ppcp-paypal-subscriptions/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\PayPalSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): PayPalSubscriptionsModule { return new PayPalSubscriptionsModule(); }; diff --git a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php index a69aaefef..e1d32fde7 100644 --- a/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php +++ b/modules/ppcp-paypal-subscriptions/src/PayPalSubscriptionsModule.php @@ -22,9 +22,10 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Onboarding\Environment; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -34,22 +35,27 @@ use WP_Post; /** * Class SavedPaymentCheckerModule */ -class PayPalSubscriptionsModule implements ModuleInterface { +class PayPalSubscriptionsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'save_post', /** @@ -641,6 +647,8 @@ class PayPalSubscriptionsModule implements ModuleInterface { } } ); + + return true; } /** diff --git a/modules/ppcp-save-payment-methods/extensions.php b/modules/ppcp-save-payment-methods/extensions.php index 1758f8960..cbcb904d7 100644 --- a/modules/ppcp-save-payment-methods/extensions.php +++ b/modules/ppcp-save-payment-methods/extensions.php @@ -9,6 +9,4 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; -use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; - return array(); diff --git a/modules/ppcp-save-payment-methods/module.php b/modules/ppcp-save-payment-methods/module.php index 85f1b6416..a4187d4e3 100644 --- a/modules/ppcp-save-payment-methods/module.php +++ b/modules/ppcp-save-payment-methods/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavePaymentMethods; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): SavePaymentMethodsModule { return new SavePaymentMethodsModule(); }; diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index 9dcb02359..4f5c8e8ba 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -22,9 +22,10 @@ use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CaptureCardPayment; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreatePaymentToken; use WooCommerce\PayPalCommerce\SavePaymentMethods\Endpoint\CreateSetupToken; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -33,26 +34,30 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class SavePaymentMethodsModule */ -class SavePaymentMethodsModule implements ModuleInterface { - +class SavePaymentMethodsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use ContextTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { if ( ! $c->get( 'save-payment-methods.eligible' ) ) { - return; + return true; } add_filter( @@ -335,6 +340,8 @@ class SavePaymentMethodsModule implements ModuleInterface { $endpoint->handle_request(); } ); + + return true; } /** diff --git a/modules/ppcp-saved-payment-checker/extensions.php b/modules/ppcp-saved-payment-checker/extensions.php index e712a03fc..dcdbd169d 100644 --- a/modules/ppcp-saved-payment-checker/extensions.php +++ b/modules/ppcp-saved-payment-checker/extensions.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $subscription_helper = $container->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); diff --git a/modules/ppcp-saved-payment-checker/module.php b/modules/ppcp-saved-payment-checker/module.php index 9f5217ab8..63e55692b 100644 --- a/modules/ppcp-saved-payment-checker/module.php +++ b/modules/ppcp-saved-payment-checker/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\SavedPaymentChecker; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): SavedPaymentCheckerModule { return new SavedPaymentCheckerModule(); }; diff --git a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php index 6a6ae3c79..2c2d58ff1 100644 --- a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php +++ b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php @@ -11,31 +11,37 @@ namespace WooCommerce\PayPalCommerce\SavedPaymentChecker; use Psr\Log\LoggerInterface; use WC_Order; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class SavedPaymentCheckerModule */ -class SavedPaymentCheckerModule implements ModuleInterface { +class SavedPaymentCheckerModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { /** * Set authorize intent for vaulted subscriptions, so we can void if payment not saved. @@ -138,5 +144,7 @@ class SavedPaymentCheckerModule implements ModuleInterface { } } ); + + return true; } } diff --git a/modules/ppcp-session/module.php b/modules/ppcp-session/module.php index 80392d729..b75551e9f 100644 --- a/modules/ppcp-session/module.php +++ b/modules/ppcp-session/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Session; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): SessionModule { return new SessionModule(); }; diff --git a/modules/ppcp-session/src/SessionModule.php b/modules/ppcp-session/src/SessionModule.php index cecc1a98f..e67d6014c 100644 --- a/modules/ppcp-session/src/SessionModule.php +++ b/modules/ppcp-session/src/SessionModule.php @@ -14,16 +14,19 @@ use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class SessionModule */ -class SessionModule implements ModuleInterface { +class SessionModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * A flag to avoid multiple requests to reload order. * @@ -34,17 +37,21 @@ class SessionModule implements ModuleInterface { /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_init', function () use ( $c ) { @@ -96,13 +103,7 @@ class SessionModule implements ModuleInterface { 10, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/ppcp-status-report/module.php b/modules/ppcp-status-report/module.php index 35edc894c..0dd438cdb 100644 --- a/modules/ppcp-status-report/module.php +++ b/modules/ppcp-status-report/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\StatusReport; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): StatusReportModule { return new StatusReportModule(); }; diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php index f26412778..ec9703814 100644 --- a/modules/ppcp-status-report/src/StatusReportModule.php +++ b/modules/ppcp-status-report/src/StatusReportModule.php @@ -9,10 +9,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\StatusReport; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; @@ -26,16 +27,21 @@ use WooCommerce\PayPalCommerce\Webhooks\WebhookEventStorage; /** * Class StatusReportModule */ -class StatusReportModule implements ModuleInterface { +class StatusReportModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -43,7 +49,7 @@ class StatusReportModule implements ModuleInterface { * * @param ContainerInterface $c A services container instance. */ - public function run( ContainerInterface $c ): void { + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_system_status_report', function () use ( $c ) { @@ -183,12 +189,9 @@ class StatusReportModule implements ModuleInterface { ); } ); - } - /** - * {@inheritDoc} - */ - public function getKey() { } + return true; + } /** * It returns the current onboarding status. diff --git a/modules/ppcp-uninstall/extensions.php b/modules/ppcp-uninstall/extensions.php index 94e44737e..6f40091e7 100644 --- a/modules/ppcp-uninstall/extensions.php +++ b/modules/ppcp-uninstall/extensions.php @@ -14,7 +14,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => static function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => static function ( array $fields, ContainerInterface $container ): array { $uninstall_fields = array( 'uninstall_heading' => array( 'heading' => __( 'Uninstall/Clear Database', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-uninstall/module.php b/modules/ppcp-uninstall/module.php index c3783cbb3..a3fca3039 100644 --- a/modules/ppcp-uninstall/module.php +++ b/modules/ppcp-uninstall/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Uninstall; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): UninstallModule { return new UninstallModule(); }; diff --git a/modules/ppcp-uninstall/src/UninstallModule.php b/modules/ppcp-uninstall/src/UninstallModule.php index 25d65ae0f..2cb2dce52 100644 --- a/modules/ppcp-uninstall/src/UninstallModule.php +++ b/modules/ppcp-uninstall/src/UninstallModule.php @@ -12,31 +12,37 @@ namespace WooCommerce\PayPalCommerce\Uninstall; use Exception; use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; use WooCommerce\PayPalCommerce\Uninstall\Assets\ClearDatabaseAssets; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; /** * Class UninstallModule */ -class UninstallModule implements ModuleInterface { +class UninstallModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $container ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $container ): bool { $page_id = $container->get( 'wcgateway.current-ppcp-settings-page-id' ); if ( Settings::CONNECTION_TAB_ID === $page_id ) { $this->registerClearDatabaseAssets( $container->get( 'uninstall.clear-db-assets' ) ); @@ -50,6 +56,8 @@ class UninstallModule implements ModuleInterface { $action_names = $container->get( 'uninstall.ppcp-all-action-names' ); $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names, $action_names ); + + return true; } /** diff --git a/modules/ppcp-vaulting/module.php b/modules/ppcp-vaulting/module.php index fb42c4f9c..4266fbf00 100644 --- a/modules/ppcp-vaulting/module.php +++ b/modules/ppcp-vaulting/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Vaulting; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): VaultingModule { return new VaultingModule(); }; diff --git a/modules/ppcp-vaulting/src/VaultingModule.php b/modules/ppcp-vaulting/src/VaultingModule.php index c9e7bd40a..c150dfe96 100644 --- a/modules/ppcp-vaulting/src/VaultingModule.php +++ b/modules/ppcp-vaulting/src/VaultingModule.php @@ -13,9 +13,10 @@ use Psr\Log\LoggerInterface; use RuntimeException; use WC_Payment_Token; use WC_Payment_Tokens; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; @@ -26,17 +27,21 @@ use WP_User_Query; /** * Class StatusReportModule */ -class VaultingModule implements ModuleInterface { - +class VaultingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; + } + + /** + * {@inheritDoc} + */ + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } /** @@ -45,7 +50,7 @@ class VaultingModule implements ModuleInterface { * @param ContainerInterface $container A services container instance. * @throws NotFoundException When service could not be found. */ - public function run( ContainerInterface $container ): void { + public function run( ContainerInterface $container ): bool { $listener = $container->get( 'vaulting.customer-approval-listener' ); assert( $listener instanceof CustomerApprovalListener ); @@ -203,6 +208,8 @@ class VaultingModule implements ModuleInterface { return $methods; } ); + + return true; } /** @@ -260,9 +267,4 @@ class VaultingModule implements ModuleInterface { $timestamp += $interval_in_seconds; } } - - /** - * {@inheritDoc} - */ - public function getKey() { } } diff --git a/modules/ppcp-wc-gateway/extensions.php b/modules/ppcp-wc-gateway/extensions.php index aca11f1b1..fe2de006b 100644 --- a/modules/ppcp-wc-gateway/extensions.php +++ b/modules/ppcp-wc-gateway/extensions.php @@ -17,15 +17,15 @@ use Psr\Log\LoggerInterface; return array( - 'api.merchant_email' => static function ( ContainerInterface $container ): string { + 'api.merchant_email' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'merchant_email' ) ? (string) $settings->get( 'merchant_email' ) : ''; }, - 'api.merchant_id' => static function ( ContainerInterface $container ): string { + 'api.merchant_id' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'merchant_id' ) ? (string) $settings->get( 'merchant_id' ) : ''; }, - 'api.partner_merchant_id' => static function ( ContainerInterface $container ): string { + 'api.partner_merchant_id' => static function ( string $previous, ContainerInterface $container ): string { $environment = $container->get( 'onboarding.environment' ); /** @@ -36,20 +36,20 @@ return array( return $environment->current_environment_is( Environment::SANDBOX ) ? (string) $container->get( 'api.partner_merchant_id-sandbox' ) : (string) $container->get( 'api.partner_merchant_id-production' ); }, - 'api.key' => static function ( ContainerInterface $container ): string { + 'api.key' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); $key = $settings->has( 'client_id' ) ? (string) $settings->get( 'client_id' ) : ''; return $key; }, - 'api.secret' => static function ( ContainerInterface $container ): string { + 'api.secret' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'client_secret' ) ? (string) $settings->get( 'client_secret' ) : ''; }, - 'api.prefix' => static function ( ContainerInterface $container ): string { + 'api.prefix' => static function ( string $previous, ContainerInterface $container ): string { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'prefix' ) ? (string) $settings->get( 'prefix' ) : 'WC-'; }, - 'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface { + 'woocommerce.logger.woocommerce' => function ( LoggerInterface $previous, ContainerInterface $container ): LoggerInterface { if ( ! function_exists( 'wc_get_logger' ) || ! $container->get( 'wcgateway.logging.is-enabled' ) ) { return new NullLogger(); } @@ -60,7 +60,7 @@ return array( $source ); }, - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $files = array( 'paypal-smart-button-fields.php', 'connection-tab-fields.php', diff --git a/modules/ppcp-wc-gateway/module.php b/modules/ppcp-wc-gateway/module.php index 64b4601d8..ce8f30ed6 100644 --- a/modules/ppcp-wc-gateway/module.php +++ b/modules/ppcp-wc-gateway/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcGateway; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WCGatewayModule { return new WCGatewayModule(); }; diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 6af49c778..c3efb1210 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -13,10 +13,11 @@ use Psr\Log\LoggerInterface; use Throwable; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\WcGateway\Endpoint\RefreshFeatureStatusEndpoint; -use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WC_Order; use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository; use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture; @@ -54,22 +55,27 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class WcGatewayModule */ -class WCGatewayModule implements ModuleInterface { +class WCGatewayModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $this->register_payment_gateways( $c ); $this->register_order_functionality( $c ); $this->register_columns( $c ); @@ -430,6 +436,8 @@ class WCGatewayModule implements ModuleInterface { $c->get( 'wcgateway.cli.settings.command' ) ); } + + return true; } /** @@ -698,13 +706,4 @@ class WCGatewayModule implements ModuleInterface { 2 ); } - - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } } diff --git a/modules/ppcp-wc-subscriptions/module.php b/modules/ppcp-wc-subscriptions/module.php index a298bac91..2d87674af 100644 --- a/modules/ppcp-wc-subscriptions/module.php +++ b/modules/ppcp-wc-subscriptions/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WcSubscriptionsModule { return new WcSubscriptionsModule(); }; diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index c5fb04541..dc58f836e 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -11,12 +11,12 @@ namespace WooCommerce\PayPalCommerce\WcSubscriptions; use Psr\Log\LoggerInterface; use WC_Order; -use WC_Subscription; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; @@ -28,24 +28,28 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; /** * Class SubscriptionModule */ -class WcSubscriptionsModule implements ModuleInterface { - +class WcSubscriptionsModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; use TransactionIdHandlingTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { add_action( 'woocommerce_scheduled_subscription_payment_' . PayPalGateway::ID, /** @@ -142,14 +146,8 @@ class WcSubscriptionsModule implements ModuleInterface { 20, 2 ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } /** diff --git a/modules/ppcp-webhooks/extensions.php b/modules/ppcp-webhooks/extensions.php index 7368e3c89..5f61258b6 100644 --- a/modules/ppcp-webhooks/extensions.php +++ b/modules/ppcp-webhooks/extensions.php @@ -10,10 +10,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; use WooCommerce\PayPalCommerce\Onboarding\State; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( - 'wcgateway.settings.fields' => static function ( $container, array $fields ): array { + 'wcgateway.settings.fields' => static function ( array $fields, ContainerInterface $container ): array { $status_page_fields = array( 'webhook_status_heading' => array( 'heading' => __( 'Webhook Status', 'woocommerce-paypal-payments' ), diff --git a/modules/ppcp-webhooks/module.php b/modules/ppcp-webhooks/module.php index 7d223a528..2425c3049 100644 --- a/modules/ppcp-webhooks/module.php +++ b/modules/ppcp-webhooks/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): WebhookModule { return new WebhookModule(); }; diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 2458e5b4d..04c389263 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -10,10 +10,11 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; use WooCommerce\PayPalCommerce\Onboarding\State; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use Exception; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -25,22 +26,27 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets; /** * Class WebhookModule */ -class WebhookModule implements ModuleInterface { +class WebhookModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $container ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $container ): bool { $logger = $container->get( 'woocommerce.logger.woocommerce' ); assert( $logger instanceof LoggerInterface ); @@ -158,13 +164,7 @@ class WebhookModule implements ModuleInterface { ); } ); - } - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { + return true; } } diff --git a/modules/woocommerce-logging/module.php b/modules/woocommerce-logging/module.php index b4ebf4c90..ac9624d5f 100644 --- a/modules/woocommerce-logging/module.php +++ b/modules/woocommerce-logging/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\WooCommerce\Logging; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return function (): ModuleInterface { +return function (): WooCommerceLoggingModule { return new WooCommerceLoggingModule(); }; diff --git a/modules/woocommerce-logging/src/WooCommerceLoggingModule.php b/modules/woocommerce-logging/src/WooCommerceLoggingModule.php index b314bc445..f345da263 100644 --- a/modules/woocommerce-logging/src/WooCommerceLoggingModule.php +++ b/modules/woocommerce-logging/src/WooCommerceLoggingModule.php @@ -9,38 +9,36 @@ declare(strict_types=1); namespace WooCommerce\WooCommerce\Logging; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class WooCommerceLoggingModule */ -class WooCommerceLoggingModule implements ModuleInterface { +class WooCommerceLoggingModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; } - /** - * Returns the key for the module. - * - * @return string|void + * {@inheritDoc} */ - public function getKey() { + public function run( ContainerInterface $c ): bool { + return true; } } diff --git a/src/DhiiToModularityModule.php b/src/DhiiToModularityModule.php deleted file mode 100644 index 83baeccd8..000000000 --- a/src/DhiiToModularityModule.php +++ /dev/null @@ -1,142 +0,0 @@ -modules = $modules; - } - - /** - * Performs module-specific setup and initializes this object based on module service providers. - * - * @return void - * @throws Vendor\Dhii\Modular\Module\Exception\ModuleExceptionInterface Throws in case of module setup failure. - */ - private function setup(): void { - if ( $this->is_initialized ) { - return; - } - - $this->services = array(); - $this->extensions = array(); - - foreach ( $this->modules as $module ) { - $service_provider = $module->setup(); - - $this->services = array_merge( - $this->services, - $service_provider->getFactories() - ); - - foreach ( $service_provider->getExtensions() as $key => $extension ) { - if ( ! isset( $this->extensions[ $key ] ) ) { - $this->extensions[ $key ] = array(); - } - $this->extensions[ $key ][] = $extension; - } - } - - $this->is_initialized = true; - } - - /** - * {@inheritDoc} - */ - public function services(): array { - $this->setup(); - return $this->services; - } - - /** - * {@inheritDoc} - */ - public function extensions(): array { - $this->setup(); - - return array_map( - function ( $extension_group ) { - /** - * Maps Dhii extensions to modularity. - * - * @param mixed $previous The previous value. - * @return ContainerInterface $container The container instance. - * - * @psalm-suppress MissingClosureParamType - * @psalm-suppress MissingClosureReturnType - */ - return function ( $previous, ContainerInterface $container ) use ( $extension_group ) { - $value = $previous; - foreach ( $extension_group as $extension ) { - $value = $extension( $container, $value ); - } - return $value; - }; - }, - $this->extensions - ); - } - - /** - * {@inheritDoc} - */ - public function run( ContainerInterface $container ): bool { - foreach ( $this->modules as $module ) { - $module->run( $container ); - } - return true; - } - -} diff --git a/src/PluginModule.php b/src/PluginModule.php index 5ec283941..fa4784f5c 100644 --- a/src/PluginModule.php +++ b/src/PluginModule.php @@ -9,37 +9,36 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class PluginModule */ -class PluginModule implements ModuleInterface { +class PluginModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/services.php', - require __DIR__ . '/extensions.php' - ); + public function services(): array { + return require __DIR__ . '/services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/extensions.php'; } /** - * Returns the key for the module. - * - * @return string|void + * {@inheritDoc} */ - public function getKey() { + public function run( ContainerInterface $c ): bool { + return true; } } diff --git a/tests/PHPUnit/ModularTestCase.php b/tests/PHPUnit/ModularTestCase.php index a880acfbe..f7a5561cb 100644 --- a/tests/PHPUnit/ModularTestCase.php +++ b/tests/PHPUnit/ModularTestCase.php @@ -4,10 +4,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce; use WooCommerce\PayPalCommerce\Helper\RedirectorStub; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use function Brain\Monkey\Functions\when; @@ -68,16 +68,19 @@ class ModularTestCase extends TestCase } ], $overriddenServices); - $module = new class ($overriddenServices) implements ModuleInterface { + $module = new class ($overriddenServices) implements ServiceModule, ExecutableModule { + use ModuleClassNameIdTrait; + public function __construct(array $services) { $this->services = $services; } - public function setup(): ServiceProviderInterface{ - return new ServiceProvider($this->services, []); + public function services(): array { + return $this->services; } - public function run(ContainerInterface $c): void { + public function run(ContainerInterface $c): bool { + return true; } }; diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php index d27ec6ff7..1422b23c7 100644 --- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php +++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php @@ -7,7 +7,6 @@ use Exception; use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory; use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Dictionary; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; @@ -23,6 +22,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper; use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ReadOnlyContainer; use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; use WooCommerce\PayPalCommerce\TestCase; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -37,7 +37,7 @@ class OrderProcessorTest extends TestCase public function setUp(): void { parent::setUp(); - $this->environment = new Environment(new Dictionary([])); + $this->environment = new Environment(new ReadOnlyContainer([], [], [], [])); } public function testAuthorize() { diff --git a/tests/PHPUnit/WcSubscriptions/RenewalHandlerTest.php b/tests/PHPUnit/WcSubscriptions/RenewalHandlerTest.php index 3899fa0f7..ef754852b 100644 --- a/tests/PHPUnit/WcSubscriptions/RenewalHandlerTest.php +++ b/tests/PHPUnit/WcSubscriptions/RenewalHandlerTest.php @@ -3,7 +3,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\WcSubscriptions; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\Dictionary; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint; @@ -22,6 +21,7 @@ use WooCommerce\PayPalCommerce\TestCase; use Mockery; use Mockery\Adapter\Phpunit\MockeryPHPUnitIntegration; use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ReadOnlyContainer; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; @@ -50,7 +50,7 @@ class RenewalHandlerTest extends TestCase $this->purchaseUnitFactory = Mockery::mock(PurchaseUnitFactory::class); $this->shippingPreferenceFactory = Mockery::mock(ShippingPreferenceFactory::class); $this->payerFactory = Mockery::mock(PayerFactory::class); - $this->environment = new Environment(new Dictionary([])); + $this->environment = new Environment(new ReadOnlyContainer([], [], [], [])); $authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class); $settings = Mockery::mock(Settings::class); $settings From 2d21035457dc3a2256a641a8ae339b9daa17ff9d Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 20 Dec 2023 15:33:40 +0000 Subject: [PATCH 014/357] Fix uninstall dependency. --- uninstall.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/uninstall.php b/uninstall.php index 59c98d3c0..26fa6cb67 100644 --- a/uninstall.php +++ b/uninstall.php @@ -8,7 +8,7 @@ declare(strict_types=1); use WooCommerce\PayPalCommerce\Uninstall\ClearDatabaseInterface; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CachingContainer; +use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { @@ -35,7 +35,7 @@ require $main_plugin_file; $bootstrap = require "{$root_dir}/bootstrap.php"; $app_container = $bootstrap( $root_dir ); - assert( $app_container instanceof CachingContainer ); + assert( $app_container instanceof ContainerInterface ); $settings = $app_container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); From 4f61ed45a76325557b5a363976a100241dd39e12 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Wed, 20 Dec 2023 15:56:11 +0000 Subject: [PATCH 015/357] Moving inpsyde/modularity to require-dev. --- composer.json | 4 +- composer.lock | 138 +++++++++++++++++++++++++------------------------- 2 files changed, 71 insertions(+), 71 deletions(-) diff --git a/composer.json b/composer.json index 96c9459ef..79fb463ef 100644 --- a/composer.json +++ b/composer.json @@ -11,14 +11,14 @@ "wikimedia/composer-merge-plugin": "^2.0", "wp-oop/wordpress-interface": "^0.1.0-alpha1", "dhii/versions": "^0.1.0-alpha1", - "symfony/polyfill-php80": "^1.19", - "inpsyde/modularity": "^1.7" + "symfony/polyfill-php80": "^1.19" }, "require-dev": { "psr/container": "^1.0", "dhii/module-interface": "^0.2 || ^0.3", "container-interop/service-provider": "^0.4.0", "dhii/containers": "^0.1.0-alpha1", + "inpsyde/modularity": "^1.7", "woocommerce/woocommerce-sniffs": "^0.1.0", "phpunit/phpunit": "^7.0 | ^8.0 | ^9.0", "brain/monkey": "^2.4", diff --git a/composer.lock b/composer.lock index fd07c70cd..b6a1169eb 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": "5c45654af6d2f3addad4a089950e515f", + "content-hash": "6451ae49d3d58728421aa734385ab707", "packages": [ { "name": "container-interop/service-provider", @@ -294,74 +294,6 @@ }, "time": "2021-12-08T16:54:50+00:00" }, - { - "name": "inpsyde/modularity", - "version": "1.7.2", - "source": { - "type": "git", - "url": "https://github.com/inpsyde/modularity.git", - "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/inpsyde/modularity/zipball/43f4b3a7c8fbb585a44fb7a619e2546b338a8934", - "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934", - "shasum": "" - }, - "require": { - "ext-json": "*", - "php": ">=7.2", - "psr/container": "^1.1.0 || ^2" - }, - "require-dev": { - "brain/monkey": "^2.6.1", - "inpsyde/php-coding-standards": "^1", - "johnpbloch/wordpress-core": ">=5.8", - "mikey179/vfsstream": "^v1.6.10", - "php-stubs/wordpress-stubs": ">=5.8@stable", - "phpunit/phpunit": "^8.5.21 || ^9.6.7", - "vimeo/psalm": "^4.13.1" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Inpsyde\\Modularity\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "GPL-2.0-or-later" - ], - "authors": [ - { - "name": "Inpsyde GmbH", - "email": "hello@inpsyde.com", - "homepage": "https://inpsyde.com/", - "role": "Company" - }, - { - "name": "Christian Leucht", - "email": "c.leucht@inpsyde.com", - "role": "Developer" - }, - { - "name": "Giuseppe Mazzapica", - "email": "g.mazzapica@inpsyde.com", - "role": "Developer" - } - ], - "description": "Modular PSR-11 implementation for WordPress plugins, themes or libraries.", - "support": { - "issues": "https://github.com/inpsyde/modularity/issues", - "source": "https://github.com/inpsyde/modularity/tree/1.7.2" - }, - "time": "2023-11-16T11:19:47+00:00" - }, { "name": "psr/container", "version": "1.1.1", @@ -1783,6 +1715,74 @@ }, "time": "2020-07-09T08:09:16+00:00" }, + { + "name": "inpsyde/modularity", + "version": "1.7.2", + "source": { + "type": "git", + "url": "https://github.com/inpsyde/modularity.git", + "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/inpsyde/modularity/zipball/43f4b3a7c8fbb585a44fb7a619e2546b338a8934", + "reference": "43f4b3a7c8fbb585a44fb7a619e2546b338a8934", + "shasum": "" + }, + "require": { + "ext-json": "*", + "php": ">=7.2", + "psr/container": "^1.1.0 || ^2" + }, + "require-dev": { + "brain/monkey": "^2.6.1", + "inpsyde/php-coding-standards": "^1", + "johnpbloch/wordpress-core": ">=5.8", + "mikey179/vfsstream": "^v1.6.10", + "php-stubs/wordpress-stubs": ">=5.8@stable", + "phpunit/phpunit": "^8.5.21 || ^9.6.7", + "vimeo/psalm": "^4.13.1" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.x-dev" + } + }, + "autoload": { + "psr-4": { + "Inpsyde\\Modularity\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "GPL-2.0-or-later" + ], + "authors": [ + { + "name": "Inpsyde GmbH", + "email": "hello@inpsyde.com", + "homepage": "https://inpsyde.com/", + "role": "Company" + }, + { + "name": "Christian Leucht", + "email": "c.leucht@inpsyde.com", + "role": "Developer" + }, + { + "name": "Giuseppe Mazzapica", + "email": "g.mazzapica@inpsyde.com", + "role": "Developer" + } + ], + "description": "Modular PSR-11 implementation for WordPress plugins, themes or libraries.", + "support": { + "issues": "https://github.com/inpsyde/modularity/issues", + "source": "https://github.com/inpsyde/modularity/tree/1.7.2" + }, + "time": "2023-11-16T11:19:47+00:00" + }, { "name": "mockery/mockery", "version": "1.3.6", From cbc8de5a9d0e28f619df778f45ff23fb4bdb9e43 Mon Sep 17 00:00:00 2001 From: Pedro Silva Date: Fri, 19 Apr 2024 15:26:55 +0100 Subject: [PATCH 016/357] Migrated AXO to modularity. --- modules/ppcp-axo/extensions.php | 2 +- modules/ppcp-axo/module.php | 4 +--- modules/ppcp-axo/src/AxoModule.php | 36 +++++++++++++++--------------- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 93e36e31c..51860df61 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager; return array( - 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array { + 'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array { $insert_after = function( array $array, string $key, array $new ): array { $keys = array_keys( $array ); diff --git a/modules/ppcp-axo/module.php b/modules/ppcp-axo/module.php index 8e0a3064f..4d71cadde 100644 --- a/modules/ppcp-axo/module.php +++ b/modules/ppcp-axo/module.php @@ -9,8 +9,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; - -return static function (): ModuleInterface { +return static function (): AxoModule { return new AxoModule(); }; diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 6680c9fd7..b22e107ba 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -16,29 +16,36 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; -use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; -use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait; +use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; /** * Class AxoModule */ -class AxoModule implements ModuleInterface { +class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule { + use ModuleClassNameIdTrait; + /** * {@inheritDoc} */ - public function setup(): ServiceProviderInterface { - return new ServiceProvider( - require __DIR__ . '/../services.php', - require __DIR__ . '/../extensions.php' - ); + public function services(): array { + return require __DIR__ . '/../services.php'; } /** * {@inheritDoc} */ - public function run( ContainerInterface $c ): void { + public function extensions(): array { + return require __DIR__ . '/../extensions.php'; + } + + /** + * {@inheritDoc} + */ + public function run( ContainerInterface $c ): bool { $module = $this; add_filter( @@ -173,6 +180,7 @@ class AxoModule implements ModuleInterface { 1 ); + return true; } /** @@ -213,12 +221,4 @@ class AxoModule implements ModuleInterface { return $localized_script_data; } - - /** - * Returns the key for the module. - * - * @return string|void - */ - public function getKey() { - } } From 0038922fe84bf7e881db5c430b170cacc1c2536f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 9 Jul 2024 15:40:16 +0400 Subject: [PATCH 017/357] Add filter for the total --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..2a7e4fe82 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -131,6 +131,7 @@ class WooCommerceOrderCreator { } $total = $product->get_price() * $quantity; + $total = apply_filters( 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', $total, $cart_item ); $item->set_name( $product->get_name() ); $item->set_subtotal( $total ); From fd56d758c57faee57f0cd52a7e3c93081c9cf8ed Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 9 Jul 2024 15:40:50 +0400 Subject: [PATCH 018/357] Add service to check if NYP is active --- modules/ppcp-compat/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index 70ae6dac2..d27091162 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -83,6 +83,9 @@ return array( 'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool { return class_exists( 'WC_Connect_Loader' ); }, + 'compat.nyp.is_supported_plugin_version_active' => function (): bool { + return function_exists( 'wc_nyp_init' ); + }, 'compat.module.url' => static function ( ContainerInterface $container ): string { /** From 6599686329e67c7ab8388f12252e1c561fb2d932 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 9 Jul 2024 15:41:14 +0400 Subject: [PATCH 019/357] Add NYP compat layer --- modules/ppcp-compat/src/CompatModule.php | 25 ++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index a2d0a3bad..aa6829cc0 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -56,6 +56,11 @@ class CompatModule implements ModuleInterface { $this->fix_page_builders(); $this->exclude_cache_plugins_js_minification( $c ); $this->set_elementor_checkout_context(); + + $is_nyp_active = $c->get( 'compat.nyp.is_supported_plugin_version_active' ); + if ( $is_nyp_active ) { + $this->initialize_nyp_compat_layer(); + } } /** @@ -378,4 +383,24 @@ class CompatModule implements ModuleInterface { 3 ); } + + /** + * Sets up the compatibility layer for PayPal Shipping callback & WooCommerce Name Your Price plugin. + * + * @return void + */ + protected function initialize_nyp_compat_layer(): void { + add_filter( + 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', + static function( $total, $cart_item ) { + if ( ! isset( $cart_item['nyp'] ) ) { + return $total; + } + + return $cart_item['nyp']; + }, + 10, + 2 + ); + } } From b350a20e345d1aa9325eea60f58b11fba974cb1e Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 9 Jul 2024 17:05:05 +0400 Subject: [PATCH 020/357] Fix the PSALM --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index aa6829cc0..32a8ff4cc 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -392,7 +392,7 @@ class CompatModule implements ModuleInterface { protected function initialize_nyp_compat_layer(): void { add_filter( 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', - static function( $total, $cart_item ) { + static function( string $total, array $cart_item ): string { if ( ! isset( $cart_item['nyp'] ) ) { return $total; } From 49faab654a2eddce4a909ce8740089a69eaea475 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 10 Jul 2024 09:23:04 +0400 Subject: [PATCH 021/357] Prevent enabling Standard Card Button when ACDC is enabled --- .../resources/js/gateway-settings.js | 9 ++--- .../GatewayWithoutPayPalAdminNotice.php | 36 ++++++++++++++++--- .../ppcp-wc-gateway/src/WCGatewayModule.php | 8 +++++ 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js index eee948657..9257bd86d 100644 --- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js +++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js @@ -125,10 +125,6 @@ document.addEventListener( } function shouldDisableCardButton() { - if (currentTabId() === 'ppcp-card-button-gateway') { - return false; - } - return PayPalCommerceGatewaySettings.is_acdc_enabled || jQuery('#ppcp-allow_card_button_gateway').is(':checked'); } @@ -157,6 +153,11 @@ document.addEventListener( } if (shouldDisableCardButton()) { + const standardCardButtonInput = document.querySelector('#woocommerce_ppcp-card-button-gateway_enabled'); + if (standardCardButtonInput) { + standardCardButtonInput.disabled = true; + } + disabledSources = disabledSources.concat('card'); } diff --git a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php index 81b057423..051b38178 100644 --- a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php +++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php @@ -22,6 +22,7 @@ class GatewayWithoutPayPalAdminNotice { private const NOTICE_OK = ''; private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway'; private const NOTICE_DISABLED_LOCATION = 'disabled_location'; + private const NOTICE_DISABLED_CARD_BUTTON = 'disabled_card'; /** * The gateway ID. @@ -114,6 +115,15 @@ class GatewayWithoutPayPalAdminNotice { 'woocommerce-paypal-payments' ); break; + case self::NOTICE_DISABLED_CARD_BUTTON: + /* translators: %1$s Standard Card Button section URL, %2$s Advanced Card Processing section URL. */ + $text = __( + 'The Standard Card Button cannot be used while Advanced Card Processing is enabled.', + 'woocommerce-paypal-payments' + ); + $url1 = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-card-button-gateway' ); + $url2 = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-credit-card-gateway' ); + break; default: return null; } @@ -125,11 +135,20 @@ class GatewayWithoutPayPalAdminNotice { $name = $gateway->get_method_title(); - $message = sprintf( - $text, - $name, - admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ) - ); + if ( $notice_type === self::NOTICE_DISABLED_CARD_BUTTON ) { + $message = sprintf( + $text, + $url1, + $url2 + ); + } else { + $message = sprintf( + $text, + $name, + admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ) + ); + } + return new Message( $message, 'warning' ); } @@ -160,6 +179,13 @@ class GatewayWithoutPayPalAdminNotice { return self::NOTICE_DISABLED_LOCATION; } + $is_dcc_enabled = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ?? false; + $is_card_button_allowed = $this->settings->has( 'allow_card_button_gateway' ) && $this->settings->get( 'allow_card_button_gateway' ); + + if ( $is_dcc_enabled && $is_card_button_allowed ) { + return self::NOTICE_DISABLED_CARD_BUTTON; + } + return self::NOTICE_OK; } diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 76de0478c..0b1315376 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -528,6 +528,14 @@ class WCGatewayModule implements ModuleInterface { return $methods; } + $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; + $standard_card_button = get_option( 'woocommerce_ppcp-card-button-gateway_settings' ); + + if ( $is_dcc_enabled && isset( $standard_card_button['enabled'] ) ) { + $standard_card_button['enabled'] = 'no'; + update_option( 'woocommerce_ppcp-card-button-gateway_settings', $standard_card_button ); + } + $dcc_applies = $container->get( 'api.helpers.dccapplies' ); assert( $dcc_applies instanceof DccApplies ); From 5123a1511c1e53d6bc2647f354082f2b049da044 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 10 Jul 2024 10:22:14 +0400 Subject: [PATCH 022/357] Add notice when upgrading to plugin version >= 3.0.0 --- woocommerce-paypal-payments.php | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 32ea572df..dafcaf9a1 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -222,6 +222,22 @@ define( 'PAYPAL_INTEGRATION_DATE', '2024-06-25' ); } ); + add_action( + 'in_plugin_update_message-woocommerce-paypal-payments/woocommerce-paypal-payments.php', + static function( array $plugin_data, \stdClass $new_data ) { + if ( version_compare( $plugin_data['Version'], '3.0.0', '<' ) && + version_compare( $new_data->new_version, '3.0.0', '>=' ) ) { + printf( + '

%s: %s

', + esc_html__( 'Warning', 'woocommerce-paypal-payments' ), + esc_html__( 'WooCommerce PayPal Payments version 3.0.0 contains significant changes that may impact your website. We strongly recommend reviewing the changes and testing the update on a staging site before updating it on your production environment.', 'woocommerce-paypal-payments' ) + ); + } + }, + 10, + 2 + ); + /** * Check if WooCommerce is active. * From 8a37f10883343c1a9bd7ccd865ecefe00b0c7834 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 10 Jul 2024 13:59:00 +0400 Subject: [PATCH 023/357] Update contributor account inpsyde => syde --- readme.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.txt b/readme.txt index c1d1b0504..938721db9 100644 --- a/readme.txt +++ b/readme.txt @@ -1,5 +1,5 @@ === WooCommerce PayPal Payments === -Contributors: woocommerce, automattic, inpsyde +Contributors: woocommerce, automattic, syde Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay Requires at least: 5.3 Tested up to: 6.5 From 300804a738d3438f77e9f959c4909ffc8cc503b6 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 10 Jul 2024 14:30:09 +0400 Subject: [PATCH 024/357] Fix psalm error --- .../src/Notice/GatewayWithoutPayPalAdminNotice.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php index 051b38178..77185e6d1 100644 --- a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php +++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php @@ -100,6 +100,9 @@ class GatewayWithoutPayPalAdminNotice { public function message(): ?Message { $notice_type = $this->check(); + $url1 = ''; + $url2 = ''; + switch ( $notice_type ) { case self::NOTICE_DISABLED_GATEWAY: /* translators: %1$s the gateway name, %2$s URL. */ From 59953ae58c97ed7215c0f6802f0b0bcae1d4a979 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 10 Jul 2024 18:08:46 +0400 Subject: [PATCH 025/357] Remove redundant else condition --- .../Notice/GatewayWithoutPayPalAdminNotice.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php index 77185e6d1..2dfc88904 100644 --- a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php +++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php @@ -19,9 +19,9 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; * Creates the admin message about the gateway being enabled without the PayPal gateway. */ class GatewayWithoutPayPalAdminNotice { - private const NOTICE_OK = ''; - private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway'; - private const NOTICE_DISABLED_LOCATION = 'disabled_location'; + private const NOTICE_OK = ''; + private const NOTICE_DISABLED_GATEWAY = 'disabled_gateway'; + private const NOTICE_DISABLED_LOCATION = 'disabled_location'; private const NOTICE_DISABLED_CARD_BUTTON = 'disabled_card'; /** @@ -138,18 +138,18 @@ class GatewayWithoutPayPalAdminNotice { $name = $gateway->get_method_title(); + $message = sprintf( + $text, + $name, + admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ) + ); + if ( $notice_type === self::NOTICE_DISABLED_CARD_BUTTON ) { $message = sprintf( $text, $url1, $url2 ); - } else { - $message = sprintf( - $text, - $name, - admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' ) - ); } return new Message( $message, 'warning' ); From 484d026cced1675c36b02894b8c727c89504b468 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 10 Jul 2024 19:48:40 +0400 Subject: [PATCH 026/357] Remove the return type to avoid potential errors --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 32a8ff4cc..e06f4c518 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -392,7 +392,7 @@ class CompatModule implements ModuleInterface { protected function initialize_nyp_compat_layer(): void { add_filter( 'woocommerce_paypal_payments_shipping_callback_cart_line_item_total', - static function( string $total, array $cart_item ): string { + static function( string $total, array $cart_item ) { if ( ! isset( $cart_item['nyp'] ) ) { return $total; } From 56eaf73a29f0cbe5ef2ea37395a102c904d7cc9c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 11 Jul 2024 15:52:40 +0200 Subject: [PATCH 027/357] =?UTF-8?q?=E2=9A=A1=EF=B8=8F=20Optimize=20loading?= =?UTF-8?q?=20of=20plugin=20scripts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Assets/OnboardingAssets.php | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php index 4b0c9c165..c5385216d 100644 --- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php +++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php @@ -96,21 +96,21 @@ class OnboardingAssets { */ public function register(): bool { - $url = untrailingslashit( $this->module_url ) . '/assets/css/onboarding.css'; wp_register_style( 'ppcp-onboarding', - $url, + $this->module_url . '/assets/css/onboarding.css', array(), $this->version ); - $url = untrailingslashit( $this->module_url ) . '/assets/js/settings.js'; + wp_register_script( 'ppcp-settings', - $url, + $this->module_url . '/assets/js/settings.js', array(), $this->version, true ); + wp_localize_script( 'ppcp-settings', 'PayPalCommerceSettings', @@ -122,14 +122,14 @@ class OnboardingAssets { ) ); - $url = untrailingslashit( $this->module_url ) . '/assets/js/onboarding.js'; wp_register_script( 'ppcp-onboarding', - $url, + $this->module_url . '/assets/js/onboarding.js', array( 'jquery' ), $this->version, true ); + wp_localize_script( 'ppcp-onboarding', 'PayPalCommerceGatewayOnboarding', @@ -164,17 +164,22 @@ class OnboardingAssets { /** * Enqueues the necessary scripts. * - * @return bool + * @return void */ - public function enqueue(): bool { - wp_enqueue_style( 'ppcp-onboarding' ); - wp_enqueue_script( 'ppcp-settings' ); - if ( ! $this->should_render_onboarding_script() ) { - return false; + public function enqueue(): void { + // Do not enqueue anything when we are not on a PayPal Payments settings tab. + if ( ! $this->page_id ) { + return; } - wp_enqueue_script( 'ppcp-onboarding' ); - return true; + // Enqueue general assets for the plugin's settings page. + wp_enqueue_script( 'ppcp-settings' ); + wp_enqueue_style( 'ppcp-onboarding' ); // File also contains general settings styles. + + // Conditionally enqueue the onboarding script, when needed. + if ( $this->should_render_onboarding_script() ) { + wp_enqueue_script( 'ppcp-onboarding' ); + } } /** From 4b8843d93d64606b57d8492d83548fa83f7c81c9 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 12 Jul 2024 13:49:50 +0200 Subject: [PATCH 028/357] Google Pay: Fix the incorrect popup triggering (2645) --- .../resources/js/GooglepayButton.js | 89 +++++++++---------- .../resources/js/GooglepayManager.js | 57 ++++++++++-- .../ppcp-googlepay/resources/js/boot-admin.js | 6 +- 3 files changed, 96 insertions(+), 56 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index b3ca0a935..e94a49e1a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,4 +1,3 @@ -import ContextHandlerFactory from "./Context/ContextHandlerFactory"; import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; @@ -7,7 +6,7 @@ import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/A class GooglepayButton { - constructor(context, externalHandler, buttonConfig, ppcpConfig) { + constructor(context, externalHandler, buttonConfig, ppcpConfig, contextHandler) { apmButtonsInit(ppcpConfig); this.isInitialized = false; @@ -16,24 +15,18 @@ class GooglepayButton { this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; + this.contextHandler = contextHandler; this.paymentsClient = null; - this.contextHandler = ContextHandlerFactory.create( - this.context, - this.buttonConfig, - this.ppcpConfig, - this.externalHandler - ); - this.log = function() { - if ( this.buttonConfig.is_debug ) { + if (this.buttonConfig.is_debug) { //console.log('[GooglePayButton]', ...arguments); } } } - init(config) { + init(config, transactionInfo) { if (this.isInitialized) { return; } @@ -48,6 +41,7 @@ class GooglepayButton { } this.googlePayConfig = config; + this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; @@ -73,16 +67,16 @@ class GooglepayButton { } this.isInitialized = false; - this.init(this.googlePayConfig); + this.init(this.googlePayConfig, this.transactionInfo); } validateConfig() { - if ( ['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { + if (['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); return false; } - if ( !this.contextHandler ) { + if (!this.contextHandler) { console.error('[GooglePayButton] Invalid context handler.', this.contextHandler); return false; } @@ -125,7 +119,7 @@ class GooglepayButton { onPaymentAuthorized: this.onPaymentAuthorized.bind(this) } - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { + if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); } @@ -218,7 +212,7 @@ class GooglepayButton { async onButtonClick() { this.log('onButtonClick', this.context); - const paymentDataRequest = await this.paymentDataRequest(); + const paymentDataRequest = this.paymentDataRequest(); this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. @@ -226,7 +220,7 @@ class GooglepayButton { this.paymentsClient.loadPaymentData(paymentDataRequest); } - async paymentDataRequest() { + paymentDataRequest() { let baseRequest = { apiVersion: 2, apiVersionMinor: 0 @@ -235,11 +229,11 @@ class GooglepayButton { const googlePayConfig = this.googlePayConfig; const paymentDataRequest = Object.assign({}, baseRequest); paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = await this.contextHandler.transactionInfo(); + paymentDataRequest.transactionInfo = this.transactionInfo; paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - if ( this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed() ) { - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; + if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { + paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; paymentDataRequest.shippingAddressRequired = true; paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); paymentDataRequest.shippingOptionRequired = true; @@ -266,37 +260,42 @@ class GooglepayButton { this.log('paymentData', paymentData); return new Promise(async (resolve, reject) => { - let paymentDataRequestUpdate = {}; + try { + let paymentDataRequestUpdate = {}; - const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); - const transactionInfo = await this.contextHandler.transactionInfo(); + const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); + const transactionInfo = this.transactionInfo; - this.log('onPaymentDataChanged:updatedData', updatedData); - this.log('onPaymentDataChanged:transactionInfo', transactionInfo); + this.log('onPaymentDataChanged:updatedData', updatedData); + this.log('onPaymentDataChanged:transactionInfo', transactionInfo); - updatedData.country_code = transactionInfo.countryCode; - updatedData.currency_code = transactionInfo.currencyCode; - updatedData.total_str = transactionInfo.totalPrice; + updatedData.country_code = transactionInfo.countryCode; + updatedData.currency_code = transactionInfo.currencyCode; + updatedData.total_str = transactionInfo.totalPrice; + + // Handle unserviceable address. + if (!(updatedData.shipping_options?.shippingOptions?.length)) { + paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); + resolve(paymentDataRequestUpdate); + return; + } + + switch (paymentData.callbackTrigger) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); + break; + } - // Handle unserviceable address. - if (!(updatedData.shipping_options?.shippingOptions?.length)) { - paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); resolve(paymentDataRequestUpdate); - return; + } catch(error) { + console.error('Error during onPaymentDataChanged:', error); + reject(error); } - - switch (paymentData.callbackTrigger) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - } - - resolve(paymentDataRequestUpdate); }); } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 72475cfe5..e03a61ed8 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,5 +1,6 @@ import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; import GooglepayButton from "./GooglepayButton"; +import ContextHandlerFactory from "./Context/ContextHandlerFactory"; class GooglepayManager { @@ -8,34 +9,72 @@ class GooglepayManager { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.googlePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; this.buttons = []; - buttonModuleWatcher.watchContextBootstrap((bootstrap) => { + buttonModuleWatcher.watchContextBootstrap(async (bootstrap) => { + if (!this.contextHandler) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); + } + const button = new GooglepayButton( bootstrap.context, bootstrap.handler, buttonConfig, ppcpConfig, + this.contextHandler ); this.buttons.push(button); - if (this.googlePayConfig) { - button.init(this.googlePayConfig); + // Initialize button only if googlePayConfig and transactionInfo are already fetched. + if (this.googlePayConfig && this.transactionInfo) { + button.init(this.googlePayConfig, this.transactionInfo); + } else { + await this.init(); + if (this.googlePayConfig && this.transactionInfo) { + button.init(this.googlePayConfig, this.transactionInfo); + } } }); } - init() { - (async () => { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await paypal.Googlepay().config(); + async init() { + try { + if (!this.googlePayConfig) { + // Gets GooglePay configuration of the PayPal merchant. + this.googlePayConfig = await paypal.Googlepay().config(); + } + + if (!this.transactionInfo) { + this.transactionInfo = await this.fetchTransactionInfo(); + } for (const button of this.buttons) { - button.init(this.googlePayConfig); + button.init(this.googlePayConfig, this.transactionInfo); } - })(); + } catch(error) { + console.error('Error during initialization:', error); + } + } + + async fetchTransactionInfo() { + try { + if (!this.contextHandler) { + throw new Error('ContextHandler is not initialized'); + } + return await this.contextHandler.transactionInfo(); + } catch(error) { + console.error('Error fetching transaction info:', error); + throw error; + } } reinit() { diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 3e561e6e4..62fda993e 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -1,6 +1,7 @@ import GooglepayButton from './GooglepayButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; /** * Accessor that creates and returns a single PreviewButtonManager instance. @@ -94,9 +95,10 @@ class GooglePayPreviewButton extends PreviewButton { } createButton(buttonConfig) { - const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig); + const contextHandler = ContextHandlerFactory.create('preview', buttonConfig, this.ppcpConfig, null); + const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig, contextHandler); - button.init(this.apiConfig); + button.init(this.apiConfig, contextHandler.transactionInfo()); } /** From d7e58d6f6f8f175ecdd646d7fb3cb855d831ca89 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 15 Jul 2024 11:21:56 +0400 Subject: [PATCH 029/357] Fixed notice formatting --- woocommerce-paypal-payments.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index dafcaf9a1..8394055a2 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.1 + * Version: 2.8.0 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 @@ -228,7 +228,7 @@ define( 'PAYPAL_INTEGRATION_DATE', '2024-06-25' ); if ( version_compare( $plugin_data['Version'], '3.0.0', '<' ) && version_compare( $new_data->new_version, '3.0.0', '>=' ) ) { printf( - '

%s: %s

', + '
%s: %s', esc_html__( 'Warning', 'woocommerce-paypal-payments' ), esc_html__( 'WooCommerce PayPal Payments version 3.0.0 contains significant changes that may impact your website. We strongly recommend reviewing the changes and testing the update on a staging site before updating it on your production environment.', 'woocommerce-paypal-payments' ) ); From 26daa3a52fa46551236517d3f56e174919c542ed Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 15 Jul 2024 11:23:01 +0400 Subject: [PATCH 030/357] Revert version number --- woocommerce-paypal-payments.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 8394055a2..d18ab2969 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.0 + * Version: 2.8.1 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 From 525fee7edd17c88507cebfe61c068f7613856503 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 10:58:03 +0200 Subject: [PATCH 031/357] Remove the redundant async --- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index e94a49e1a..64ea52062 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -209,7 +209,7 @@ class GooglepayButton { /** * Show Google Pay payment sheet when Google Pay payment button is clicked */ - async onButtonClick() { + onButtonClick() { this.log('onButtonClick', this.context); const paymentDataRequest = this.paymentDataRequest(); From 8b43a06f9f298e465a80b61832b1c3dc5fc26e57 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 11:21:56 +0200 Subject: [PATCH 032/357] Fix JS linting --- .../resources/js/GooglepayButton.js | 866 ++++++++++-------- .../resources/js/GooglepayManager.js | 143 ++- .../ppcp-googlepay/resources/js/boot-admin.js | 174 ++-- 3 files changed, 638 insertions(+), 545 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 64ea52062..53a2f9931 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,397 +1,479 @@ -import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; -import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder"; -import UpdatePaymentData from "./Helper/UpdatePaymentData"; -import {apmButtonsInit} from "../../../ppcp-button/resources/js/modules/Helper/ApmButtons"; +import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; +import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; +import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; +import UpdatePaymentData from './Helper/UpdatePaymentData'; +import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; class GooglepayButton { - - constructor(context, externalHandler, buttonConfig, ppcpConfig, contextHandler) { - apmButtonsInit(ppcpConfig); - - this.isInitialized = false; - - this.context = context; - this.externalHandler = externalHandler; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.contextHandler = contextHandler; - - this.paymentsClient = null; - - this.log = function() { - if (this.buttonConfig.is_debug) { - //console.log('[GooglePayButton]', ...arguments); - } - } - } - - init(config, transactionInfo) { - if (this.isInitialized) { - return; - } - this.isInitialized = true; - - if (!this.validateConfig()) { - return; - } - - if (!this.contextHandler.validateContext()) { - return; - } - - this.googlePayConfig = config; - this.transactionInfo = transactionInfo; - this.allowedPaymentMethods = config.allowedPaymentMethods; - this.baseCardPaymentMethod = this.allowedPaymentMethods[0]; - - this.initClient(); - this.initEventHandlers(); - - this.paymentsClient.isReadyToPay( - this.buildReadyToPayRequest(this.allowedPaymentMethods, config) - ) - .then((response) => { - if (response.result) { - this.addButton(this.baseCardPaymentMethod); - } - }) - .catch(function(err) { - console.error(err); - }); - } - - reinit() { - if (!this.googlePayConfig) { - return; - } - - this.isInitialized = false; - this.init(this.googlePayConfig, this.transactionInfo); - } - - validateConfig() { - if (['PRODUCTION', 'TEST'].indexOf(this.buttonConfig.environment) === -1) { - console.error('[GooglePayButton] Invalid environment.', this.buttonConfig.environment); - return false; - } - - if (!this.contextHandler) { - console.error('[GooglePayButton] Invalid context handler.', this.contextHandler); - return false; - } - - return true; - } - - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - let config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper - } - - if (this.context === 'mini-cart') { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - - // Handle incompatible types. - if (config.buttonStyle.type === 'buy') { - config.buttonStyle.type = 'pay'; - } - } - - if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) { - config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - - initClient() { - const callbacks = { - onPaymentAuthorized: this.onPaymentAuthorized.bind(this) - } - - if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { - callbacks['onPaymentDataChanged'] = this.onPaymentDataChanged.bind(this); - } - - this.paymentsClient = new google.payments.api.PaymentsClient({ - environment: this.buttonConfig.environment, - // add merchant info maybe - paymentDataCallbacks: callbacks - }); - } - - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - - if (wrapper === ppcpButtonWrapper) { - throw new Error(`[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${wrapper}"`); - } - - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper); - setVisible(wrapper, $ppcpButtonWrapper.is(':visible')); - setEnabled(wrapper, !$ppcpButtonWrapper.hasClass('ppcp-disabled')); - } - - jQuery(document).on('ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', (ev, data) => { - if (jQuery(data.selector).is(ppcpButtonWrapper)) { - syncButtonVisibility(); - } - }); - - syncButtonVisibility(); - } - - buildReadyToPayRequest(allowedPaymentMethods, baseRequest) { - return Object.assign({}, baseRequest, { - allowedPaymentMethods: allowedPaymentMethods, - }); - } - - /** - * Add a Google Pay purchase button - */ - addButton(baseCardPaymentMethod) { - this.log('addButton', this.context); - - const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); - - this.waitForWrapper(wrapper, () => { - jQuery(wrapper).addClass('ppcp-button-' + ppcpStyle.shape); - - if (ppcpStyle.height) { - jQuery(wrapper).css('height', `${ppcpStyle.height}px`) - } - - const button = - this.paymentsClient.createButton({ - onClick: this.onButtonClick.bind(this), - allowedPaymentMethods: [baseCardPaymentMethod], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', - buttonSizeMode: 'fill', - }); - - jQuery(wrapper).append(button); - }); - } - - waitForWrapper(selector, callback, delay = 100, timeout = 2000) { - const startTime = Date.now(); - const interval = setInterval(() => { - const el = document.querySelector(selector); - const timeElapsed = Date.now() - startTime; - - if (el) { - clearInterval(interval); - callback(el); - } else if (timeElapsed > timeout) { - clearInterval(interval); - } - }, delay); - } - - //------------------------ - // Button click - //------------------------ - - /** - * Show Google Pay payment sheet when Google Pay payment button is clicked - */ - onButtonClick() { - this.log('onButtonClick', this.context); - - const paymentDataRequest = this.paymentDataRequest(); - this.log('onButtonClick: paymentDataRequest', paymentDataRequest, this.context); - - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. - - this.paymentsClient.loadPaymentData(paymentDataRequest); - } - - paymentDataRequest() { - let baseRequest = { - apiVersion: 2, - apiVersionMinor: 0 - } - - const googlePayConfig = this.googlePayConfig; - const paymentDataRequest = Object.assign({}, baseRequest); - paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods; - paymentDataRequest.transactionInfo = this.transactionInfo; - paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; - - if (this.buttonConfig.shipping.enabled && this.contextHandler.shippingAllowed()) { - paymentDataRequest.callbackIntents = ["SHIPPING_ADDRESS", "SHIPPING_OPTION", "PAYMENT_AUTHORIZATION"]; - paymentDataRequest.shippingAddressRequired = true; - paymentDataRequest.shippingAddressParameters = this.shippingAddressParameters(); - paymentDataRequest.shippingOptionRequired = true; - } else { - paymentDataRequest.callbackIntents = ['PAYMENT_AUTHORIZATION']; - } - - return paymentDataRequest; - } - - //------------------------ - // Shipping processing - //------------------------ - - shippingAddressParameters() { - return { - allowedCountryCodes: this.buttonConfig.shipping.countries, - phoneNumberRequired: true - }; - } - - onPaymentDataChanged(paymentData) { - this.log('onPaymentDataChanged', this.context); - this.log('paymentData', paymentData); - - return new Promise(async (resolve, reject) => { - try { - let paymentDataRequestUpdate = {}; - - const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData); - const transactionInfo = this.transactionInfo; - - this.log('onPaymentDataChanged:updatedData', updatedData); - this.log('onPaymentDataChanged:transactionInfo', transactionInfo); - - updatedData.country_code = transactionInfo.countryCode; - updatedData.currency_code = transactionInfo.currencyCode; - updatedData.total_str = transactionInfo.totalPrice; - - // Handle unserviceable address. - if (!(updatedData.shipping_options?.shippingOptions?.length)) { - paymentDataRequestUpdate.error = this.unserviceableShippingAddressError(); - resolve(paymentDataRequestUpdate); - return; - } - - switch (paymentData.callbackTrigger) { - case 'INITIALIZE': - case 'SHIPPING_ADDRESS': - paymentDataRequestUpdate.newShippingOptionParameters = updatedData.shipping_options; - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - case 'SHIPPING_OPTION': - paymentDataRequestUpdate.newTransactionInfo = this.calculateNewTransactionInfo(updatedData); - break; - } - - resolve(paymentDataRequestUpdate); - } catch(error) { - console.error('Error during onPaymentDataChanged:', error); - reject(error); - } - }); - } - - unserviceableShippingAddressError() { - return { - reason: "SHIPPING_ADDRESS_UNSERVICEABLE", - message: "Cannot ship to the selected address", - intent: "SHIPPING_ADDRESS" - }; - } - - calculateNewTransactionInfo(updatedData) { - return { - countryCode: updatedData.country_code, - currencyCode: updatedData.currency_code, - totalPriceStatus: 'FINAL', - totalPrice: updatedData.total_str - }; - } - - - //------------------------ - // Payment process - //------------------------ - - onPaymentAuthorized(paymentData) { - this.log('onPaymentAuthorized', this.context); - return this.processPayment(paymentData); - } - - async processPayment(paymentData) { - this.log('processPayment', this.context); - - return new Promise(async (resolve, reject) => { - try { - let id = await this.contextHandler.createOrder(); - - this.log('processPayment: createOrder', id, this.context); - - const confirmOrderResponse = await widgetBuilder.paypal.Googlepay().confirmOrder({ - orderId: id, - paymentMethodData: paymentData.paymentMethodData - }); - - this.log('processPayment: confirmOrder', confirmOrderResponse, this.context); - - /** Capture the Order on the Server */ - if (confirmOrderResponse.status === "APPROVED") { - - let approveFailed = false; - await this.contextHandler.approveOrder({ - orderID: id - }, { // actions mock object. - restart: () => new Promise((resolve, reject) => { - approveFailed = true; - resolve(); - }), - order: { - get: () => new Promise((resolve, reject) => { - resolve(null); - }) - } - }); - - if (!approveFailed) { - resolve(this.processPaymentResponse('SUCCESS')); - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'FAILED TO APPROVE')); - } - - } else { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', 'TRANSACTION FAILED')); - } - } catch(err) { - resolve(this.processPaymentResponse('ERROR', 'PAYMENT_AUTHORIZATION', err.message)); - } - }); - } - - processPaymentResponse(state, intent = null, message = null) { - let response = { - transactionState: state, - } - - if (intent || message) { - response.error = { - intent: intent, - message: message, - } - } - - this.log('processPaymentResponse', response, this.context); - - return response; - } - + constructor( + context, + externalHandler, + buttonConfig, + ppcpConfig, + contextHandler + ) { + apmButtonsInit( ppcpConfig ); + + this.isInitialized = false; + + this.context = context; + this.externalHandler = externalHandler; + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.contextHandler = contextHandler; + + this.paymentsClient = null; + + this.log = function () { + if ( this.buttonConfig.is_debug ) { + //console.log('[GooglePayButton]', ...arguments); + } + }; + } + + init( config, transactionInfo ) { + if ( this.isInitialized ) { + return; + } + this.isInitialized = true; + + if ( ! this.validateConfig() ) { + return; + } + + if ( ! this.contextHandler.validateContext() ) { + return; + } + + this.googlePayConfig = config; + this.transactionInfo = transactionInfo; + this.allowedPaymentMethods = config.allowedPaymentMethods; + this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; + + this.initClient(); + this.initEventHandlers(); + + this.paymentsClient + .isReadyToPay( + this.buildReadyToPayRequest( + this.allowedPaymentMethods, + config + ) + ) + .then( ( response ) => { + if ( response.result ) { + this.addButton( this.baseCardPaymentMethod ); + } + } ) + .catch( function ( err ) { + console.error( err ); + } ); + } + + reinit() { + if ( ! this.googlePayConfig ) { + return; + } + + this.isInitialized = false; + this.init( this.googlePayConfig, this.transactionInfo ); + } + + validateConfig() { + if ( + [ 'PRODUCTION', 'TEST' ].indexOf( + this.buttonConfig.environment + ) === -1 + ) { + console.error( + '[GooglePayButton] Invalid environment.', + this.buttonConfig.environment + ); + return false; + } + + if ( ! this.contextHandler ) { + console.error( + '[GooglePayButton] Invalid context handler.', + this.contextHandler + ); + return false; + } + + return true; + } + + /** + * Returns configurations relative to this button context. + */ + contextConfig() { + const config = { + wrapper: this.buttonConfig.button.wrapper, + ppcpStyle: this.ppcpConfig.button.style, + buttonStyle: this.buttonConfig.button.style, + ppcpButtonWrapper: this.ppcpConfig.button.wrapper, + }; + + if ( this.context === 'mini-cart' ) { + config.wrapper = this.buttonConfig.button.mini_cart_wrapper; + config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; + config.buttonStyle = this.buttonConfig.button.mini_cart_style; + config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; + + // Handle incompatible types. + if ( config.buttonStyle.type === 'buy' ) { + config.buttonStyle.type = 'pay'; + } + } + + if ( + [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 + ) { + config.ppcpButtonWrapper = + '#express-payment-method-ppcp-gateway-paypal'; + } + + return config; + } + + initClient() { + const callbacks = { + onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), + }; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + callbacks.onPaymentDataChanged = + this.onPaymentDataChanged.bind( this ); + } + + this.paymentsClient = new google.payments.api.PaymentsClient( { + environment: this.buttonConfig.environment, + // add merchant info maybe + paymentDataCallbacks: callbacks, + } ); + } + + initEventHandlers() { + const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); + } + + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + + syncButtonVisibility(); + } + + buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { + return Object.assign( {}, baseRequest, { + allowedPaymentMethods, + } ); + } + + /** + * Add a Google Pay purchase button + * @param baseCardPaymentMethod + */ + addButton( baseCardPaymentMethod ) { + this.log( 'addButton', this.context ); + + const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); + + this.waitForWrapper( wrapper, () => { + jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); + + if ( ppcpStyle.height ) { + jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); + } + + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: buttonStyle.color || 'black', + buttonType: buttonStyle.type || 'pay', + buttonLocale: buttonStyle.language || 'en', + buttonSizeMode: 'fill', + } ); + + jQuery( wrapper ).append( button ); + } ); + } + + waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { + const startTime = Date.now(); + const interval = setInterval( () => { + const el = document.querySelector( selector ); + const timeElapsed = Date.now() - startTime; + + if ( el ) { + clearInterval( interval ); + callback( el ); + } else if ( timeElapsed > timeout ) { + clearInterval( interval ); + } + }, delay ); + } + + //------------------------ + // Button click + //------------------------ + + /** + * Show Google Pay payment sheet when Google Pay payment button is clicked + */ + onButtonClick() { + this.log( 'onButtonClick', this.context ); + + const paymentDataRequest = this.paymentDataRequest(); + this.log( + 'onButtonClick: paymentDataRequest', + paymentDataRequest, + this.context + ); + + window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. + + this.paymentsClient.loadPaymentData( paymentDataRequest ); + } + + paymentDataRequest() { + const baseRequest = { + apiVersion: 2, + apiVersionMinor: 0, + }; + + const googlePayConfig = this.googlePayConfig; + const paymentDataRequest = Object.assign( {}, baseRequest ); + paymentDataRequest.allowedPaymentMethods = + googlePayConfig.allowedPaymentMethods; + paymentDataRequest.transactionInfo = this.transactionInfo; + paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo; + + if ( + this.buttonConfig.shipping.enabled && + this.contextHandler.shippingAllowed() + ) { + paymentDataRequest.callbackIntents = [ + 'SHIPPING_ADDRESS', + 'SHIPPING_OPTION', + 'PAYMENT_AUTHORIZATION', + ]; + paymentDataRequest.shippingAddressRequired = true; + paymentDataRequest.shippingAddressParameters = + this.shippingAddressParameters(); + paymentDataRequest.shippingOptionRequired = true; + } else { + paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ]; + } + + return paymentDataRequest; + } + + //------------------------ + // Shipping processing + //------------------------ + + shippingAddressParameters() { + return { + allowedCountryCodes: this.buttonConfig.shipping.countries, + phoneNumberRequired: true, + }; + } + + onPaymentDataChanged( paymentData ) { + this.log( 'onPaymentDataChanged', this.context ); + this.log( 'paymentData', paymentData ); + + return new Promise( async ( resolve, reject ) => { + try { + const paymentDataRequestUpdate = {}; + + const updatedData = await new UpdatePaymentData( + this.buttonConfig.ajax.update_payment_data + ).update( paymentData ); + const transactionInfo = this.transactionInfo; + + this.log( 'onPaymentDataChanged:updatedData', updatedData ); + this.log( + 'onPaymentDataChanged:transactionInfo', + transactionInfo + ); + + updatedData.country_code = transactionInfo.countryCode; + updatedData.currency_code = transactionInfo.currencyCode; + updatedData.total_str = transactionInfo.totalPrice; + + // Handle unserviceable address. + if ( ! updatedData.shipping_options?.shippingOptions?.length ) { + paymentDataRequestUpdate.error = + this.unserviceableShippingAddressError(); + resolve( paymentDataRequestUpdate ); + return; + } + + switch ( paymentData.callbackTrigger ) { + case 'INITIALIZE': + case 'SHIPPING_ADDRESS': + paymentDataRequestUpdate.newShippingOptionParameters = + updatedData.shipping_options; + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + case 'SHIPPING_OPTION': + paymentDataRequestUpdate.newTransactionInfo = + this.calculateNewTransactionInfo( updatedData ); + break; + } + + resolve( paymentDataRequestUpdate ); + } catch ( error ) { + console.error( 'Error during onPaymentDataChanged:', error ); + reject( error ); + } + } ); + } + + unserviceableShippingAddressError() { + return { + reason: 'SHIPPING_ADDRESS_UNSERVICEABLE', + message: 'Cannot ship to the selected address', + intent: 'SHIPPING_ADDRESS', + }; + } + + calculateNewTransactionInfo( updatedData ) { + return { + countryCode: updatedData.country_code, + currencyCode: updatedData.currency_code, + totalPriceStatus: 'FINAL', + totalPrice: updatedData.total_str, + }; + } + + //------------------------ + // Payment process + //------------------------ + + onPaymentAuthorized( paymentData ) { + this.log( 'onPaymentAuthorized', this.context ); + return this.processPayment( paymentData ); + } + + async processPayment( paymentData ) { + this.log( 'processPayment', this.context ); + + return new Promise( async ( resolve, reject ) => { + try { + const id = await this.contextHandler.createOrder(); + + this.log( 'processPayment: createOrder', id, this.context ); + + const confirmOrderResponse = await widgetBuilder.paypal + .Googlepay() + .confirmOrder( { + orderId: id, + paymentMethodData: paymentData.paymentMethodData, + } ); + + this.log( + 'processPayment: confirmOrder', + confirmOrderResponse, + this.context + ); + + /** Capture the Order on the Server */ + if ( confirmOrderResponse.status === 'APPROVED' ) { + let approveFailed = false; + await this.contextHandler.approveOrder( + { + orderID: id, + }, + { + // actions mock object. + restart: () => + new Promise( ( resolve, reject ) => { + approveFailed = true; + resolve(); + } ), + order: { + get: () => + new Promise( ( resolve, reject ) => { + resolve( null ); + } ), + }, + } + ); + + if ( ! approveFailed ) { + resolve( this.processPaymentResponse( 'SUCCESS' ) ); + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'FAILED TO APPROVE' + ) + ); + } + } else { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + 'TRANSACTION FAILED' + ) + ); + } + } catch ( err ) { + resolve( + this.processPaymentResponse( + 'ERROR', + 'PAYMENT_AUTHORIZATION', + err.message + ) + ); + } + } ); + } + + processPaymentResponse( state, intent = null, message = null ) { + const response = { + transactionState: state, + }; + + if ( intent || message ) { + response.error = { + intent, + message, + }; + } + + this.log( 'processPaymentResponse', response, this.context ); + + return response; + } } export default GooglepayButton; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index e03a61ed8..71c07989e 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -1,88 +1,85 @@ -import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher"; -import GooglepayButton from "./GooglepayButton"; -import ContextHandlerFactory from "./Context/ContextHandlerFactory"; +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import GooglepayButton from './GooglepayButton'; +import ContextHandlerFactory from './Context/ContextHandlerFactory'; class GooglepayManager { + constructor( buttonConfig, ppcpConfig ) { + this.buttonConfig = buttonConfig; + this.ppcpConfig = ppcpConfig; + this.googlePayConfig = null; + this.transactionInfo = null; + this.contextHandler = null; - constructor(buttonConfig, ppcpConfig) { + this.buttons = []; - this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; - this.googlePayConfig = null; - this.transactionInfo = null; - this.contextHandler = null; + buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { + if ( ! this.contextHandler ) { + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); + } - this.buttons = []; + const button = new GooglepayButton( + bootstrap.context, + bootstrap.handler, + buttonConfig, + ppcpConfig, + this.contextHandler + ); - buttonModuleWatcher.watchContextBootstrap(async (bootstrap) => { - if (!this.contextHandler) { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); - } + this.buttons.push( button ); - const button = new GooglepayButton( - bootstrap.context, - bootstrap.handler, - buttonConfig, - ppcpConfig, - this.contextHandler - ); + // Initialize button only if googlePayConfig and transactionInfo are already fetched. + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } else { + await this.init(); + if ( this.googlePayConfig && this.transactionInfo ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } + } ); + } - this.buttons.push(button); + async init() { + try { + if ( ! this.googlePayConfig ) { + // Gets GooglePay configuration of the PayPal merchant. + this.googlePayConfig = await paypal.Googlepay().config(); + } - // Initialize button only if googlePayConfig and transactionInfo are already fetched. - if (this.googlePayConfig && this.transactionInfo) { - button.init(this.googlePayConfig, this.transactionInfo); - } else { - await this.init(); - if (this.googlePayConfig && this.transactionInfo) { - button.init(this.googlePayConfig, this.transactionInfo); - } - } - }); - } + if ( ! this.transactionInfo ) { + this.transactionInfo = await this.fetchTransactionInfo(); + } - async init() { - try { - if (!this.googlePayConfig) { - // Gets GooglePay configuration of the PayPal merchant. - this.googlePayConfig = await paypal.Googlepay().config(); - } + for ( const button of this.buttons ) { + button.init( this.googlePayConfig, this.transactionInfo ); + } + } catch ( error ) { + console.error( 'Error during initialization:', error ); + } + } - if (!this.transactionInfo) { - this.transactionInfo = await this.fetchTransactionInfo(); - } - - for (const button of this.buttons) { - button.init(this.googlePayConfig, this.transactionInfo); - } - } catch(error) { - console.error('Error during initialization:', error); - } - } - - async fetchTransactionInfo() { - try { - if (!this.contextHandler) { - throw new Error('ContextHandler is not initialized'); - } - return await this.contextHandler.transactionInfo(); - } catch(error) { - console.error('Error fetching transaction info:', error); - throw error; - } - } - - reinit() { - for (const button of this.buttons) { - button.reinit(); - } - } + async fetchTransactionInfo() { + try { + if ( ! this.contextHandler ) { + throw new Error( 'ContextHandler is not initialized' ); + } + return await this.contextHandler.transactionInfo(); + } catch ( error ) { + console.error( 'Error fetching transaction info:', error ); + throw error; + } + } + reinit() { + for ( const button of this.buttons ) { + button.reinit(); + } + } } export default GooglepayManager; diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 62fda993e..2c0c008d2 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -7,110 +7,124 @@ import ContextHandlerFactory from './Context/ContextHandlerFactory'; * Accessor that creates and returns a single PreviewButtonManager instance. */ const buttonManager = () => { - if (!GooglePayPreviewButtonManager.instance) { - GooglePayPreviewButtonManager.instance = new GooglePayPreviewButtonManager(); - } + if ( ! GooglePayPreviewButtonManager.instance ) { + GooglePayPreviewButtonManager.instance = + new GooglePayPreviewButtonManager(); + } - return GooglePayPreviewButtonManager.instance; + return GooglePayPreviewButtonManager.instance; }; - /** * Manages all GooglePay preview buttons on this page. */ class GooglePayPreviewButtonManager extends PreviewButtonManager { - constructor() { - const args = { - methodName: 'GooglePay', - buttonConfig: window.wc_ppcp_googlepay_admin, - }; + constructor() { + const args = { + methodName: 'GooglePay', + buttonConfig: window.wc_ppcp_googlepay_admin, + }; - super(args); - } + super( args ); + } - /** - * Responsible for fetching and returning the PayPal configuration object for this payment - * method. - * - * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. - * @return {Promise<{}>} - */ - async fetchConfig(payPal) { - const apiMethod = payPal?.Googlepay()?.config; + /** + * Responsible for fetching and returning the PayPal configuration object for this payment + * method. + * + * @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder. + * @return {Promise<{}>} + */ + async fetchConfig( payPal ) { + const apiMethod = payPal?.Googlepay()?.config; - if (!apiMethod) { - this.error('configuration object cannot be retrieved from PayPal'); - return {}; - } + if ( ! apiMethod ) { + this.error( + 'configuration object cannot be retrieved from PayPal' + ); + return {}; + } - try { - return await apiMethod(); - } catch (error) { - if (error.message.includes('Not Eligible')) { - this.apiError = 'Not Eligible'; - } - return null; - } - } + try { + return await apiMethod(); + } catch ( error ) { + if ( error.message.includes( 'Not Eligible' ) ) { + this.apiError = 'Not Eligible'; + } + return null; + } + } - /** - * This method is responsible for creating a new PreviewButton instance and returning it. - * - * @param {string} wrapperId - CSS ID of the wrapper element. - * @return {GooglePayPreviewButton} - */ - createButtonInstance(wrapperId) { - return new GooglePayPreviewButton({ - selector: wrapperId, - apiConfig: this.apiConfig, - }); - } + /** + * This method is responsible for creating a new PreviewButton instance and returning it. + * + * @param {string} wrapperId - CSS ID of the wrapper element. + * @return {GooglePayPreviewButton} + */ + createButtonInstance( wrapperId ) { + return new GooglePayPreviewButton( { + selector: wrapperId, + apiConfig: this.apiConfig, + } ); + } } - /** * A single GooglePay preview button instance. */ class GooglePayPreviewButton extends PreviewButton { - constructor(args) { - super(args); + constructor( args ) { + super( args ); - this.selector = `${args.selector}GooglePay`; - this.defaultAttributes = { - button: { - style: { - type: 'pay', - color: 'black', - language: 'en', - }, - }, - }; - } + this.selector = `${ args.selector }GooglePay`; + this.defaultAttributes = { + button: { + style: { + type: 'pay', + color: 'black', + language: 'en', + }, + }, + }; + } - createNewWrapper() { - const element = super.createNewWrapper(); - element.addClass('ppcp-button-googlepay'); + createNewWrapper() { + const element = super.createNewWrapper(); + element.addClass( 'ppcp-button-googlepay' ); - return element; - } + return element; + } - createButton(buttonConfig) { - const contextHandler = ContextHandlerFactory.create('preview', buttonConfig, this.ppcpConfig, null); - const button = new GooglepayButton('preview', null, buttonConfig, this.ppcpConfig, contextHandler); + createButton( buttonConfig ) { + const contextHandler = ContextHandlerFactory.create( + 'preview', + buttonConfig, + this.ppcpConfig, + null + ); + const button = new GooglepayButton( + 'preview', + null, + buttonConfig, + this.ppcpConfig, + contextHandler + ); - button.init(this.apiConfig, contextHandler.transactionInfo()); - } + button.init( this.apiConfig, contextHandler.transactionInfo() ); + } - /** - * Merge form details into the config object for preview. - * Mutates the previewConfig object; no return value. - */ - dynamicPreviewConfig(buttonConfig, ppcpConfig) { - // Merge the current form-values into the preview-button configuration. - if (ppcpConfig.button && buttonConfig.button) { - Object.assign(buttonConfig.button.style, ppcpConfig.button.style); - } - } + /** + * Merge form details into the config object for preview. + * Mutates the previewConfig object; no return value. + * @param buttonConfig + * @param ppcpConfig + */ + dynamicPreviewConfig( buttonConfig, ppcpConfig ) { + // Merge the current form-values into the preview-button configuration. + if ( ppcpConfig.button && buttonConfig.button ) { + Object.assign( buttonConfig.button.style, ppcpConfig.button.style ); + } + } } // Initialize the preview button manager. From 311d513fbf224b13fd9b9d4ad16df1340c0f6526 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 15 Jul 2024 11:40:09 +0200 Subject: [PATCH 033/357] Add WC gateway --- modules/ppcp-googlepay/GooglePayGateway.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 modules/ppcp-googlepay/GooglePayGateway.php diff --git a/modules/ppcp-googlepay/GooglePayGateway.php b/modules/ppcp-googlepay/GooglePayGateway.php new file mode 100644 index 000000000..936c7ce48 --- /dev/null +++ b/modules/ppcp-googlepay/GooglePayGateway.php @@ -0,0 +1,18 @@ + Date: Mon, 15 Jul 2024 13:06:00 +0200 Subject: [PATCH 034/357] Add WC gateway --- modules/ppcp-googlepay/GooglePayGateway.php | 18 ------ modules/ppcp-googlepay/services.php | 4 +- .../ppcp-googlepay/src/GooglePayGateway.php | 59 +++++++++++++++++++ .../ppcp-googlepay/src/GooglepayModule.php | 22 +++++++ 4 files changed, 84 insertions(+), 19 deletions(-) delete mode 100644 modules/ppcp-googlepay/GooglePayGateway.php create mode 100644 modules/ppcp-googlepay/src/GooglePayGateway.php diff --git a/modules/ppcp-googlepay/GooglePayGateway.php b/modules/ppcp-googlepay/GooglePayGateway.php deleted file mode 100644 index 936c7ce48..000000000 --- a/modules/ppcp-googlepay/GooglePayGateway.php +++ /dev/null @@ -1,18 +0,0 @@ - static function ( ContainerInterface $container ): GooglePayGateway { + return new GooglePayGateway(); + }, ); diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php new file mode 100644 index 000000000..c2dc3effc --- /dev/null +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -0,0 +1,59 @@ +id = self::ID; + + $this->method_title = __( 'Google Pay', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'Google Pay', 'woocommerce-paypal-payments' ); + + $this->title = $this->get_option( 'title', $this->method_title ); + $this->description = $this->get_option( 'description', $this->method_description ); + + $this->init_form_fields(); + $this->init_settings(); + } + + /** + * Initialize the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'Google Pay', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable Google Pay payment gateway.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } +} diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index e2b0ea8c5..ee989a019 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; +use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; @@ -159,6 +160,27 @@ class GooglepayModule implements ModuleInterface { }, 1 ); + + add_filter( + 'woocommerce_payment_gateways', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $methods ) use ( $c ): array { + if ( ! is_array( $methods ) ) { + return $methods; + } + + $googlepay_gateway = $c->get( 'googlepay.wc-gateway' ); + assert( $googlepay_gateway instanceof WC_Payment_Gateway ); + + $methods[] = $googlepay_gateway; + + return $methods; + } + ); } /** From 397acdd2bf34f0fd945934662b6e53dd38928f0e Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 15 Jul 2024 16:45:10 +0400 Subject: [PATCH 035/357] Prevent displaying smart button multiple times on variable product page --- .../js/modules/ContextBootstrap/SingleProductBootstap.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js index c9978306e..74d6cd31b 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js @@ -7,6 +7,7 @@ import {getPlanIdFromVariation} from "../Helper/Subscriptions" import SimulateCart from "../Helper/SimulateCart"; import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils"; import merge from "deepmerge"; +import {debounce} from "../../../../../ppcp-blocks/resources/js/Helper/debounce"; class SingleProductBootstap { constructor(gateway, renderer, errorHandler) { @@ -17,7 +18,8 @@ class SingleProductBootstap { this.formSelector = 'form.cart'; // Prevent simulate cart being called too many times in a burst. - this.simulateCartThrottled = throttle(this.simulateCart, this.gateway.simulate_cart.throttling || 5000); + this.simulateCartThrottled = throttle(this.simulateCart.bind(this), this.gateway.simulate_cart.throttling || 5000); + this.debouncedHandleChange = debounce(this.handleChange.bind(this), 100); this.renderer.onButtonsInit(this.gateway.button.wrapper, () => { this.handleChange(); @@ -31,7 +33,7 @@ class SingleProductBootstap { } handleChange() { - this.subscriptionButtonsLoaded = false + this.subscriptionButtonsLoaded = false; if (!this.shouldRender()) { this.renderer.disableSmartButtons(this.gateway.button.wrapper); @@ -65,8 +67,9 @@ class SingleProductBootstap { } jQuery(document).on('change', this.formSelector, () => { - this.handleChange(); + this.debouncedHandleChange(); }); + this.mutationObserver.observe(form, { childList: true, subtree: true }); const addToCartButton = form.querySelector('.single_add_to_cart_button'); From e52939200f0e68ff804b2b4f9d77ce96cd6d3c84 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 15 Jul 2024 14:54:10 +0200 Subject: [PATCH 036/357] Hide wc place order button when applepay payment method is selected --- .../js/modules/ContextBootstrap/CheckoutBootstap.js | 7 ++++++- .../resources/js/modules/Helper/CheckoutMethodState.js | 1 + 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 848285815..ad75a3d62 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -181,9 +181,14 @@ class CheckoutBootstap { const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes( currentPaymentMethod ); + const isGooglePayMethod = + currentPaymentMethod === PaymentMethods.GOOGLEPAY; const isSavedCard = isCard && isSavedCardSelected(); const isNotOurGateway = - ! isPaypal && ! isCard && ! isSeparateButtonGateway; + ! isPaypal && + ! isCard && + ! isSeparateButtonGateway && + ! isGooglePayMethod; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 0ea05f255..3e284c8ef 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -3,6 +3,7 @@ export const PaymentMethods = { CARDS: 'ppcp-credit-card-gateway', OXXO: 'ppcp-oxxo-gateway', CARD_BUTTON: 'ppcp-card-button-gateway', + GOOGLEPAY: 'ppcp-googlepay', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; From f8ed3328a80904b7459a565881fe595a632f1c32 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 15 Jul 2024 15:56:35 +0200 Subject: [PATCH 037/357] Dispaly googlepay payment method only if googlepay button is enabled --- modules/ppcp-googlepay/src/GooglepayModule.php | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index ee989a019..8167e87f9 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -173,10 +173,15 @@ class GooglepayModule implements ModuleInterface { return $methods; } - $googlepay_gateway = $c->get( 'googlepay.wc-gateway' ); - assert( $googlepay_gateway instanceof WC_Payment_Gateway ); + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); - $methods[] = $googlepay_gateway; + if ( $settings->has( 'googlepay_button_enabled' ) && $settings->get( 'googlepay_button_enabled' ) ) { + $googlepay_gateway = $c->get( 'googlepay.wc-gateway' ); + assert( $googlepay_gateway instanceof WC_Payment_Gateway ); + + $methods[] = $googlepay_gateway; + } return $methods; } From 473b286de0745ae8b3a20c97f3eb20bfcffbb839 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 15 Jul 2024 16:13:12 +0200 Subject: [PATCH 038/357] Do not display googlepay button inside smart buttons in classic checkout if goolepay button is enabled --- modules/ppcp-googlepay/src/Assets/Button.php | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 6fab601b5..7e1d4962f 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Countries; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; +use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; @@ -25,6 +26,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; */ class Button implements ButtonInterface { + use ContextTrait; + /** * The URL to the module. * @@ -235,7 +238,7 @@ class Button implements ButtonInterface { $button_enabled_product = $this->settings_status->is_smart_button_enabled_for_location( 'product' ); $button_enabled_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' ); - $button_enabled_checkout = true; + $button_enabled_checkout = ! ( $this->context() === 'checkout' && $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ) ); $button_enabled_payorder = true; $button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); @@ -409,7 +412,7 @@ class Button implements ButtonInterface { */ public function script_data(): array { $shipping = array( - 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) + 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' ) ? boolval( $this->settings->get( 'googlepay_button_shipping_enabled' ) ) : false, 'configured' => wc_shipping_enabled() && wc_get_shipping_method_count( false, true ) > 0, From b5fa8972d07924faf6e74e37c95d4c2571ee374d Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 15 Jul 2024 19:18:51 +0400 Subject: [PATCH 039/357] Store the WC STORE API endpoint in constant --- modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php index f9322700d..be196fa03 100644 --- a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php +++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php @@ -22,7 +22,8 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData; * Class UpdateShippingEndpoint */ class UpdateShippingEndpoint implements EndpointInterface { - const ENDPOINT = 'ppc-update-shipping'; + const ENDPOINT = 'ppc-update-shipping'; + const WC_STORE_API_ENDPOINT = '/wp-json/wc/store/cart/'; /** * The Request Data Helper. From c5fd3a6cea5bed5b883dc866c79b604e78a5d089 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 15 Jul 2024 19:19:32 +0400 Subject: [PATCH 040/357] Use the WC STORE API endpoint constant and the `home_url` --- modules/ppcp-button/src/Assets/SmartButton.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 6b92a9084..ceaa05ffa 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1165,11 +1165,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages ), 'update_customer_shipping' => array( 'shipping_options' => array( - 'endpoint' => '/wp-json/wc/store/cart/select-shipping-rate', + 'endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate'), ), 'shipping_address' => array( - 'cart_endpoint' => '/wp-json/wc/store/cart/', - 'update_customer_endpoint' => '/wp-json/wc/store/v1/cart/update-customer/', + 'cart_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT ), + 'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer'), ), 'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ), 'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ), From f9aa2b565d3eb70b0449fc7c7e792d4d2836a1f2 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 15 Jul 2024 19:20:24 +0400 Subject: [PATCH 041/357] Return the callback to fix the shipping options update process --- .../ppcp-button/resources/js/modules/Renderer/Renderer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index b70342dbf..448432e3c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -141,6 +141,7 @@ class Renderer { // Check the condition and add the handler if needed if ( this.defaultSettings.should_handle_shipping_in_paypal ) { options.onShippingOptionsChange = ( data, actions ) => { + let shippingOptionsChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( venmoButtonClicked ) @@ -150,8 +151,11 @@ class Renderer { this.defaultSettings ) : null; + + return shippingOptionsChange }; options.onShippingAddressChange = ( data, actions ) => { + let shippingAddressChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( venmoButtonClicked ) @@ -161,6 +165,8 @@ class Renderer { this.defaultSettings ) : null; + + return shippingAddressChange }; } From f643f9e1d7f902a10baeda2dc585a43ef8de1ecb Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 15 Jul 2024 19:20:35 +0400 Subject: [PATCH 042/357] Return the callback to fix the shipping options update process --- modules/ppcp-blocks/resources/js/checkout-block.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index 6df1a99f8..bf36dcb65 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -606,9 +606,11 @@ const PayPalComponent = ( { } return ( data, actions ) => { - shouldHandleShippingInPayPal() + let shippingAddressChange = shouldHandleShippingInPayPal() ? handleShippingAddressChange( data, actions ) : null; + + return shippingAddressChange; }; }; From 2f0b01ebcb2aa9c0b7ec4a8854201acc63176ff1 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 15 Jul 2024 19:38:10 +0400 Subject: [PATCH 043/357] Fix the coding styles --- modules/ppcp-button/src/Assets/SmartButton.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index ceaa05ffa..4b309818e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1165,11 +1165,11 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages ), 'update_customer_shipping' => array( 'shipping_options' => array( - 'endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate'), + 'endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'select-shipping-rate' ), ), 'shipping_address' => array( 'cart_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT ), - 'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer'), + 'update_customer_endpoint' => home_url( UpdateShippingEndpoint::WC_STORE_API_ENDPOINT . 'update-customer' ), ), 'wp_rest_nonce' => wp_create_nonce( 'wc_store_api' ), 'update_shipping_method' => \WC_AJAX::get_endpoint( 'update_shipping_method' ), From 82f46baa162c3658ed961e347ebd933e2db4f8f1 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 15 Jul 2024 18:42:44 +0200 Subject: [PATCH 044/357] Add button method for checkout context --- .../resources/js/GooglepayButton.js | 37 +++++++++++++++++++ .../ppcp-googlepay/src/GooglepayModule.php | 4 ++ 2 files changed, 41 insertions(+) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 973648af3..44daed173 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -62,6 +62,30 @@ class GooglepayButton { ) .then( ( response ) => { if ( response.result ) { + if ( this.context === 'checkout' ) { + const wrapper = document.getElementById( + 'ppc-button-ppcp-googlepay' + ); + + if ( wrapper ) { + const { ppcpStyle, buttonStyle } = + this.contextConfig(); + wrapper.className = `ppcp-button-${ ppcpStyle.shape }`; + + if ( ppcpStyle.height ) { + wrapper.style.height = `${ ppcpStyle.height }px`; + } + + this.addButtonCheckout( + this.baseCardPaymentMethod, + wrapper, + buttonStyle + ); + + return; + } + } + this.addButton( this.baseCardPaymentMethod ); } } ) @@ -221,6 +245,19 @@ class GooglepayButton { } ); } + addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: buttonStyle.color || 'black', + buttonType: buttonStyle.type || 'pay', + buttonLocale: buttonStyle.language || 'en', + buttonSizeMode: 'fill', + } ); + + wrapper.appendChild( button ); + } + waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { const startTime = Date.now(); const interval = setInterval( () => { diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 8167e87f9..03ad140bb 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -186,6 +186,10 @@ class GooglepayModule implements ModuleInterface { return $methods; } ); + + add_action('woocommerce_review_order_after_payment', function () { + echo '
'; + }); } /** From 37ca92c57e9a9a83cd42a78626340b9b7e696fba Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Mon, 15 Jul 2024 22:38:35 +0200 Subject: [PATCH 045/357] Fix the GooglePay button preview in the settings widget --- modules/ppcp-googlepay/resources/js/boot-admin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js index 5f996bcde..b9f7b3c87 100644 --- a/modules/ppcp-googlepay/resources/js/boot-admin.js +++ b/modules/ppcp-googlepay/resources/js/boot-admin.js @@ -111,7 +111,7 @@ class GooglePayPreviewButton extends PreviewButton { contextHandler ); - button.init( this.apiConfig, contextHandler.transactionInfo() ); + button.init( this.apiConfig, null ); } /** From e70891d3591d6c229c0046a284147f98f66b7a77 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 16 Jul 2024 14:17:12 +0200 Subject: [PATCH 046/357] Add googlepay wc gateway --- .../ppcp-button/src/Assets/SmartButton.php | 1 + .../resources/js/GooglepayButton.js | 2 +- modules/ppcp-googlepay/services.php | 8 +- modules/ppcp-googlepay/src/Assets/Button.php | 1 + .../ppcp-googlepay/src/GooglePayGateway.php | 173 +++++++++++++++++- .../ppcp-googlepay/src/GooglepayModule.php | 4 +- 6 files changed, 184 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 6b92a9084..1ce9804e2 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -34,6 +34,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule; use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksModule; diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 44daed173..5a6cac6a6 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -27,7 +27,7 @@ class GooglepayButton { this.log = function () { if ( this.buttonConfig.is_debug ) { - //console.log('[GooglePayButton]', ...arguments); + console.log( '[GooglePayButton]', ...arguments ); } }; } diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 9d6cbd2a8..a566630e6 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -939,6 +939,12 @@ return array( ); }, 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { - return new GooglePayGateway(); + return new GooglePayGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'wc-subscriptions.helper' ), + $container->get( 'api.factory.paypal-checkout-url' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'wcgateway.transaction-url-provider' ) + ); }, ); diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 7e1d4962f..aa009b5b7 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -239,6 +239,7 @@ class Button implements ButtonInterface { $button_enabled_product = $this->settings_status->is_smart_button_enabled_for_location( 'product' ); $button_enabled_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' ); $button_enabled_checkout = ! ( $this->context() === 'checkout' && $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ) ); + $button_enabled_checkout = true; $button_enabled_payorder = true; $button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index c2dc3effc..89c5f88a5 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -9,12 +9,78 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Googlepay; +use Exception; +use WC_Order; use WC_Payment_Gateway; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; +use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; +use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; +use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; +use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; +/** + * Class GooglePayGateway + */ class GooglePayGateway extends WC_Payment_Gateway { + use ProcessPaymentTrait; + const ID = 'ppcp-googlepay'; - public function __construct() { + /** + * The processor for orders. + * + * @var OrderProcessor + */ + protected $order_processor; + + /** + * The subscription helper. + * + * @var SubscriptionHelper + */ + protected $subscription_helper; + + /** + * The function return the PayPal checkout URL for the given order ID. + * + * @var callable(string):string + */ + private $paypal_checkout_url_factory; + + /** + * The Refund Processor. + * + * @var RefundProcessor + */ + private $refund_processor; + + /** + * Service able to provide transaction url for an order. + * + * @var TransactionUrlProvider + */ + protected $transaction_url_provider; + + /** + * GooglePayGateway constructor. + * + * @param OrderProcessor $order_processor The Order Processor. + * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID. + * @param RefundProcessor $refund_processor The Refund Processor. + * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. + */ + public function __construct( + OrderProcessor $order_processor, + SubscriptionHelper $subscription_helper, + callable $paypal_checkout_url_factory, + RefundProcessor $refund_processor, + TransactionUrlProvider $transaction_url_provider + ) { $this->id = self::ID; $this->method_title = __( 'Google Pay', 'woocommerce-paypal-payments' ); @@ -25,6 +91,11 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->init_form_fields(); $this->init_settings(); + $this->order_processor = $order_processor; + $this->subscription_helper = $subscription_helper; + $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; + $this->refund_processor = $refund_processor; + $this->transaction_url_provider = $transaction_url_provider; } /** @@ -56,4 +127,104 @@ class GooglePayGateway extends WC_Payment_Gateway { ), ); } + + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + /** + * If customer has chosen change Subscription payment. + */ + if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); + if ( $saved_paypal_payment ) { + $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); + $wc_order->save(); + + return $this->handle_payment_success( $wc_order ); + } + } + + /** + * If the WC_Order is paid through the approved webhook. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + + try { + try { + $this->order_processor->process( $wc_order ); + + do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order ); + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalOrderMissingException $exc ) { + $order = $this->order_processor->create_order( $wc_order ); + + return array( + 'result' => 'success', + 'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ), + ); + } + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( Exception $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + + /** + * Process refund. + * + * If the gateway declares 'refunds' support, this will allow it to refund. + * a passed in amount. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, \WC_Order::class ) ) { + return false; + } + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + + /** + * Return transaction url for this gateway and given order. + * + * @param \WC_Order $order WC order to get transaction url by. + * + * @return string + */ + public function get_transaction_url( $order ): string { + $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); + + return parent::get_transaction_url( $order ); + } } diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 03ad140bb..7c558c6a2 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -187,8 +187,8 @@ class GooglepayModule implements ModuleInterface { } ); - add_action('woocommerce_review_order_after_payment', function () { - echo '
'; + add_action('woocommerce_review_order_after_submit', function () { + echo '
Google Pay...
'; }); } From ce5214970c268b989e8fbcf4f92e75b9cea808ee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 16 Jul 2024 14:33:03 +0200 Subject: [PATCH 047/357] AXO: Add whitespace stripping on submission --- modules/ppcp-axo/resources/js/AxoManager.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/ppcp-axo/resources/js/AxoManager.js b/modules/ppcp-axo/resources/js/AxoManager.js index b0837bcd6..34b8d24c5 100644 --- a/modules/ppcp-axo/resources/js/AxoManager.js +++ b/modules/ppcp-axo/resources/js/AxoManager.js @@ -709,6 +709,8 @@ class AxoManager { }` ); + this.emailInput.value = this.stripSpaces( this.emailInput.value ); + this.$( this.el.paymentContainer.selector + '-detail' ).html( '' ); this.$( this.el.paymentContainer.selector + '-form' ).html( '' ); @@ -1134,6 +1136,10 @@ class AxoManager { return emailPattern.test( value ); } + stripSpaces( str ) { + return str.replace( /\s+/g, '' ); + } + validateEmail( billingEmail ) { const billingEmailSelector = document.querySelector( billingEmail ); const value = document.querySelector( billingEmail + ' input' ).value; From 895cd5c4c14687d8b9ae15870d02b5e7bb7d3162 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 16 Jul 2024 16:00:11 +0200 Subject: [PATCH 048/357] Return payment method if it exist --- modules/ppcp-googlepay/services.php | 3 +- .../ppcp-googlepay/src/GooglePayGateway.php | 29 +++++++++++++------ .../src/Checkout/DisableGateways.php | 6 ++++ 3 files changed, 28 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index a566630e6..ae86d129b 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -944,7 +944,8 @@ return array( $container->get( 'wc-subscriptions.helper' ), $container->get( 'api.factory.paypal-checkout-url' ), $container->get( 'wcgateway.processor.refunds' ), - $container->get( 'wcgateway.transaction-url-provider' ) + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'session.handler' ) ); }, ); diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index 89c5f88a5..144d5da9e 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -13,6 +13,7 @@ use Exception; use WC_Order; use WC_Payment_Gateway; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; +use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages; @@ -65,21 +66,30 @@ class GooglePayGateway extends WC_Payment_Gateway { */ protected $transaction_url_provider; + /** + * The Session Handler. + * + * @var SessionHandler + */ + protected $session_handler; + /** * GooglePayGateway constructor. * - * @param OrderProcessor $order_processor The Order Processor. - * @param SubscriptionHelper $subscription_helper The subscription helper. + * @param OrderProcessor $order_processor The Order Processor. + * @param SubscriptionHelper $subscription_helper The subscription helper. * @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID. - * @param RefundProcessor $refund_processor The Refund Processor. - * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. + * @param RefundProcessor $refund_processor The Refund Processor. + * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. + * @param SessionHandler $session_handler The Session Handler. */ public function __construct( OrderProcessor $order_processor, SubscriptionHelper $subscription_helper, callable $paypal_checkout_url_factory, RefundProcessor $refund_processor, - TransactionUrlProvider $transaction_url_provider + TransactionUrlProvider $transaction_url_provider, + SessionHandler $session_handler ) { $this->id = self::ID; @@ -91,11 +101,12 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->init_form_fields(); $this->init_settings(); - $this->order_processor = $order_processor; - $this->subscription_helper = $subscription_helper; + $this->order_processor = $order_processor; + $this->subscription_helper = $subscription_helper; $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; - $this->refund_processor = $refund_processor; - $this->transaction_url_provider = $transaction_url_provider; + $this->refund_processor = $refund_processor; + $this->transaction_url_provider = $transaction_url_provider; + $this->session_handler = $session_handler; } /** diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php index de07ecfef..a55fe621c 100644 --- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php +++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php @@ -106,6 +106,12 @@ class DisableGateways { return $methods; } + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $payment_method = wc_clean( wp_unslash( $_POST['payment_method'] ?? '' ) ); + if ( $payment_method && is_string( $payment_method ) ) { + return array( $payment_method => $methods[ $payment_method ] ); + } + return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] ); } From c941430b7de4c48958963753958ebce76d7de5f5 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 16 Jul 2024 16:45:32 +0200 Subject: [PATCH 049/357] Show googlepay button conditionally --- .../resources/js/modules/ContextBootstrap/CheckoutBootstap.js | 2 ++ modules/ppcp-googlepay/resources/css/styles.scss | 4 ++++ modules/ppcp-googlepay/resources/js/GooglepayButton.js | 3 ++- modules/ppcp-googlepay/src/GooglepayModule.php | 2 +- 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index ad75a3d62..33d1ecfd3 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -232,6 +232,8 @@ class CheckoutBootstap { } } + setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod ); + jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); } diff --git a/modules/ppcp-googlepay/resources/css/styles.scss b/modules/ppcp-googlepay/resources/css/styles.scss index c60212a2e..6cf2119a0 100644 --- a/modules/ppcp-googlepay/resources/css/styles.scss +++ b/modules/ppcp-googlepay/resources/css/styles.scss @@ -13,3 +13,7 @@ min-width: 0 !important; } } + +#ppc-button-ppcp-googlepay { + display: none; +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 5a6cac6a6..494373a57 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -27,7 +27,7 @@ class GooglepayButton { this.log = function () { if ( this.buttonConfig.is_debug ) { - console.log( '[GooglePayButton]', ...arguments ); + //console.log('[GooglePayButton]', ...arguments); } }; } @@ -70,6 +70,7 @@ class GooglepayButton { if ( wrapper ) { const { ppcpStyle, buttonStyle } = this.contextConfig(); + wrapper.className = `ppcp-button-${ ppcpStyle.shape }`; if ( ppcpStyle.height ) { diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 7c558c6a2..6d787ce5e 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -188,7 +188,7 @@ class GooglepayModule implements ModuleInterface { ); add_action('woocommerce_review_order_after_submit', function () { - echo '
Google Pay...
'; + echo '
'; }); } From 335f6803441190f04900d79e2118e6b82f54d57a Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 16 Jul 2024 17:51:02 +0200 Subject: [PATCH 050/357] Extract logic from gateway to modules --- modules/ppcp-googlepay/services.php | 1 - .../ppcp-googlepay/src/GooglePayGateway.php | 34 +------------------ .../src/WcSubscriptionsModule.php | 20 ++++++++++- modules/ppcp-webhooks/src/WebhookModule.php | 13 +++++++ 4 files changed, 33 insertions(+), 35 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index ae86d129b..6997976bf 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -941,7 +941,6 @@ return array( 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { return new GooglePayGateway( $container->get( 'wcgateway.order-processor' ), - $container->get( 'wc-subscriptions.helper' ), $container->get( 'api.factory.paypal-checkout-url' ), $container->get( 'wcgateway.processor.refunds' ), $container->get( 'wcgateway.transaction-url-provider' ), diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index 144d5da9e..6b02560b2 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -21,7 +21,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; -use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; /** * Class GooglePayGateway @@ -38,13 +37,6 @@ class GooglePayGateway extends WC_Payment_Gateway { */ protected $order_processor; - /** - * The subscription helper. - * - * @var SubscriptionHelper - */ - protected $subscription_helper; - /** * The function return the PayPal checkout URL for the given order ID. * @@ -77,7 +69,6 @@ class GooglePayGateway extends WC_Payment_Gateway { * GooglePayGateway constructor. * * @param OrderProcessor $order_processor The Order Processor. - * @param SubscriptionHelper $subscription_helper The subscription helper. * @param callable(string):string $paypal_checkout_url_factory The function return the PayPal checkout URL for the given order ID. * @param RefundProcessor $refund_processor The Refund Processor. * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. @@ -85,7 +76,6 @@ class GooglePayGateway extends WC_Payment_Gateway { */ public function __construct( OrderProcessor $order_processor, - SubscriptionHelper $subscription_helper, callable $paypal_checkout_url_factory, RefundProcessor $refund_processor, TransactionUrlProvider $transaction_url_provider, @@ -102,7 +92,6 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->init_form_fields(); $this->init_settings(); $this->order_processor = $order_processor; - $this->subscription_helper = $subscription_helper; $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; $this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider; @@ -155,28 +144,7 @@ class GooglePayGateway extends WC_Payment_Gateway { ); } - /** - * If customer has chosen change Subscription payment. - */ - if ( $this->subscription_helper->has_subscription( $order_id ) && $this->subscription_helper->is_subscription_change_payment() ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); - if ( $saved_paypal_payment ) { - $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); - $wc_order->save(); - - return $this->handle_payment_success( $wc_order ); - } - } - - /** - * If the WC_Order is paid through the approved webhook. - */ - //phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { - return $this->handle_payment_success( $wc_order ); - } - //phpcs:enable WordPress.Security.NonceVerification.Recommended + do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order ); try { try { diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index df31b45bc..f2ccf3f0f 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -23,6 +23,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod; @@ -33,7 +34,7 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; */ class WcSubscriptionsModule implements ModuleInterface { - use TransactionIdHandlingTrait; + use TransactionIdHandlingTrait, ProcessPaymentTrait; /** * {@inheritDoc} @@ -255,6 +256,23 @@ class WcSubscriptionsModule implements ModuleInterface { 10, 3 ); + + add_action( 'woocommerce_paypal_payments_before_process_order', function(WC_Order $wc_order) use ( $c ) { + $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); + assert( $subscriptions_helper instanceof SubscriptionHelper ); + + // If the customer has chosen to change the subscription payment. + if ( $subscriptions_helper->has_subscription( $wc_order->get_id() ) && $subscriptions_helper->is_subscription_change_payment() ) { + // phpcs:ignore WordPress.Security.NonceVerification.Missing + $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); + if ( $saved_paypal_payment ) { + $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); + $wc_order->save(); + + return $this->handle_payment_success( $wc_order ); + } + } + }); } /** diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 2458e5b4d..f6343f0c2 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Webhooks; +use WC_Order; use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; @@ -16,6 +17,7 @@ use Exception; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; +use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint; @@ -27,6 +29,8 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets; */ class WebhookModule implements ModuleInterface { + use ProcessPaymentTrait; + /** * {@inheritDoc} */ @@ -158,6 +162,15 @@ class WebhookModule implements ModuleInterface { ); } ); + + // If the WC_Order is paid through the approved webhook. + add_action( 'woocommerce_paypal_payments_before_process_order', function(WC_Order $wc_order) { + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable + }); } /** From db4787a36017f58db71114440299e12d2212e679 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 16 Jul 2024 17:57:33 +0200 Subject: [PATCH 051/357] Remove temp leftovers --- modules/ppcp-button/src/Assets/SmartButton.php | 1 - modules/ppcp-googlepay/src/Assets/Button.php | 1 - 2 files changed, 2 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 45a2ec447..4b309818e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -34,7 +34,6 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Button\Helper\DisabledFundingSources; use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply; -use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\PayLaterBlock\PayLaterBlockModule; use WooCommerce\PayPalCommerce\PayLaterWCBlocks\PayLaterWCBlocksModule; diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index aa009b5b7..258b862cc 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -238,7 +238,6 @@ class Button implements ButtonInterface { $button_enabled_product = $this->settings_status->is_smart_button_enabled_for_location( 'product' ); $button_enabled_cart = $this->settings_status->is_smart_button_enabled_for_location( 'cart' ); - $button_enabled_checkout = ! ( $this->context() === 'checkout' && $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ) ); $button_enabled_checkout = true; $button_enabled_payorder = true; $button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ); From 3c23382c03524866c1eb4567c756c779a5222bcc Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 17 Jul 2024 12:53:11 +0400 Subject: [PATCH 052/357] Don't declare Automatic Recurring Payments support when PayPal subscriptions mode is disabled --- .../src/WcSubscriptionsModule.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index df31b45bc..ce13f2a7d 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -416,7 +416,9 @@ class WcSubscriptionsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - if ( $subscriptions_helper->plugin_is_active() ) { + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : null; + + if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', @@ -467,7 +469,12 @@ class WcSubscriptionsModule implements ModuleInterface { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - if ( $subscriptions_helper->plugin_is_active() ) { + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : null; + + if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', From 6d927ead36162848813d55d8159ea2c1ecb473a8 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 17 Jul 2024 12:58:01 +0400 Subject: [PATCH 053/357] Fix condition --- modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index ce13f2a7d..af68dbded 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -416,7 +416,7 @@ class WcSubscriptionsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : null; + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( @@ -472,7 +472,7 @@ class WcSubscriptionsModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : null; + $subscriptions_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : ''; if ( 'disable_paypal_subscriptions' !== $subscriptions_mode && $subscriptions_helper->plugin_is_active() ) { $supports = array( From f66e6aff99cf5abf198bf09f55c9704ce44677da Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 17 Jul 2024 12:36:44 +0200 Subject: [PATCH 054/357] Google Pay: Fix the context being stuck on mini-cart --- .../resources/js/GooglepayManager.js | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js index 71c07989e..e267f1b8a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayManager.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js @@ -13,14 +13,12 @@ class GooglepayManager { this.buttons = []; buttonModuleWatcher.watchContextBootstrap( async ( bootstrap ) => { - if ( ! this.contextHandler ) { - this.contextHandler = ContextHandlerFactory.create( - bootstrap.context, - buttonConfig, - ppcpConfig, - bootstrap.handler - ); - } + this.contextHandler = ContextHandlerFactory.create( + bootstrap.context, + buttonConfig, + ppcpConfig, + bootstrap.handler + ); const button = new GooglepayButton( bootstrap.context, From 7559c5a2acd7095605955a70c142d07db6de4245 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 17 Jul 2024 12:40:44 +0200 Subject: [PATCH 055/357] Bump 2.8.2-rc1 version --- changelog.txt | 18 ++++++++++++++++++ package.json | 2 +- readme.txt | 22 ++++++++++++++++++++-- woocommerce-paypal-payments.php | 6 +++--- 4 files changed, 42 insertions(+), 6 deletions(-) diff --git a/changelog.txt b/changelog.txt index 55745879f..e980e51a0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,23 @@ *** Changelog *** += 2.8.2 - xxxx-xx-xx = +* Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 +* Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 +* Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 +* Fix - Shipping methods during callback not updated correctly #2421 +* Fix - Preserve subscription renewal processing when switching Subscriptions Mode or disabling gateway #2394 +* Fix - Remove shipping callback for Venmo express button #2374 +* Fix - Google Pay: Fix issuse with data.paymentSource being undefined #2390 +* Fix - Loading of non-Order as a WC_Order causes warnings and potential data corruption #2343 +* Fix - Apple Pay and Google Pay buttons don't appear in PayPal Button stack on multi-step Checkout #2372 +* Fix - Apple Pay: Fix when shipping is disabled #2391 +* Fix - Wrong string in smart button preview on Standard Payments tab #2409 +* Fix - Don't break orders screen when there is an exception for package tracking #2369 +* Fix - Pay Later button preview is missing #2371 +* Fix - Apple Pay button layout #2367 +* Enhancement - Remove BCDC button from block Express Checkout area #2381 +* Enhancement - Extend Advanced Card Processing country eligibility for China #2397 + = 2.8.1 - 2024-07-01 = * Fix - Don't render tracking metabox if PayPal order does not belong to connected merchant #2360 * Fix - Fatal error when the ppcp-paylater-configurator module is disabled via code snippet #2327 diff --git a/package.json b/package.json index a0b03e3a8..61c625ca9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "2.8.1", + "version": "2.8.2", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index c1d1b0504..ffe94e2a5 100644 --- a/readme.txt +++ b/readme.txt @@ -2,9 +2,9 @@ Contributors: woocommerce, automattic, inpsyde Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple pay, subscriptions, debit card, credit card, google pay Requires at least: 5.3 -Tested up to: 6.5 +Tested up to: 6.6 Requires PHP: 7.2 -Stable tag: 2.8.1 +Stable tag: 2.8.2 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -179,6 +179,24 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == += 2.8.2 - xxxx-xx-xx = +* Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 +* Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 +* Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 +* Fix - Shipping methods during callback not updated correctly #2421 +* Fix - Preserve subscription renewal processing when switching Subscriptions Mode or disabling gateway #2394 +* Fix - Remove shipping callback for Venmo express button #2374 +* Fix - Google Pay: Fix issuse with data.paymentSource being undefined #2390 +* Fix - Loading of non-Order as a WC_Order causes warnings and potential data corruption #2343 +* Fix - Apple Pay and Google Pay buttons don't appear in PayPal Button stack on multi-step Checkout #2372 +* Fix - Apple Pay: Fix when shipping is disabled #2391 +* Fix - Wrong string in smart button preview on Standard Payments tab #2409 +* Fix - Don't break orders screen when there is an exception for package tracking #2369 +* Fix - Pay Later button preview is missing #2371 +* Fix - Apple Pay button layout #2367 +* Enhancement - Remove BCDC button from block Express Checkout area #2381 +* Enhancement - Extend Advanced Card Processing country eligibility for China #2397 + = 2.8.1 - 2024-07-01 = * Fix - Don't render tracking metabox if PayPal order does not belong to connected merchant #2360 * Fix - Fatal error when the ppcp-paylater-configurator module is disabled via code snippet #2327 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 66bf62645..69c797785 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,14 +3,14 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.1 + * Version: 2.8.2 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 * Requires PHP: 7.2 * Requires Plugins: woocommerce * WC requires at least: 3.9 - * WC tested up to: 9.0 + * WC tested up to: 9.1 * Text Domain: woocommerce-paypal-payments * * @package WooCommerce\PayPalCommerce @@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2024-06-25' ); +define( 'PAYPAL_INTEGRATION_DATE', '2024-07-17' ); define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); From af286c8f823c2aaf7cee27f0ecf2b0c0c64fd446 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 16:43:51 +0400 Subject: [PATCH 056/357] Add needs shipping to classic cart --- modules/ppcp-button/resources/js/modules/Renderer/Renderer.js | 2 +- modules/ppcp-button/src/Assets/SmartButton.php | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 448432e3c..7d6139d4c 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -139,7 +139,7 @@ class Renderer { }; // Check the condition and add the handler if needed - if ( this.defaultSettings.should_handle_shipping_in_paypal ) { + if ( this.defaultSettings.should_handle_shipping_in_paypal && this.defaultSettings.needShipping ) { options.onShippingOptionsChange = ( data, actions ) => { let shippingOptionsChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 4b309818e..02e01397e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1094,6 +1094,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $url_params = $this->url_params(); + $cart = WC()->cart; + $this->request_data->enqueue_nonce_fix(); $localize = array( 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ), @@ -1295,6 +1297,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), + 'needShipping' => $cart && $cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From c199ce1f8bc51dca7fae0f50487e0730a92fd7e3 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 16:44:35 +0400 Subject: [PATCH 057/357] Add needs shipping to block pages --- .../resources/js/checkout-block.js | 26 +++++++++++-------- .../ppcp-blocks/src/PayPalPaymentMethod.php | 2 ++ 2 files changed, 17 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js index bf36dcb65..d1b6ad990 100644 --- a/modules/ppcp-blocks/resources/js/checkout-block.js +++ b/modules/ppcp-blocks/resources/js/checkout-block.js @@ -227,7 +227,7 @@ const PayPalComponent = ( { throw new Error( config.scriptData.labels.error.generic ); } - if ( ! shouldHandleShippingInPayPal() ) { + if ( ! shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } else { setGotoContinuationOnError( true ); @@ -318,7 +318,7 @@ const PayPalComponent = ( { throw new Error( config.scriptData.labels.error.generic ); } - if ( ! shouldHandleShippingInPayPal() ) { + if ( ! shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } else { setGotoContinuationOnError( true ); @@ -364,16 +364,20 @@ const PayPalComponent = ( { }; const shouldHandleShippingInPayPal = () => { - if ( config.finalReviewEnabled ) { - return false; - } - - return ( - window.ppcpFundingSource !== 'venmo' || - ! config.scriptData.vaultingEnabled - ); + return shouldskipFinalConfirmation() && config.needShipping }; + const shouldskipFinalConfirmation = () => { + if ( config.finalReviewEnabled ) { + return false; + } + + return ( + window.ppcpFundingSource !== 'venmo' || + ! config.scriptData.vaultingEnabled + ); + }; + let handleShippingOptionsChange = null; let handleShippingAddressChange = null; let handleSubscriptionShippingOptionsChange = null; @@ -544,7 +548,7 @@ const PayPalComponent = ( { if ( config.scriptData.continuation ) { return true; } - if ( shouldHandleShippingInPayPal() ) { + if ( shouldskipFinalConfirmation() ) { location.href = getCheckoutRedirectUrl(); } return true; diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index d94eec53d..8da3ecefd 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -210,6 +210,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ public function get_payment_method_data() { $script_data = $this->smart_button()->script_data(); + $cart = WC()->cart; if ( isset( $script_data['continuation'] ) ) { $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() ); @@ -254,6 +255,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { ), ), 'scriptData' => $script_data, + 'needShipping' => $cart && $cart->needs_shipping(), ); } From 4f8dfb22e2e9f31b21c87397718fa532e7f4ddef Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 17 Jul 2024 16:49:24 +0400 Subject: [PATCH 058/357] Don't declare ACDC subscriptions support when vaulting is disabled --- .../ppcp-wc-subscriptions/src/WcSubscriptionsModule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index af68dbded..835ec4d4a 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -444,7 +444,12 @@ class WcSubscriptionsModule implements ModuleInterface { $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscriptions_helper instanceof SubscriptionHelper ); - if ( $subscriptions_helper->plugin_is_active() ) { + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $vaulting_enabled = $settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' ); + + if ( $vaulting_enabled && $subscriptions_helper->plugin_is_active() ) { $supports = array( 'subscriptions', 'subscription_cancellation', From 40805c1a133f526954f8f5a96b41176413dc43bd Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 17 Jul 2024 15:16:58 +0200 Subject: [PATCH 059/357] Add googlepay button to pay for order page --- .../resources/js/GooglepayButton.js | 5 ++++- modules/ppcp-googlepay/src/GooglepayModule.php | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 494373a57..9de1cb0b2 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -62,7 +62,10 @@ class GooglepayButton { ) .then( ( response ) => { if ( response.result ) { - if ( this.context === 'checkout' ) { + if ( + this.context === 'checkout' || + this.context === 'pay-now' + ) { const wrapper = document.getElementById( 'ppc-button-ppcp-googlepay' ); diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php index 6d787ce5e..b7feedc07 100644 --- a/modules/ppcp-googlepay/src/GooglepayModule.php +++ b/modules/ppcp-googlepay/src/GooglepayModule.php @@ -187,9 +187,19 @@ class GooglepayModule implements ModuleInterface { } ); - add_action('woocommerce_review_order_after_submit', function () { - echo '
'; - }); + add_action( + 'woocommerce_review_order_after_submit', + function () { + echo '
'; + } + ); + + add_action( + 'woocommerce_pay_order_after_submit', + function () { + echo '
'; + } + ); } /** From 39ffbaa6e73e28156f36877ce9147a63320d6f40 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 17 Jul 2024 15:23:10 +0200 Subject: [PATCH 060/357] Add googlepay and apm css classes to button wrapper --- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9de1cb0b2..4c7dc3185 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -74,7 +74,11 @@ class GooglepayButton { const { ppcpStyle, buttonStyle } = this.contextConfig(); - wrapper.className = `ppcp-button-${ ppcpStyle.shape }`; + wrapper.classList.add( + `ppcp-button-${ ppcpStyle.shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); if ( ppcpStyle.height ) { wrapper.style.height = `${ ppcpStyle.height }px`; From 0e3c550082b707216b2f210cd8a0ec37c89d1428 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 17:29:13 +0400 Subject: [PATCH 061/357] Fix the coding styles --- modules/ppcp-blocks/src/PayPalPaymentMethod.php | 3 +-- modules/ppcp-button/src/Assets/SmartButton.php | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php index 8da3ecefd..1d0654bac 100644 --- a/modules/ppcp-blocks/src/PayPalPaymentMethod.php +++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php @@ -210,7 +210,6 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { */ public function get_payment_method_data() { $script_data = $this->smart_button()->script_data(); - $cart = WC()->cart; if ( isset( $script_data['continuation'] ) ) { $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() ); @@ -255,7 +254,7 @@ class PayPalPaymentMethod extends AbstractPaymentMethodType { ), ), 'scriptData' => $script_data, - 'needShipping' => $cart && $cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), ); } diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 02e01397e..d4a775b8a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1094,8 +1094,6 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages $url_params = $this->url_params(); - $cart = WC()->cart; - $this->request_data->enqueue_nonce_fix(); $localize = array( 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ), @@ -1297,7 +1295,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), - 'needShipping' => $cart && $cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From e7ece3d394a8481e6517dd8deac5ebfab4d977b8 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 17:37:38 +0400 Subject: [PATCH 062/357] Fix the coding styles --- modules/ppcp-button/src/Assets/SmartButton.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index d4a775b8a..e54b3c97f 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1295,7 +1295,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'is_logged' => is_user_logged_in(), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), - 'needShipping' => WC()->cart->needs_shipping(), + 'needShipping' => WC()->cart->needs_shipping(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), ); From e547d7e09703066ac0c5e9d26f07093b220d4f87 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 17 Jul 2024 16:39:53 +0200 Subject: [PATCH 063/357] Show button in PayPal gateway if Google Pay gateway is disabled --- .../resources/js/GooglepayButton.js | 5 +++-- modules/ppcp-googlepay/src/Assets/Button.php | 19 ++++++++++++------- .../ppcp-googlepay/src/GooglePayGateway.php | 8 ++++++++ 3 files changed, 23 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 4c7dc3185..8b74c780a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -63,8 +63,9 @@ class GooglepayButton { .then( ( response ) => { if ( response.result ) { if ( - this.context === 'checkout' || - this.context === 'pay-now' + ( this.context === 'checkout' || + this.context === 'pay-now' ) && + this.buttonConfig.is_wc_gateway_enabled === '1' ) { const wrapper = document.getElementById( 'ppc-button-ppcp-googlepay' diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 258b862cc..bd6faea79 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -15,6 +15,7 @@ use WC_Countries; use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface; use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; use WooCommerce\PayPalCommerce\Googlepay\Endpoint\UpdatePaymentDataEndpoint; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Session\SessionHandler; use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; @@ -424,19 +425,23 @@ class Button implements ButtonInterface { $is_enabled = $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' ); + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $is_wc_gateway_enabled = isset( $available_gateways[ GooglePayGateway::ID ] ); + return array( - 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, - 'is_enabled' => $is_enabled, - 'sdk_url' => $this->sdk_url, - 'button' => array( + 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION', + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, + 'is_enabled' => $is_enabled, + 'is_wc_gateway_enabled' => $is_wc_gateway_enabled, + 'sdk_url' => $this->sdk_url, + 'button' => array( 'wrapper' => '#ppc-button-googlepay-container', 'style' => $this->button_styles_for_context( 'cart' ), // For now use cart. Pass the context if necessary. 'mini_cart_wrapper' => '#ppc-button-googlepay-container-minicart', 'mini_cart_style' => $this->button_styles_for_context( 'mini-cart' ), ), - 'shipping' => $shipping, - 'ajax' => array( + 'shipping' => $shipping, + 'ajax' => array( 'update_payment_data' => array( 'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ), 'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ), diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index 6b02560b2..349699214 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -96,6 +96,14 @@ class GooglePayGateway extends WC_Payment_Gateway { $this->refund_processor = $refund_processor; $this->transaction_url_provider = $transaction_url_provider; $this->session_handler = $session_handler; + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); } /** From 8f4fa3562bf95fcb8a90a7374c1721cf2648cf66 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Wed, 17 Jul 2024 19:15:19 +0400 Subject: [PATCH 064/357] Add tax configurator --- .../src/Helper/WooCommerceOrderCreator.php | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..be2ecc170 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -14,8 +14,10 @@ use WC_Cart; use WC_Order; use WC_Order_Item_Product; use WC_Order_Item_Shipping; +use WC_Product; use WC_Subscription; use WC_Subscriptions_Product; +use WC_Tax; use WooCommerce\PayPalCommerce\ApiClient\Entity\Order; use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer; use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping; @@ -106,6 +108,7 @@ class WooCommerceOrderCreator { * @param Payer|null $payer The payer. * @param Shipping|null $shipping The shipping. * @return void + * @psalm-suppress InvalidScalarArgument */ protected function configure_line_items( WC_Order $wc_order, WC_Cart $wc_cart, ?Payer $payer, ?Shipping $shipping ): void { $cart_contents = $wc_cart->get_cart(); @@ -130,18 +133,20 @@ class WooCommerceOrderCreator { return; } - $total = $product->get_price() * $quantity; + $subtotal = wc_get_price_excluding_tax( $product, array( 'qty' => $quantity ) ); $item->set_name( $product->get_name() ); - $item->set_subtotal( $total ); - $item->set_total( $total ); + $item->set_subtotal( $subtotal ); + $item->set_total( $subtotal ); + + $this->configure_taxes( $product, $item, $subtotal ); $product_id = $product->get_id(); if ( $this->is_subscription( $product_id ) ) { $subscription = $this->create_subscription( $wc_order, $product_id ); $sign_up_fee = WC_Subscriptions_Product::get_sign_up_fee( $product ); - $subscription_total = $total + $sign_up_fee; + $subscription_total = (float) $subtotal + (float) $sign_up_fee; $item->set_subtotal( $subscription_total ); $item->set_total( $subscription_total ); @@ -282,6 +287,30 @@ class WooCommerceOrderCreator { } } + /** + * Configures the taxes. + * + * @param WC_Product $product The Product. + * @param WC_Order_Item_Product $item The line item. + * @param float|string $subtotal The subtotal. + * @return void + * @psalm-suppress InvalidScalarArgument + */ + protected function configure_taxes( WC_Product $product, WC_Order_Item_Product $item, $subtotal ): void { + $tax_rates = WC_Tax::get_rates( $product->get_tax_class() ); + $taxes = WC_Tax::calc_tax( $subtotal, $tax_rates, true ); + + $item->set_tax_class( $product->get_tax_class() ); + $item->set_total_tax( (float) array_sum( $taxes ) ); + + foreach ( $taxes as $tax_rate_id => $tax_amount ) { + if ( $tax_amount > 0 ) { + $item->add_meta_data( 'tax_rate_id', $tax_rate_id, true ); + $item->add_meta_data( 'tax_amount', $tax_amount, true ); + } + } + } + /** * Checks if the product with given ID is WC subscription. * From 0d7b557fa8e9df05b5e72d974558914ab8ae4e09 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 17 Jul 2024 19:06:40 +0200 Subject: [PATCH 065/357] AXO: Add warning when shipping config is not compatible --- modules/ppcp-axo/extensions.php | 12 ++++++++++++ modules/ppcp-axo/services.php | 21 +++++++++++++++++++-- modules/ppcp-axo/src/AxoModule.php | 10 ++++++++++ modules/ppcp-axo/src/Helper/ApmApplies.php | 15 --------------- 4 files changed, 41 insertions(+), 17 deletions(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 8d8bf4378..5fd342741 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -126,6 +126,18 @@ return array( 'requirements' => array( 'dcc', 'axo' ), 'gateway' => array( 'dcc', 'axo' ), ), + 'axo_shipping_config_notice' => array( + 'heading' => '', + 'html' => $container->get( 'axo.shipping-config-notice' ), + 'type' => 'ppcp-html', + 'classes' => array( 'ppcp-field-indent' ), + 'class' => array(), + 'screens' => array( + State::STATE_ONBOARDED, + ), + 'requirements' => array( 'dcc', 'axo' ), + 'gateway' => array( 'dcc', 'axo' ), + ), 'axo_gateway_title' => array( 'title' => __( 'Gateway Title', 'woocommerce-paypal-payments' ), 'type' => 'text', diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 9d4e6d5fe..399a8b3e3 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -25,7 +25,7 @@ return array( $apm_applies = $container->get( 'axo.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency() && $apm_applies->for_settings(); + return $apm_applies->for_country_currency(); }, 'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { @@ -202,7 +202,6 @@ return array( return '

' . $notice_content . '

'; }, - 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); @@ -230,6 +229,24 @@ return array( return '

' . $notice_content . '

'; }, + 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { + $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); + + if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Shipping destination settings page. */ + __( + 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', + 'woocommerce-paypal-payments' + ), + esc_url( $shipping_settings_link ) + ); + } else { + return ''; + } + + return '

' . $notice_content . '

'; + }, 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint { return new FrontendLoggerEndpoint( $container->get( 'button.request-data' ), diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..6350b5adc 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -341,6 +341,7 @@ class AxoModule implements ModuleInterface { return ! is_user_logged_in() && CartCheckoutDetector::has_classic_checkout() + && $this->is_compatible_shipping_config() && $is_axo_enabled && $is_dcc_enabled && ! $this->is_excluded_endpoint(); @@ -396,4 +397,13 @@ class AxoModule implements ModuleInterface { // Exclude the Order Pay endpoint. return is_wc_endpoint_url( 'order-pay' ); } + + /** + * Condition to evaluate if the shipping configuration is compatible. + * + * @return bool + */ + private function is_compatible_shipping_config(): bool { + return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() ); + } } diff --git a/modules/ppcp-axo/src/Helper/ApmApplies.php b/modules/ppcp-axo/src/Helper/ApmApplies.php index c5fe645a9..c8a709f55 100644 --- a/modules/ppcp-axo/src/Helper/ApmApplies.php +++ b/modules/ppcp-axo/src/Helper/ApmApplies.php @@ -64,19 +64,4 @@ class ApmApplies { } return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); } - - /** - * Returns whether the settings are compatible with AXO. - * - * @return bool - */ - public function for_settings(): bool { - if ( get_option( 'woocommerce_ship_to_destination' ) === 'billing_only' ) { // Force shipping to the customer billing address. - return false; - } - return true; - } - - - } From e9a71a9237a08e36fcb63a20954ec53b47c8b8c9 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 18 Jul 2024 00:27:35 +0200 Subject: [PATCH 066/357] Refactor the checkout and shipping notice logic to minimize the markup and make the code more readable --- modules/ppcp-axo/extensions.php | 16 +--- modules/ppcp-axo/services.php | 73 ++++----------- .../src/Helper/SettingsNoticeGenerator.php | 92 +++++++++++++++++++ 3 files changed, 110 insertions(+), 71 deletions(-) create mode 100644 modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5fd342741..5c8f694aa 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -114,21 +114,9 @@ return array( ), 'classes' => array( 'ppcp-valign-label-middle', 'ppcp-align-label-center' ), ), - 'axo_checkout_config_notice' => array( + 'axo_main_notice' => array( 'heading' => '', - 'html' => $container->get( 'axo.checkout-config-notice' ), - 'type' => 'ppcp-html', - 'classes' => array( 'ppcp-field-indent' ), - 'class' => array(), - 'screens' => array( - State::STATE_ONBOARDED, - ), - 'requirements' => array( 'dcc', 'axo' ), - 'gateway' => array( 'dcc', 'axo' ), - ), - 'axo_shipping_config_notice' => array( - 'heading' => '', - 'html' => $container->get( 'axo.shipping-config-notice' ), + 'html' => $container->get( 'axo.shipping-config-notice' ) . $container->get( 'axo.checkout-config-notice' ), 'type' => 'ppcp-html', 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 399a8b3e3..e3a45bb05 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Axo; use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Axo\Helper\ApmApplies; +use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; @@ -36,6 +37,10 @@ return array( ); }, + 'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator { + return new SettingsNoticeGenerator(); + }, + // If AXO is configured and onboarded. 'axo.available' => static function ( ContainerInterface $container ): bool { return true; @@ -160,48 +165,19 @@ return array( }, 'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { - $checkout_page_link = esc_url( get_edit_post_link( wc_get_page_id( 'checkout' ) ) ?? '' ); - $block_checkout_docs_link = __( - 'https://woocommerce.com/document/cart-checkout-blocks-status/#reverting-to-the-cart-and-checkout-shortcodes', - 'woocommerce-paypal-payments' - ); + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); - if ( CartCheckoutDetector::has_elementor_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store currently uses the Elementor Checkout widget. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } elseif ( CartCheckoutDetector::has_block_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store currently uses the WooCommerce Checkout block. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } elseif ( ! CartCheckoutDetector::has_classic_checkout() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ - __( - 'Warning: The Checkout page of your store does not seem to be properly configured or uses an incompatible third-party Checkout solution. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', - 'woocommerce-paypal-payments' - ), - esc_url( $checkout_page_link ), - esc_url( $block_checkout_docs_link ) - ); - } else { - return ''; - } - - return '

' . $notice_content . '

'; + return $settings_notice_generator->generate_checkout_notice(); }, + + 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + return $settings_notice_generator->generate_shipping_notice(); + }, + 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); @@ -229,24 +205,7 @@ return array( return '

' . $notice_content . '

'; }, - 'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string { - $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); - if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { - $notice_content = sprintf( - /* translators: %1$s: URL to the Shipping destination settings page. */ - __( - 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', - 'woocommerce-paypal-payments' - ), - esc_url( $shipping_settings_link ) - ); - } else { - return ''; - } - - return '

' . $notice_content . '

'; - }, 'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint { return new FrontendLoggerEndpoint( $container->get( 'button.request-data' ), diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php new file mode 100644 index 000000000..4f835d549 --- /dev/null +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -0,0 +1,92 @@ +Warning: The Checkout page of your store currently uses the Elementor Checkout widget. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } elseif ( CartCheckoutDetector::has_block_checkout() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ + __( + 'Warning: The Checkout page of your store currently uses the WooCommerce Checkout block. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } elseif ( ! CartCheckoutDetector::has_classic_checkout() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Checkout edit page. %2$s: URL to the block checkout docs. */ + __( + 'Warning: The Checkout page of your store does not seem to be properly configured or uses an incompatible third-party Checkout solution. To enable Fastlane and accelerate payments, the page must include either the Classic Checkout or the [woocommerce_checkout] shortcode. See this page for instructions on how to switch to the classic layout.', + 'woocommerce-paypal-payments' + ), + esc_url( $checkout_page_link ), + esc_url( $block_checkout_docs_link ) + ); + } + + return $notice_content ? '

' . $notice_content . '

' : ''; + } + + /** + * Generates the shipping notice. + * + * @return string + */ + public function generate_shipping_notice(): string { + $shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' ); + + $notice_content = ''; + + if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) { + $notice_content = sprintf( + /* translators: %1$s: URL to the Shipping destination settings page. */ + __( + 'Warning: The Shipping destination of your store is currently configured to Force shipping to the customer billing address. To enable Fastlane and accelerate payments, the shipping destination must be configured either to Default to customer shipping address or Default to customer billing address so buyers can set separate billing and shipping details.', + 'woocommerce-paypal-payments' + ), + esc_url( $shipping_settings_link ) + ); + } + + return $notice_content ? '

' . $notice_content . '

' : ''; + } +} From 78fda07e8becadf6be6694c3e9de775617954c6b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 18 Jul 2024 12:11:11 +0200 Subject: [PATCH 067/357] AXO: Ensure Fastlane scripts do not load when PayPal is disabled --- modules/ppcp-axo/src/AxoModule.php | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..d9ac8f8c2 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -77,9 +77,10 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); + $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; - if ( ! $is_dcc_enabled ) { + if ( ! $is_paypal_enabled || ! $is_dcc_enabled ) { return $methods; } @@ -144,11 +145,17 @@ class AxoModule implements ModuleInterface { function () use ( $c ) { $module = $this; + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); // Check if the module is applicable, correct country, currency, ... etc. - if ( ! $c->get( 'axo.eligible' ) + if ( ! $is_paypal_enabled + || ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) || $subscription_helper->cart_contains_subscription() ) { return; From 694caac891206339ef61d6b4f501865e266ee93f Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 16:11:52 +0400 Subject: [PATCH 068/357] Patch the order with no shipping methods, instead of throwing an error --- .../ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php | 9 --------- 1 file changed, 9 deletions(-) diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php index be196fa03..c8e0417ab 100644 --- a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php +++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php @@ -97,15 +97,6 @@ class UpdateShippingEndpoint implements EndpointInterface { $pu = $this->purchase_unit_factory->from_wc_cart( null, true ); $pu_data = $pu->to_array(); - if ( ! isset( $pu_data['shipping']['options'] ) ) { - wp_send_json_error( - array( - 'message' => 'No shipping methods.', - ) - ); - return false; - } - // TODO: maybe should patch only if methods changed. // But it seems a bit difficult to detect, // e.g. ->order($id) may not have Shipping because we drop it when address or name are missing. From c3d46d89af1c1cb54cf511fc465cb0dedf153464 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:15:02 +0400 Subject: [PATCH 069/357] Add shipping option validation --- .../src/Helper/WooCommerceOrderCreator.php | 33 +++++++++++++------ 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..0c989e6a0 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -9,8 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Button\Helper; +use Exception; use RuntimeException; use WC_Cart; +use WC_Data_Exception; use WC_Order; use WC_Order_Item_Product; use WC_Order_Item_Shipping; @@ -83,17 +85,22 @@ class WooCommerceOrderCreator { throw new RuntimeException( 'Problem creating WC order.' ); } - $payer = $order->payer(); - $shipping = $order->purchase_units()[0]->shipping(); + try { + $payer = $order->payer(); + $shipping = $order->purchase_units()[0]->shipping(); - $this->configure_payment_source( $wc_order ); - $this->configure_customer( $wc_order ); - $this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping ); - $this->configure_shipping( $wc_order, $payer, $shipping ); - $this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() ); + $this->configure_payment_source( $wc_order ); + $this->configure_customer( $wc_order ); + $this->configure_line_items( $wc_order, $wc_cart, $payer, $shipping ); + $this->configure_shipping( $wc_order, $payer, $shipping, $wc_cart ); + $this->configure_coupons( $wc_order, $wc_cart->get_applied_coupons() ); - $wc_order->calculate_totals(); - $wc_order->save(); + $wc_order->calculate_totals(); + $wc_order->save(); + } catch ( Exception $exception ) { + $wc_order->delete( true ); + throw new RuntimeException( 'Failed to create WooCommerce order: ' . $exception->getMessage() ); + } return $wc_order; } @@ -172,9 +179,11 @@ class WooCommerceOrderCreator { * @param WC_Order $wc_order The WC order. * @param Payer|null $payer The payer. * @param Shipping|null $shipping The shipping. + * @param WC_Cart $wc_cart The Cart. * @return void + * @throws WC_Data_Exception|RuntimeException When failing to configure shipping. */ - protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping ): void { + protected function configure_shipping( WC_Order $wc_order, ?Payer $payer, ?Shipping $shipping, WC_Cart $wc_cart ): void { $shipping_address = null; $billing_address = null; $shipping_options = null; @@ -212,6 +221,10 @@ class WooCommerceOrderCreator { $shipping_options = $shipping->options()[0] ?? ''; } + if ( $wc_cart->needs_shipping() && empty( $shipping_options ) ) { + throw new RuntimeException( 'No shipping method has been selected.' ); + } + if ( $shipping_address ) { $wc_order->set_shipping_address( $shipping_address ); } From 15250e5a83f32cdf79e5f704f91c356f2834deec Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:15:39 +0400 Subject: [PATCH 070/357] Redirect to continuation when error happens --- .../js/modules/OnApproveHandler/onApproveForContinue.js | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js index c60c163fd..54f4e123a 100644 --- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js +++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js @@ -20,10 +20,7 @@ const onApprove = ( context, errorHandler ) => { } ) .then( ( data ) => { if ( ! data.success ) { - errorHandler.genericError(); - return actions.restart().catch( ( err ) => { - errorHandler.genericError(); - } ); + location.href = context.config.redirect; } const orderReceivedUrl = data.data?.order_received_url; From fe046ea48c1ee57051ff8720ad9342f774d8d859 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Thu, 18 Jul 2024 20:19:33 +0400 Subject: [PATCH 071/357] Add the missing argument --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 0c989e6a0..ed1d4b82f 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -154,7 +154,7 @@ class WooCommerceOrderCreator { $item->set_total( $subscription_total ); $subscription->add_product( $product ); - $this->configure_shipping( $subscription, $payer, $shipping ); + $this->configure_shipping( $subscription, $payer, $shipping, $wc_cart ); $this->configure_payment_source( $subscription ); $this->configure_coupons( $subscription, $wc_cart->get_applied_coupons() ); From d685de73b71519a40a929e844a97c3b3e83856fc Mon Sep 17 00:00:00 2001 From: George Burduli Date: Fri, 19 Jul 2024 18:25:28 +0400 Subject: [PATCH 072/357] Removed currency matrix from card fields module --- modules/ppcp-card-fields/services.php | 811 +----------------- .../src/Helper/CardFieldsApplies.php | 33 +- 2 files changed, 44 insertions(+), 800 deletions(-) diff --git a/modules/ppcp-card-fields/services.php b/modules/ppcp-card-fields/services.php index 85f8004b6..d9d6b4dd8 100644 --- a/modules/ppcp-card-fields/services.php +++ b/modules/ppcp-card-fields/services.php @@ -17,7 +17,7 @@ return array( $save_payment_methods_applies = $container->get( 'card-fields.helpers.save-payment-methods-applies' ); assert( $save_payment_methods_applies instanceof CardFieldsApplies ); - return $save_payment_methods_applies->for_country_currency(); + return $save_payment_methods_applies->for_country(); }, 'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies { return new CardFieldsApplies( @@ -30,782 +30,39 @@ return array( return apply_filters( 'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix', array( - 'AU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'AT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CA' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CN' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'HU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LV' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GB' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'US' => array( - 'AUD', - 'CAD', - 'EUR', - 'GBP', - 'JPY', - 'USD', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), + 'AU', + 'AT', + 'BE', + 'BG', + 'CA', + 'CN', + 'CY', + 'CZ', + 'DK', + 'EE', + 'FI', + 'FR', + 'DE', + 'GR', + 'HU', + 'IE', + 'IT', + 'LV', + 'LI', + 'LT', + 'LU', + 'MT', + 'NL', + 'PL', + 'PT', + 'RO', + 'SK', + 'SI', + 'ES', + 'SE', + 'GB', + 'US', + 'NO', ) ); }, diff --git a/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php b/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php index c5111b9bb..43e885e73 100644 --- a/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php +++ b/modules/ppcp-card-fields/src/Helper/CardFieldsApplies.php @@ -1,6 +1,6 @@ allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_country_matrix = $allowed_country_matrix; + $this->country = $country; } /** - * Returns whether Card Fields can be used in the current country and the current currency. + * Returns whether Card Fields can be used in the current country. * * @return bool */ - public function for_country_currency(): bool { - if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { - return false; - } - return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); + public function for_country(): bool { + return ! in_array( $this->country, $this->allowed_country_matrix, true ); } } From 9b0bb2c8b4b46b3a39d8d85cf5c6c27af34e54f6 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 11:52:04 +0200 Subject: [PATCH 073/357] Do not provide support for wc susbcriotions for now --- .../src/WcSubscriptionsModule.php | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index f2ccf3f0f..10b44d2fd 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -256,23 +256,6 @@ class WcSubscriptionsModule implements ModuleInterface { 10, 3 ); - - add_action( 'woocommerce_paypal_payments_before_process_order', function(WC_Order $wc_order) use ( $c ) { - $subscriptions_helper = $c->get( 'wc-subscriptions.helper' ); - assert( $subscriptions_helper instanceof SubscriptionHelper ); - - // If the customer has chosen to change the subscription payment. - if ( $subscriptions_helper->has_subscription( $wc_order->get_id() ) && $subscriptions_helper->is_subscription_change_payment() ) { - // phpcs:ignore WordPress.Security.NonceVerification.Missing - $saved_paypal_payment = wc_clean( wp_unslash( $_POST['saved_paypal_payment'] ?? '' ) ); - if ( $saved_paypal_payment ) { - $wc_order->update_meta_data( 'payment_token_id', $saved_paypal_payment ); - $wc_order->save(); - - return $this->handle_payment_success( $wc_order ); - } - } - }); } /** From caee378ba4f8ba0a95aa97c5b78c3dc12e2305b7 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 22 Jul 2024 14:02:52 +0400 Subject: [PATCH 074/357] Fix CardFieldsApplies method, service and filter names --- modules/ppcp-card-fields/services.php | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-card-fields/services.php b/modules/ppcp-card-fields/services.php index d9d6b4dd8..1e32cc4f6 100644 --- a/modules/ppcp-card-fields/services.php +++ b/modules/ppcp-card-fields/services.php @@ -21,14 +21,13 @@ return array( }, 'card-fields.helpers.save-payment-methods-applies' => static function ( ContainerInterface $container ) : CardFieldsApplies { return new CardFieldsApplies( - $container->get( 'card-fields.supported-country-currency-matrix' ), - $container->get( 'api.shop.currency' ), + $container->get( 'card-fields.supported-country-matrix' ), $container->get( 'api.shop.country' ) ); }, - 'card-fields.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'card-fields.supported-country-matrix' => static function ( ContainerInterface $container ) : array { return apply_filters( - 'woocommerce_paypal_payments_card_fields_supported_country_currency_matrix', + 'woocommerce_paypal_payments_card_fields_supported_country_matrix', array( 'AU', 'AT', From e592bbb47b9daab911ed94f1d7b9a09656bd76a3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 12:10:34 +0200 Subject: [PATCH 075/357] Do not provide support for wc susbcriotions for now --- modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php index 10b44d2fd..df31b45bc 100644 --- a/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php +++ b/modules/ppcp-wc-subscriptions/src/WcSubscriptionsModule.php @@ -23,7 +23,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcSubscriptions\Endpoint\SubscriptionChangePaymentMethod; @@ -34,7 +33,7 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper; */ class WcSubscriptionsModule implements ModuleInterface { - use TransactionIdHandlingTrait, ProcessPaymentTrait; + use TransactionIdHandlingTrait; /** * {@inheritDoc} From 86e0007ae6b7a56c9f0851150b5de12a3d5844e9 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 12:53:41 +0200 Subject: [PATCH 076/357] Add gateway icon, title and description --- modules/ppcp-googlepay/services.php | 5 +++-- .../ppcp-googlepay/src/GooglePayGateway.php | 22 ++++++++++++++----- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 6997976bf..11cbd740b 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -938,13 +938,14 @@ return array( esc_html( $button_text ) ); }, - 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { + 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { return new GooglePayGateway( $container->get( 'wcgateway.order-processor' ), $container->get( 'api.factory.paypal-checkout-url' ), $container->get( 'wcgateway.processor.refunds' ), $container->get( 'wcgateway.transaction-url-provider' ), - $container->get( 'session.handler' ) + $container->get( 'session.handler' ), + $container->get( 'googlepay.url' ) ); }, ); diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index 349699214..bc5e209b6 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -65,6 +65,13 @@ class GooglePayGateway extends WC_Payment_Gateway { */ protected $session_handler; + /** + * The URL to the module. + * + * @var string + */ + private $module_url; + /** * GooglePayGateway constructor. * @@ -73,21 +80,26 @@ class GooglePayGateway extends WC_Payment_Gateway { * @param RefundProcessor $refund_processor The Refund Processor. * @param TransactionUrlProvider $transaction_url_provider Service providing transaction view URL based on order. * @param SessionHandler $session_handler The Session Handler. + * @param string $module_url The URL to the module. */ public function __construct( OrderProcessor $order_processor, callable $paypal_checkout_url_factory, RefundProcessor $refund_processor, TransactionUrlProvider $transaction_url_provider, - SessionHandler $session_handler + SessionHandler $session_handler, + string $module_url ) { $this->id = self::ID; - $this->method_title = __( 'Google Pay', 'woocommerce-paypal-payments' ); - $this->method_description = __( 'Google Pay', 'woocommerce-paypal-payments' ); + $this->method_title = __( 'Google Pay (via PayPal) ', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the Google Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); - $this->title = $this->get_option( 'title', $this->method_title ); - $this->description = $this->get_option( 'description', $this->method_description ); + $this->title = $this->get_option( 'title', __( 'Google Pay', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->module_url = $module_url; + $this->icon = esc_url( $this->module_url ) . 'assets/images/googlepay.png'; $this->init_form_fields(); $this->init_settings(); From 10439ff02b209e7a16b7135924594c1a1a0ef52d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 22 Jul 2024 13:30:20 +0200 Subject: [PATCH 077/357] =?UTF-8?q?=E2=9C=A8=20Add=20empty=20ApplePay=20ga?= =?UTF-8?q?teway=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/ApplePayGateway.php | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 modules/ppcp-applepay/ApplePayGateway.php diff --git a/modules/ppcp-applepay/ApplePayGateway.php b/modules/ppcp-applepay/ApplePayGateway.php new file mode 100644 index 000000000..6073ba859 --- /dev/null +++ b/modules/ppcp-applepay/ApplePayGateway.php @@ -0,0 +1,20 @@ + Date: Mon, 22 Jul 2024 14:35:11 +0200 Subject: [PATCH 078/357] Show settings tabs in googlepay gateway settings page --- modules/ppcp-wc-gateway/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index f0d848216..ce41619ca 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -20,6 +20,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies; use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway; use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers; use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator; +use WooCommerce\PayPalCommerce\Googlepay\GooglePayGateway; use WooCommerce\PayPalCommerce\Onboarding\Environment; use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer; use WooCommerce\PayPalCommerce\Onboarding\State; @@ -196,6 +197,7 @@ return array( OXXOGateway::ID, Settings::PAY_LATER_TAB_ID, AxoGateway::ID, + GooglePayGateway::ID, ), true ); @@ -217,6 +219,7 @@ return array( CardButtonGateway::ID, Settings::PAY_LATER_TAB_ID, Settings::CONNECTION_TAB_ID, + GooglePayGateway::ID, ), true ); From 93506fe5b743b8214b8aa286e32d1b38b141e6fe Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 14:42:03 +0200 Subject: [PATCH 079/357] Revert webhook handling for now so psalm does not complain --- modules/ppcp-googlepay/src/GooglePayGateway.php | 9 +++++++++ modules/ppcp-webhooks/src/WebhookModule.php | 9 --------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-googlepay/src/GooglePayGateway.php b/modules/ppcp-googlepay/src/GooglePayGateway.php index bc5e209b6..26a84f326 100644 --- a/modules/ppcp-googlepay/src/GooglePayGateway.php +++ b/modules/ppcp-googlepay/src/GooglePayGateway.php @@ -164,6 +164,15 @@ class GooglePayGateway extends WC_Payment_Gateway { ); } + /** + * If the WC_Order is paid through the approved webhook. + */ + //phpcs:disable WordPress.Security.NonceVerification.Recommended + if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + return $this->handle_payment_success( $wc_order ); + } + //phpcs:enable WordPress.Security.NonceVerification.Recommended + do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order ); try { diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index f6343f0c2..3a9af0a57 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -162,15 +162,6 @@ class WebhookModule implements ModuleInterface { ); } ); - - // If the WC_Order is paid through the approved webhook. - add_action( 'woocommerce_paypal_payments_before_process_order', function(WC_Order $wc_order) { - //phpcs:disable WordPress.Security.NonceVerification.Recommended - if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { - return $this->handle_payment_success( $wc_order ); - } - //phpcs:enable - }); } /** From 467960f5a23e0c9bd71fd2e325213728005de815 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 14:49:01 +0200 Subject: [PATCH 080/357] Revert webhook handling for now so psalm does not complain --- modules/ppcp-webhooks/src/WebhookModule.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php index 3a9af0a57..939a8d512 100644 --- a/modules/ppcp-webhooks/src/WebhookModule.php +++ b/modules/ppcp-webhooks/src/WebhookModule.php @@ -17,7 +17,6 @@ use Exception; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use Psr\Log\LoggerInterface; -use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint; use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint; @@ -29,8 +28,6 @@ use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets; */ class WebhookModule implements ModuleInterface { - use ProcessPaymentTrait; - /** * {@inheritDoc} */ From 5adb04cb3f99fd72ed9c207de1ac9c977360e62c Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:16:42 +0400 Subject: [PATCH 081/357] Create stubs for WC Bookings --- .psalm/wc-bookings.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .psalm/wc-bookings.php diff --git a/.psalm/wc-bookings.php b/.psalm/wc-bookings.php new file mode 100644 index 000000000..914b83bed --- /dev/null +++ b/.psalm/wc-bookings.php @@ -0,0 +1,14 @@ + Date: Mon, 22 Jul 2024 18:16:59 +0400 Subject: [PATCH 082/357] include stubs for WC Bookings --- psalm.xml.dist | 1 + 1 file changed, 1 insertion(+) diff --git a/psalm.xml.dist b/psalm.xml.dist index 092361998..33986e444 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -35,6 +35,7 @@ + From 2c6d84c32c4274541c31b5836c7707a33ebda3e0 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:18:11 +0400 Subject: [PATCH 083/357] Add action to hook when WC order is created programmatically --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 625157a05..41d233531 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -95,6 +95,8 @@ class WooCommerceOrderCreator { $wc_order->calculate_totals(); $wc_order->save(); + do_action( 'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created', $wc_order, $wc_cart ); + return $wc_order; } From 7ba58ab98a6b522ec5b2b23cb55f6599f243fb6e Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:18:52 +0400 Subject: [PATCH 084/357] Add service to check if WC Bookings is active --- modules/ppcp-compat/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index 70ae6dac2..b812d3c1f 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -83,6 +83,9 @@ return array( 'compat.wc_shipping_tax.is_supported_plugin_version_active' => function (): bool { return class_exists( 'WC_Connect_Loader' ); }, + 'compat.wc_bookings.is_supported_plugin_version_active' => function (): bool { + return class_exists( 'WC_Bookings' ); + }, 'compat.module.url' => static function ( ContainerInterface $container ): string { /** From 2a31189c0409f5115adb6d6456202336d7eb2c98 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:19:33 +0400 Subject: [PATCH 085/357] Add compat layer for WC Bookings --- modules/ppcp-compat/src/CompatModule.php | 58 ++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 09c35c24f..6032a0427 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -9,6 +9,10 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Compat; +use Exception; +use Psr\Log\LoggerInterface; +use WC_Cart; +use WC_Order; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -56,6 +60,13 @@ class CompatModule implements ModuleInterface { $this->fix_page_builders(); $this->exclude_cache_plugins_js_minification( $c ); $this->set_elementor_checkout_context(); + + $logger = $c->get( 'woocommerce.logger.woocommerce' ); + + $is_wc_bookings_active = $c->get( 'compat.wc_bookings.is_supported_plugin_version_active' ); + if ( $is_wc_bookings_active ) { + $this->initialize_wc_bookings_compat_layer( $logger ); + } } /** @@ -387,4 +398,51 @@ class CompatModule implements ModuleInterface { 3 ); } + + /** + * Sets up the compatibility layer for WooCommerce Bookings plugin. + * + * @param LoggerInterface $logger The logger. + * @return void + */ + protected function initialize_wc_bookings_compat_layer( LoggerInterface $logger ): void { + add_action( + 'woocommerce_paypal_payments_shipping_callback_woocommerce_order_created', + static function ( WC_Order $wc_order, WC_Cart $wc_cart ) use ( $logger ): void { + try { + $cart_contents = $wc_cart->get_cart(); + foreach ( $cart_contents as $cart_item ) { + if ( empty( $cart_item['booking'] ) ) { + continue; + } + + foreach ( $wc_order->get_items() as $wc_order_item ) { + $booking_data = array( + 'cost' => $cart_item['booking']['_cost'] ?? 0, + 'start_date' => $cart_item['booking']['_start_date'] ?? 0, + 'end_date' => $cart_item['booking']['_end_date'] ?? 0, + 'all_day' => $cart_item['booking']['_all_day'] ?? 0, + 'local_timezone' => $cart_item['booking']['_local_timezone'] ?? 0, + 'order_item_id' => $wc_order_item->get_id(), + ); + + if ( isset( $cart_item['booking']['_resource_id'] ) ) { + $booking_data['resource_id'] = $cart_item['booking']['_resource_id']; + } + + if ( isset( $cart_item['booking']['_persons'] ) ) { + $booking_data['persons'] = $cart_item['booking']['_persons']; + } + + create_wc_booking( $cart_item['product_id'], $booking_data, $wc_order->get_status() ); + } + } + } catch ( Exception $exception ) { + $logger->warning( 'Failed to create booking for WooCommerce Bookings plugin: ' . $exception->getMessage() ); + } + }, + 10, + 2 + ); + } } From 25f229c25286225ba366ac5ee24bcd4a27f6891a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:23:02 +0400 Subject: [PATCH 086/357] Set booking status to 'unpaid' --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 6032a0427..27b4008b1 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -434,7 +434,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, $wc_order->get_status() ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); } } } catch ( Exception $exception ) { From 3f02d8326aa710d1766d67c620c397b4c91b7de0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 22 Jul 2024 16:25:35 +0200 Subject: [PATCH 087/357] Do not override `settings` base class property --- .../src/AdvancedCardPaymentMethod.php | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php index 2c6fd60f4..df113f8f0 100644 --- a/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php +++ b/modules/ppcp-blocks/src/AdvancedCardPaymentMethod.php @@ -52,7 +52,7 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { * * @var Settings */ - protected $settings; + protected $plugin_settings; /** * AdvancedCardPaymentMethod constructor. @@ -70,12 +70,12 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { $smart_button, Settings $settings ) { - $this->name = CreditCardGateway::ID; - $this->module_url = $module_url; - $this->version = $version; - $this->gateway = $gateway; - $this->smart_button = $smart_button; - $this->settings = $settings; + $this->name = CreditCardGateway::ID; + $this->module_url = $module_url; + $this->version = $version; + $this->gateway = $gateway; + $this->smart_button = $smart_button; + $this->plugin_settings = $settings; } /** @@ -118,8 +118,8 @@ class AdvancedCardPaymentMethod extends AbstractPaymentMethodType { 'scriptData' => $script_data, 'supports' => $this->gateway->supports, 'save_card_text' => esc_html__( 'Save your card', 'woocommerce-paypal-payments' ), - 'is_vaulting_enabled' => $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ), - 'card_icons' => $this->settings->has( 'card_icons' ) ? (array) $this->settings->get( 'card_icons' ) : array(), + 'is_vaulting_enabled' => $this->plugin_settings->has( 'vault_enabled_dcc' ) && $this->plugin_settings->get( 'vault_enabled_dcc' ), + 'card_icons' => $this->plugin_settings->has( 'card_icons' ) ? (array) $this->plugin_settings->get( 'card_icons' ) : array(), ); } From 6a71c302b0372d67b3feba26ed817220b683df13 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:45:57 +0400 Subject: [PATCH 088/357] Check if line item is bookable --- modules/ppcp-compat/src/CompatModule.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 27b4008b1..79406f5f2 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -417,6 +417,12 @@ class CompatModule implements ModuleInterface { } foreach ( $wc_order->get_items() as $wc_order_item ) { + $product = wc_get_product( $wc_order_item->get_product_id() ); + + if ( ! is_wc_booking_product( $product ) ) { + continue; + } + $booking_data = array( 'cost' => $cart_item['booking']['_cost'] ?? 0, 'start_date' => $cart_item['booking']['_start_date'] ?? 0, @@ -434,7 +440,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'pending-confirmation' ); } } } catch ( Exception $exception ) { From 6df22c2444a7823ab993e1fb415521b4f2f0329a Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:46:22 +0400 Subject: [PATCH 089/357] Improve stubs for WC Bookings --- .psalm/wc-bookings.php | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.psalm/wc-bookings.php b/.psalm/wc-bookings.php index 914b83bed..2ef838f51 100644 --- a/.psalm/wc-bookings.php +++ b/.psalm/wc-bookings.php @@ -12,3 +12,9 @@ * @return mixed WC_Booking object on success or false on fail */ function create_wc_booking( $product_id, $new_booking_data = array(), $status = 'confirmed', $exact = false ) {} + +/** + * Returns true if the product is a booking product, false if not + * @return bool + */ +function is_wc_booking_product( $product ) {} From 4c030e813490cf435eb4b083ab184732220d4147 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 22 Jul 2024 18:55:23 +0400 Subject: [PATCH 090/357] Check if item is product --- modules/ppcp-compat/src/CompatModule.php | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index 79406f5f2..a5fe3fc48 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -13,6 +13,7 @@ use Exception; use Psr\Log\LoggerInterface; use WC_Cart; use WC_Order; +use WC_Order_Item_Product; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface; use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface; @@ -417,7 +418,12 @@ class CompatModule implements ModuleInterface { } foreach ( $wc_order->get_items() as $wc_order_item ) { - $product = wc_get_product( $wc_order_item->get_product_id() ); + if ( ! is_a( $wc_order_item, WC_Order_Item_Product::class ) ) { + continue; + } + + $product_id = $wc_order_item->get_variation_id() ?: $wc_order_item->get_product_id(); + $product = wc_get_product( $product_id ); if ( ! is_wc_booking_product( $product ) ) { continue; From ffefce57b13679ccb1a6d86091bad85a78a60958 Mon Sep 17 00:00:00 2001 From: Wesley Rosa Date: Mon, 22 Jul 2024 17:09:21 -0300 Subject: [PATCH 091/357] woorelease: Product version bump update --- changelog.txt | 2 +- readme.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/changelog.txt b/changelog.txt index e980e51a0..aaf167948 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,6 +1,6 @@ *** Changelog *** -= 2.8.2 - xxxx-xx-xx = += 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 * Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 diff --git a/readme.txt b/readme.txt index ffe94e2a5..e701a54d2 100644 --- a/readme.txt +++ b/readme.txt @@ -179,7 +179,7 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == -= 2.8.2 - xxxx-xx-xx = += 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 * Fix - W3 Total Cache: Remove type from file parameter as sometimes null gets passed causing errors #2403 From e300ccaa0beacdb01cf389f02acb71f8f163a6e9 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Tue, 23 Jul 2024 14:23:38 +0400 Subject: [PATCH 092/357] Set the default status to unpaid --- modules/ppcp-compat/src/CompatModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php index a5fe3fc48..b3c8a5288 100644 --- a/modules/ppcp-compat/src/CompatModule.php +++ b/modules/ppcp-compat/src/CompatModule.php @@ -446,7 +446,7 @@ class CompatModule implements ModuleInterface { $booking_data['persons'] = $cart_item['booking']['_persons']; } - create_wc_booking( $cart_item['product_id'], $booking_data, 'pending-confirmation' ); + create_wc_booking( $cart_item['product_id'], $booking_data, 'unpaid' ); } } } catch ( Exception $exception ) { From 112b98875bbd0e87f8e20dab474d867c6bca1363 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 23 Jul 2024 15:38:07 +0200 Subject: [PATCH 093/357] Refactor save payment method js logic --- .../resources/js/Configuration.js | 190 +++++++++ .../resources/js/RenderCardFields.js | 55 +++ .../resources/js/add-payment-method.js | 368 ++++-------------- 3 files changed, 331 insertions(+), 282 deletions(-) create mode 100644 modules/ppcp-save-payment-methods/resources/js/Configuration.js create mode 100644 modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js new file mode 100644 index 000000000..366314de1 --- /dev/null +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -0,0 +1,190 @@ +import { + getCurrentPaymentMethod, + PaymentMethods, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; + +class Configuration { + constructor( ppcp_add_payment_method, errorHandler ) { + this.ppcp_add_payment_method = ppcp_add_payment_method; + this.errorHandler = errorHandler; + } + + buttonConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_payment_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + window.location.href = + this.ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + }; + } + + cardFieldsConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + payment_method: PaymentMethods.CARDS, + verification_method: + this.ppcp_add_payment_method + .verification_method, + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_payment_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + if ( + this.ppcp_add_payment_method + .is_subscription_change_payment_page + ) { + const subscriptionId = + this.ppcp_add_payment_method + .subscription_id_to_change_payment; + if ( subscriptionId && result.data ) { + const req = await fetch( + this.ppcp_add_payment_method.ajax + .subscription_change_payment_method + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .subscription_change_payment_method + .nonce, + subscription_id: subscriptionId, + payment_method: + getCurrentPaymentMethod(), + wc_payment_token_id: result.data, + } ), + } + ); + + const res = await req.json(); + if ( res.success === true ) { + window.location.href = `${ this.ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; + return; + } + } + + return; + } + + window.location.href = + this.ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + onError: ( error ) => { + console.error( error ); + this.errorHandler.message( + this.ppcp_add_payment_method.error_message + ); + }, + }; + } +} + +export default Configuration; diff --git a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js new file mode 100644 index 000000000..00c37ca6d --- /dev/null +++ b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js @@ -0,0 +1,55 @@ +import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; + +class RenderCardFields { + constructor( cardFields ) { + this.cardFields = cardFields; + } + + render() { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + this.cardFields + .NameField( { style: { input: styles } } ) + .render( nameField.parentNode ); + nameField.hidden = true; + } + + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + this.cardFields + .NumberField( { style: { input: styles } } ) + .render( numberField.parentNode ); + numberField.hidden = true; + } + + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + this.cardFields + .ExpiryField( { style: { input: styles } } ) + .render( expiryField.parentNode ); + expiryField.hidden = true; + } + + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + this.cardFields + .CVVField( { style: { input: styles } } ) + .render( cvvField.parentNode ); + cvvField.hidden = true; + } + } +} + +export default RenderCardFields; diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index 659d515f4..684de0eb2 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -4,303 +4,107 @@ import { PaymentMethods, } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { loadScript } from '@paypal/paypal-js'; +import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; + +import Configuration from './Configuration'; +import RenderCardFields from './RenderCardFields'; import { setVisible, setVisibleByClass, } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; -import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; -const errorHandler = new ErrorHandler( - ppcp_add_payment_method.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) -); - -const init = () => { - setVisibleByClass( - ORDER_BUTTON_SELECTOR, - getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, - 'ppcp-hidden' - ); - setVisible( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`, - getCurrentPaymentMethod() === PaymentMethods.PAYPAL - ); -}; - -document.addEventListener( 'DOMContentLoaded', () => { - jQuery( document.body ).on( - 'click init_add_payment_method', - '.payment_methods input.input-radio', - function () { - init(); - } - ); - - if ( ppcp_add_payment_method.is_subscription_change_payment_page ) { - const saveToAccount = document.querySelector( - '#wc-ppcp-credit-card-gateway-new-payment-method' - ); - if ( saveToAccount ) { - saveToAccount.checked = true; - saveToAccount.disabled = true; - } - } - - setTimeout( () => { - loadScript( { - clientId: ppcp_add_payment_method.client_id, - merchantId: ppcp_add_payment_method.merchant_id, - dataUserIdToken: ppcp_add_payment_method.id_token, - components: 'buttons,card-fields', - } ).then( ( paypal ) => { - errorHandler.clear(); - - const paypalButtonContainer = document.querySelector( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` - ); - if ( paypalButtonContainer ) { - paypal - .Buttons( { - createVaultSetupToken: async () => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - ppcp_add_payment_method.ajax - .create_payment_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { - window.location.href = - ppcp_add_payment_method.payment_methods_page; - return; - } - - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - } ) - .render( - `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` - ); +( function ( { ppcp_add_payment_method, jQuery } ) { + document.addEventListener( 'DOMContentLoaded', () => { + jQuery( document.body ).on( + 'click init_add_payment_method', + '.payment_methods input.input-radio', + function () { + setVisibleByClass( + ORDER_BUTTON_SELECTOR, + getCurrentPaymentMethod() !== PaymentMethods.PAYPAL, + 'ppcp-hidden' + ); + setVisible( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method`, + getCurrentPaymentMethod() === PaymentMethods.PAYPAL + ); } + ); - const cardField = paypal.CardFields( { - createVaultSetupToken: async () => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: PaymentMethods.CARDS, - verification_method: - ppcp_add_payment_method.verification_method, - } ), - } - ); + // TODO move to wc subscriptions module + if ( ppcp_add_payment_method.is_subscription_change_payment_page ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } + setTimeout( () => { + loadScript( { + clientId: ppcp_add_payment_method.client_id, + merchantId: ppcp_add_payment_method.merchant_id, + dataUserIdToken: ppcp_add_payment_method.id_token, + components: 'buttons,card-fields', + } ).then( ( paypal ) => { + const errorHandler = new ErrorHandler( + ppcp_add_payment_method.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + errorHandler.clear(); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - payment_method: PaymentMethods.CARDS, - } ), - } - ); + const configuration = new Configuration( + ppcp_add_payment_method, + errorHandler + ); - const result = await response.json(); - if ( result.success === true ) { + const paypalButtonContainer = document.querySelector( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + + if ( paypalButtonContainer ) { + paypal + .Buttons( configuration.buttonConfiguration() ) + .render( + `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` + ); + } + + const cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + + if ( cardFields.isEligible() ) { + const renderCardFields = new RenderCardFields( cardFields ); + renderCardFields.render(); + } + + document + .querySelector( '#place_order' ) + ?.addEventListener( 'click', ( event ) => { + const cardPaymentToken = document.querySelector( + 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' + )?.value; if ( - ppcp_add_payment_method.is_subscription_change_payment_page + getCurrentPaymentMethod() !== + 'ppcp-credit-card-gateway' || + ( cardPaymentToken && cardPaymentToken !== 'new' ) ) { - const subscriptionId = - ppcp_add_payment_method.subscription_id_to_change_payment; - if ( subscriptionId && result.data ) { - const req = await fetch( - ppcp_add_payment_method.ajax - .subscription_change_payment_method - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: ppcp_add_payment_method.ajax - .subscription_change_payment_method - .nonce, - subscription_id: subscriptionId, - payment_method: - getCurrentPaymentMethod(), - wc_payment_token_id: result.data, - } ), - } - ); - - const res = await req.json(); - if ( res.success === true ) { - window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; - return; - } - } - return; } - window.location.href = - ppcp_add_payment_method.payment_methods_page; - return; - } + event.preventDefault(); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - errorHandler.message( - ppcp_add_payment_method.error_message - ); - }, - } ); - - if ( cardField.isEligible() ) { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - cardField - .NameField( { style: { input: styles } } ) - .render( nameField.parentNode ); - nameField.hidden = true; - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - cardField - .NumberField( { style: { input: styles } } ) - .render( numberField.parentNode ); - numberField.hidden = true; - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - cardField - .ExpiryField( { style: { input: styles } } ) - .render( expiryField.parentNode ); - expiryField.hidden = true; - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - cardField - .CVVField( { style: { input: styles } } ) - .render( cvvField.parentNode ); - cvvField.hidden = true; - } - } - - document - .querySelector( '#place_order' ) - ?.addEventListener( 'click', ( event ) => { - const cardPaymentToken = document.querySelector( - 'input[name="wc-ppcp-credit-card-gateway-payment-token"]:checked' - )?.value; - if ( - getCurrentPaymentMethod() !== - 'ppcp-credit-card-gateway' || - ( cardPaymentToken && cardPaymentToken !== 'new' ) - ) { - return; - } - - event.preventDefault(); - - cardField.submit().catch( ( error ) => { - console.error( error ); + cardFields.submit().catch( ( error ) => { + console.error( error ); + } ); } ); - } ); - } ); - }, 1000 ); + } ); + }, 1000 ); + } ); +} )( { + ppcp_add_payment_method: window.ppcp_add_payment_method, + jQuery: window.jQuery, } ); From eb216425fb3729be9b649076b716186b207aae45 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 22 Jul 2024 18:29:39 +0200 Subject: [PATCH 094/357] =?UTF-8?q?=F0=9F=9A=A7=20New=20Gateway=20for=20Ap?= =?UTF-8?q?ple=20Pay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/ApplePayGateway.php | 20 -- .../ppcp-applepay/resources/css/styles.scss | 5 + .../resources/js/ApplepayButton.js | 200 ++++++++------ .../resources/js/Helper/utils.js | 2 +- modules/ppcp-applepay/services.php | 10 + modules/ppcp-applepay/src/ApplePayGateway.php | 234 +++++++++++++++++ modules/ppcp-applepay/src/ApplepayModule.php | 45 +++- .../src/Assets/ApplePayButton.php | 7 +- .../src/Assets/DataToAppleButtonScripts.php | 244 +++++++----------- .../ContextBootstrap/CheckoutBootstap.js | 11 +- .../js/modules/Helper/CheckoutMethodState.js | 1 + .../ppcp-wc-gateway/src/Settings/Settings.php | 5 +- 12 files changed, 516 insertions(+), 268 deletions(-) delete mode 100644 modules/ppcp-applepay/ApplePayGateway.php create mode 100644 modules/ppcp-applepay/src/ApplePayGateway.php diff --git a/modules/ppcp-applepay/ApplePayGateway.php b/modules/ppcp-applepay/ApplePayGateway.php deleted file mode 100644 index 6073ba859..000000000 --- a/modules/ppcp-applepay/ApplePayGateway.php +++ /dev/null @@ -1,20 +0,0 @@ - { - this.addButton(); - const id_minicart = - '#apple-' + this.buttonConfig.button.mini_cart_wrapper; - const id = '#apple-' + this.buttonConfig.button.wrapper; + const idMinicart = this.buttonConfig.button.mini_cart_wrapper; + const idButton = this.buttonConfig.button.wrapper; - if ( this.context === 'mini-cart' ) { - document - .querySelector( id_minicart ) - ?.addEventListener( 'click', ( evt ) => { - evt.preventDefault(); - this.onButtonClick(); - } ); - } else { - document - .querySelector( id ) - ?.addEventListener( 'click', ( evt ) => { - evt.preventDefault(); - this.onButtonClick(); - } ); - } - } ); - } else { - jQuery( '#' + this.buttonConfig.button.wrapper ).hide(); - jQuery( '#' + this.buttonConfig.button.mini_cart_wrapper ).hide(); + if ( ! this.isEligible ) { + jQuery( '#' + idButton ).hide(); + jQuery( '#' + idMinicart ).hide(); jQuery( '#express-payment-method-ppcp-applepay' ).hide(); + + return; } + + // Add click-handler to the button. + const setupButtonEvents = ( id ) => { + document + .getElementById( id ) + ?.addEventListener( 'click', ( evt ) => { + evt.preventDefault(); + this.onButtonClick(); + } ); + }; + + this.fetchTransactionInfo().then( () => { + this.addButton(); + + if ( this.context === 'mini-cart' ) { + setupButtonEvents( idMinicart ); + } else { + setupButtonEvents( idButton ); + } + } ); } reinit() { @@ -144,11 +169,11 @@ class ApplepayButton { initEventHandlers() { const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - const wrapper_id = '#' + wrapper; + const wrapperId = '#' + wrapper; - if ( wrapper_id === ppcpButtonWrapper ) { + if ( wrapperId === ppcpButtonWrapper ) { throw new Error( - `[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper_id }"` + `[ApplePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapperId }"` ); } @@ -158,9 +183,9 @@ class ApplepayButton { } const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper_id, $ppcpButtonWrapper.is( ':visible' ) ); + setVisible( wrapperId, $ppcpButtonWrapper.is( ':visible' ) ); setEnabled( - wrapper_id, + wrapperId, ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) ); }; @@ -192,6 +217,7 @@ class ApplepayButton { session.onshippingcontactselected = this.onShippingContactSelected( session ); } + session.onvalidatemerchant = this.onValidateMerchant( session ); session.onpaymentauthorized = this.onPaymentAuthorized( session ); return session; @@ -211,19 +237,19 @@ class ApplepayButton { const color = this.buttonConfig.button.color; const id = 'apple-' + wrapper; - if ( appleContainer ) { - appleContainer.innerHTML = ``; + if ( ! appleContainer ) { + return; } - const $wrapper = jQuery( '#' + wrapper ); - $wrapper.addClass( 'ppcp-button-' + ppcpStyle.shape ); + appleContainer.innerHTML = ``; + appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { - $wrapper.css( + appleContainer.style.setProperty( '--apple-pay-button-height', `${ ppcpStyle.height }px` ); - $wrapper.css( 'height', `${ ppcpStyle.height }px` ); + appleContainer.style.height = `${ ppcpStyle.height }px`; } } @@ -239,7 +265,8 @@ class ApplepayButton { const paymentRequest = this.paymentRequest(); - window.ppcpFundingSource = 'apple_pay'; // Do this on another place like on create order endpoint handler. + // Do this on another place like on create order endpoint handler. + window.ppcpFundingSource = 'apple_pay'; // Trigger woocommerce validation if we are in the checkout page. if ( this.context === 'checkout' ) { @@ -323,8 +350,8 @@ class ApplepayButton { } /** - * Indicates how payment completion should be handled if with the context handler default actions. - * Or with ApplePay module specific completion. + * Indicates how payment completion should be handled if with the context handler default + * actions. Or with Apple Pay module specific completion. * * @return {boolean} */ @@ -333,18 +360,17 @@ class ApplepayButton { if ( ! this.contextHandler.shippingAllowed() ) { return true; } + // Use WC form data mode in Checkout. - if ( + return ( this.context === 'checkout' && ! this.shouldUpdateButtonWithFormData() - ) { - return true; - } - return false; + ); } /** - * Updates ApplePay paymentRequest with form data. + * Updates Apple Pay paymentRequest with form data. + * * @param paymentRequest */ updateRequestDataWithForm( paymentRequest ) { @@ -358,8 +384,9 @@ class ApplepayButton { ); // Add custom data. - // "applicationData" is originating a "PayPalApplePayError: An internal server error has occurred" on paypal.Applepay().confirmOrder(). - // paymentRequest.applicationData = this.fillApplicationData(this.formData); + // "applicationData" is originating a "PayPalApplePayError: An internal server error has + // occurred" on paypal.Applepay().confirmOrder(). paymentRequest.applicationData = + // this.fillApplicationData(this.formData); if ( ! this.shouldRequireShippingInButton() ) { return; @@ -425,7 +452,8 @@ class ApplepayButton { 'email', 'phone', ], - requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing email and phone fields. + requiredBillingContactFields: [ 'postalAddress' ], // ApplePay does not implement billing + // email and phone fields. }; if ( ! this.shouldRequireShippingInButton() ) { @@ -453,14 +481,11 @@ class ApplepayButton { } refreshContextData() { - switch ( this.context ) { - case 'product': + if ( 'product' === this.context ) { // Refresh product data that makes the price change. - this.productQuantity = - document.querySelector( 'input.qty' )?.value; + this.productQuantity = document.querySelector( 'input.qty' )?.value; this.products = this.contextHandler.products(); this.log( 'Products updated', this.products ); - break; } } @@ -468,8 +493,35 @@ class ApplepayButton { // Payment process //------------------------ + /** + * Make ajax call to change the verification-status of the current domain. + * + * @param {boolean} isValid + */ + adminValidation( isValid ) { + // eslint-disable-next-line no-unused-vars + const ignored = fetch( this.buttonConfig.ajax_url, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + }, + body: new URLSearchParams( { + action: 'ppcp_validate', + 'woocommerce-process-checkout-nonce': this.nonce, + validation: isValid, + } ).toString(), + } ); + } + + /** + * Returns an event handler that Apple Pay calls when displaying the payment sheet. + * + * @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant + * + * @param session + * @return {(function(*): void)|*} + */ onValidateMerchant( session ) { - this.log( 'onvalidatemerchant', this.buttonConfig.ajax_url ); return ( applePayValidateMerchantEvent ) => { this.log( 'onvalidatemerchant call' ); @@ -479,34 +531,15 @@ class ApplepayButton { validationUrl: applePayValidateMerchantEvent.validationURL, } ) .then( ( validateResult ) => { - this.log( 'onvalidatemerchant ok' ); session.completeMerchantValidation( validateResult.merchantSession ); - //call backend to update validation to true - jQuery.ajax( { - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: true, - 'woocommerce-process-checkout-nonce': this.nonce, - }, - } ); + + this.adminValidation( true ); } ) .catch( ( validateError ) => { - this.log( 'onvalidatemerchant error', validateError ); console.error( validateError ); - //call backend to update validation to false - jQuery.ajax( { - url: this.buttonConfig.ajax_url, - type: 'POST', - data: { - action: 'ppcp_validate', - validation: false, - 'woocommerce-process-checkout-nonce': this.nonce, - }, - } ); + this.adminValidation( false ); this.log( 'onvalidatemerchant session abort' ); session.abort(); } ); @@ -537,7 +570,8 @@ class ApplepayButton { } this.selectedShippingMethod = event.shippingMethod; - // Sort the response shipping methods, so that the selected shipping method is the first one. + // Sort the response shipping methods, so that the selected shipping method is + // the first one. response.newShippingMethods = response.newShippingMethods.sort( ( a, b ) => { if ( @@ -680,6 +714,7 @@ class ApplepayButton { function form() { return document.querySelector( 'form.cart' ); } + const processInWooAndCapture = async ( data ) => { return new Promise( ( resolve, reject ) => { try { @@ -781,7 +816,8 @@ class ApplepayButton { if ( this.shouldCompletePaymentWithContextHandler() ) { - // No shipping, expect immediate capture, ex: PayNow, Checkout with form data. + // No shipping, expect immediate capture, ex: PayNow, Checkout with + // form data. let approveFailed = false; await this.contextHandler.approveOrder( @@ -950,4 +986,4 @@ class ApplepayButton { } } -export default ApplepayButton; +export default ApplePayButton; diff --git a/modules/ppcp-applepay/resources/js/Helper/utils.js b/modules/ppcp-applepay/resources/js/Helper/utils.js index 3b8edc2c8..793fe7f42 100644 --- a/modules/ppcp-applepay/resources/js/Helper/utils.js +++ b/modules/ppcp-applepay/resources/js/Helper/utils.js @@ -1,4 +1,4 @@ -export const buttonID = 'applepay-container'; +export const buttonID = 'ppc-button-applepay-container'; export const endpoints = { validation: '_apple_pay_validation', createOrderCart: '_apple_pay_create_order_cart', diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 6079fcb40..491e5f2aa 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -976,5 +976,15 @@ return array( esc_html( $button_text ) ); }, + 'applepay.wc-gateway' => static function ( ContainerInterface $container ): ApplePayGateway { + return new ApplePayGateway( + $container->get( 'wcgateway.order-processor' ), + $container->get( 'api.factory.paypal-checkout-url' ), + $container->get( 'wcgateway.processor.refunds' ), + $container->get( 'wcgateway.transaction-url-provider' ), + $container->get( 'session.handler' ), + $container->get( 'applepay.url' ) + ); + }, ); diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php new file mode 100644 index 000000000..327ccae53 --- /dev/null +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -0,0 +1,234 @@ +id = self::ID; + + $this->method_title = __( 'Apple Pay (via PayPal) ', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'The separate payment gateway with the Apple Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + + $this->title = $this->get_option( 'title', __( 'Apple Pay', 'woocommerce-paypal-payments' ) ); + $this->description = $this->get_option( 'description', '' ); + + $this->module_url = $module_url; + $this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.png'; + + $this->init_form_fields(); + $this->init_settings(); + $this->order_processor = $order_processor; + $this->paypal_checkout_url_factory = $paypal_checkout_url_factory; + $this->refund_processor = $refund_processor; + $this->transaction_url_provider = $transaction_url_provider; + $this->session_handler = $session_handler; + + add_action( + 'woocommerce_update_options_payment_gateways_' . $this->id, + array( + $this, + 'process_admin_options', + ) + ); + } + + /** + * Initialize the form fields. + */ + public function init_form_fields() { + $this->form_fields = array( + 'enabled' => array( + 'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ), + 'type' => 'checkbox', + 'label' => __( 'Enable Apple Pay', 'woocommerce-paypal-payments' ), + 'default' => 'no', + 'desc_tip' => true, + 'description' => __( 'Enable/Disable Apple Pay payment gateway.', 'woocommerce-paypal-payments' ), + ), + 'title' => array( + 'title' => __( 'Title', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->title, + 'desc_tip' => true, + 'description' => __( 'This controls the title which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + 'description' => array( + 'title' => __( 'Description', 'woocommerce-paypal-payments' ), + 'type' => 'text', + 'default' => $this->description, + 'desc_tip' => true, + 'description' => __( 'This controls the description which the user sees during checkout.', 'woocommerce-paypal-payments' ), + ), + ); + } + + /** + * Process payment for a WooCommerce order. + * + * @param int $order_id The WooCommerce order id. + * + * @return array + */ + public function process_payment( $order_id ) : array { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return $this->handle_payment_failure( + null, + new GatewayGenericException( new Exception( 'WC order was not found.' ) ) + ); + } + + do_action( 'woocommerce_paypal_payments_before_process_order', $wc_order ); + + try { + try { + $this->order_processor->process( $wc_order ); + + do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order ); + + return $this->handle_payment_success( $wc_order ); + } catch ( PayPalOrderMissingException $exc ) { + $order = $this->order_processor->create_order( $wc_order ); + + return array( + 'result' => 'success', + 'redirect' => ( $this->paypal_checkout_url_factory )( $order->id() ), + ); + } + } catch ( PayPalApiException $error ) { + return $this->handle_payment_failure( + $wc_order, + new Exception( + Messages::generic_payment_error_message() . ' ' . $error->getMessage(), + $error->getCode(), + $error + ) + ); + } catch ( Exception $error ) { + return $this->handle_payment_failure( $wc_order, $error ); + } + } + + /** + * Process refund. + * + * If the gateway declares 'refunds' support, this will allow it to refund. + * a passed in amount. + * + * @param int $order_id Order ID. + * @param float $amount Refund amount. + * @param string $reason Refund reason. + * + * @return boolean True or false based on success, or a WP_Error object. + */ + public function process_refund( $order_id, $amount = null, $reason = '' ) : bool { + $order = wc_get_order( $order_id ); + if ( ! is_a( $order, WC_Order::class ) ) { + return false; + } + + return $this->refund_processor->process( $order, (float) $amount, (string) $reason ); + } + + /** + * Return transaction url for this gateway and given order. + * + * @param WC_Order $order WC order to get transaction url by. + * + * @return string + */ + public function get_transaction_url( $order ) : string { + $this->view_transaction_url = $this->transaction_url_provider->get_transaction_url_base( $order ); + + return parent::get_transaction_url( $order ); + } +} diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php index 409a9c81e..878d4b322 100644 --- a/modules/ppcp-applepay/src/ApplepayModule.php +++ b/modules/ppcp-applepay/src/ApplepayModule.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Applepay; +use WC_Payment_Gateway; use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry; use WooCommerce\PayPalCommerce\Applepay\Assets\ApplePayButton; use WooCommerce\PayPalCommerce\Applepay\Assets\AppleProductStatus; @@ -117,6 +118,48 @@ class ApplepayModule implements ModuleInterface { 100, 2 ); + + add_filter( + 'woocommerce_payment_gateways', + /** + * Param types removed to avoid third-party issues. + * + * @psalm-suppress MissingClosureParamType + */ + static function ( $methods ) use ( $c ): array { + if ( ! is_array( $methods ) ) { + return $methods; + } + + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + if ( $settings->has( 'applepay_button_enabled' ) && $settings->get( 'applepay_button_enabled' ) ) { + $applepay_gateway = $c->get( 'applepay.wc-gateway' ); + assert( $applepay_gateway instanceof WC_Payment_Gateway ); + + $methods[] = $applepay_gateway; + } + + return $methods; + } + ); + + add_action( + 'woocommerce_review_order_after_submit', + function () { + // Wrapper ID: #ppc-button-ppcp-applepay. + echo '
'; + } + ); + + add_action( + 'woocommerce_pay_order_after_submit', + function () { + // Wrapper ID: #ppc-button-ppcp-applepay. + echo '
'; + } + ); } /** @@ -306,7 +349,7 @@ class ApplepayModule implements ModuleInterface { * @param bool $is_sandbox The environment for this merchant. * @return string */ - public function validation_string( bool $is_sandbox ) { + public function validation_string( bool $is_sandbox ) : string { $sandbox_string = $this->sandbox_validation_string(); $live_string = $this->live_validation_string(); return $is_sandbox ? $sandbox_string : $live_string; diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 926fc5ce2..b710e75ca 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -20,12 +20,13 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait; +use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait; /** * Class ApplePayButton */ class ApplePayButton implements ButtonInterface { - use RequestHandlerTrait; + use RequestHandlerTrait, ContextTrait; /** * The settings. @@ -973,7 +974,7 @@ class ApplePayButton implements ButtonInterface { add_action( $render_placeholder, function () { - echo ''; + echo ''; }, 21 ); @@ -986,7 +987,7 @@ class ApplePayButton implements ButtonInterface { */ protected function applepay_button(): void { ?> -
+
data_for_product_page( - $shop_country_code, - $currency_code, - $total_label - ); + return $this->data_for_product_page(); } - return $this->data_for_cart_page( - $shop_country_code, - $currency_code, - $total_label - ); + return $this->data_for_cart_page(); } /** * Returns the appropriate admin data to send to ApplePay script * * @return array - * @throws NotFoundException When the setting is not found. */ public function apple_pay_script_data_for_admin() : array { + return $this->data_for_admin_page(); + } + + /** + * Returns the full config array for the Apple Pay integration with default values. + * + * @param array $product - Optional. Product details for the payment button. + * + * @return array + */ + private function get_apple_pay_data( array $product = [] ) : array { + // true: Use Apple Pay as distinct gateway. + // false: integrate it with the smart buttons. + $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); + $is_wc_gateway_enabled = isset( $available_gateways[ ApplePayGateway::ID ] ); + + // use_wc: Use WC checkout data + // use_applepay: Use data provided by Apple Pay. + $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) + ? $this->settings->get( 'applepay_checkout_data_mode' ) + : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; + + // Store country, currency and name. $base_location = wc_get_base_location(); $shop_country_code = $base_location['country']; $currency_code = get_woocommerce_currency(); $total_label = get_bloginfo( 'name' ); - return $this->data_for_admin_page( - $shop_country_code, - $currency_code, - $total_label + // Button layout (label, color, language). + $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; + $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; + $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; + $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); + $is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' ); + + return array( + 'sdk_url' => $this->sdk_url, + 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, + 'is_admin' => false, + 'is_enabled' => $is_enabled, + 'is_wc_gateway_enabled' => $is_wc_gateway_enabled, + 'preferences' => array( + 'checkout_data_mode' => $checkout_data_mode, + ), + 'button' => array( + 'wrapper' => 'ppc-button-applepay-container', + 'mini_cart_wrapper' => 'ppc-button-applepay-container-minicart', + 'type' => $type, + 'color' => $color, + 'lang' => $lang, + ), + 'product' => $product, + 'shop' => array( + 'countryCode' => $shop_country_code, + 'currencyCode' => $currency_code, + 'totalLabel' => $total_label, + ), + 'ajax_url' => admin_url( 'admin-ajax.php' ), + 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), ); } /** * Check if the product needs shipping * - * @param \WC_Product $product The product. - * * @return bool */ - protected function check_if_need_shipping( $product ) { + protected function check_if_need_shipping( WC_Product $product ) : bool { if ( ! wc_shipping_enabled() || 0 === wc_get_shipping_method_count( @@ -104,30 +139,20 @@ class DataToAppleButtonScripts { ) { return false; } - $needs_shipping = false; if ( $product->needs_shipping() ) { - $needs_shipping = true; + return true; } - return $needs_shipping; + return false; } /** * Prepares the data for the product page. * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. - * * @return array - * @throws NotFoundException When the setting is not found. */ - protected function data_for_product_page( - $shop_country_code, - $currency_code, - $total_label - ) { + protected function data_for_product_page() : array { $product = wc_get_product( get_the_id() ); if ( ! $product ) { return array(); @@ -136,146 +161,53 @@ class DataToAppleButtonScripts { if ( $product->get_type() === 'variable' || $product->get_type() === 'variable-subscription' ) { $is_variation = true; } + $product_need_shipping = $this->check_if_need_shipping( $product ); $product_id = get_the_id(); $product_price = $product->get_price(); $product_stock = $product->get_stock_status(); - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, - 'is_admin' => false, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => $product_need_shipping, - 'id' => $product_id, - 'price' => $product_price, - 'isVariation' => $is_variation, - 'stock' => $product_stock, - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), - ); + return $this->get_apple_pay_data( array( + 'needShipping' => $product_need_shipping, + 'id' => $product_id, + 'price' => $product_price, + 'isVariation' => $is_variation, + 'stock' => $product_stock, + ) ); } /** * Prepares the data for the cart page. * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. - * * @return array */ - protected function data_for_cart_page( - $shop_country_code, - $currency_code, - $total_label - ) { + protected function data_for_cart_page() : array { $cart = WC()->cart; if ( ! $cart ) { return array(); } - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false, - 'is_admin' => false, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => $cart->needs_shipping(), - 'subtotal' => $cart->get_subtotal(), - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - 'nonce' => wp_create_nonce( 'woocommerce-process_checkout' ), - ); + return $this->get_apple_pay_data( array( + 'needShipping' => $cart->needs_shipping(), + 'subtotal' => $cart->get_subtotal(), + ) ); } /** * Prepares the data for the cart page. - * Consider refactoring this method along with data_for_cart_page() and data_for_product_page() methods. - * - * @param string $shop_country_code The shop country code. - * @param string $currency_code The currency code. - * @param string $total_label The label for the total amount. + * Consider refactoring this method along with data_for_cart_page() and data_for_product_page() + * methods. * * @return array */ - protected function data_for_admin_page( - $shop_country_code, - $currency_code, - $total_label - ) { - $type = $this->settings->has( 'applepay_button_type' ) ? $this->settings->get( 'applepay_button_type' ) : ''; - $color = $this->settings->has( 'applepay_button_color' ) ? $this->settings->get( 'applepay_button_color' ) : ''; - $lang = $this->settings->has( 'applepay_button_language' ) ? $this->settings->get( 'applepay_button_language' ) : ''; - $lang = apply_filters( 'woocommerce_paypal_payments_applepay_button_language', $lang ); - $checkout_data_mode = $this->settings->has( 'applepay_checkout_data_mode' ) ? $this->settings->get( 'applepay_checkout_data_mode' ) : PropertiesDictionary::BILLING_DATA_MODE_DEFAULT; - $is_enabled = $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' ); + protected function data_for_admin_page() : array { + $data = $this->get_apple_pay_data( array( + 'needShipping' => false, + 'subtotal' => 0, + ) ); - return array( - 'sdk_url' => $this->sdk_url, - 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, - 'is_admin' => true, - 'is_enabled' => $is_enabled, - 'preferences' => array( - 'checkout_data_mode' => $checkout_data_mode, - ), - 'button' => array( - 'wrapper' => 'applepay-container', - 'mini_cart_wrapper' => 'applepay-container-minicart', - 'type' => $type, - 'color' => $color, - 'lang' => $lang, - ), - 'product' => array( - 'needShipping' => false, - 'subtotal' => 0, - ), - 'shop' => array( - 'countryCode' => $shop_country_code, - 'currencyCode' => $currency_code, - 'totalLabel' => $total_label, - ), - 'ajax_url' => admin_url( 'admin-ajax.php' ), - ); + $data['is_admin'] = true; + + return $data; } } diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 848285815..0ec918667 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -1,3 +1,5 @@ +/* global PayPalCommerceGateway */ + import CheckoutActionHandler from '../ActionHandler/CheckoutActionHandler'; import { setVisible, setVisibleByClass } from '../Helper/Hiding'; import { @@ -181,9 +183,14 @@ class CheckoutBootstap { const isSeparateButtonGateway = [ PaymentMethods.CARD_BUTTON ].includes( currentPaymentMethod ); + const isApplePayMethod = + currentPaymentMethod === PaymentMethods.APPLEPAY; const isSavedCard = isCard && isSavedCardSelected(); const isNotOurGateway = - ! isPaypal && ! isCard && ! isSeparateButtonGateway; + ! isPaypal && + ! isCard && + ! isSeparateButtonGateway && + ! isApplePayMethod; const isFreeTrial = PayPalCommerceGateway.is_free_trial_cart; const hasVaultedPaypal = PayPalCommerceGateway.vaulted_paypal_email !== ''; @@ -227,6 +234,8 @@ class CheckoutBootstap { } } + setVisible( '#ppc-button-ppcp-applepay', isApplePayMethod ); + jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); } diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 0ea05f255..6fa3d6c73 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -3,6 +3,7 @@ export const PaymentMethods = { CARDS: 'ppcp-credit-card-gateway', OXXO: 'ppcp-oxxo-gateway', CARD_BUTTON: 'ppcp-card-button-gateway', + APPLEPAY: 'ppcp-applepay', }; export const ORDER_BUTTON_SELECTOR = '#place_order'; diff --git a/modules/ppcp-wc-gateway/src/Settings/Settings.php b/modules/ppcp-wc-gateway/src/Settings/Settings.php index 25fcb808d..e91256653 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Settings.php +++ b/modules/ppcp-wc-gateway/src/Settings/Settings.php @@ -117,19 +117,16 @@ class Settings implements ContainerInterface { /** * Stores the settings to the database. */ - public function persist() { - + public function persist() : bool { return update_option( self::KEY, $this->settings ); } - /** * Loads the settings. * * @return bool */ private function load(): bool { - if ( $this->settings ) { return false; } From a473459ddbf377f3f9d01f596365f89a8e4287fd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 23 Jul 2024 17:36:10 +0200 Subject: [PATCH 095/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Minor=20code=20cle?= =?UTF-8?q?anup=20and=20documentation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 192 +++++++++++------- .../resources/js/ApplepayManager.js | 15 +- .../js/ApplepayManagerBlockEditor.js | 10 +- .../resources/js/Context/BaseHandler.js | 8 - .../resources/js/Context/PreviewHandler.js | 6 +- .../ppcp-applepay/resources/js/boot-admin.js | 4 +- .../ppcp-applepay/resources/js/boot-block.js | 8 +- modules/ppcp-applepay/resources/js/boot.js | 4 +- 8 files changed, 142 insertions(+), 105 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index b703a9565..3a1a13a00 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -10,6 +10,23 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +/** + * List of valid context values that the button can have. + * + * @type {Object} + */ +const CONTEXT = { + Product: 'product', + Cart: 'cart', + Checkout: 'checkout', + PayNow: 'pay-now', + MiniCart: 'mini-cart', + BlockCart: 'cart-block', + BlockCheckout: 'checkout-block', + Preview: 'preview', + Blocks: [ 'cart-block', 'checkout-block' ], +}; + /** * A payment button for Apple Pay. * @@ -32,6 +49,20 @@ class ApplePayButton { */ context = ''; + externalHandler = null; + buttonConfig = null; + ppcpConfig = null; + paymentsClient = null; + formData = null; + contextHandler = null; + updatedContactInfo = []; + selectedShippingMethod = []; + + /** + * Stores initialization data sent to the button. + */ + initialPaymentRequest = null; + constructor( context, externalHandler, buttonConfig, ppcpConfig ) { apmButtonsInit( ppcpConfig ); @@ -39,8 +70,6 @@ class ApplePayButton { this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; - this.paymentsClient = null; - this.formData = null; this.contextHandler = ContextHandlerFactory.create( this.context, @@ -48,18 +77,6 @@ class ApplePayButton { this.ppcpConfig ); - this.updatedContactInfo = []; - this.selectedShippingMethod = []; - this.nonce = - document.getElementById( 'woocommerce-process-checkout-nonce' ) - ?.value || buttonConfig.nonce; - - // Stores initialization data sent to the button. - this.initialPaymentRequest = null; - - // Default eligibility status. - this.isEligible = true; - this.log = function () { if ( this.buttonConfig.is_debug ) { //console.log('[ApplePayButton]', ...arguments); @@ -76,6 +93,39 @@ class ApplePayButton { document.ppcpApplepayButtons[ this.context ] = this; } + /** + * The nonce for ajax requests. + * + * @return {string} The nonce value + */ + get nonce() { + const input = document.getElementById( + 'woocommerce-process-checkout-nonce' + ); + + return input?.value || this.buttonConfig.nonce; + } + + /** + * Whether the current page qualifies to use the Apple Pay button. + * + * In admin, the button is always eligible, to display an accurate preview. + * On front-end, PayPal's response decides if customers can use Apple Pay. + * + * @return {boolean} True, if the button can be displayed. + */ + get isEligible() { + if ( ! this.isInitialized ) { + return true; + } + + if ( this.buttonConfig.is_admin ) { + return true; + } + + return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); + } + init( config ) { if ( this.isInitialized ) { return; @@ -90,9 +140,6 @@ class ApplePayButton { this.isInitialized = true; this.applePayConfig = config; - this.isEligible = - ( this.applePayConfig.isEligible && window.ApplePaySession ) || - this.buttonConfig.is_admin; const idMinicart = this.buttonConfig.button.mini_cart_wrapper; const idButton = this.buttonConfig.button.wrapper; @@ -118,7 +165,7 @@ class ApplePayButton { this.fetchTransactionInfo().then( () => { this.addButton(); - if ( this.context === 'mini-cart' ) { + if ( CONTEXT.MiniCart === this.context ) { setupButtonEvents( idMinicart ); } else { setupButtonEvents( idButton ); @@ -143,23 +190,22 @@ class ApplePayButton { * Returns configurations relative to this button context. */ contextConfig() { - const config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper, - }; + const config = {}; - if ( this.context === 'mini-cart' ) { + if ( CONTEXT.MiniCart === this.context ) { config.wrapper = this.buttonConfig.button.mini_cart_wrapper; config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; config.buttonStyle = this.buttonConfig.button.mini_cart_style; config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; + } else { + config.wrapper = this.buttonConfig.button.wrapper; + config.ppcpStyle = this.ppcpConfig.button.style; + config.buttonStyle = this.buttonConfig.button.style; + config.ppcpButtonWrapper = this.ppcpConfig.button.wrapper; } - if ( - [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 - ) { + // Block editor configuration. + if ( CONTEXT.Blocks.includes( this.context ) ) { config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway-paypal'; } @@ -203,8 +249,9 @@ class ApplePayButton { } /** - * Starts an ApplePay session. - * @param paymentRequest + * Starts an Apple Pay session. + * + * @param {Object} paymentRequest The payment request object. */ applePaySession( paymentRequest ) { this.log( 'applePaySession', paymentRequest ); @@ -269,7 +316,7 @@ class ApplePayButton { window.ppcpFundingSource = 'apple_pay'; // Trigger woocommerce validation if we are in the checkout page. - if ( this.context === 'checkout' ) { + if ( CONTEXT.Checkout === this.context ) { const checkoutFormSelector = 'form.woocommerce-checkout'; const errorHandler = new ErrorHandler( PayPalCommerceGateway.labels.error.generic, @@ -323,13 +370,13 @@ class ApplePayButton { /** * If the button should show the shipping fields. * - * @return {false|*} + * @return {boolean} True, if shipping fields should be captured by ApplePay. */ shouldRequireShippingInButton() { return ( this.contextHandler.shippingAllowed() && this.buttonConfig.product.needShipping && - ( this.context !== 'checkout' || + ( CONTEXT.Checkout !== this.context || this.shouldUpdateButtonWithFormData() ) ); } @@ -337,10 +384,10 @@ class ApplePayButton { /** * If the button should be updated with the form addresses. * - * @return {boolean} + * @return {boolean} True, when Apple Pay data should be submitted to WooCommerce. */ shouldUpdateButtonWithFormData() { - if ( this.context !== 'checkout' ) { + if ( CONTEXT.Checkout !== this.context ) { return false; } return ( @@ -353,7 +400,7 @@ class ApplePayButton { * Indicates how payment completion should be handled if with the context handler default * actions. Or with Apple Pay module specific completion. * - * @return {boolean} + * @return {boolean} True, when the Apple Pay data should be submitted to WooCommerce. */ shouldCompletePaymentWithContextHandler() { // Data already handled, ex: PayNow @@ -363,7 +410,7 @@ class ApplePayButton { // Use WC form data mode in Checkout. return ( - this.context === 'checkout' && + CONTEXT.Checkout === this.context && ! this.shouldUpdateButtonWithFormData() ); } @@ -371,7 +418,7 @@ class ApplePayButton { /** * Updates Apple Pay paymentRequest with form data. * - * @param paymentRequest + * @param {Object} paymentRequest Object to extend with form data. */ updateRequestDataWithForm( paymentRequest ) { if ( ! this.shouldUpdateButtonWithFormData() ) { @@ -481,11 +528,11 @@ class ApplePayButton { } refreshContextData() { - if ( 'product' === this.context ) { - // Refresh product data that makes the price change. + if ( CONTEXT.Product === this.context ) { + // Refresh product data that makes the price change. this.productQuantity = document.querySelector( 'input.qty' )?.value; - this.products = this.contextHandler.products(); - this.log( 'Products updated', this.products ); + this.products = this.contextHandler.products(); + this.log( 'Products updated', this.products ); } } @@ -518,8 +565,9 @@ class ApplePayButton { * * @see https://developer.apple.com/documentation/apple_pay_on_the_web/applepaysession/1778021-onvalidatemerchant * - * @param session - * @return {(function(*): void)|*} + * @param {Object} session The ApplePaySession object. + * + * @return {(function(*): void)|*} Callback that runs after the merchant validation */ onValidateMerchant( session ) { return ( applePayValidateMerchantEvent ) => { @@ -548,14 +596,14 @@ class ApplePayButton { onShippingMethodSelected( session ) { this.log( 'onshippingmethodselected', this.buttonConfig.ajax_url ); - const ajax_url = this.buttonConfig.ajax_url; + const ajaxUrl = this.buttonConfig.ajax_url; return ( event ) => { this.log( 'onshippingmethodselected call' ); const data = this.getShippingMethodData( event ); jQuery.ajax( { - url: ajax_url, + url: ajaxUrl, method: 'POST', data, success: ( @@ -599,7 +647,7 @@ class ApplePayButton { onShippingContactSelected( session ) { this.log( 'onshippingcontactselected', this.buttonConfig.ajax_url ); - const ajax_url = this.buttonConfig.ajax_url; + const ajaxUrl = this.buttonConfig.ajax_url; return ( event ) => { this.log( 'onshippingcontactselected call' ); @@ -607,7 +655,7 @@ class ApplePayButton { const data = this.getShippingContactData( event ); jQuery.ajax( { - url: ajax_url, + url: ajaxUrl, method: 'POST', data, success: ( @@ -637,15 +685,15 @@ class ApplePayButton { } getShippingContactData( event ) { - const product_id = this.buttonConfig.product.id; + const productId = this.buttonConfig.product.id; this.refreshContextData(); switch ( this.context ) { - case 'product': + case CONTEXT.Product: return { action: 'ppcp_update_shipping_contact', - product_id, + product_id: productId, products: JSON.stringify( this.products ), caller_page: 'productDetail', product_quantity: this.productQuantity, @@ -653,11 +701,12 @@ class ApplePayButton { need_shipping: this.shouldRequireShippingInButton(), 'woocommerce-process-checkout-nonce': this.nonce, }; - case 'cart': - case 'checkout': - case 'cart-block': - case 'checkout-block': - case 'mini-cart': + + case CONTEXT.Cart: + case CONTEXT.Checkout: + case CONTEXT.BlockCart: + case CONTEXT.BlockCheckout: + case CONTEXT.MiniCart: return { action: 'ppcp_update_shipping_contact', simplified_contact: event.shippingContact, @@ -669,12 +718,12 @@ class ApplePayButton { } getShippingMethodData( event ) { - const product_id = this.buttonConfig.product.id; + const productId = this.buttonConfig.product.id; this.refreshContextData(); switch ( this.context ) { - case 'product': + case CONTEXT.Product: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, @@ -682,17 +731,18 @@ class ApplePayButton { this.updatedContactInfo || this.initialPaymentRequest.shippingContact || this.initialPaymentRequest.billingContact, - product_id, + product_id: productId, products: JSON.stringify( this.products ), caller_page: 'productDetail', product_quantity: this.productQuantity, 'woocommerce-process-checkout-nonce': this.nonce, }; - case 'cart': - case 'checkout': - case 'cart-block': - case 'checkout-block': - case 'mini-cart': + + case CONTEXT.Cart: + case CONTEXT.Checkout: + case CONTEXT.BlockCart: + case CONTEXT.BlockCheckout: + case CONTEXT.MiniCart: return { action: 'ppcp_update_shipping_method', shipping_method: event.shippingMethod, @@ -711,10 +761,6 @@ class ApplePayButton { return async ( event ) => { this.log( 'onpaymentauthorized call' ); - function form() { - return document.querySelector( 'form.cart' ); - } - const processInWooAndCapture = async ( data ) => { return new Promise( ( resolve, reject ) => { try { @@ -729,7 +775,7 @@ class ApplePayButton { ( this.initialPaymentRequest.shippingMethods || [] )[ 0 ]; - const request_data = { + const requestData = { action: 'ppcp_create_order', caller_page: this.context, product_id: this.buttonConfig.product.id ?? null, @@ -754,7 +800,7 @@ class ApplePayButton { jQuery.ajax( { url: this.buttonConfig.ajax_url, method: 'POST', - data: request_data, + data: requestData, complete: ( jqXHR, textStatus ) => { this.log( 'onpaymentauthorized complete' ); }, @@ -829,15 +875,15 @@ class ApplePayButton { restart: () => new Promise( ( resolve, reject ) => { - approveFailed = true; - resolve(); + approveFailed = true; + resolve(); } ), order: { get: () => new Promise( ( resolve, reject ) => { - resolve( null ); + resolve( null ); } ), }, diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js index 0992d9e7d..264610f28 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManager.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js @@ -1,7 +1,9 @@ -import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; -import ApplepayButton from './ApplepayButton'; +/* global paypal */ -class ApplepayManager { +import buttonModuleWatcher from '../../../ppcp-button/resources/js/modules/ButtonModuleWatcher'; +import ApplePayButton from './ApplepayButton'; + +class ApplePayManager { constructor( buttonConfig, ppcpConfig ) { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -9,7 +11,7 @@ class ApplepayManager { this.buttons = []; buttonModuleWatcher.watchContextBootstrap( ( bootstrap ) => { - const button = new ApplepayButton( + const button = new ApplePayButton( bootstrap.context, bootstrap.handler, buttonConfig, @@ -40,8 +42,7 @@ class ApplepayManager { } /** - * Gets ApplePay configuration of the PayPal merchant. - * @return {Promise} + * Gets Apple Pay configuration of the PayPal merchant. */ async config() { this.ApplePayConfig = await paypal.Applepay().config(); @@ -49,4 +50,4 @@ class ApplepayManager { } } -export default ApplepayManager; +export default ApplePayManager; diff --git a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js index 2f4db9d41..1c10bb997 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js +++ b/modules/ppcp-applepay/resources/js/ApplepayManagerBlockEditor.js @@ -1,6 +1,8 @@ -import ApplepayButton from './ApplepayButton'; +/* global paypal */ -class ApplepayManagerBlockEditor { +import ApplePayButton from './ApplepayButton'; + +class ApplePayManagerBlockEditor { constructor( buttonConfig, ppcpConfig ) { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -17,7 +19,7 @@ class ApplepayManagerBlockEditor { try { this.applePayConfig = await paypal.Applepay().config(); - const button = new ApplepayButton( + const button = new ApplePayButton( this.ppcpConfig.context, null, this.buttonConfig, @@ -31,4 +33,4 @@ class ApplepayManagerBlockEditor { } } -export default ApplepayManagerBlockEditor; +export default ApplePayManagerBlockEditor; diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index f763ac5d9..cc3a3aeb2 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,6 +1,5 @@ import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; -import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription'; class BaseHandler { constructor( buttonConfig, ppcpConfig ) { @@ -76,13 +75,6 @@ class BaseHandler { document.querySelector( '.woocommerce-notices-wrapper' ) ); } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) - ); - } } export default BaseHandler; diff --git a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js index 8740705e9..5febfe0c3 100644 --- a/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/PreviewHandler.js @@ -1,10 +1,6 @@ import BaseHandler from './BaseHandler'; class PreviewHandler extends BaseHandler { - constructor( buttonConfig, ppcpConfig, externalHandler ) { - super( buttonConfig, ppcpConfig, externalHandler ); - } - transactionInfo() { // We need to return something as ApplePay button initialization expects valid data. return { @@ -19,7 +15,7 @@ class PreviewHandler extends BaseHandler { throw new Error( 'Create order fail. This is just a preview.' ); } - approveOrder( data, actions ) { + approveOrder() { throw new Error( 'Approve order fail. This is just a preview.' ); } diff --git a/modules/ppcp-applepay/resources/js/boot-admin.js b/modules/ppcp-applepay/resources/js/boot-admin.js index 080d7c8aa..81d8c0b7b 100644 --- a/modules/ppcp-applepay/resources/js/boot-admin.js +++ b/modules/ppcp-applepay/resources/js/boot-admin.js @@ -1,4 +1,4 @@ -import ApplepayButton from './ApplepayButton'; +import ApplePayButton from './ApplepayButton'; import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton'; import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager'; @@ -86,7 +86,7 @@ class ApplePayPreviewButton extends PreviewButton { } createButton( buttonConfig ) { - const button = new ApplepayButton( + const button = new ApplePayButton( 'preview', null, buttonConfig, diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js index 8445466eb..c447cb064 100644 --- a/modules/ppcp-applepay/resources/js/boot-block.js +++ b/modules/ppcp-applepay/resources/js/boot-block.js @@ -4,8 +4,8 @@ import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Help import { cartHasSubscriptionProducts } from '../../../ppcp-blocks/resources/js/Helper/Subscription'; import { loadCustomScript } from '@paypal/paypal-js'; import CheckoutHandler from './Context/CheckoutHandler'; -import ApplepayManager from './ApplepayManager'; -import ApplepayManagerBlockEditor from './ApplepayManagerBlockEditor'; +import ApplePayManager from './ApplepayManager'; +import ApplePayManagerBlockEditor from './ApplepayManagerBlockEditor'; const ppcpData = wc.wcSettings.getSetting( 'ppcp-gateway_data' ); const ppcpConfig = ppcpData.scriptData; @@ -24,8 +24,8 @@ const ApplePayComponent = ( props ) => { const bootstrap = function () { const ManagerClass = props.isEditing - ? ApplepayManagerBlockEditor - : ApplepayManager; + ? ApplePayManagerBlockEditor + : ApplePayManager; const manager = new ManagerClass( buttonConfig, ppcpConfig ); manager.init(); }; diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js index aee735231..8eddafbcb 100644 --- a/modules/ppcp-applepay/resources/js/boot.js +++ b/modules/ppcp-applepay/resources/js/boot.js @@ -1,13 +1,13 @@ import { loadCustomScript } from '@paypal/paypal-js'; import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'; -import ApplepayManager from './ApplepayManager'; +import ApplePayManager from './ApplepayManager'; import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper'; ( function ( { buttonConfig, ppcpConfig, jQuery } ) { let manager; const bootstrap = function () { - manager = new ApplepayManager( buttonConfig, ppcpConfig ); + manager = new ApplePayManager( buttonConfig, ppcpConfig ); manager.init(); }; From f4abf7028e1bfbac98de2ec7e3d6199f563a3829 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 14:20:40 +0200 Subject: [PATCH 096/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Make=20is?= =?UTF-8?q?Initialized=20flag=20private?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reason: The button instance is added to the global document scope, this helps to protect internal component state --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 3a1a13a00..d1a56d616 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -40,7 +40,7 @@ class ApplePayButton { * * @type {boolean} */ - isInitialized = false; + #isInitialized = false; /** * Context describes the button's location on the website and what details it submits. @@ -115,7 +115,7 @@ class ApplePayButton { * @return {boolean} True, if the button can be displayed. */ get isEligible() { - if ( ! this.isInitialized ) { + if ( ! this.#isInitialized ) { return true; } @@ -127,7 +127,7 @@ class ApplePayButton { } init( config ) { - if ( this.isInitialized ) { + if ( this.#isInitialized ) { return; } @@ -138,7 +138,7 @@ class ApplePayButton { this.log( 'Init', this.context ); this.initEventHandlers(); - this.isInitialized = true; + this.#isInitialized = true; this.applePayConfig = config; const idMinicart = this.buttonConfig.button.mini_cart_wrapper; @@ -178,7 +178,7 @@ class ApplePayButton { return; } - this.isInitialized = false; + this.#isInitialized = false; this.init( this.applePayConfig ); } From f75c3610d813522c6a2e35608e244f855ee7438f Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 14:21:26 +0200 Subject: [PATCH 097/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Slightly?= =?UTF-8?q?=20improve=20the=20debug-log=20output?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d1a56d616..63368e305 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -77,20 +77,24 @@ class ApplePayButton { this.ppcpConfig ); - this.log = function () { - if ( this.buttonConfig.is_debug ) { - //console.log('[ApplePayButton]', ...arguments); - } - }; - this.refreshContextData(); // Debug helpers - jQuery( document ).on( 'ppcp-applepay-debug', () => { - console.log( 'ApplePayButton', this.context, this ); - } ); document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; document.ppcpApplepayButtons[ this.context ] = this; + + this.log = function () { + if ( ! this.buttonConfig.is_debug ) { + return; + } + console.log( `[ApplePayButton | ${ this.context }]`, ...arguments ); + }; + + if ( this.buttonConfig.is_debug ) { + jQuery( document ).on( 'ppcp-applepay-debug', () => { + this.log( this ); + } ); + } } /** @@ -135,7 +139,7 @@ class ApplePayButton { return; } - this.log( 'Init', this.context ); + this.log( 'Init' ); this.initEventHandlers(); this.#isInitialized = true; @@ -274,7 +278,7 @@ class ApplePayButton { * Adds an Apple Pay purchase button. */ addButton() { - this.log( 'addButton', this.context ); + this.log( 'addButton' ); const { wrapper, ppcpStyle } = this.contextConfig(); @@ -308,7 +312,7 @@ class ApplePayButton { * Show Apple Pay payment sheet when Apple Pay payment button is clicked */ async onButtonClick() { - this.log( 'onButtonClick', this.context ); + this.log( 'onButtonClick' ); const paymentRequest = this.paymentRequest(); From 43d7e0788fe4e5eeb58cd22546644ebb0e8d093c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 15:19:35 +0200 Subject: [PATCH 098/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Chore:=20Minor=20c?= =?UTF-8?q?ode=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 1 + modules/ppcp-applepay/src/ApplePayGateway.php | 13 +++--- .../src/Assets/ApplePayButton.php | 43 ++++++++----------- 3 files changed, 25 insertions(+), 32 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 63368e305..4f09d28d2 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,3 +1,4 @@ +/* global jQuery */ /* global ApplePaySession */ /* global PayPalCommerceGateway */ diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php index 327ccae53..8b46dda64 100644 --- a/modules/ppcp-applepay/src/ApplePayGateway.php +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -9,17 +9,17 @@ declare( strict_types = 1 ); namespace WooCommerce\PayPalCommerce\Applepay; +use Exception; +use WC_Order; use WC_Payment_Gateway; +use WooCommerce\PayPalCommerce\Session\SessionHandler; +use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\ProcessPaymentTrait; use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor; use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor; use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider; -use WooCommerce\PayPalCommerce\Session\SessionHandler; -use WC_Order; use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException; -use Exception; use WooCommerce\PayPalCommerce\WcGateway\Exception\PayPalOrderMissingException; -use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\WcGateway\Gateway\Messages; /** @@ -114,10 +114,7 @@ class ApplePayGateway extends WC_Payment_Gateway { add_action( 'woocommerce_update_options_payment_gateways_' . $this->id, - array( - $this, - 'process_admin_options', - ) + array( $this, 'process_admin_options' ) ); } diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index b710e75ca..d9a9d5393 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -341,7 +341,7 @@ class ApplePayButton implements ButtonInterface { } $response = $this->response_templates->apple_formatted_response( $payment_details ); $this->response_templates->response_success( $response ); - } catch ( \Exception $e ) { + } catch ( Exception $e ) { $this->response_templates->response_with_data_errors( array( array( @@ -383,7 +383,7 @@ class ApplePayButton implements ButtonInterface { } $response = $this->response_templates->apple_formatted_response( $payment_details ); $this->response_templates->response_success( $response ); - } catch ( \Exception $e ) { + } catch ( Exception $e ) { $this->response_templates->response_with_data_errors( array( array( @@ -400,7 +400,7 @@ class ApplePayButton implements ButtonInterface { * On error returns an array of errors to be handled by the script * On success returns the new order data * - * @throws \Exception When validation fails. + * @throws Exception When validation fails. */ public function create_wc_order(): void { $applepay_request_data_object = $this->applepay_data_object_http(); @@ -421,15 +421,18 @@ class ApplePayButton implements ButtonInterface { $applepay_request_data_object->order_data( $context ); $this->update_posted_data( $applepay_request_data_object ); + if ( $context === 'product' ) { $cart_item_key = $this->prepare_cart( $applepay_request_data_object ); $cart = WC()->cart; $address = $applepay_request_data_object->shipping_address(); + $this->calculate_totals_single_product( $cart, $address, $applepay_request_data_object->shipping_method() ); + if ( ! $cart_item_key ) { $this->response_templates->response_with_data_errors( array( @@ -439,20 +442,17 @@ class ApplePayButton implements ButtonInterface { ), ) ); - return; - } + } else { add_filter( 'woocommerce_payment_successful_result', function ( array $result ) use ( $cart, $cart_item_key ) : array { - if ( ! is_string( $cart_item_key ) ) { - return $result; - } $this->clear_current_cart( $cart, $cart_item_key ); $this->reload_cart( $cart ); return $result; } ); } + } WC()->checkout()->process_checkout(); } @@ -461,17 +461,20 @@ class ApplePayButton implements ButtonInterface { /** * Checks if the nonce in the data object is valid * - * @return bool|int + * @return bool */ protected function is_nonce_valid(): bool { $nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS ); if ( ! $nonce ) { return false; } - return wp_verify_nonce( + + // Return value 1 indicates "valid nonce, generated in past 12 hours". + // Return value 2 also indicated valid nonce, but older than 12 hours. + return 1 === wp_verify_nonce( $nonce, 'woocommerce-process_checkout' - ) === 1; + ); } /** @@ -512,7 +515,7 @@ class ApplePayButton implements ButtonInterface { $address, $applepay_request_data_object->shipping_method() ); - if ( is_string( $cart_item_key ) ) { + if ( $cart_item_key ) { $this->clear_current_cart( $cart, $cart_item_key ); $this->reload_cart( $cart ); } @@ -820,9 +823,9 @@ class ApplePayButton implements ButtonInterface { /** * Removes the old cart, saves it, and creates a new one * + * @throws Exception If it cannot be added to cart. * @param ApplePayDataObjectHttp $applepay_request_data_object The request data object. - * @return bool | string The cart item key after adding to the new cart. - * @throws \Exception If it cannot be added to cart. + * @return string The cart item key after adding to the new cart. */ public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string { $this->save_old_cart(); @@ -839,7 +842,7 @@ class ApplePayButton implements ButtonInterface { ); $this->cart_products->add_products( array( $product ) ); - return $this->cart_products->cart_item_keys()[0]; + return $this->cart_products->cart_item_keys()[0] ?? ''; } /** @@ -982,6 +985,7 @@ class ApplePayButton implements ButtonInterface { return true; } + /** * ApplePay button markup */ @@ -993,15 +997,6 @@ class ApplePayButton implements ButtonInterface { Date: Wed, 24 Jul 2024 15:21:09 +0200 Subject: [PATCH 099/357] =?UTF-8?q?=E2=9C=A8=20Hide=20Apple=20Pay=20gatewa?= =?UTF-8?q?y=20on=20ineligible=20devices?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 24 ++++++++++++-- .../src/Assets/ApplePayButton.php | 32 +++++++++++++------ 2 files changed, 44 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4f09d28d2..21b410427 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -150,13 +150,31 @@ class ApplePayButton { const idButton = this.buttonConfig.button.wrapper; if ( ! this.isEligible ) { - jQuery( '#' + idButton ).hide(); - jQuery( '#' + idMinicart ).hide(); - jQuery( '#express-payment-method-ppcp-applepay' ).hide(); + const hideContainers = [ + // Payment button (Pay now, smart button block) + `#${ idButton }`, + // Mini Cart button + `#${ idMinicart }`, + // Block Checkout: Express checkout button. + '#express-payment-method-ppcp-applepay', + ]; + + hideContainers.forEach( ( selector ) => { + const elements = document.querySelectorAll( selector ); + + elements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + } ); return; } + // Classic Checkout: Make the Apple Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); + // Add click-handler to the button. const setupButtonEvents = ( id ) => { document diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index d9a9d5393..22adce359 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -443,15 +443,15 @@ class ApplePayButton implements ButtonInterface { ) ); } else { - add_filter( - 'woocommerce_payment_successful_result', - function ( array $result ) use ( $cart, $cart_item_key ) : array { - $this->clear_current_cart( $cart, $cart_item_key ); - $this->reload_cart( $cart ); - return $result; - } - ); - } + add_filter( + 'woocommerce_payment_successful_result', + function ( array $result ) use ( $cart, $cart_item_key ) : array { + $this->clear_current_cart( $cart, $cart_item_key ); + $this->reload_cart( $cart ); + return $result; + } + ); + } } WC()->checkout()->process_checkout(); @@ -953,6 +953,7 @@ class ApplePayButton implements ButtonInterface { $render_placeholder, function () { $this->applepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -997,6 +998,19 @@ class ApplePayButton implements ButtonInterface { + + Date: Wed, 24 Jul 2024 16:00:55 +0200 Subject: [PATCH 100/357] Add card payment token for free trial subscriptions (WIP) --- modules/ppcp-button/resources/js/button.js | 21 +++-- .../Renderer/CardFieldsFreeTrialRenderer.js | 91 +++++++++++++++++++ .../resources/js/Configuration.js | 9 ++ .../src/Endpoint/CreatePaymentToken.php | 5 + .../src/Gateway/CreditCardGateway.php | 14 +++ 5 files changed, 134 insertions(+), 6 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index 9f5485b38..d252c4392 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -7,6 +7,7 @@ import Renderer from './modules/Renderer/Renderer'; import ErrorHandler from './modules/ErrorHandler'; import HostedFieldsRenderer from './modules/Renderer/HostedFieldsRenderer'; import CardFieldsRenderer from './modules/Renderer/CardFieldsRenderer'; +import CardFieldsFreeTrialRenderer from './modules/Renderer/CardFieldsFreeTrialRenderer'; import MessageRenderer from './modules/Renderer/MessageRenderer'; import Spinner from './modules/Helper/Spinner'; import { @@ -215,12 +216,20 @@ const bootstrap = () => { spinner ); if ( typeof paypal.CardFields !== 'undefined' ) { - creditCardRenderer = new CardFieldsRenderer( - PayPalCommerceGateway, - errorHandler, - spinner, - onCardFieldsBeforeSubmit - ); + if ( PayPalCommerceGateway.is_free_trial_cart ) { + creditCardRenderer = new CardFieldsFreeTrialRenderer( + PayPalCommerceGateway, + errorHandler, + spinner + ); + } else { + creditCardRenderer = new CardFieldsRenderer( + PayPalCommerceGateway, + errorHandler, + spinner, + onCardFieldsBeforeSubmit + ); + } } const renderer = new Renderer( diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js new file mode 100644 index 000000000..c813e7fc7 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -0,0 +1,91 @@ +import { show } from '../Helper/Hiding'; +import ErrorHandler from '../ErrorHandler'; +import RenderCardFields from '../../../../../ppcp-save-payment-methods/resources/js/RenderCardFields'; +import Configuration from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; + +class CardFieldsFreeTrialRenderer { + constructor( defaultConfig, errorHandler, spinner ) { + this.defaultConfig = defaultConfig; + this.errorHandler = errorHandler; + this.spinner = spinner; + } + + render( wrapper, contextConfig ) { + if ( + ( this.defaultConfig.context !== 'checkout' && + this.defaultConfig.context !== 'pay-now' ) || + wrapper === null || + document.querySelector( wrapper ) === null + ) { + return; + } + + const buttonSelector = wrapper + ' button'; + + const gateWayBox = document.querySelector( + '.payment_box.payment_method_ppcp-credit-card-gateway' + ); + if ( ! gateWayBox ) { + return; + } + + const oldDisplayStyle = gateWayBox.style.display; + gateWayBox.style.display = 'block'; + + const hideDccGateway = document.querySelector( '#ppcp-hide-dcc' ); + if ( hideDccGateway ) { + hideDccGateway.parentNode.removeChild( hideDccGateway ); + } + + const errorHandler = new ErrorHandler( + this.defaultConfig.labels.error.generic, + document.querySelector( '.woocommerce-notices-wrapper' ) + ); + errorHandler.clear(); + + const configuration = new Configuration( + this.defaultConfig, + errorHandler + ); + + const cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + + if ( cardFields.isEligible() ) { + const renderCardFields = new RenderCardFields( cardFields ); + renderCardFields.render(); + } + + gateWayBox.style.display = oldDisplayStyle; + + show( buttonSelector ); + + if ( this.defaultConfig.cart_contains_subscription ) { + const saveToAccount = document.querySelector( + '#wc-ppcp-credit-card-gateway-new-payment-method' + ); + if ( saveToAccount ) { + saveToAccount.checked = true; + saveToAccount.disabled = true; + } + } + + document + .querySelector( buttonSelector ) + ?.addEventListener( 'click', ( event ) => { + event.preventDefault(); + this.spinner.block(); + this.errorHandler.clear(); + + cardFields.submit().catch( ( error ) => { + console.error( error ); + } ); + } ); + } + + disableFields() {} + enableFields() {} +} + +export default CardFieldsFreeTrialRenderer; diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index 366314de1..e779dbdbc 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -108,6 +108,8 @@ class Configuration { ); }, onApprove: async ( { vaultSetupToken } ) => { + const isFreeTrialCart = + this.ppcp_add_payment_method?.is_free_trial_cart ?? false; const response = await fetch( this.ppcp_add_payment_method.ajax.create_payment_token .endpoint, @@ -122,12 +124,19 @@ class Configuration { .create_payment_token.nonce, vault_setup_token: vaultSetupToken, payment_method: PaymentMethods.CARDS, + is_free_trial_cart: isFreeTrialCart, } ), } ); const result = await response.json(); if ( result.success === true ) { + const context = this.ppcp_add_payment_method?.context ?? ''; + if ( context === 'checkout' ) { + document.querySelector( '#place_order' ).click(); + return; + } + if ( this.ppcp_add_payment_method .is_subscription_change_payment_page diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index 4fea1f188..acd4f988f 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -115,6 +115,11 @@ class CreatePaymentToken implements EndpointInterface { if ( isset( $result->payment_source->card ) ) { $wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result ); + + $is_free_trial_cart = $data['is_free_trial_cart'] ?? ''; + if($is_free_trial_cart === '1') { + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', $wc_token_id ); + } } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index b2ce4cbc5..319510b68 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -426,12 +426,26 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { public function process_payment( $order_id ) { $wc_order = wc_get_order( $order_id ); if ( ! is_a( $wc_order, WC_Order::class ) ) { + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); + return $this->handle_payment_failure( null, new GatewayGenericException( new Exception( 'WC order was not found.' ) ) ); } + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; + WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); + if($card_payment_token_for_free_trial) { + $tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() ); + foreach ( $tokens as $token ) { + if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) { + $wc_order->payment_complete(); + return $this->handle_payment_success( $wc_order ); + } + } + } + // phpcs:ignore WordPress.Security.NonceVerification.Missing $card_payment_token_id = wc_clean( wp_unslash( $_POST['wc-ppcp-credit-card-gateway-payment-token'] ?? '' ) ); From 29e195e7f3e0af2871b00e0c915c8e47588bff11 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 24 Jul 2024 17:00:58 +0200 Subject: [PATCH 101/357] Ensure saved card payment use default payment flow for free trial subscriptions --- modules/ppcp-button/resources/js/button.js | 5 ++++- modules/ppcp-button/src/Assets/SmartButton.php | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js index d252c4392..505fa60af 100644 --- a/modules/ppcp-button/resources/js/button.js +++ b/modules/ppcp-button/resources/js/button.js @@ -216,7 +216,10 @@ const bootstrap = () => { spinner ); if ( typeof paypal.CardFields !== 'undefined' ) { - if ( PayPalCommerceGateway.is_free_trial_cart ) { + if ( + PayPalCommerceGateway.is_free_trial_cart && + PayPalCommerceGateway.user?.has_wc_card_payment_tokens !== true + ) { creditCardRenderer = new CardFieldsFreeTrialRenderer( PayPalCommerceGateway, errorHandler, diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 4b309818e..43acaa81a 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Button\Assets; use Exception; use Psr\Log\LoggerInterface; use WC_Order; +use WC_Payment_Tokens; use WC_Product; use WC_Product_Variation; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; @@ -1293,6 +1294,7 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, 'user' => array( 'is_logged' => is_user_logged_in(), + 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens(get_current_user_id()), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), @@ -2132,4 +2134,19 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages return $location; } } + + /** + * Whether the given user has WC card payment tokens. + * + * @param int $user_id + * @return bool + */ + private function user_has_wc_card_payment_tokens(int $user_id): bool { + $tokens = WC_Payment_Tokens::get_customer_tokens( $user_id, CreditCardGateway::ID ); + if($tokens) { + return true; + } + + return false; + } } From 525b75e6a1ee50543b6b2c647d7307a11ac8c5cf Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 18:27:04 +0200 Subject: [PATCH 102/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Entangle=20code=20?= =?UTF-8?q?into=20getters=20and=20atomic=20functions?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 290 +++++++++++++----- 1 file changed, 206 insertions(+), 84 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 21b410427..28ab2e990 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,4 +1,6 @@ +/* eslint-env browser */ /* global jQuery */ + /* global ApplePaySession */ /* global PayPalCommerceGateway */ @@ -11,6 +13,25 @@ import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +/** + * Plugin-specific styling. + * + * Note that most properties of this object do not apply to the Apple Pay button. + * + * @typedef {Object} PPCPStyle + * @property {string} shape - Outline shape. + * @property {?number} height - Button height in pixel. + */ + +/** + * Style options that are defined by the Apple Pay SDK and are required to render the button. + * + * @typedef {Object} ApplePayStyle + * @property {string} type - Defines the button label. + * @property {string} color - Button color + * @property {string} lang - The locale; an empty string will apply the user-agent's language. + */ + /** * List of valid context values that the button can have. * @@ -43,6 +64,9 @@ class ApplePayButton { */ #isInitialized = false; + #wrapperId = ''; + #ppcpButtonWrapperId = ''; + /** * Context describes the button's location on the website and what details it submits. * @@ -92,9 +116,9 @@ class ApplePayButton { }; if ( this.buttonConfig.is_debug ) { - jQuery( document ).on( 'ppcp-applepay-debug', () => { + jQuery( document ).on( 'ppcp-applepay-debug', () => { this.log( this ); - } ); + } ); } } @@ -131,6 +155,127 @@ class ApplePayButton { return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); } + /** + * Returns the wrapper ID for the current button context. + * The ID varies for the MiniCart context. + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get wrapperId() { + if ( ! this.#wrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.buttonConfig.button.mini_cart_wrapper; + } else { + id = this.buttonConfig.button.wrapper; + } + + this.#wrapperId = id.replace( /^#/, '' ); + } + + return this.#wrapperId; + } + + /** + * Returns the wrapper ID for the ppcpButton + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get ppcpButtonWrapperId() { + if ( ! this.#ppcpButtonWrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.ppcpConfig.button.mini_cart_wrapper; + } else if ( CONTEXT.Blocks.includes( this.context ) ) { + id = '#express-payment-method-ppcp-gateway-paypal'; + } else { + id = this.ppcpConfig.button.wrapper; + } + + this.#ppcpButtonWrapperId = id.replace( /^#/, '' ); + } + + return this.#ppcpButtonWrapperId; + } + + /** + * Returns the context-relevant PPCP style object. + * The style for the MiniCart context can be different. + * + * The PPCP style are custom style options, that are provided by this plugin. + * + * @return {PPCPStyle} The style object. + */ + get ppcpStyle() { + if ( CONTEXT.MiniCart === this.context ) { + return this.ppcpConfig.button.mini_cart_style; + } + + return this.ppcpConfig.button.style; + } + + /** + * Returns default style options that are propagated to and rendered by the Apple Pay button. + * + * These styles are the official style options provided by the Apple Pay SDK. + * + * @return {ApplePayStyle} The style object. + */ + get buttonStyle() { + return { + type: this.buttonConfig.button.type, + lang: this.buttonConfig.button.lang, + color: this.buttonConfig.button.color, + }; + } + + /** + * Returns the HTML element that wraps the current button + * + * @return {HTMLElement|null} The wrapper element, or null. + */ + get wrapperElement() { + return document.getElementById( this.wrapperId ); + } + + /** + * Returns an array of HTMLElements that belong to the payment button. + * + * @return {HTMLElement[]} List of payment button wrapper elements. + */ + get allElements() { + const selectors = []; + + // Payment button (Pay now, smart button block) + selectors.push( `#${ this.wrapperId }` ); + + // Block Checkout: Express checkout button. + if ( CONTEXT.Blocks.includes( this.context ) ) { + selectors.push( '#express-payment-method-ppcp-applepay' ); + } + + // Classic Checkout: Apple Pay gateway. + if ( CONTEXT.Checkout === this.context ) { + selectors.push( '.wc_payment_method.payment_method_ppcp-applepay' ); + } + + this.log( 'Wrapper Elements:', selectors ); + return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => + Array.from( document.querySelectorAll( selector ) ) + ); + } + + /** + * Checks whether the main button-wrapper is present in the current DOM. + * + * @return {boolean} True, if the button context (wrapper element) is found. + */ + get isPresent() { + return this.wrapperElement instanceof HTMLElement; + } + init( config ) { if ( this.#isInitialized ) { return; @@ -146,54 +291,24 @@ class ApplePayButton { this.#isInitialized = true; this.applePayConfig = config; - const idMinicart = this.buttonConfig.button.mini_cart_wrapper; - const idButton = this.buttonConfig.button.wrapper; - if ( ! this.isEligible ) { - const hideContainers = [ - // Payment button (Pay now, smart button block) - `#${ idButton }`, - // Mini Cart button - `#${ idMinicart }`, - // Block Checkout: Express checkout button. - '#express-payment-method-ppcp-applepay', - ]; + this.hide(); + } else { + this.show(); - hideContainers.forEach( ( selector ) => { - const elements = document.querySelectorAll( selector ); + this.fetchTransactionInfo().then( () => { + const button = this.addButton(); - elements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - } ); + if ( ! button ) { + return; + } - return; - } - - // Classic Checkout: Make the Apple Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-apple-pay' ) - .forEach( ( el ) => el.remove() ); - - // Add click-handler to the button. - const setupButtonEvents = ( id ) => { - document - .getElementById( id ) - ?.addEventListener( 'click', ( evt ) => { + button.addEventListener( 'click', ( evt ) => { evt.preventDefault(); this.onButtonClick(); } ); - }; - - this.fetchTransactionInfo().then( () => { - this.addButton(); - - if ( CONTEXT.MiniCart === this.context ) { - setupButtonEvents( idMinicart ); - } else { - setupButtonEvents( idButton ); - } - } ); + } ); + } } reinit() { @@ -205,40 +320,43 @@ class ApplePayButton { this.init( this.applePayConfig ); } + /** + * Hides all wrappers that belong to this ApplePayButton instance. + */ + hide() { + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + } + + /** + * Ensures all wrapper elements of this ApplePayButton instance are visible. + */ + show() { + if ( ! this.isPresent ) { + this.log( 'Cannot show button, wrapper is not present' ); + return; + } + + // Classic Checkout: Make the Apple Pay gateway visible after page load. + if ( CONTEXT.Checkout === this.context ) { + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); + } + + this.allElements.forEach( ( element ) => { + element.style.display = ''; + } ); + } + async fetchTransactionInfo() { this.transactionInfo = await this.contextHandler.transactionInfo(); } - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - const config = {}; - - if ( CONTEXT.MiniCart === this.context ) { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - } else { - config.wrapper = this.buttonConfig.button.wrapper; - config.ppcpStyle = this.ppcpConfig.button.style; - config.buttonStyle = this.buttonConfig.button.style; - config.ppcpButtonWrapper = this.ppcpConfig.button.wrapper; - } - - // Block editor configuration. - if ( CONTEXT.Blocks.includes( this.context ) ) { - config.ppcpButtonWrapper = - '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); - const wrapperId = '#' + wrapper; + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapperId = `#${ this.wrapperId }`; if ( wrapperId === ppcpButtonWrapper ) { throw new Error( @@ -295,23 +413,23 @@ class ApplePayButton { /** * Adds an Apple Pay purchase button. + * + * @return {HTMLElement|null} The newly created `` element. Null on failure. */ addButton() { this.log( 'addButton' ); - const { wrapper, ppcpStyle } = this.contextConfig(); - - const appleContainer = document.getElementById( wrapper ); - const type = this.buttonConfig.button.type; - const language = this.buttonConfig.button.lang; - const color = this.buttonConfig.button.color; - const id = 'apple-' + wrapper; + const appleContainer = document.getElementById( this.wrapperId ); + const style = this.buttonStyle; + const id = 'apple-' + this.wrapperId; if ( ! appleContainer ) { - return; + return null; } - appleContainer.innerHTML = ``; + const ppcpStyle = this.ppcpStyle; + + appleContainer.innerHTML = ``; appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { @@ -321,6 +439,8 @@ class ApplePayButton { ); appleContainer.style.height = `${ ppcpStyle.height }px`; } + + return appleContainer.querySelector( 'apple-pay-button' ); } //------------------------ @@ -345,6 +465,7 @@ class ApplePayButton { PayPalCommerceGateway.labels.error.generic, document.querySelector( '.woocommerce-notices-wrapper' ) ); + try { const formData = new FormData( document.querySelector( checkoutFormSelector ) @@ -366,6 +487,7 @@ class ApplePayButton { PayPalCommerceGateway.ajax.validate_checkout.nonce ) : null; + if ( formValidator ) { try { const errors = await formValidator.validate( @@ -898,15 +1020,15 @@ class ApplePayButton { restart: () => new Promise( ( resolve, reject ) => { - approveFailed = true; - resolve(); + approveFailed = true; + resolve(); } ), order: { get: () => new Promise( ( resolve, reject ) => { - resolve( null ); + resolve( null ); } ), }, From 4a0e410f52487afb31d76816e1a96af2565a153d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 18:55:22 +0200 Subject: [PATCH 103/357] =?UTF-8?q?=F0=9F=94=A7=20Add=20missing=20config?= =?UTF-8?q?=20to=20eslintrc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - env.browser: Linter recognizes browser elements, like `HTMLElement` - globals.jQuery: Library is present on all pages --- .eslintrc | 6 +++++- modules/ppcp-applepay/resources/js/ApplepayButton.js | 3 --- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/.eslintrc b/.eslintrc index 7a97d815d..947e3acbe 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,7 +1,11 @@ { "extends": [ "plugin:@wordpress/eslint-plugin/recommended" ], + "env": { + "browser": true + }, "globals": { - "wc": true + "wc": true, + "jQuery": "readonly" }, "rules": { "no-console": ["error", { "allow": ["warn", "error"] }] diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 28ab2e990..3c9f0acf7 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -1,6 +1,3 @@ -/* eslint-env browser */ -/* global jQuery */ - /* global ApplePaySession */ /* global PayPalCommerceGateway */ From 5df93b2dd48d5f90c6e7d61bb778eda4182ec359 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 19:42:37 +0200 Subject: [PATCH 104/357] =?UTF-8?q?=F0=9F=92=AC=20Improve=20the=20gateway?= =?UTF-8?q?=20description=20for=20admins?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/src/ApplePayGateway.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/src/ApplePayGateway.php b/modules/ppcp-applepay/src/ApplePayGateway.php index 8b46dda64..1cccda0f2 100644 --- a/modules/ppcp-applepay/src/ApplePayGateway.php +++ b/modules/ppcp-applepay/src/ApplePayGateway.php @@ -96,7 +96,7 @@ class ApplePayGateway extends WC_Payment_Gateway { $this->id = self::ID; $this->method_title = __( 'Apple Pay (via PayPal) ', 'woocommerce-paypal-payments' ); - $this->method_description = __( 'The separate payment gateway with the Apple Pay button. If disabled, the button is included in the PayPal gateway.', 'woocommerce-paypal-payments' ); + $this->method_description = __( 'Display Apple Pay as a standalone payment option instead of bundling it with PayPal.', 'woocommerce-paypal-payments' ); $this->title = $this->get_option( 'title', __( 'Apple Pay', 'woocommerce-paypal-payments' ) ); $this->description = $this->get_option( 'description', '' ); From f1c5b70877f79880c17356ecaa6f3a81a293a599 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:02:27 +0200 Subject: [PATCH 105/357] =?UTF-8?q?=E2=9C=A8=20Extract=20payment=20button?= =?UTF-8?q?=20to=20own=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 3c9f0acf7..024782a1d 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -43,7 +43,10 @@ const CONTEXT = { BlockCart: 'cart-block', BlockCheckout: 'checkout-block', Preview: 'preview', + // Block editor contexts. Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], }; /** @@ -152,6 +155,22 @@ class ApplePayButton { return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); } + /** + * Determines if the current payment button should be rendered as a stand-alone gateway. + * The return value `false` usually means, that the payment button is bundled with all available + * payment buttons. + * + * The decision depends on the button context (placement) and the plugin settings. + * + * @return {boolean} True, if the current button represents a stand-alone gateway. + */ + get isSeparateGateway() { + return ( + this.buttonConfig.is_wc_gateway_enabled && + CONTEXT.Gateways.includes( this.context ) + ); + } + /** * Returns the wrapper ID for the current button context. * The ID varies for the MiniCart context. @@ -164,6 +183,8 @@ class ApplePayButton { if ( CONTEXT.MiniCart === this.context ) { id = this.buttonConfig.button.mini_cart_wrapper; + } else if ( this.isSeparateGateway ) { + id = 'ppc-button-ppcp-applepay'; } else { id = this.buttonConfig.button.wrapper; } From cffaa7a92f7a14462753536cba8889cd58c145bd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:03:23 +0200 Subject: [PATCH 106/357] =?UTF-8?q?=F0=9F=92=84=20Fix=20button=20size=20in?= =?UTF-8?q?=20stand-alone=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 024782a1d..4d135bce5 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -437,28 +437,32 @@ class ApplePayButton { addButton() { this.log( 'addButton' ); - const appleContainer = document.getElementById( this.wrapperId ); + const wrapper = this.wrapperElement; const style = this.buttonStyle; const id = 'apple-' + this.wrapperId; - if ( ! appleContainer ) { + if ( ! wrapper ) { return null; } const ppcpStyle = this.ppcpStyle; - appleContainer.innerHTML = ``; - appleContainer.classList.add( 'ppcp-button-' + ppcpStyle.shape ); + wrapper.innerHTML = ``; + wrapper.classList.add( + `ppcp-button-${ ppcpStyle.shape }`, + 'ppcp-button-apm', + 'ppcp-button-applepay' + ); if ( ppcpStyle.height ) { - appleContainer.style.setProperty( + wrapper.style.setProperty( '--apple-pay-button-height', `${ ppcpStyle.height }px` ); - appleContainer.style.height = `${ ppcpStyle.height }px`; + wrapper.style.height = `${ ppcpStyle.height }px`; } - return appleContainer.querySelector( 'apple-pay-button' ); + return wrapper.querySelector( 'apple-pay-button' ); } //------------------------ From 354a9ff175ce72105034612af2bee7aa917db618 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:13:14 +0200 Subject: [PATCH 107/357] =?UTF-8?q?=F0=9F=92=A1=20Better=20debug=20logs=20?= =?UTF-8?q?for=20show/hide=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 4d135bce5..68edf2e6a 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -312,6 +312,12 @@ class ApplePayButton { if ( ! this.isEligible ) { this.hide(); } else { + // Bail if the button wrapper is not present; handles mini-cart logic on checkout page. + if ( ! this.isPresent ) { + this.log( 'Abort init (no wrapper found)' ); + return; + } + this.show(); this.fetchTransactionInfo().then( () => { @@ -342,6 +348,7 @@ class ApplePayButton { * Hides all wrappers that belong to this ApplePayButton instance. */ hide() { + this.log( 'Hide button' ); this.allElements.forEach( ( element ) => { element.style.display = 'none'; } ); @@ -351,8 +358,9 @@ class ApplePayButton { * Ensures all wrapper elements of this ApplePayButton instance are visible. */ show() { + this.log( 'Show button' ); if ( ! this.isPresent ) { - this.log( 'Cannot show button, wrapper is not present' ); + this.log( '!! Cannot show button, wrapper is not present' ); return; } From a4d3af848c378ae296e2da950e42433227a7f535 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:13:30 +0200 Subject: [PATCH 108/357] =?UTF-8?q?=F0=9F=A9=B9=20Hide=20gateway=20on=20Pa?= =?UTF-8?q?yNow=20page=20when=20ineligible?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/js/ApplepayButton.js | 12 +++++------- modules/ppcp-applepay/src/Assets/ApplePayButton.php | 1 + 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 68edf2e6a..227f1533b 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -275,7 +275,7 @@ class ApplePayButton { } // Classic Checkout: Apple Pay gateway. - if ( CONTEXT.Checkout === this.context ) { + if ( CONTEXT.Gateways.includes( this.context ) ) { selectors.push( '.wc_payment_method.payment_method_ppcp-applepay' ); } @@ -364,12 +364,10 @@ class ApplePayButton { return; } - // Classic Checkout: Make the Apple Pay gateway visible after page load. - if ( CONTEXT.Checkout === this.context ) { - document - .querySelectorAll( 'style#ppcp-hide-apple-pay' ) - .forEach( ( el ) => el.remove() ); - } + // Classic Checkout/PayNow: Make the Apple Pay gateway visible after page load. + document + .querySelectorAll( 'style#ppcp-hide-apple-pay' ) + .forEach( ( el ) => el.remove() ); this.allElements.forEach( ( element ) => { element.style.display = ''; diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php index 22adce359..bfcc88133 100644 --- a/modules/ppcp-applepay/src/Assets/ApplePayButton.php +++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php @@ -966,6 +966,7 @@ class ApplePayButton implements ButtonInterface { $render_placeholder, function () { $this->applepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); From b9b13d7b05718e9cc55ba76618e6b4a8d2f64a8e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:42:57 +0200 Subject: [PATCH 109/357] =?UTF-8?q?=E2=9A=B0=EF=B8=8F=20Remove=20unnecessa?= =?UTF-8?q?ry=20styles=20again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/css/styles.scss | 5 ----- 1 file changed, 5 deletions(-) diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index 26c43b63e..3818b8db5 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -52,8 +52,3 @@ } } } - -// Initially hide the APM button until it's explicitly activated. -#ppc-button-ppcp-applepay { - display: none; -} From 75f4a6f94a154fdc66fbc5ba586c237c92e235ac Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 24 Jul 2024 20:47:59 +0200 Subject: [PATCH 110/357] =?UTF-8?q?=F0=9F=8E=A8=20Fully=20disable=20debug?= =?UTF-8?q?=20logic=20via=20WP=5FDEBUG?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index 227f1533b..ce998f04c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -104,18 +104,20 @@ class ApplePayButton { this.refreshContextData(); + this.log = () => {}; + // Debug helpers - document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; - document.ppcpApplepayButtons[ this.context ] = this; - - this.log = function () { - if ( ! this.buttonConfig.is_debug ) { - return; - } - console.log( `[ApplePayButton | ${ this.context }]`, ...arguments ); - }; - if ( this.buttonConfig.is_debug ) { + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[ this.context ] = this; + + this.log = function () { + console.log( + `[ApplePayButton | ${ this.context }]`, + ...arguments + ); + }; + jQuery( document ).on( 'ppcp-applepay-debug', () => { this.log( this ); } ); From 8535601810a7605b9f1721d4ac03110903510cee Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 25 Jul 2024 00:18:15 +0200 Subject: [PATCH 111/357] AXO: Add a notice informing about potentially conflicting plugins being active --- modules/ppcp-axo/extensions.php | 9 ++++- modules/ppcp-axo/services.php | 7 ++++ .../src/Helper/SettingsNoticeGenerator.php | 38 +++++++++++++++++++ .../ppcp-wc-gateway/resources/css/common.scss | 5 +++ 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5c8f694aa..3bee1d391 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -116,7 +116,14 @@ return array( ), 'axo_main_notice' => array( 'heading' => '', - 'html' => $container->get( 'axo.shipping-config-notice' ) . $container->get( 'axo.checkout-config-notice' ), + 'html' => implode( + '', + array( + $container->get( 'axo.shipping-config-notice' ), + $container->get( 'axo.checkout-config-notice' ), + $container->get( 'axo.incompatible-plugins-notice' ), + ) + ), 'type' => 'ppcp-html', 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index e3a45bb05..31e448bfd 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -178,6 +178,13 @@ return array( return $settings_notice_generator->generate_shipping_notice(); }, + 'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + return $settings_notice_generator->generate_incompatible_plugins_notice(); + }, + 'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string { $settings = $container->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php index 4f835d549..f375e2943 100644 --- a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -89,4 +89,42 @@ class SettingsNoticeGenerator { return $notice_content ? '

' . $notice_content . '

' : ''; } + + /** + * Generates the incompatible plugins notice. + * + * @return string + */ + public function generate_incompatible_plugins_notice(): string { + $incompatible_plugins = array( + 'Elementor' => did_action( 'elementor/loaded' ), + 'CheckoutWC' => defined( 'CFW_NAME' ), + ); + + $active_plugins_list = array_filter( $incompatible_plugins ); + + if ( empty( $active_plugins_list ) ) { + return ''; + } + + $incompatible_plugin_items = array_map( + function ( $plugin ) { + return "
  • {$plugin}
  • "; + }, + array_keys( $active_plugins_list ) + ); + + $plugins_settings_link = esc_url( admin_url( 'plugins.php' ) ); + $notice_content = sprintf( + /* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */ + __( + 'Note: The accelerated guest buyer experience provided by Fastlane may not be fully compatible with some of the following active plugins:
      %2$s
    ', + 'woocommerce-paypal-payments' + ), + $plugins_settings_link, + implode( '', $incompatible_plugin_items ) + ); + + return '

    ' . $notice_content . '

    '; + } } diff --git a/modules/ppcp-wc-gateway/resources/css/common.scss b/modules/ppcp-wc-gateway/resources/css/common.scss index b694547c8..ac4bcab32 100644 --- a/modules/ppcp-wc-gateway/resources/css/common.scss +++ b/modules/ppcp-wc-gateway/resources/css/common.scss @@ -133,6 +133,11 @@ $background-ident-color: #fbfbfb; } } + .ppcp-notice-list { + list-style-type: disc; + padding-left: 20px; + } + th, td { border-top: 1px solid $border-color; } From 40c81683d926630290b4c595f54c111446ef24e2 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Thu, 25 Jul 2024 14:08:45 +0400 Subject: [PATCH 112/357] Add DHL DE plugin compatibility --- modules/ppcp-compat/services.php | 3 + modules/ppcp-order-tracking/services.php | 6 + .../Integration/DhlShipmentIntegration.php | 114 ++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php index d27091162..6b48e0e3c 100644 --- a/modules/ppcp-compat/services.php +++ b/modules/ppcp-compat/services.php @@ -77,6 +77,9 @@ return array( 'compat.ywot.is_supported_plugin_version_active' => function (): bool { return function_exists( 'yith_ywot_init' ); }, + 'compat.dhl.is_supported_plugin_version_active' => function (): bool { + return function_exists( 'PR_DHL' ); + }, 'compat.shipstation.is_supported_plugin_version_active' => function (): bool { return function_exists( 'woocommerce_shipstation_init' ); }, diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php index 7eda7c68b..ad7aaa3ea 100644 --- a/modules/ppcp-order-tracking/services.php +++ b/modules/ppcp-order-tracking/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\OrderTracking; +use WooCommerce\PayPalCommerce\OrderTracking\Integration\DhlShipmentIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\GermanizedShipmentIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipmentTrackingIntegration; use WooCommerce\PayPalCommerce\OrderTracking\Integration\ShipStationIntegration; @@ -118,6 +119,7 @@ return array( $is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' ); $is_wc_shipment_active = $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' ); $is_yith_ywot_active = $container->get( 'compat.ywot.is_supported_plugin_version_active' ); + $is_dhl_de_active = $container->get( 'compat.dhl.is_supported_plugin_version_active' ); $is_ship_station_active = $container->get( 'compat.shipstation.is_supported_plugin_version_active' ); $is_wc_shipping_tax_active = $container->get( 'compat.wc_shipping_tax.is_supported_plugin_version_active' ); @@ -135,6 +137,10 @@ return array( $integrations[] = new YithShipmentIntegration( $shipment_factory, $logger, $endpoint ); } + if ( $is_dhl_de_active ) { + $integrations[] = new DhlShipmentIntegration( $shipment_factory, $logger, $endpoint ); + } + if ( $is_ship_station_active ) { $integrations[] = new ShipStationIntegration( $shipment_factory, $logger, $endpoint ); } diff --git a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php new file mode 100644 index 000000000..1e8961fcb --- /dev/null +++ b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php @@ -0,0 +1,114 @@ +shipment_factory = $shipment_factory; + $this->logger = $logger; + $this->endpoint = $endpoint; + } + + /** + * {@inheritDoc} + */ + public function integrate(): void { + add_action( + 'pr_save_dhl_label_tracking', + function( int $order_id, array $tracking_details ) { + try { + $wc_order = wc_get_order( $order_id ); + if ( ! is_a( $wc_order, WC_Order::class ) ) { + return; + } + + $foo = $tracking_details; + + $paypal_order = ppcp_get_paypal_order( $wc_order ); + $capture_id = $this->get_paypal_order_transaction_id( $paypal_order ); + $tracking_number = $tracking_details['tracking_number']; + $carrier = $tracking_details['carrier']; + + if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $capture_id ) { + return; + } + + $ppcp_shipment = $this->shipment_factory->create_shipment( + $order_id, + $capture_id, + $tracking_number, + 'SHIPPED', + 'DE_DHL', + $carrier, + array() + ); + + $tracking_information = $this->endpoint->get_tracking_information( $order_id, $tracking_number ); + + $tracking_information + ? $this->endpoint->update_tracking_information( $ppcp_shipment, $order_id ) + : $this->endpoint->add_tracking_information( $ppcp_shipment, $order_id ); + + } catch ( Exception $exception ) { + return; + } + }, + 600, + 2 + ); + } +} From c4232e3957c56f11a348f7839a83b47e817a29b2 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Thu, 25 Jul 2024 12:08:59 +0200 Subject: [PATCH 113/357] Add card payment for guest free trial (WIP) --- .../Renderer/CardFieldsFreeTrialRenderer.js | 9 ++- .../resources/js/Configuration.js | 59 +++++++++++++++++++ .../src/Gateway/CreditCardGateway.php | 16 +++++ 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js index c813e7fc7..d2700aadb 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -48,9 +48,14 @@ class CardFieldsFreeTrialRenderer { errorHandler ); - const cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + let cardFields = paypal.CardFields( + configuration.addPaymentMethodConfiguration() ); + if ( this.defaultConfig.user.is_logged ) { + cardFields = paypal.CardFields( + configuration.cardFieldsConfiguration() + ); + } if ( cardFields.isEligible() ) { const renderCardFields = new RenderCardFields( cardFields ); diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index e779dbdbc..42ad02a77 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -194,6 +194,65 @@ class Configuration { }, }; } + + addPaymentMethodConfiguration() { + return { + createVaultSetupToken: async () => { + const response = await fetch( + this.ppcp_add_payment_method.ajax.create_setup_token + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_setup_token.nonce, + payment_method: getCurrentPaymentMethod(), + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + console.error( result ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + this.ppcp_add_payment_method.ajax + .create_payment_token_for_guest.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: this.ppcp_add_payment_method.ajax + .create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + document.querySelector( '#place_order' ).click(); + return; + } + + console.error( result ); + }, + onError: ( error ) => { + console.error( error ); + }, + }; + } } export default Configuration; diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 319510b68..393328660 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -434,6 +434,22 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { ); } + $guest_card_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null; + WC()->session->get( 'ppcp_guest_payment_for_free_trial', null ); + if($guest_card_payment_for_free_trial) { + $customer_id = $guest_card_payment_for_free_trial->customer->id ?? ''; + if ( $customer_id ) { + update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id ); + } + + if ( isset( $guest_card_payment_for_free_trial->payment_source->card ) ) { + $this->wc_payment_tokens->create_payment_token_card( $wc_order->get_customer_id(), $guest_card_payment_for_free_trial ); + + $wc_order->payment_complete(); + return $this->handle_payment_success( $wc_order ); + } + } + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); if($card_payment_token_for_free_trial) { From 64fae60da44ee071c27c14733acfdbb5f5b262a3 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 25 Jul 2024 15:48:35 +0200 Subject: [PATCH 114/357] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Undo=20a=20type-hi?= =?UTF-8?q?nt=20that=20causes=20CI=20failure?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Our Stub object has a void return value; CI fails with this type-hint, as the Stub is not compatible with the actual object. --- modules/ppcp-wc-gateway/src/Settings/Settings.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-wc-gateway/src/Settings/Settings.php b/modules/ppcp-wc-gateway/src/Settings/Settings.php index e91256653..e083a91d1 100644 --- a/modules/ppcp-wc-gateway/src/Settings/Settings.php +++ b/modules/ppcp-wc-gateway/src/Settings/Settings.php @@ -117,7 +117,7 @@ class Settings implements ContainerInterface { /** * Stores the settings to the database. */ - public function persist() : bool { + public function persist() { return update_option( self::KEY, $this->settings ); } From a14276bf528f89fe8ddc7bbf2530b7b365986ee9 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Thu, 25 Jul 2024 16:50:00 +0200 Subject: [PATCH 115/357] =?UTF-8?q?=F0=9F=92=9A=20CI:=20Fix=20all=20report?= =?UTF-8?q?ed=20phpcs=20issues?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/Assets/DataToAppleButtonScripts.php | 40 +++++++++++-------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php index 004500355..52663a4f0 100644 --- a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php +++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php @@ -73,7 +73,7 @@ class DataToAppleButtonScripts { * * @return array */ - private function get_apple_pay_data( array $product = [] ) : array { + private function get_apple_pay_data( array $product = array() ) : array { // true: Use Apple Pay as distinct gateway. // false: integrate it with the smart buttons. $available_gateways = WC()->payment_gateways->get_available_payment_gateways(); @@ -128,6 +128,8 @@ class DataToAppleButtonScripts { /** * Check if the product needs shipping * + * @param WC_Product $product Product to check. + * * @return bool */ protected function check_if_need_shipping( WC_Product $product ) : bool { @@ -167,13 +169,15 @@ class DataToAppleButtonScripts { $product_price = $product->get_price(); $product_stock = $product->get_stock_status(); - return $this->get_apple_pay_data( array( - 'needShipping' => $product_need_shipping, - 'id' => $product_id, - 'price' => $product_price, - 'isVariation' => $is_variation, - 'stock' => $product_stock, - ) ); + return $this->get_apple_pay_data( + array( + 'needShipping' => $product_need_shipping, + 'id' => $product_id, + 'price' => $product_price, + 'isVariation' => $is_variation, + 'stock' => $product_stock, + ) + ); } /** @@ -187,10 +191,12 @@ class DataToAppleButtonScripts { return array(); } - return $this->get_apple_pay_data( array( - 'needShipping' => $cart->needs_shipping(), - 'subtotal' => $cart->get_subtotal(), - ) ); + return $this->get_apple_pay_data( + array( + 'needShipping' => $cart->needs_shipping(), + 'subtotal' => $cart->get_subtotal(), + ) + ); } /** @@ -201,10 +207,12 @@ class DataToAppleButtonScripts { * @return array */ protected function data_for_admin_page() : array { - $data = $this->get_apple_pay_data( array( - 'needShipping' => false, - 'subtotal' => 0, - ) ); + $data = $this->get_apple_pay_data( + array( + 'needShipping' => false, + 'subtotal' => 0, + ) + ); $data['is_admin'] = true; From 2da1fa0323062b0f9f22a3b2e4be2d704a9e7c41 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Fri, 26 Jul 2024 16:20:08 +0400 Subject: [PATCH 116/357] Automatically update PayPal Package Tracking Status metabox --- .../resources/js/tracking-compat.js | 29 +++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js index 589e747b5..a907ddc8f 100644 --- a/modules/ppcp-compat/resources/js/tracking-compat.js +++ b/modules/ppcp-compat/resources/js/tracking-compat.js @@ -15,6 +15,8 @@ document.addEventListener( 'DOMContentLoaded', () => { ); const wcShipmentTaxBuyLabelButtonSelector = '.components-modal__screen-overlay .label-purchase-modal__sidebar .purchase-section button.components-button'; + const dhlGenerateLabelButton = + document.getElementById( 'dhl-label-button' ); const toggleLoaderVisibility = function () { const loader = document.querySelector( '.ppcp-tracking-loader' ); @@ -44,6 +46,20 @@ document.addEventListener( 'DOMContentLoaded', () => { } }; + const waitForButtonRemoval = function ( button ) { + if ( document.body.contains( button ) ) { + setTimeout( () => waitForButtonRemoval( button ), 100 ); + } else { + jQuery( orderTrackingContainerSelector ).load( + loadLocation, + '', + function () { + toggleLoaderVisibility(); + } + ); + } + }; + if ( gzdSyncEnabled && typeof gzdSaveButton !== 'undefined' && @@ -66,10 +82,19 @@ document.addEventListener( 'DOMContentLoaded', () => { } ); } + if ( + typeof dhlGenerateLabelButton !== 'undefined' && + dhlGenerateLabelButton != null + ) { + dhlGenerateLabelButton.addEventListener( 'click', function ( event ) { + toggleLoaderVisibility(); + waitForButtonRemoval( dhlGenerateLabelButton ); + } ); + } + if ( wcShippingTaxSyncEnabled && - typeof wcShippingTaxSyncEnabled !== 'undefined' && - wcShippingTaxSyncEnabled != null + typeof wcShippingTaxSyncEnabled !== 'undefined' ) { document.addEventListener( 'click', function ( event ) { const wcShipmentTaxBuyLabelButton = event.target.closest( From 9f18f2e899345fc0e2b5248409a7df34b16130bf Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 26 Jul 2024 15:39:25 +0200 Subject: [PATCH 117/357] Refactor: extract logic to corresponding modules --- .../ActionHandler/CheckoutActionHandler.js | 55 - .../ContextBootstrap/CheckoutBootstap.js | 3 +- .../Renderer/CardFieldsFreeTrialRenderer.js | 26 +- .../js/modules/Renderer/CardFieldsRenderer.js | 81 +- modules/ppcp-card-fields/package.json | 31 + .../resources/js}/CardFieldsHelper.js | 0 .../ppcp-card-fields/resources/js/Render.js | 47 + modules/ppcp-card-fields/webpack.config.js | 38 + modules/ppcp-card-fields/yarn.lock | 2194 +++++++++++++++++ .../resources/js/Configuration.js | 442 ++-- .../resources/js/RenderCardFields.js | 55 - .../resources/js/add-payment-method.js | 25 +- package.json | 3 + 13 files changed, 2549 insertions(+), 451 deletions(-) create mode 100644 modules/ppcp-card-fields/package.json rename modules/{ppcp-button/resources/js/modules/Helper => ppcp-card-fields/resources/js}/CardFieldsHelper.js (100%) create mode 100644 modules/ppcp-card-fields/resources/js/Render.js create mode 100644 modules/ppcp-card-fields/webpack.config.js create mode 100644 modules/ppcp-card-fields/yarn.lock delete mode 100644 modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js index a93a24212..4c2fa9b2d 100644 --- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js +++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js @@ -173,61 +173,6 @@ class CheckoutActionHandler { }, }; } - - addPaymentMethodConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.config.ajax.create_setup_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.config.ajax.create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - console.error( result ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.config.ajax.create_payment_token_for_guest.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.config.ajax - .create_payment_token_for_guest.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { - document.querySelector( '#place_order' ).click(); - return; - } - - console.error( result ); - }, - onError: ( error ) => { - console.error( error ); - }, - }; - } } export default CheckoutActionHandler; diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 33d1ecfd3..916758eb4 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -7,6 +7,7 @@ import { PaymentMethods, } from '../Helper/CheckoutMethodState'; import BootstrapHelper from '../Helper/BootstrapHelper'; +import { addPaymentMethodConfiguration } from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; class CheckoutBootstap { constructor( gateway, renderer, spinner, errorHandler ) { @@ -160,7 +161,7 @@ class CheckoutBootstap { PayPalCommerceGateway.vault_v3_enabled ) { this.renderer.render( - actionHandler.addPaymentMethodConfiguration(), + addPaymentMethodConfiguration( PayPalCommerceGateway ), {}, actionHandler.configuration() ); diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js index d2700aadb..b97569762 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsFreeTrialRenderer.js @@ -1,7 +1,9 @@ import { show } from '../Helper/Hiding'; -import ErrorHandler from '../ErrorHandler'; -import RenderCardFields from '../../../../../ppcp-save-payment-methods/resources/js/RenderCardFields'; -import Configuration from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; +import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render'; +import { + addPaymentMethodConfiguration, + cardFieldsConfiguration, +} from '../../../../../ppcp-save-payment-methods/resources/js/Configuration'; class CardFieldsFreeTrialRenderer { constructor( defaultConfig, errorHandler, spinner ) { @@ -37,29 +39,19 @@ class CardFieldsFreeTrialRenderer { hideDccGateway.parentNode.removeChild( hideDccGateway ); } - const errorHandler = new ErrorHandler( - this.defaultConfig.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) - ); - errorHandler.clear(); - - const configuration = new Configuration( - this.defaultConfig, - errorHandler - ); + this.errorHandler.clear(); let cardFields = paypal.CardFields( - configuration.addPaymentMethodConfiguration() + addPaymentMethodConfiguration( this.defaultConfig ) ); if ( this.defaultConfig.user.is_logged ) { cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + cardFieldsConfiguration( this.defaultConfig, this.errorHandler ) ); } if ( cardFields.isEligible() ) { - const renderCardFields = new RenderCardFields( cardFields ); - renderCardFields.render(); + renderFields( cardFields ); } gateWayBox.style.display = oldDisplayStyle; diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js index b3e70011f..4a0ec09f2 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/CardFieldsRenderer.js @@ -1,5 +1,5 @@ import { show } from '../Helper/Hiding'; -import { cardFieldStyles } from '../Helper/CardFieldsHelper'; +import { renderFields } from '../../../../../ppcp-card-fields/resources/js/Render'; class CardFieldsRenderer { constructor( @@ -45,7 +45,7 @@ class CardFieldsRenderer { hideDccGateway.parentNode.removeChild( hideDccGateway ); } - const cardField = paypal.CardFields( { + const cardFields = paypal.CardFields( { createOrder: contextConfig.createOrder, onApprove( data ) { return contextConfig.onApprove( data ); @@ -56,79 +56,8 @@ class CardFieldsRenderer { }, } ); - if ( cardField.isEligible() ) { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( nameField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - nameField.getAttribute( 'placeholder' ); - } - cardField - .NameField( fieldOptions ) - .render( nameField.parentNode ); - nameField.remove(); - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( numberField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - numberField.getAttribute( 'placeholder' ); - } - cardField - .NumberField( fieldOptions ) - .render( numberField.parentNode ); - numberField.remove(); - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( expiryField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - expiryField.getAttribute( 'placeholder' ); - } - cardField - .ExpiryField( fieldOptions ) - .render( expiryField.parentNode ); - expiryField.remove(); - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - const fieldOptions = { - style: { input: styles }, - }; - if ( cvvField.getAttribute( 'placeholder' ) ) { - fieldOptions.placeholder = - cvvField.getAttribute( 'placeholder' ); - } - cardField - .CVVField( fieldOptions ) - .render( cvvField.parentNode ); - cvvField.remove(); - } - + if ( cardFields.isEligible() ) { + renderFields( cardFields ); document.dispatchEvent( new CustomEvent( 'hosted_fields_loaded' ) ); } @@ -169,7 +98,7 @@ class CardFieldsRenderer { return; } - cardField.submit().catch( ( error ) => { + cardFields.submit().catch( ( error ) => { this.spinner.unblock(); console.error( error ); this.errorHandler.message( diff --git a/modules/ppcp-card-fields/package.json b/modules/ppcp-card-fields/package.json new file mode 100644 index 000000000..20ea98a53 --- /dev/null +++ b/modules/ppcp-card-fields/package.json @@ -0,0 +1,31 @@ +{ + "name": "ppcp-card-fields", + "version": "1.0.0", + "license": "GPL-3.0-or-later", + "browserslist": [ + "> 0.5%", + "Safari >= 8", + "Chrome >= 41", + "Firefox >= 43", + "Edge >= 14" + ], + "dependencies": { + "core-js": "^3.25.0" + }, + "devDependencies": { + "@babel/core": "^7.19", + "@babel/preset-env": "^7.19", + "babel-loader": "^8.2", + "cross-env": "^7.0.3", + "file-loader": "^6.2.0", + "sass": "^1.42.1", + "sass-loader": "^12.1.0", + "webpack": "^5.76", + "webpack-cli": "^4.10" + }, + "scripts": { + "build": "cross-env BABEL_ENV=default NODE_ENV=production webpack", + "watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch", + "dev": "cross-env BABEL_ENV=default webpack --watch" + } +} diff --git a/modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js b/modules/ppcp-card-fields/resources/js/CardFieldsHelper.js similarity index 100% rename from modules/ppcp-button/resources/js/modules/Helper/CardFieldsHelper.js rename to modules/ppcp-card-fields/resources/js/CardFieldsHelper.js diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js new file mode 100644 index 000000000..8b46f17d7 --- /dev/null +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -0,0 +1,47 @@ +import { cardFieldStyles } from './CardFieldsHelper'; + +export function renderFields( cardFields ) { + const nameField = document.getElementById( + 'ppcp-credit-card-gateway-card-name' + ); + if ( nameField ) { + const styles = cardFieldStyles( nameField ); + cardFields + .NameField( { style: { input: styles } } ) + .render( nameField.parentNode ); + nameField.hidden = true; + } + + const numberField = document.getElementById( + 'ppcp-credit-card-gateway-card-number' + ); + if ( numberField ) { + const styles = cardFieldStyles( numberField ); + cardFields + .NumberField( { style: { input: styles } } ) + .render( numberField.parentNode ); + numberField.hidden = true; + } + + const expiryField = document.getElementById( + 'ppcp-credit-card-gateway-card-expiry' + ); + if ( expiryField ) { + const styles = cardFieldStyles( expiryField ); + cardFields + .ExpiryField( { style: { input: styles } } ) + .render( expiryField.parentNode ); + expiryField.hidden = true; + } + + const cvvField = document.getElementById( + 'ppcp-credit-card-gateway-card-cvc' + ); + if ( cvvField ) { + const styles = cardFieldStyles( cvvField ); + cardFields + .CVVField( { style: { input: styles } } ) + .render( cvvField.parentNode ); + cvvField.hidden = true; + } +} diff --git a/modules/ppcp-card-fields/webpack.config.js b/modules/ppcp-card-fields/webpack.config.js new file mode 100644 index 000000000..a952f7de0 --- /dev/null +++ b/modules/ppcp-card-fields/webpack.config.js @@ -0,0 +1,38 @@ +const path = require( 'path' ); +const isProduction = process.env.NODE_ENV === 'production'; + +module.exports = { + devtool: isProduction ? 'source-map' : 'eval-source-map', + mode: isProduction ? 'production' : 'development', + target: 'web', + entry: { + render: path.resolve( './resources/js/Render.js' ), + helper: path.resolve( './resources/js/CardFieldsHelper.js' ), + }, + output: { + path: path.resolve( __dirname, 'assets/' ), + filename: 'js/[name].js', + }, + module: { + rules: [ + { + test: /\.js?$/, + exclude: /node_modules/, + loader: 'babel-loader', + }, + { + test: /\.scss$/, + exclude: /node_modules/, + use: [ + { + loader: 'file-loader', + options: { + name: 'css/[name].css', + }, + }, + { loader: 'sass-loader' }, + ], + }, + ], + }, +}; diff --git a/modules/ppcp-card-fields/yarn.lock b/modules/ppcp-card-fields/yarn.lock new file mode 100644 index 000000000..42b77f28b --- /dev/null +++ b/modules/ppcp-card-fields/yarn.lock @@ -0,0 +1,2194 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ampproject/remapping@^2.2.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.3.0.tgz#ed441b6fa600072520ce18b43d2c8cc8caecc7f4" + integrity sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.24" + +"@babel/code-frame@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.7.tgz#882fd9e09e8ee324e496bd040401c6f046ef4465" + integrity sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA== + dependencies: + "@babel/highlight" "^7.24.7" + picocolors "^1.0.0" + +"@babel/compat-data@^7.22.6", "@babel/compat-data@^7.24.8": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.9.tgz#53eee4e68f1c1d0282aa0eb05ddb02d033fc43a0" + integrity sha512-e701mcfApCJqMMueQI0Fb68Amflj83+dvAvHawoBpAz+GDjCIyGHzNwnefjsWJ3xiYAqqiQFoWbspGYBdb2/ng== + +"@babel/core@^7.19": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.9.tgz#dc07c9d307162c97fa9484ea997ade65841c7c82" + integrity sha512-5e3FI4Q3M3Pbr21+5xJwCv6ZT6KmGkI0vw3Tozy5ODAQFTIWe37iT8Cr7Ice2Ntb+M3iSKCEWMB1MBgKrW3whg== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.9" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-module-transforms" "^7.24.9" + "@babel/helpers" "^7.24.8" + "@babel/parser" "^7.24.8" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.9" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + +"@babel/generator@^7.24.8", "@babel/generator@^7.24.9": + version "7.24.10" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.10.tgz#a4ab681ec2a78bbb9ba22a3941195e28a81d8e76" + integrity sha512-o9HBZL1G2129luEUlG1hB4N/nlYNWHnpwlND9eOMclRqqu1YDy2sSYVCFUZwl8I1Gxh+QSRrP2vD7EpUmFVXxg== + dependencies: + "@babel/types" "^7.24.9" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" + integrity sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz#37d66feb012024f2422b762b9b2a7cfe27c7fba3" + integrity sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-compilation-targets@^7.22.6", "@babel/helper-compilation-targets@^7.24.7", "@babel/helper-compilation-targets@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.24.8.tgz#b607c3161cd9d1744977d4f97139572fe778c271" + integrity sha512-oU+UoqCHdp+nWVDkpldqIQL/i/bvAv53tRqLG/s+cOXxe66zOYLU7ar/Xs3LdmBihrUMEUhwu6dMZwbNOYDwvw== + dependencies: + "@babel/compat-data" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.24.7": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.8.tgz#47f546408d13c200c0867f9d935184eaa0851b09" + integrity sha512-4f6Oqnmyp2PP3olgUMmOwC3akxSm5aBYraQ6YDdKy7NcAMkDECHWG0DEnV6M2UAkERgIBhYt8S27rURPg7SxWA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.8" + "@babel/helper-optimise-call-expression" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + semver "^6.3.1" + +"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz#be4f435a80dc2b053c76eeb4b7d16dd22cfc89da" + integrity sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + regexpu-core "^5.3.1" + semver "^6.3.1" + +"@babel/helper-define-polyfill-provider@^0.6.1", "@babel/helper-define-polyfill-provider@^0.6.2": + version "0.6.2" + resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.2.tgz#18594f789c3594acb24cfdb4a7f7b7d2e8bd912d" + integrity sha512-LV76g+C502biUK6AyZ3LK10vDpDyCzZnhZFXkH1L75zHPj68+qc8Zfpx2th+gzwA2MzyK+1g/3EPl62yFnVttQ== + dependencies: + "@babel/helper-compilation-targets" "^7.22.6" + "@babel/helper-plugin-utils" "^7.22.5" + debug "^4.1.1" + lodash.debounce "^4.0.8" + resolve "^1.14.2" + +"@babel/helper-environment-visitor@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz#4b31ba9551d1f90781ba83491dd59cf9b269f7d9" + integrity sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz#75f1e1725742f39ac6584ee0b16d94513da38dd2" + integrity sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-hoist-variables@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz#b4ede1cde2fd89436397f30dc9376ee06b0f25ee" + integrity sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-member-expression-to-functions@^7.24.7", "@babel/helper-member-expression-to-functions@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.8.tgz#6155e079c913357d24a4c20480db7c712a5c3fb6" + integrity sha512-LABppdt+Lp/RlBxqrh4qgf1oEH/WxdzQNDJIu5gC/W1GyvPVrOBiItmmM8wan2fm4oYqFuFfkXmlGpLQhPY8CA== + dependencies: + "@babel/traverse" "^7.24.8" + "@babel/types" "^7.24.8" + +"@babel/helper-module-imports@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz#f2f980392de5b84c3328fc71d38bd81bbb83042b" + integrity sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-module-transforms@^7.24.7", "@babel/helper-module-transforms@^7.24.8", "@babel/helper-module-transforms@^7.24.9": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.24.9.tgz#e13d26306b89eea569180868e652e7f514de9d29" + integrity sha512-oYbh+rtFKj/HwBQkFlUzvcybzklmVdVV3UU+mN7n2t/q3yGHbuVdNxyFvSBO1tfvjyArpHNcWMAzsSPdyI46hw== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/helper-optimise-call-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" + integrity sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.24.7", "@babel/helper-plugin-utils@^7.24.8", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.8.tgz#94ee67e8ec0e5d44ea7baeb51e571bd26af07878" + integrity sha512-FFWx5142D8h2Mgr/iPVGH5G7w6jDn4jUSpZTyDnQO0Yn7Ks2Kuz6Pci8H6MPCoUJegd/UZQ3tAvfLCxQSnWWwg== + +"@babel/helper-remap-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz#b3f0f203628522713849d49403f1a414468be4c7" + integrity sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-wrap-function" "^7.24.7" + +"@babel/helper-replace-supers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz#f933b7eed81a1c0265740edc91491ce51250f765" + integrity sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-member-expression-to-functions" "^7.24.7" + "@babel/helper-optimise-call-expression" "^7.24.7" + +"@babel/helper-simple-access@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz#bcade8da3aec8ed16b9c4953b74e506b51b5edb3" + integrity sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-skip-transparent-expression-wrappers@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz#5f8fa83b69ed5c27adc56044f8be2b3ea96669d9" + integrity sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ== + dependencies: + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helper-split-export-declaration@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz#83949436890e07fa3d6873c61a96e3bbf692d856" + integrity sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA== + dependencies: + "@babel/types" "^7.24.7" + +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + +"@babel/helper-validator-identifier@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" + integrity sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w== + +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + +"@babel/helper-wrap-function@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" + integrity sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw== + dependencies: + "@babel/helper-function-name" "^7.24.7" + "@babel/template" "^7.24.7" + "@babel/traverse" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/helpers@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.24.8.tgz#2820d64d5d6686cca8789dd15b074cd862795873" + integrity sha512-gV2265Nkcz7weJJfvDoAEVzC1e2OTDpkGbEsebse8koXUJUXPsCMi7sRo/+SPMuMZ9MtUPnGwITTnQnU5YjyaQ== + dependencies: + "@babel/template" "^7.24.7" + "@babel/types" "^7.24.8" + +"@babel/highlight@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" + integrity sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw== + dependencies: + "@babel/helper-validator-identifier" "^7.24.7" + chalk "^2.4.2" + js-tokens "^4.0.0" + picocolors "^1.0.0" + +"@babel/parser@^7.24.7", "@babel/parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.8.tgz#58a4dbbcad7eb1d48930524a3fd93d93e9084c6f" + integrity sha512-WzfbgXOkGzZiXXCqk43kKwZjzwx4oulxZi3nq2TYL9mOjQv6kYwul9mz6ID36njuL7Xkp6nJEfok848Zj10j/w== + +"@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" + integrity sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz#468096ca44bbcbe8fcc570574e12eb1950e18107" + integrity sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz#e4eabdd5109acc399b38d7999b2ef66fc2022f89" + integrity sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.7" + +"@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz#71b21bb0286d5810e63a1538aa901c58e87375ec" + integrity sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-proposal-private-property-in-object@7.21.0-placeholder-for-preset-env.2": + version "7.21.0-placeholder-for-preset-env.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz#7844f9289546efa9febac2de4cfe358a050bd703" + integrity sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w== + +"@babel/plugin-syntax-async-generators@^7.8.4": + version "7.8.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d" + integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-class-properties@^7.12.13": + version "7.12.13" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10" + integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA== + dependencies: + "@babel/helper-plugin-utils" "^7.12.13" + +"@babel/plugin-syntax-class-static-block@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.14.5.tgz#195df89b146b4b78b3bf897fd7a257c84659d406" + integrity sha512-b+YyPmr6ldyNnM6sqYeMWE+bgJcJpO6yS4QD7ymxgH34GBPNDM/THBh8iunyvKIZztiwLH4CJZ0RxTk9emgpjw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-dynamic-import@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz#62bf98b2da3cd21d626154fc96ee5b3cb68eacb3" + integrity sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-export-namespace-from@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz#028964a9ba80dbc094c915c487ad7c4e7a66465a" + integrity sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.3" + +"@babel/plugin-syntax-import-assertions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz#2a0b406b5871a20a841240586b1300ce2088a778" + integrity sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-attributes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz#b4f9ea95a79e6912480c4b626739f86a076624ca" + integrity sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-syntax-import-meta@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-json-strings@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" + integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-logical-assignment-operators@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699" + integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9" + integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-numeric-separator@^7.10.4": + version "7.10.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97" + integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + +"@babel/plugin-syntax-object-rest-spread@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871" + integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1" + integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-optional-chaining@^7.8.3": + version "7.8.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a" + integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg== + dependencies: + "@babel/helper-plugin-utils" "^7.8.0" + +"@babel/plugin-syntax-private-property-in-object@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.5.tgz#0dc6671ec0ea22b6e94a1114f857970cd39de1ad" + integrity sha512-0wVnp9dxJ72ZUJDV27ZfbSj6iHLoytYZmh3rFcxNnvsJF3ktkzLDZPy/mA17HGsaQT3/DQsWYX1f1QGWkCoVUg== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-top-level-await@^7.14.5": + version "7.14.5" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c" + integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw== + dependencies: + "@babel/helper-plugin-utils" "^7.14.5" + +"@babel/plugin-syntax-unicode-sets-regex@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-unicode-sets-regex/-/plugin-syntax-unicode-sets-regex-7.18.6.tgz#d49a3b3e6b52e5be6740022317580234a6a47357" + integrity sha512-727YkEAPwSIQTv5im8QHz3upqp92JTWhidIC81Tdx4VJYIte/VndKf1qKrfnnhPLiPghStWfvC/iFaMCQu7Nqg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.18.6" + "@babel/helper-plugin-utils" "^7.18.6" + +"@babel/plugin-transform-arrow-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz#4f6886c11e423bd69f3ce51dbf42424a5f275514" + integrity sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-async-generator-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz#7330a5c50e05181ca52351b8fd01642000c96cfd" + integrity sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g== + dependencies: + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + "@babel/plugin-syntax-async-generators" "^7.8.4" + +"@babel/plugin-transform-async-to-generator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz#72a3af6c451d575842a7e9b5a02863414355bdcc" + integrity sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-remap-async-to-generator" "^7.24.7" + +"@babel/plugin-transform-block-scoped-functions@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz#a4251d98ea0c0f399dafe1a35801eaba455bbf1f" + integrity sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-block-scoping@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz#42063e4deb850c7bd7c55e626bf4e7ab48e6ce02" + integrity sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-class-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz#256879467b57b0b68c7ddfc5b76584f398cd6834" + integrity sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-class-static-block@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz#c82027ebb7010bc33c116d4b5044fbbf8c05484d" + integrity sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + +"@babel/plugin-transform-classes@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.8.tgz#ad23301fe5bc153ca4cf7fb572a9bc8b0b711cf7" + integrity sha512-VXy91c47uujj758ud9wx+OMgheXm4qJfyhj1P18YvlrQkNOSrwsteHk+EFS3OMGfhMhpZa0A+81eE7G4QC+3CA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-replace-supers" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz#4cab3214e80bc71fae3853238d13d097b004c707" + integrity sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/template" "^7.24.7" + +"@babel/plugin-transform-destructuring@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.8.tgz#c828e814dbe42a2718a838c2a2e16a408e055550" + integrity sha512-36e87mfY8TnRxc7yc6M9g9gOB7rKgSahqkIKwLpz4Ppk2+zC2Cy1is0uwtuSG6AE4zlTOUa+7JGz9jCJGLqQFQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-dotall-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz#5f8bf8a680f2116a7207e16288a5f974ad47a7a0" + integrity sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-duplicate-keys@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz#dd20102897c9a2324e5adfffb67ff3610359a8ee" + integrity sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-dynamic-import@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz#4d8b95e3bae2b037673091aa09cd33fecd6419f4" + integrity sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + +"@babel/plugin-transform-exponentiation-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz#b629ee22645f412024297d5245bce425c31f9b0d" + integrity sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-export-namespace-from@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz#176d52d8d8ed516aeae7013ee9556d540c53f197" + integrity sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + +"@babel/plugin-transform-for-of@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz#f25b33f72df1d8be76399e1b8f3f9d366eb5bc70" + integrity sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-function-name@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz#6d8601fbffe665c894440ab4470bc721dd9131d6" + integrity sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-json-strings@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz#f3e9c37c0a373fee86e36880d45b3664cedaf73a" + integrity sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-json-strings" "^7.8.3" + +"@babel/plugin-transform-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz#36b505c1e655151a9d7607799a9988fc5467d06c" + integrity sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-logical-assignment-operators@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz#a58fb6eda16c9dc8f9ff1c7b1ba6deb7f4694cb0" + integrity sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + +"@babel/plugin-transform-member-expression-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz#3b4454fb0e302e18ba4945ba3246acb1248315df" + integrity sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-amd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz#65090ed493c4a834976a3ca1cde776e6ccff32d7" + integrity sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-modules-commonjs@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.8.tgz#ab6421e564b717cb475d6fff70ae7f103536ea3c" + integrity sha512-WHsk9H8XxRs3JXKWFiqtQebdh9b/pTk4EgueygFzYlTKAg0Ud985mSevdNjdXdFBATSKVJGQXP1tv6aGbssLKA== + dependencies: + "@babel/helper-module-transforms" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-simple-access" "^7.24.7" + +"@babel/plugin-transform-modules-systemjs@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz#f8012316c5098f6e8dee6ecd58e2bc6f003d0ce7" + integrity sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw== + dependencies: + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + +"@babel/plugin-transform-modules-umd@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz#edd9f43ec549099620df7df24e7ba13b5c76efc8" + integrity sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A== + dependencies: + "@babel/helper-module-transforms" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz#9042e9b856bc6b3688c0c2e4060e9e10b1460923" + integrity sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-new-target@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz#31ff54c4e0555cc549d5816e4ab39241dfb6ab00" + integrity sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-nullish-coalescing-operator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz#1de4534c590af9596f53d67f52a92f12db984120" + integrity sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + +"@babel/plugin-transform-numeric-separator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz#bea62b538c80605d8a0fac9b40f48e97efa7de63" + integrity sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + +"@babel/plugin-transform-object-rest-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz#d13a2b93435aeb8a197e115221cab266ba6e55d6" + integrity sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q== + dependencies: + "@babel/helper-compilation-targets" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-object-rest-spread" "^7.8.3" + "@babel/plugin-transform-parameters" "^7.24.7" + +"@babel/plugin-transform-object-super@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz#66eeaff7830bba945dd8989b632a40c04ed625be" + integrity sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-replace-supers" "^7.24.7" + +"@babel/plugin-transform-optional-catch-binding@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz#00eabd883d0dd6a60c1c557548785919b6e717b4" + integrity sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" + +"@babel/plugin-transform-optional-chaining@^7.24.7", "@babel/plugin-transform-optional-chaining@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.8.tgz#bb02a67b60ff0406085c13d104c99a835cdf365d" + integrity sha512-5cTOLSMs9eypEy8JUVvIKOu6NgvbJMnpG62VpIHrTmROdQ+L5mDAaI40g25k5vXti55JWNX5jCkq3HZxXBQANw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + "@babel/plugin-syntax-optional-chaining" "^7.8.3" + +"@babel/plugin-transform-parameters@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz#5881f0ae21018400e320fc7eb817e529d1254b68" + integrity sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-private-methods@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz#e6318746b2ae70a59d023d5cc1344a2ba7a75f5e" + integrity sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ== + dependencies: + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-private-property-in-object@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz#4eec6bc701288c1fab5f72e6a4bbc9d67faca061" + integrity sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.24.7" + "@babel/helper-create-class-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/plugin-syntax-private-property-in-object" "^7.14.5" + +"@babel/plugin-transform-property-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz#f0d2ed8380dfbed949c42d4d790266525d63bbdc" + integrity sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-regenerator@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz#021562de4534d8b4b1851759fd7af4e05d2c47f8" + integrity sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + regenerator-transform "^0.15.2" + +"@babel/plugin-transform-reserved-words@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz#80037fe4fbf031fc1125022178ff3938bb3743a4" + integrity sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-shorthand-properties@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz#85448c6b996e122fa9e289746140aaa99da64e73" + integrity sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-spread@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz#e8a38c0fde7882e0fb8f160378f74bd885cc7bb3" + integrity sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + "@babel/helper-skip-transparent-expression-wrappers" "^7.24.7" + +"@babel/plugin-transform-sticky-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz#96ae80d7a7e5251f657b5cf18f1ea6bf926f5feb" + integrity sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-template-literals@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz#a05debb4a9072ae8f985bcf77f3f215434c8f8c8" + integrity sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-typeof-symbol@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.8.tgz#383dab37fb073f5bfe6e60c654caac309f92ba1c" + integrity sha512-adNTUpDCVnmAE58VEqKlAA6ZBlNkMnWD0ZcW76lyNFN3MJniyGFZfNwERVk8Ap56MCnXztmDr19T4mPTztcuaw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.8" + +"@babel/plugin-transform-unicode-escapes@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz#2023a82ced1fb4971630a2e079764502c4148e0e" + integrity sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw== + dependencies: + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-property-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz#9073a4cd13b86ea71c3264659590ac086605bbcd" + integrity sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz#dfc3d4a51127108099b19817c0963be6a2adf19f" + integrity sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/plugin-transform-unicode-sets-regex@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz#d40705d67523803a576e29c63cef6e516b858ed9" + integrity sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg== + dependencies: + "@babel/helper-create-regexp-features-plugin" "^7.24.7" + "@babel/helper-plugin-utils" "^7.24.7" + +"@babel/preset-env@^7.19": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.24.8.tgz#e0db94d7f17d6f0e2564e8d29190bc8cdacec2d1" + integrity sha512-vObvMZB6hNWuDxhSaEPTKCwcqkAIuDtE+bQGn4XMXne1DSLzFVY8Vmj1bm+mUQXYNN8NmaQEO+r8MMbzPr1jBQ== + dependencies: + "@babel/compat-data" "^7.24.8" + "@babel/helper-compilation-targets" "^7.24.8" + "@babel/helper-plugin-utils" "^7.24.8" + "@babel/helper-validator-option" "^7.24.8" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key" "^7.24.7" + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.24.7" + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.24.7" + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly" "^7.24.7" + "@babel/plugin-proposal-private-property-in-object" "7.21.0-placeholder-for-preset-env.2" + "@babel/plugin-syntax-async-generators" "^7.8.4" + "@babel/plugin-syntax-class-properties" "^7.12.13" + "@babel/plugin-syntax-class-static-block" "^7.14.5" + "@babel/plugin-syntax-dynamic-import" "^7.8.3" + "@babel/plugin-syntax-export-namespace-from" "^7.8.3" + "@babel/plugin-syntax-import-assertions" "^7.24.7" + "@babel/plugin-syntax-import-attributes" "^7.24.7" + "@babel/plugin-syntax-import-meta" "^7.10.4" + "@babel/plugin-syntax-json-strings" "^7.8.3" + "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4" + "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" + "@babel/plugin-syntax-numeric-separator" "^7.10.4" + "@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-private-property-in-object" "^7.14.5" + "@babel/plugin-syntax-top-level-await" "^7.14.5" + "@babel/plugin-syntax-unicode-sets-regex" "^7.18.6" + "@babel/plugin-transform-arrow-functions" "^7.24.7" + "@babel/plugin-transform-async-generator-functions" "^7.24.7" + "@babel/plugin-transform-async-to-generator" "^7.24.7" + "@babel/plugin-transform-block-scoped-functions" "^7.24.7" + "@babel/plugin-transform-block-scoping" "^7.24.7" + "@babel/plugin-transform-class-properties" "^7.24.7" + "@babel/plugin-transform-class-static-block" "^7.24.7" + "@babel/plugin-transform-classes" "^7.24.8" + "@babel/plugin-transform-computed-properties" "^7.24.7" + "@babel/plugin-transform-destructuring" "^7.24.8" + "@babel/plugin-transform-dotall-regex" "^7.24.7" + "@babel/plugin-transform-duplicate-keys" "^7.24.7" + "@babel/plugin-transform-dynamic-import" "^7.24.7" + "@babel/plugin-transform-exponentiation-operator" "^7.24.7" + "@babel/plugin-transform-export-namespace-from" "^7.24.7" + "@babel/plugin-transform-for-of" "^7.24.7" + "@babel/plugin-transform-function-name" "^7.24.7" + "@babel/plugin-transform-json-strings" "^7.24.7" + "@babel/plugin-transform-literals" "^7.24.7" + "@babel/plugin-transform-logical-assignment-operators" "^7.24.7" + "@babel/plugin-transform-member-expression-literals" "^7.24.7" + "@babel/plugin-transform-modules-amd" "^7.24.7" + "@babel/plugin-transform-modules-commonjs" "^7.24.8" + "@babel/plugin-transform-modules-systemjs" "^7.24.7" + "@babel/plugin-transform-modules-umd" "^7.24.7" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.24.7" + "@babel/plugin-transform-new-target" "^7.24.7" + "@babel/plugin-transform-nullish-coalescing-operator" "^7.24.7" + "@babel/plugin-transform-numeric-separator" "^7.24.7" + "@babel/plugin-transform-object-rest-spread" "^7.24.7" + "@babel/plugin-transform-object-super" "^7.24.7" + "@babel/plugin-transform-optional-catch-binding" "^7.24.7" + "@babel/plugin-transform-optional-chaining" "^7.24.8" + "@babel/plugin-transform-parameters" "^7.24.7" + "@babel/plugin-transform-private-methods" "^7.24.7" + "@babel/plugin-transform-private-property-in-object" "^7.24.7" + "@babel/plugin-transform-property-literals" "^7.24.7" + "@babel/plugin-transform-regenerator" "^7.24.7" + "@babel/plugin-transform-reserved-words" "^7.24.7" + "@babel/plugin-transform-shorthand-properties" "^7.24.7" + "@babel/plugin-transform-spread" "^7.24.7" + "@babel/plugin-transform-sticky-regex" "^7.24.7" + "@babel/plugin-transform-template-literals" "^7.24.7" + "@babel/plugin-transform-typeof-symbol" "^7.24.8" + "@babel/plugin-transform-unicode-escapes" "^7.24.7" + "@babel/plugin-transform-unicode-property-regex" "^7.24.7" + "@babel/plugin-transform-unicode-regex" "^7.24.7" + "@babel/plugin-transform-unicode-sets-regex" "^7.24.7" + "@babel/preset-modules" "0.1.6-no-external-plugins" + babel-plugin-polyfill-corejs2 "^0.4.10" + babel-plugin-polyfill-corejs3 "^0.10.4" + babel-plugin-polyfill-regenerator "^0.6.1" + core-js-compat "^3.37.1" + semver "^6.3.1" + +"@babel/preset-modules@0.1.6-no-external-plugins": + version "0.1.6-no-external-plugins" + resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.6-no-external-plugins.tgz#ccb88a2c49c817236861fee7826080573b8a923a" + integrity sha512-HrcgcIESLm9aIR842yhJ5RWan/gebQUJ6E/E5+rf0y9o6oj7w0Br+sWuL6kEQ/o/AdfvR1Je9jG18/gnpwjEyA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/types" "^7.4.4" + esutils "^2.0.2" + +"@babel/regjsgen@^0.8.0": + version "0.8.0" + resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" + integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== + +"@babel/runtime@^7.8.4": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.8.tgz#5d958c3827b13cc6d05e038c07fb2e5e3420d82e" + integrity sha512-5F7SDGs1T72ZczbRwbGO9lQi0NLjQxzl6i4lJxLxfW9U5UluCSyEJeniWvnhl3/euNiqQVbo8zruhsDfid0esA== + dependencies: + regenerator-runtime "^0.14.0" + +"@babel/template@^7.24.7": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.24.7.tgz#02efcee317d0609d2c07117cb70ef8fb17ab7315" + integrity sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.24.7" + "@babel/types" "^7.24.7" + +"@babel/traverse@^7.24.7", "@babel/traverse@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.8.tgz#6c14ed5232b7549df3371d820fbd9abfcd7dfab7" + integrity sha512-t0P1xxAPzEDcEPmjprAQq19NWum4K0EQPjMwZQZbHt+GiZqvjCHjj755Weq1YRPVzBI+3zSfvScfpnuIecVFJQ== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.24.8" + "@babel/helper-environment-visitor" "^7.24.7" + "@babel/helper-function-name" "^7.24.7" + "@babel/helper-hoist-variables" "^7.24.7" + "@babel/helper-split-export-declaration" "^7.24.7" + "@babel/parser" "^7.24.8" + "@babel/types" "^7.24.8" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.24.7", "@babel/types@^7.24.8", "@babel/types@^7.24.9", "@babel/types@^7.4.4": + version "7.24.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.9.tgz#228ce953d7b0d16646e755acf204f4cf3d08cc73" + integrity sha512-xm8XrMKz0IlUdocVbYJe0Z9xEgidU7msskG8BbhnTPK/HZ2z/7FP7ykqPgrUH+C+r414mNfNWam1f2vqOjqjYQ== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + +"@discoveryjs/json-ext@^0.5.0": + version "0.5.7" + resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" + integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== + +"@jridgewell/gen-mapping@^0.3.5": + version "0.3.5" + resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" + integrity sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg== + dependencies: + "@jridgewell/set-array" "^1.2.1" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping" "^0.3.24" + +"@jridgewell/resolve-uri@^3.1.0": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + +"@jridgewell/set-array@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.2.1.tgz#558fb6472ed16a4c850b889530e6b36438c49280" + integrity sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A== + +"@jridgewell/source-map@^0.3.3": + version "0.3.6" + resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.6.tgz#9d71ca886e32502eb9362c9a74a46787c36df81a" + integrity sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ== + dependencies: + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + +"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz#3188bcb273a414b0d215fd22a58540b989b9409a" + integrity sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ== + +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": + version "0.3.25" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" + integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== + dependencies: + "@jridgewell/resolve-uri" "^3.1.0" + "@jridgewell/sourcemap-codec" "^1.4.14" + +"@types/eslint-scope@^3.7.3": + version "3.7.7" + resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" + integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== + dependencies: + "@types/eslint" "*" + "@types/estree" "*" + +"@types/eslint@*": + version "9.6.0" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.0.tgz#51d4fe4d0316da9e9f2c80884f2c20ed5fb022ff" + integrity sha512-gi6WQJ7cHRgZxtkQEoyHMppPjq9Kxo5Tjn2prSKDSmZrCz8TZ3jSRCeTJm+WoM+oB0WG37bRqLzaaU3q7JypGg== + dependencies: + "@types/estree" "*" + "@types/json-schema" "*" + +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== + +"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + +"@types/node@*": + version "20.14.12" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.12.tgz#129d7c3a822cb49fc7ff661235f19cfefd422b49" + integrity sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ== + dependencies: + undici-types "~5.26.4" + +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== + dependencies: + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== + +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== + +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== + +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== + dependencies: + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" + "@xtuc/long" "4.2.2" + +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== + +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" + +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== + dependencies: + "@xtuc/ieee754" "^1.2.0" + +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== + dependencies: + "@xtuc/long" "4.2.2" + +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@xtuc/long" "4.2.2" + +"@webpack-cli/configtest@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/configtest/-/configtest-1.2.0.tgz#7b20ce1c12533912c3b217ea68262365fa29a6f5" + integrity sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg== + +"@webpack-cli/info@^1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/info/-/info-1.5.0.tgz#6c78c13c5874852d6e2dd17f08a41f3fe4c261b1" + integrity sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ== + dependencies: + envinfo "^7.7.3" + +"@webpack-cli/serve@^1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1" + integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q== + +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== + +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== + +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + +acorn@^8.7.1, acorn@^8.8.2: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + +ajv-keywords@^3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" + integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== + +ajv@^6.12.4, ajv@^6.12.5: + version "6.12.6" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" + integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== + 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" + +ansi-styles@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== + dependencies: + color-convert "^1.9.0" + +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +babel-loader@^8.2: + version "8.3.0" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.3.0.tgz#124936e841ba4fe8176786d6ff28add1f134d6a8" + integrity sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q== + dependencies: + find-cache-dir "^3.3.1" + loader-utils "^2.0.0" + make-dir "^3.1.0" + schema-utils "^2.6.5" + +babel-plugin-polyfill-corejs2@^0.4.10: + version "0.4.11" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz#30320dfe3ffe1a336c15afdcdafd6fd615b25e33" + integrity sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q== + dependencies: + "@babel/compat-data" "^7.22.6" + "@babel/helper-define-polyfill-provider" "^0.6.2" + semver "^6.3.1" + +babel-plugin-polyfill-corejs3@^0.10.4: + version "0.10.4" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz#789ac82405ad664c20476d0233b485281deb9c77" + integrity sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.1" + core-js-compat "^3.36.1" + +babel-plugin-polyfill-regenerator@^0.6.1: + version "0.6.2" + resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz#addc47e240edd1da1058ebda03021f382bba785e" + integrity sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg== + dependencies: + "@babel/helper-define-polyfill-provider" "^0.6.2" + +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== + +binary-extensions@^2.0.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== + +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + +browserslist@^4.21.10, browserslist@^4.23.0, browserslist@^4.23.1: + version "4.23.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" + integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== + dependencies: + caniuse-lite "^1.0.30001640" + electron-to-chromium "^1.4.820" + node-releases "^2.0.14" + update-browserslist-db "^1.1.0" + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +caniuse-lite@^1.0.30001640: + version "1.0.30001643" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001643.tgz#9c004caef315de9452ab970c3da71085f8241dbd" + integrity sha512-ERgWGNleEilSrHM6iUz/zJNSQTP8Mr21wDWpdgvRwcTXGAq6jMtOUPP4dqFPTdKqZ2wKTdtB+uucZ3MRpAUSmg== + +chalk@^2.4.2: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + +"chokidar@>=3.0.0 <4.0.0": + version "3.6.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" + integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chrome-trace-event@^1.0.2: + version "1.0.4" + resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b" + integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ== + +clone-deep@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/clone-deep/-/clone-deep-4.0.1.tgz#c19fd9bdbbf85942b4fd979c84dcf7d5f07c2387" + integrity sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ== + dependencies: + is-plain-object "^2.0.4" + kind-of "^6.0.2" + shallow-clone "^3.0.0" + +color-convert@^1.9.0: + version "1.9.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== + dependencies: + color-name "1.1.3" + +color-name@1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw== + +colorette@^2.0.14: + version "2.0.20" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-2.0.20.tgz#9eb793e6833067f7235902fcd3b09917a000a95a" + integrity sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w== + +commander@^2.20.0: + version "2.20.3" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" + integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== + +commander@^7.0.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-7.2.0.tgz#a36cb57d0b501ce108e4d20559a150a391d97ab7" + integrity sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw== + +commondir@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg== + +convert-source-map@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" + integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== + +core-js-compat@^3.36.1, core-js-compat@^3.37.1: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.37.1.tgz#c844310c7852f4bdf49b8d339730b97e17ff09ee" + integrity sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg== + dependencies: + browserslist "^4.23.0" + +core-js@^3.25.0: + version "3.37.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.37.1.tgz#d21751ddb756518ac5a00e4d66499df981a62db9" + integrity sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw== + +cross-env@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-env/-/cross-env-7.0.3.tgz#865264b29677dc015ba8418918965dd232fc54cf" + integrity sha512-+/HKd6EgcQCJGh2PSjZuUitQBQynKor4wrFbRg4DtAgS1aWO+gU52xpH7M9ScGgXSYmAVS9bIJ8EzuaGw0oNAw== + dependencies: + cross-spawn "^7.0.1" + +cross-spawn@^7.0.1, cross-spawn@^7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" + integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + dependencies: + path-key "^3.1.0" + shebang-command "^2.0.0" + which "^2.0.1" + +debug@^4.1.0, debug@^4.1.1, debug@^4.3.1: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== + dependencies: + ms "2.1.2" + +electron-to-chromium@^1.4.820: + version "1.5.1" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.1.tgz#24640bd4dcfaccb6d82bb4c3f4c7311503241581" + integrity sha512-FKbOCOQ5QRB3VlIbl1LZQefWIYwszlBloaXcY2rbfpu9ioJnNh3TK03YtIDKDo3WKBi8u+YV4+Fn2CkEozgf4w== + +emojis-list@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-3.0.0.tgz#5570662046ad29e2e916e71aae260abdff4f6a78" + integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q== + +enhanced-resolve@^5.17.0: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== + dependencies: + graceful-fs "^4.2.4" + tapable "^2.2.0" + +envinfo@^7.7.3: + version "7.13.0" + resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.13.0.tgz#81fbb81e5da35d74e814941aeab7c325a606fb31" + integrity sha512-cvcaMr7KqXVh4nyzGTVqTum+gAiL265x5jUWQIDLq//zOGbW+gSW/C+OWLleY/rs9Qole6AZLMXPbtIFQbqu+Q== + +es-module-lexer@^1.2.1: + version "1.5.4" + resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.5.4.tgz#a8efec3a3da991e60efa6b633a7cad6ab8d26b78" + integrity sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw== + +escalade@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.2.tgz#54076e9ab29ea5bf3d8f1ed62acffbb88272df27" + integrity sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA== + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg== + +eslint-scope@5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" + integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== + dependencies: + esrecurse "^4.3.0" + estraverse "^4.1.1" + +esrecurse@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" + integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag== + dependencies: + estraverse "^5.2.0" + +estraverse@^4.1.1: + version "4.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" + integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== + +estraverse@^5.2.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123" + integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA== + +esutils@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64" + integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== + +events@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + +fast-deep-equal@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525" + integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q== + +fast-json-stable-stringify@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633" + integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw== + +fastest-levenshtein@^1.0.12: + version "1.0.16" + resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz#210e61b6ff181de91ea9b3d1b84fdedd47e034e5" + integrity sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg== + +file-loader@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/file-loader/-/file-loader-6.2.0.tgz#baef7cf8e1840df325e4390b4484879480eebe4d" + integrity sha512-qo3glqyTa61Ytg4u73GultjHGjdRyig3tG6lPtyX/jOEJvHif9uB0/OCI2Kif6ctF3caQTW2G5gym21oAsI4pw== + dependencies: + loader-utils "^2.0.0" + schema-utils "^3.0.0" + +fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== + dependencies: + to-regex-range "^5.0.1" + +find-cache-dir@^3.3.1: + version "3.3.2" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-3.3.2.tgz#b30c5b6eff0730731aea9bbd9dbecbd80256d64b" + integrity sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig== + dependencies: + commondir "^1.0.1" + make-dir "^3.0.2" + pkg-dir "^4.1.0" + +find-up@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" + integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== + dependencies: + locate-path "^5.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + +gensync@^1.0.0-beta.2: + version "1.0.0-beta.2" + resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0" + integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob-to-regexp@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" + integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== + +globals@^11.1.0: + version "11.12.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" + integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== + +graceful-fs@^4.1.2, graceful-fs@^4.2.11, graceful-fs@^4.2.4: + version "4.2.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" + integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== + +has-flag@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + +immutable@^4.0.0: + version "4.3.7" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.7.tgz#c70145fc90d89fb02021e65c84eb0226e4e5a381" + integrity sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw== + +import-local@^3.0.2: + version "3.2.0" + resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.2.0.tgz#c3d5c745798c02a6f8b897726aba5100186ee260" + integrity sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA== + dependencies: + pkg-dir "^4.2.0" + resolve-cwd "^3.0.0" + +interpret@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" + integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-core-module@^2.13.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-object@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== + dependencies: + isobject "^3.0.1" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw== + +isobject@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg== + +jest-worker@^27.4.5: + version "27.5.1" + resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" + integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg== + dependencies: + "@types/node" "*" + merge-stream "^2.0.0" + supports-color "^8.0.0" + +js-tokens@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== + +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA== + +json-parse-even-better-errors@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d" + integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== + +json-schema-traverse@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== + +json5@^2.1.2, json5@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== + +kind-of@^6.0.2: + version "6.0.3" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" + integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== + +klona@^2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.6.tgz#85bffbf819c03b2f53270412420a4555ef882e22" + integrity sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA== + +loader-runner@^4.2.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.3.0.tgz#c1b4a163b99f614830353b16755e7149ac2314e1" + integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg== + +loader-utils@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.4.tgz#8b5cb38b5c34a9a018ee1fc0e6a066d1dfcc528c" + integrity sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw== + dependencies: + big.js "^5.2.2" + emojis-list "^3.0.0" + json5 "^2.1.2" + +locate-path@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" + integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== + dependencies: + p-locate "^4.1.0" + +lodash.debounce@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== + +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== + dependencies: + yallist "^3.0.2" + +make-dir@^3.0.2, make-dir@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f" + integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw== + dependencies: + semver "^6.0.0" + +merge-stream@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" + integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== + +mime-db@1.52.0: + version "1.52.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70" + integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg== + +mime-types@^2.1.27: + version "2.1.35" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" + integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== + dependencies: + mime-db "1.52.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +neo-async@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" + integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== + +node-releases@^2.0.14: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +p-limit@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" + integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== + dependencies: + p-try "^2.0.0" + +p-locate@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" + integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== + dependencies: + p-limit "^2.2.0" + +p-try@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" + integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-key@^3.1.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" + integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== + +path-parse@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== + +picocolors@^1.0.0, picocolors@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.1.tgz#a8ad579b571952f0e5d25892de5445bcfe25aaa1" + integrity sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew== + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +pkg-dir@^4.1.0, pkg-dir@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3" + integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ== + dependencies: + find-up "^4.0.0" + +punycode@^2.1.0: + version "2.3.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.1.tgz#027422e2faec0b25e1549c3e1bd8309b9133b6e5" + integrity sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +rechoir@^0.7.0: + version "0.7.1" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" + integrity sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg== + dependencies: + resolve "^1.9.0" + +regenerate-unicode-properties@^10.1.0: + version "10.1.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.1.tgz#6b0e05489d9076b04c436f318d9b067bba459480" + integrity sha512-X007RyZLsCJVVrjgEFVpLUTZwyOZk3oiL75ZcuYjlIWd6rNJtOjkBwQc5AsRrpbKVkxN6sklw/k/9m2jJYOf8Q== + dependencies: + regenerate "^1.4.2" + +regenerate@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.2.tgz#b9346d8827e8f5a32f7ba29637d398b69014848a" + integrity sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A== + +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + +regenerator-transform@^0.15.2: + version "0.15.2" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.2.tgz#5bbae58b522098ebdf09bca2f83838929001c7a4" + integrity sha512-hfMp2BoF0qOk3uc5V20ALGDS2ddjQaLrdl7xrGXvAIow7qeWRM2VA2HuCHkUKk9slq3VwEwLNK3DFBqDfPGYtg== + dependencies: + "@babel/runtime" "^7.8.4" + +regexpu-core@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.3.2.tgz#11a2b06884f3527aec3e93dbbf4a3b958a95546b" + integrity sha512-RAM5FlZz+Lhmo7db9L298p2vHP5ZywrVXmVXpmAD9GuL5MPH6t9ROw1iA/wfHkQ76Qe7AaPF0nGuim96/IrQMQ== + dependencies: + "@babel/regjsgen" "^0.8.0" + regenerate "^1.4.2" + regenerate-unicode-properties "^10.1.0" + regjsparser "^0.9.1" + unicode-match-property-ecmascript "^2.0.0" + unicode-match-property-value-ecmascript "^2.1.0" + +regjsparser@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.9.1.tgz#272d05aa10c7c1f67095b1ff0addae8442fc5709" + integrity sha512-dQUtn90WanSNl+7mQKcXAgZxvUe7Z0SqXlgzv0za4LwiUhyzBC58yQO3liFoUgu8GiJVInAhJjkj1N0EtQ5nkQ== + dependencies: + jsesc "~0.5.0" + +resolve-cwd@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" + integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg== + dependencies: + resolve-from "^5.0.0" + +resolve-from@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" + integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== + +resolve@^1.14.2, resolve@^1.9.0: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +sass-loader@^12.1.0: + version "12.6.0" + resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.6.0.tgz#5148362c8e2cdd4b950f3c63ac5d16dbfed37bcb" + integrity sha512-oLTaH0YCtX4cfnJZxKSLAyglED0naiYfNG1iXfU5w1LNZ+ukoA5DtyDIN5zmKVZwYNJP4KRc5Y3hkWga+7tYfA== + dependencies: + klona "^2.0.4" + neo-async "^2.6.2" + +sass@^1.42.1: + version "1.77.8" + resolved "https://registry.yarnpkg.com/sass/-/sass-1.77.8.tgz#9f18b449ea401759ef7ec1752a16373e296b52bd" + integrity sha512-4UHg6prsrycW20fqLGPShtEvo/WyHRVRHwOP4DzkUrObWoWI05QBSfzU71TVB7PFaL104TwNaHpjlWXAZbQiNQ== + dependencies: + chokidar ">=3.0.0 <4.0.0" + immutable "^4.0.0" + source-map-js ">=0.6.2 <2.0.0" + +schema-utils@^2.6.5: + version "2.7.1" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-2.7.1.tgz#1ca4f32d1b24c590c203b8e7a50bf0ea4cd394d7" + integrity sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg== + dependencies: + "@types/json-schema" "^7.0.5" + ajv "^6.12.4" + ajv-keywords "^3.5.2" + +schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.3.0.tgz#f50a88877c3c01652a15b622ae9e9795df7a60fe" + integrity sha512-pN/yOAvcC+5rQ5nERGuwrjLlYvLTbCibnZ1I7B1LaiAz9BRBlE9GMgE/eqV30P7aJQUf7Ddimy/RsbYO/GrVGg== + dependencies: + "@types/json-schema" "^7.0.8" + ajv "^6.12.5" + ajv-keywords "^3.5.2" + +semver@^6.0.0, semver@^6.3.1: + version "6.3.1" + resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" + integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== + +serialize-javascript@^6.0.1: + version "6.0.2" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" + integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== + dependencies: + randombytes "^2.1.0" + +shallow-clone@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-3.0.1.tgz#8f2981ad92531f55035b01fb230769a40e02efa3" + integrity sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA== + dependencies: + kind-of "^6.0.2" + +shebang-command@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" + integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA== + dependencies: + shebang-regex "^3.0.0" + +shebang-regex@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172" + integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A== + +"source-map-js@>=0.6.2 <2.0.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + +source-map-support@~0.5.20: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +supports-color@^5.3.0: + version "5.5.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== + dependencies: + has-flag "^3.0.0" + +supports-color@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-preserve-symlinks-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" + integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== + +tapable@^2.1.1, tapable@^2.2.0: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0" + integrity sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ== + +terser-webpack-plugin@^5.3.10: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== + dependencies: + "@jridgewell/trace-mapping" "^0.3.20" + jest-worker "^27.4.5" + schema-utils "^3.1.1" + serialize-javascript "^6.0.1" + terser "^5.26.0" + +terser@^5.26.0: + version "5.31.3" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.3.tgz#b24b7beb46062f4653f049eea4f0cd165d0f0c38" + integrity sha512-pAfYn3NIZLyZpa83ZKigvj6Rn9c/vd5KfYGX7cN1mnzqgDcxWvrU5ZtAfIKhEXz9nRecw4z3LXkjaq96/qZqAA== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog== + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + +unicode-canonical-property-names-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" + integrity sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ== + +unicode-match-property-ecmascript@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-2.0.0.tgz#54fd16e0ecb167cf04cf1f756bdcc92eba7976c3" + integrity sha512-5kaZCrbp5mmbz5ulBkDkbY0SsPOjKqVS35VpL9ulMPfSl0J0Xsm+9Evphv9CoIZFwre7aJoa94AY6seMKGVN5Q== + dependencies: + unicode-canonical-property-names-ecmascript "^2.0.0" + unicode-property-aliases-ecmascript "^2.0.0" + +unicode-match-property-value-ecmascript@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.1.0.tgz#cb5fffdcd16a05124f5a4b0bf7c3770208acbbe0" + integrity sha512-qxkjQt6qjg/mYscYMC0XKRn3Rh0wFPlfxB0xkt9CfyTvpX1Ra0+rAmdX2QyAobptSEvuy4RtpPRui6XkV+8wjA== + +unicode-property-aliases-ecmascript@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd" + integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w== + +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + +uri-js@^4.2.2: + version "4.4.1" + resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" + integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg== + dependencies: + punycode "^2.1.0" + +watchpack@^2.4.1: + version "2.4.1" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.1.tgz#29308f2cac150fa8e4c92f90e0ec954a9fed7fff" + integrity sha512-8wrBCMtVhqcXP2Sup1ctSkga6uc2Bx0IIvKyT7yTFier5AXHooSI+QyQQAtTb7+E0IUCCKyTFmXqdqgum2XWGg== + dependencies: + glob-to-regexp "^0.4.1" + graceful-fs "^4.1.2" + +webpack-cli@^4.10: + version "4.10.0" + resolved "https://registry.yarnpkg.com/webpack-cli/-/webpack-cli-4.10.0.tgz#37c1d69c8d85214c5a65e589378f53aec64dab31" + integrity sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w== + dependencies: + "@discoveryjs/json-ext" "^0.5.0" + "@webpack-cli/configtest" "^1.2.0" + "@webpack-cli/info" "^1.5.0" + "@webpack-cli/serve" "^1.7.0" + colorette "^2.0.14" + commander "^7.0.0" + cross-spawn "^7.0.3" + fastest-levenshtein "^1.0.12" + import-local "^3.0.2" + interpret "^2.2.0" + rechoir "^0.7.0" + webpack-merge "^5.7.3" + +webpack-merge@^5.7.3: + version "5.10.0" + resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.10.0.tgz#a3ad5d773241e9c682803abf628d4cd62b8a4177" + integrity sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA== + dependencies: + clone-deep "^4.0.1" + flat "^5.0.2" + wildcard "^2.0.0" + +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack@^5.76: + version "5.93.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.93.0.tgz#2e89ec7035579bdfba9760d26c63ac5c3462a5e5" + integrity sha512-Y0m5oEY1LRuwly578VqluorkXbvXKh7U3rLoQCEO04M97ScRr44afGVkI0FQFsXzysk5OgFAxjZAb9rsGQVihA== + dependencies: + "@types/eslint-scope" "^3.7.3" + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" + acorn "^8.7.1" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" + chrome-trace-event "^1.0.2" + enhanced-resolve "^5.17.0" + es-module-lexer "^1.2.1" + eslint-scope "5.1.1" + events "^3.2.0" + glob-to-regexp "^0.4.1" + graceful-fs "^4.2.11" + json-parse-even-better-errors "^2.3.1" + loader-runner "^4.2.0" + mime-types "^2.1.27" + neo-async "^2.6.2" + schema-utils "^3.2.0" + tapable "^2.1.1" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" + webpack-sources "^3.2.3" + +which@^2.0.1: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +wildcard@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.1.tgz#5ab10d02487198954836b6349f74fff961e10f67" + integrity sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ== + +yallist@^3.0.2: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd" + integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g== diff --git a/modules/ppcp-save-payment-methods/resources/js/Configuration.js b/modules/ppcp-save-payment-methods/resources/js/Configuration.js index 42ad02a77..a602d4d32 100644 --- a/modules/ppcp-save-payment-methods/resources/js/Configuration.js +++ b/modules/ppcp-save-payment-methods/resources/js/Configuration.js @@ -3,256 +3,228 @@ import { PaymentMethods, } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; -class Configuration { - constructor( ppcp_add_payment_method, errorHandler ) { - this.ppcp_add_payment_method = ppcp_add_payment_method; - this.errorHandler = errorHandler; - } - - buttonConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; +export function buttonConfiguration( ppcp_add_payment_method, errorHandler ) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - const result = await response.json(); - if ( result.success === true ) { - window.location.href = - this.ppcp_add_payment_method.payment_methods_page; - return; + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_payment_token + .nonce, + vault_setup_token: vaultSetupToken, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - }; - } + const result = await response.json(); + if ( result.success === true ) { + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } - cardFieldsConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: PaymentMethods.CARDS, - verification_method: - this.ppcp_add_payment_method - .verification_method, - } ), - } - ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + }; +} - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; +export function cardFieldsConfiguration( + ppcp_add_payment_method, + errorHandler +) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + payment_method: PaymentMethods.CARDS, + verification_method: + ppcp_add_payment_method.verification_method, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const isFreeTrialCart = - this.ppcp_add_payment_method?.is_free_trial_cart ?? false; - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_payment_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - payment_method: PaymentMethods.CARDS, - is_free_trial_cart: isFreeTrialCart, - } ), - } - ); + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } - const result = await response.json(); - if ( result.success === true ) { - const context = this.ppcp_add_payment_method?.context ?? ''; - if ( context === 'checkout' ) { - document.querySelector( '#place_order' ).click(); - return; - } - - if ( - this.ppcp_add_payment_method - .is_subscription_change_payment_page - ) { - const subscriptionId = - this.ppcp_add_payment_method - .subscription_id_to_change_payment; - if ( subscriptionId && result.data ) { - const req = await fetch( - this.ppcp_add_payment_method.ajax - .subscription_change_payment_method - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .subscription_change_payment_method - .nonce, - subscription_id: subscriptionId, - payment_method: - getCurrentPaymentMethod(), - wc_payment_token_id: result.data, - } ), - } - ); - - const res = await req.json(); - if ( res.success === true ) { - window.location.href = `${ this.ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; - return; - } - } - - return; - } - - window.location.href = - this.ppcp_add_payment_method.payment_methods_page; - return; + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const isFreeTrialCart = + ppcp_add_payment_method?.is_free_trial_cart ?? false; + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_payment_token + .nonce, + vault_setup_token: vaultSetupToken, + payment_method: PaymentMethods.CARDS, + is_free_trial_cart: isFreeTrialCart, + } ), } + ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - onError: ( error ) => { - console.error( error ); - this.errorHandler.message( - this.ppcp_add_payment_method.error_message - ); - }, - }; - } - - addPaymentMethodConfiguration() { - return { - createVaultSetupToken: async () => { - const response = await fetch( - this.ppcp_add_payment_method.ajax.create_setup_token - .endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_setup_token.nonce, - payment_method: getCurrentPaymentMethod(), - } ), - } - ); - - const result = await response.json(); - if ( result.data.id ) { - return result.data.id; - } - - console.error( result ); - }, - onApprove: async ( { vaultSetupToken } ) => { - const response = await fetch( - this.ppcp_add_payment_method.ajax - .create_payment_token_for_guest.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: this.ppcp_add_payment_method.ajax - .create_payment_token_for_guest.nonce, - vault_setup_token: vaultSetupToken, - } ), - } - ); - - const result = await response.json(); - if ( result.success === true ) { + const result = await response.json(); + if ( result.success === true ) { + const context = ppcp_add_payment_method?.context ?? ''; + if ( context === 'checkout' ) { document.querySelector( '#place_order' ).click(); return; } - console.error( result ); - }, - onError: ( error ) => { - console.error( error ); - }, - }; - } + if ( + ppcp_add_payment_method.is_subscription_change_payment_page + ) { + const subscriptionId = + ppcp_add_payment_method.subscription_id_to_change_payment; + if ( subscriptionId && result.data ) { + const req = await fetch( + ppcp_add_payment_method.ajax + .subscription_change_payment_method.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .subscription_change_payment_method + .nonce, + subscription_id: subscriptionId, + payment_method: getCurrentPaymentMethod(), + wc_payment_token_id: result.data, + } ), + } + ); + + const res = await req.json(); + if ( res.success === true ) { + window.location.href = `${ ppcp_add_payment_method.view_subscriptions_page }/${ subscriptionId }`; + return; + } + } + + return; + } + + window.location.href = + ppcp_add_payment_method.payment_methods_page; + return; + } + + this.errorHandler.message( ppcp_add_payment_method.error_message ); + }, + onError: ( error ) => { + console.error( error ); + errorHandler.message( ppcp_add_payment_method.error_message ); + }, + }; } -export default Configuration; +export function addPaymentMethodConfiguration( ppcp_add_payment_method ) { + return { + createVaultSetupToken: async () => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_setup_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax.create_setup_token + .nonce, + payment_method: getCurrentPaymentMethod(), + } ), + } + ); + + const result = await response.json(); + if ( result.data.id ) { + return result.data.id; + } + + console.error( result ); + }, + onApprove: async ( { vaultSetupToken } ) => { + const response = await fetch( + ppcp_add_payment_method.ajax.create_payment_token_for_guest + .endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: ppcp_add_payment_method.ajax + .create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + } ), + } + ); + + const result = await response.json(); + if ( result.success === true ) { + document.querySelector( '#place_order' ).click(); + return; + } + + console.error( result ); + }, + onError: ( error ) => { + console.error( error ); + }, + }; +} diff --git a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js b/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js deleted file mode 100644 index 00c37ca6d..000000000 --- a/modules/ppcp-save-payment-methods/resources/js/RenderCardFields.js +++ /dev/null @@ -1,55 +0,0 @@ -import { cardFieldStyles } from '../../../ppcp-button/resources/js/modules/Helper/CardFieldsHelper'; - -class RenderCardFields { - constructor( cardFields ) { - this.cardFields = cardFields; - } - - render() { - const nameField = document.getElementById( - 'ppcp-credit-card-gateway-card-name' - ); - if ( nameField ) { - const styles = cardFieldStyles( nameField ); - this.cardFields - .NameField( { style: { input: styles } } ) - .render( nameField.parentNode ); - nameField.hidden = true; - } - - const numberField = document.getElementById( - 'ppcp-credit-card-gateway-card-number' - ); - if ( numberField ) { - const styles = cardFieldStyles( numberField ); - this.cardFields - .NumberField( { style: { input: styles } } ) - .render( numberField.parentNode ); - numberField.hidden = true; - } - - const expiryField = document.getElementById( - 'ppcp-credit-card-gateway-card-expiry' - ); - if ( expiryField ) { - const styles = cardFieldStyles( expiryField ); - this.cardFields - .ExpiryField( { style: { input: styles } } ) - .render( expiryField.parentNode ); - expiryField.hidden = true; - } - - const cvvField = document.getElementById( - 'ppcp-credit-card-gateway-card-cvc' - ); - if ( cvvField ) { - const styles = cardFieldStyles( cvvField ); - this.cardFields - .CVVField( { style: { input: styles } } ) - .render( cvvField.parentNode ); - cvvField.hidden = true; - } - } -} - -export default RenderCardFields; diff --git a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js index 684de0eb2..8e75e3ad7 100644 --- a/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js +++ b/modules/ppcp-save-payment-methods/resources/js/add-payment-method.js @@ -5,9 +5,8 @@ import { } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; import { loadScript } from '@paypal/paypal-js'; import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler'; - -import Configuration from './Configuration'; -import RenderCardFields from './RenderCardFields'; +import { buttonConfiguration, cardFieldsConfiguration } from './Configuration'; +import { renderFields } from '../../../ppcp-card-fields/resources/js/Render'; import { setVisible, setVisibleByClass, @@ -55,30 +54,32 @@ import { ); errorHandler.clear(); - const configuration = new Configuration( - ppcp_add_payment_method, - errorHandler - ); - const paypalButtonContainer = document.querySelector( `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` ); if ( paypalButtonContainer ) { paypal - .Buttons( configuration.buttonConfiguration() ) + .Buttons( + buttonConfiguration( + ppcp_add_payment_method, + errorHandler + ) + ) .render( `#ppc-button-${ PaymentMethods.PAYPAL }-save-payment-method` ); } const cardFields = paypal.CardFields( - configuration.cardFieldsConfiguration() + cardFieldsConfiguration( + ppcp_add_payment_method, + errorHandler + ) ); if ( cardFields.isEligible() ) { - const renderCardFields = new RenderCardFields( cardFields ); - renderCardFields.render(); + renderFields( cardFields ); } document diff --git a/package.json b/package.json index 61c625ca9..d9f9c6b51 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "install:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn install", "install:modules:ppcp-axo": "cd modules/ppcp-axo && yarn install", "install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install", + "install:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn install", "install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install", "install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install", "build:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run build", @@ -37,6 +38,7 @@ "build:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run build", "build:modules:ppcp-paypal-subscriptions": "cd modules/ppcp-paypal-subscriptions && yarn run build", "build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run build", + "build:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run build", "build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build", "build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build", "build:modules": "run-p build:modules:*", @@ -54,6 +56,7 @@ "watch:modules:ppcp-save-payment-methods": "cd modules/ppcp-save-payment-methods && yarn run watch", "watch:modules:ppcp-axo": "cd modules/ppcp-axo && yarn run watch", "watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn run watch", + "watch:modules:ppcp-card-fields": "cd modules/ppcp-card-fields && yarn run watch", "watch:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run watch", "watch:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run watch", "watch:modules": "run-p watch:modules:*", From b96c87597a89b739f5295de15740887bc3370993 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Fri, 26 Jul 2024 18:06:09 +0400 Subject: [PATCH 118/357] Do not consider needShipping from single page --- .../ppcp-button/resources/js/modules/Renderer/Renderer.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js index 7d6139d4c..63bd5e2ed 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js @@ -139,7 +139,7 @@ class Renderer { }; // Check the condition and add the handler if needed - if ( this.defaultSettings.should_handle_shipping_in_paypal && this.defaultSettings.needShipping ) { + if ( this.shouldEnableShippingCallback() ) { options.onShippingOptionsChange = ( data, actions ) => { let shippingOptionsChange = ! this.isVenmoButtonClickedWhenVaultingIsEnabled( @@ -227,6 +227,12 @@ class Renderer { return venmoButtonClicked && this.defaultSettings.vaultingEnabled; }; + shouldEnableShippingCallback = () => { + console.log(this.defaultSettings.context, this.defaultSettings) + let needShipping = this.defaultSettings.needShipping || this.defaultSettings.context === 'product' + return this.defaultSettings.should_handle_shipping_in_paypal && needShipping + }; + isAlreadyRendered( wrapper, fundingSource ) { return this.renderedSources.has( wrapper + ( fundingSource ?? '' ) ); } From cbf9bff80836946b28b8f31027668e82f9ec3399 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:06:42 +0200 Subject: [PATCH 119/357] =?UTF-8?q?=F0=9F=90=9B=20Fix=20bug=20with=20undef?= =?UTF-8?q?ined=20log()=20method?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/ApplepayButton.js | 45 ++++++++++++------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index ce998f04c..d84870fd9 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -43,8 +43,10 @@ const CONTEXT = { BlockCart: 'cart-block', BlockCheckout: 'checkout-block', Preview: 'preview', + // Block editor contexts. Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. Gateways: [ 'checkout', 'pay-now' ], }; @@ -89,6 +91,8 @@ class ApplePayButton { initialPaymentRequest = null; constructor( context, externalHandler, buttonConfig, ppcpConfig ) { + this._initDebug( !! buttonConfig?.is_debug ); + apmButtonsInit( ppcpConfig ); this.context = context; @@ -103,25 +107,34 @@ class ApplePayButton { ); this.refreshContextData(); + } - this.log = () => {}; + /** + * NOOP log function to avoid errors when debugging is disabled. + */ + log() {} - // Debug helpers - if ( this.buttonConfig.is_debug ) { - document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; - document.ppcpApplepayButtons[ this.context ] = this; - - this.log = function () { - console.log( - `[ApplePayButton | ${ this.context }]`, - ...arguments - ); - }; - - jQuery( document ).on( 'ppcp-applepay-debug', () => { - this.log( this ); - } ); + /** + * Enables debugging tools, when the button's is_debug flag is set. + * + * @param {boolean} enableDebugging If debugging features should be enabled for this instance. + * @private + */ + _initDebug( enableDebugging ) { + if ( ! enableDebugging || this.#isInitialized ) { + return; } + + document.ppcpApplepayButtons = document.ppcpApplepayButtons || {}; + document.ppcpApplepayButtons[ this.context ] = this; + + this.log = ( ...args ) => { + console.log( `[ApplePayButton | ${ this.context }]`, ...args ); + }; + + jQuery( document ).on( 'ppcp-applepay-debug', () => { + this.log( this ); + } ); } /** From 09308f15b65183f145b06461db309be3aed2d773 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:20:48 +0200 Subject: [PATCH 120/357] =?UTF-8?q?=F0=9F=90=9B=20Hide=20Apple=20Pay=20but?= =?UTF-8?q?ton=20until=20gateway=20is=20selected?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-applepay/resources/css/styles.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss index 3818b8db5..3402b1a20 100644 --- a/modules/ppcp-applepay/resources/css/styles.scss +++ b/modules/ppcp-applepay/resources/css/styles.scss @@ -52,3 +52,7 @@ } } } + +#ppc-button-ppcp-applepay { + display: none; +} From c15715a02f5e2fb30502479b90894ab291998130 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 17:28:30 +0200 Subject: [PATCH 121/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20More=20reliable=20?= =?UTF-8?q?eligibility-check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Eligibility better aligns with the ApplePay SDK logic and will not insert a hidden button anymore. --- .../resources/js/ApplepayButton.js | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js index d84870fd9..fb853182c 100644 --- a/modules/ppcp-applepay/resources/js/ApplepayButton.js +++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js @@ -163,11 +163,26 @@ class ApplePayButton { return true; } - if ( this.buttonConfig.is_admin ) { + if ( CONTEXT.Preview === this.context ) { return true; } - return !! ( this.applePayConfig.isEligible && window.ApplePaySession ); + /** + * Ensure the ApplePaySession is available and accepts payments + * This check is required when using Apple Pay SDK v1; canMakePayments() returns false + * if the current device is not liked to iCloud or the Apple Wallet is not available + * for a different reason. + */ + try { + if ( ! window.ApplePaySession?.canMakePayments() ) { + return false; + } + } catch ( error ) { + console.warn( error ); + return false; + } + + return !! this.applePayConfig.isEligible; } /** From 1ac9b460b6aae0ee35dad718411628194c42f905 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 26 Jul 2024 18:32:33 +0200 Subject: [PATCH 122/357] =?UTF-8?q?=F0=9F=90=9B=20Display=20the=20PayPal?= =?UTF-8?q?=20payment=20tabs=20in=20Gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-wc-gateway/services.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..92acc6b0c 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -71,6 +71,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener; use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer; +use WooCommerce\PayPalCommerce\Applepay\ApplePayGateway; return array( 'wcgateway.paypal-gateway' => static function ( ContainerInterface $container ): PayPalGateway { @@ -198,6 +199,7 @@ return array( Settings::PAY_LATER_TAB_ID, AxoGateway::ID, GooglePayGateway::ID, + ApplePayGateway::ID, ), true ); @@ -220,6 +222,7 @@ return array( Settings::PAY_LATER_TAB_ID, Settings::CONNECTION_TAB_ID, GooglePayGateway::ID, + ApplePayGateway::ID, ), true ); From 6fbdb9417b922138218414a304c2785e656a3be3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 26 Jul 2024 18:50:35 +0200 Subject: [PATCH 123/357] Fix card add payment method test --- tests/Playwright/playwright.config.js | 134 ++++---- tests/Playwright/tests/place-order.spec.js | 301 ++++++++++-------- .../tests/save-payment-methods.spec.js | 138 ++++---- 3 files changed, 311 insertions(+), 262 deletions(-) diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js index dbe3173da..7273db2a0 100644 --- a/tests/Playwright/playwright.config.js +++ b/tests/Playwright/playwright.config.js @@ -1,78 +1,78 @@ // @ts-check -const { defineConfig, devices } = require('@playwright/test'); +const { defineConfig, devices } = require( '@playwright/test' ); -require('dotenv').config({ path: '.env' }); +require( 'dotenv' ).config( { path: '.env' } ); /** * @see https://playwright.dev/docs/test-configuration */ -module.exports = defineConfig({ - timeout: 60000, - testDir: './tests', - /* Run tests in files in parallel */ - fullyParallel: true, - /* Fail the build on CI if you accidentally left test.only in the source code. */ - forbidOnly: !!process.env.CI, - /* Retry on CI only */ - retries: process.env.CI ? 2 : 0, - /* Opt out of parallel tests on CI. */ - workers: process.env.CI ? 1 : undefined, - /* Reporter to use. See https://playwright.dev/docs/test-reporters */ - reporter: [ - [process.env.CI ? 'github' : 'list'], - ['html', {open: 'never'}], - ], - /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ - use: { - /* Base URL to use in actions like `await page.goto('/')`. */ - baseURL: process.env.BASEURL, - ignoreHTTPSErrors: true, - /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ - trace: 'on-first-retry', - }, +module.exports = defineConfig( { + timeout: 60000, + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: false, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !! process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: [ + [ process.env.CI ? 'github' : 'list' ], + [ 'html', { open: 'never' } ], + ], + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: process.env.BASEURL, + ignoreHTTPSErrors: true, + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, - /* Configure projects for major browsers */ - projects: [ - { - name: 'chromium', - use: { ...devices['Desktop Chrome'] }, - }, + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices[ 'Desktop Chrome' ] }, + }, - // { - // name: 'firefox', - // use: { ...devices['Desktop Firefox'] }, - // }, - // - // { - // name: 'webkit', - // use: { ...devices['Desktop Safari'] }, - // }, + // { + // name: 'firefox', + // use: { ...devices['Desktop Firefox'] }, + // }, + // + // { + // name: 'webkit', + // use: { ...devices['Desktop Safari'] }, + // }, - /* Test against mobile viewports. */ - // { - // name: 'Mobile Chrome', - // use: { ...devices['Pixel 5'] }, - // }, - // { - // name: 'Mobile Safari', - // use: { ...devices['iPhone 12'] }, - // }, + /* Test against mobile viewports. */ + // { + // name: 'Mobile Chrome', + // use: { ...devices['Pixel 5'] }, + // }, + // { + // name: 'Mobile Safari', + // use: { ...devices['iPhone 12'] }, + // }, - /* Test against branded browsers. */ - // { - // name: 'Microsoft Edge', - // use: { ...devices['Desktop Edge'], channel: 'msedge' }, - // }, - // { - // name: 'Google Chrome', - // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, - // }, - ], + /* Test against branded browsers. */ + // { + // name: 'Microsoft Edge', + // use: { ...devices['Desktop Edge'], channel: 'msedge' }, + // }, + // { + // name: 'Google Chrome', + // use: { ..devices['Desktop Chrome'], channel: 'chrome' }, + // }, + ], - /* Run your local dev server before starting the tests */ - // webServer: { - // command: 'npm run start', - // url: 'http://127.0.0.1:3000', - // reuseExistingServer: !process.env.CI, - // }, -}); + /* Run your local dev server before starting the tests */ + // webServer: { + // command: 'npm run start', + // url: 'http://127.0.0.1:3000', + // reuseExistingServer: !process.env.CI, + // }, +} ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js index 4a9f7523f..086d8e9c7 100644 --- a/tests/Playwright/tests/place-order.spec.js +++ b/tests/Playwright/tests/place-order.spec.js @@ -1,207 +1,242 @@ -const {test, expect} = require('@playwright/test'); -const {serverExec} = require("./utils/server"); -const {fillCheckoutForm, expectOrderReceivedPage, acceptTerms} = require("./utils/checkout"); -const {openPaypalPopup, loginIntoPaypal, waitForPaypalShippingList, completePaypalPayment} = require("./utils/paypal-popup"); +const { test, expect } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, + acceptTerms, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + loginIntoPaypal, + waitForPaypalShippingList, + completePaypalPayment, +} = require( './utils/paypal-popup' ); const { - CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, - CREDIT_CARD_CVV, - PRODUCT_URL, - PRODUCT_ID, - CHECKOUT_URL, - CHECKOUT_PAGE_ID, - CART_URL, - BLOCK_CHECKOUT_URL, - BLOCK_CHECKOUT_PAGE_ID, - BLOCK_CART_URL, - APM_ID, + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXPIRATION, + CREDIT_CARD_CVV, + PRODUCT_URL, + PRODUCT_ID, + CHECKOUT_URL, + CHECKOUT_PAGE_ID, + CART_URL, + BLOCK_CHECKOUT_URL, + BLOCK_CHECKOUT_PAGE_ID, + BLOCK_CART_URL, + APM_ID, } = process.env; -async function completeBlockContinuation(page) { - await expect(page.locator('#radio-control-wc-payment-method-options-ppcp-gateway')).toBeChecked(); +async function completeBlockContinuation( page ) { + await expect( + page.locator( '#radio-control-wc-payment-method-options-ppcp-gateway' ) + ).toBeChecked(); - await expect(page.locator('.component-frame')).toHaveCount(0); + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); - await Promise.all( - page.waitForNavigation(), - page.locator('.wc-block-components-checkout-place-order-button').click(), - ); + await Promise.all( + page.waitForNavigation(), + page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click() + ); } -async function expectContinuation(page) { - await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked(); +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); - await expect(page.locator('.component-frame')).toHaveCount(0); + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); } -async function completeContinuation(page) { - await expectContinuation(page); +async function completeContinuation( page ) { + await expectContinuation( page ); - await Promise.all([ - page.waitForNavigation(), - page.locator('#place_order').click(), - ]); + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); } -test.describe('Classic checkout', () => { - test.beforeAll(async ({ browser }) => { - await serverExec('wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID); - }); +test.describe( 'Classic checkout', () => { + test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID + ); + } ); - test('PayPal button place order from Product page', async ({page}) => { - await page.goto(PRODUCT_URL); + test( 'PayPal button place order from Product page', async ( { page } ) => { + await page.goto( PRODUCT_URL ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await fillCheckoutForm(page); + await fillCheckoutForm( page ); - await completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('Advanced Credit and Debit Card place order from Checkout page', async ({page}) => { - await page.goto(PRODUCT_URL); - await page.locator('.single_add_to_cart_button').click(); + test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { + page, + } ) => { + await page.goto( PRODUCT_URL ); + await page.locator( '.single_add_to_cart_button' ).click(); - await page.goto(CHECKOUT_URL); - await fillCheckoutForm(page); + await page.goto( CHECKOUT_URL ); + await fillCheckoutForm( page ); - await page.click("text=Credit Cards"); + await page.click( 'text=Credit Cards' ); - const creditCardNumber = page.frameLocator('#braintree-hosted-field-number').locator('#credit-card-number'); - await creditCardNumber.fill(CREDIT_CARD_NUMBER); + const creditCardNumber = page + .frameLocator( '#braintree-hosted-field-number' ) + .locator( '#credit-card-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - const expirationDate = page.frameLocator('#braintree-hosted-field-expirationDate').locator('#expiration'); - await expirationDate.fill(CREDIT_CARD_EXPIRATION); + const expirationDate = page + .frameLocator( '#braintree-hosted-field-expirationDate' ) + .locator( '#expiration' ); + await expirationDate.fill( CREDIT_CARD_EXPIRATION ); - const cvv = page.frameLocator('#braintree-hosted-field-cvv').locator('#cvv'); - await cvv.fill(CREDIT_CARD_CVV); + const cvv = page + .frameLocator( '#braintree-hosted-field-cvv' ) + .locator( '#cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); - await Promise.all([ - page.waitForNavigation(), - page.locator('.ppcp-dcc-order-button').click(), - ]); + await Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal APM button place order', async ({page}) => { - await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID); + test( 'PayPal APM button place order', async ( { page } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - await page.goto(CHECKOUT_URL); + await page.goto( CHECKOUT_URL ); - await fillCheckoutForm(page); + await fillCheckoutForm( page ); - const popup = await openPaypalPopup(page, {fundingSource: APM_ID}); + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - await popup.getByText('Continue', { exact: true }).click(); - await completePaypalPayment(popup, {selector: '[name="Successful"]'}); + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal APM button place order when redirect fails', async ({page}) => { - await page.goto(CART_URL + '?add-to-cart=' + PRODUCT_ID); + test( 'PayPal APM button place order when redirect fails', async ( { + page, + } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - await page.goto(CHECKOUT_URL); + await page.goto( CHECKOUT_URL ); - await fillCheckoutForm(page); + await fillCheckoutForm( page ); - await page.evaluate('PayPalCommerceGateway.ajax.approve_order = null'); + await page.evaluate( + 'PayPalCommerceGateway.ajax.approve_order = null' + ); - const popup = await openPaypalPopup(page, {fundingSource: APM_ID}); + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - await popup.getByText('Continue', { exact: true }).click(); - await completePaypalPayment(popup, {selector: '[name="Successful"]'}); + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); - await expect(page.locator('.woocommerce-error')).toBeVisible(); + await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); - await page.reload(); - await expectContinuation(page); + await page.reload(); + await expectContinuation( page ); - await acceptTerms(page); + await acceptTerms( page ); - await completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); - }); -}); + await expectOrderReceivedPage( page ); + } ); +} ); -test.describe('Block checkout', () => { - test.beforeAll(async ({browser}) => { - await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID); - await serverExec('wp pcp settings update blocks_final_review_enabled true'); - }); +test.describe( 'Block checkout', () => { + test.beforeAll( async ( { browser } ) => { + // await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID); + // await serverExec('wp pcp settings update blocks_final_review_enabled true'); + } ); - test('PayPal express block checkout', async ({page}) => { - await page.goto('?add-to-cart=' + PRODUCT_ID); + test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); - await page.goto(BLOCK_CHECKOUT_URL) + await page.goto( BLOCK_CHECKOUT_URL ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await completeBlockContinuation(page); + await completeBlockContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal express block cart', async ({page}) => { - await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) + test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await completeBlockContinuation(page); + await completeBlockContinuation( page ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test.describe('Without review', () => { - test.beforeAll(async ({browser}) => { - await serverExec('wp pcp settings update blocks_final_review_enabled false'); - }); + test.describe( 'Without review', () => { + test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled false' + ); + } ); - test('PayPal express block checkout', async ({page}) => { - await page.goto('?add-to-cart=' + PRODUCT_ID); + test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); - await page.goto(BLOCK_CHECKOUT_URL) + await page.goto( BLOCK_CHECKOUT_URL ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await waitForPaypalShippingList(popup); + await waitForPaypalShippingList( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await expectOrderReceivedPage(page); - }); + await expectOrderReceivedPage( page ); + } ); - test('PayPal express block cart', async ({page}) => { - await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID) + test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - const popup = await openPaypalPopup(page); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); + await loginIntoPaypal( popup ); - await waitForPaypalShippingList(popup); + await waitForPaypalShippingList( popup ); - await completePaypalPayment(popup); + await completePaypalPayment( popup ); - await expectOrderReceivedPage(page); - }); - }); -}); + await expectOrderReceivedPage( page ); + } ); + } ); +} ); diff --git a/tests/Playwright/tests/save-payment-methods.spec.js b/tests/Playwright/tests/save-payment-methods.spec.js index f6fb46315..092bd8ee4 100644 --- a/tests/Playwright/tests/save-payment-methods.spec.js +++ b/tests/Playwright/tests/save-payment-methods.spec.js @@ -1,89 +1,103 @@ -const {test, expect} = require('@playwright/test'); -const {loginAsCustomer} = require("./utils/user"); -const {openPaypalPopup, loginIntoPaypal, completePaypalPayment} = require("./utils/paypal-popup"); -const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout"); - +const { test, expect } = require( '@playwright/test' ); +const { loginAsCustomer } = require( './utils/user' ); const { - PRODUCT_URL, -} = process.env; + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, +} = require( './utils/paypal-popup' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, +} = require( './utils/checkout' ); -async function expectContinuation(page) { - await expect(page.locator('#payment_method_ppcp-gateway')).toBeChecked(); +const { PRODUCT_URL } = process.env; - await expect(page.locator('.component-frame')).toHaveCount(0); +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); } -async function completeContinuation(page) { - await expectContinuation(page); +async function completeContinuation( page ) { + await expectContinuation( page ); - await Promise.all([ - page.waitForNavigation(), - page.locator('#place_order').click(), - ]); + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); } -test('Save during purchase', async ({page}) => { - await loginAsCustomer(page) +// preconditions: shipping callback disabled and no saved payments +test( 'Save during purchase', async ( { page } ) => { + await loginAsCustomer( page ); - await page.goto(PRODUCT_URL); - const popup = await openPaypalPopup(page); + await page.goto( PRODUCT_URL ); + const popup = await openPaypalPopup( page ); - await loginIntoPaypal(popup); - await completePaypalPayment(popup); - await fillCheckoutForm(page); + await loginIntoPaypal( popup ); + await completePaypalPayment( popup ); + await fillCheckoutForm( page ); - await completeContinuation(page); + await completeContinuation( page ); - await expectOrderReceivedPage(page); -}); + await expectOrderReceivedPage( page ); +} ); -test('PayPal add payment method', async ({page}) => { - await loginAsCustomer(page); - await page.goto('/my-account/add-payment-method'); +test( 'PayPal add payment method', async ( { page } ) => { + await loginAsCustomer( page ); + await page.goto( '/my-account/add-payment-method' ); - const popup = await openPaypalPopup(page); - await loginIntoPaypal(popup); - popup.locator('#consentButton').click(); + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); - await page.waitForURL('/my-account/payment-methods'); -}); + await page.waitForURL( '/my-account/payment-methods' ); +} ); -test('ACDC add payment method', async ({page}) => { - await loginAsCustomer(page); - await page.goto('/my-account/add-payment-method'); +test( 'ACDC add payment method', async ( { page } ) => { + await loginAsCustomer( page ); + await page.goto( '/my-account/add-payment-method' ); - await page.click("text=Debit & Credit Cards"); + await page.click( 'text=Debit & Credit Cards' ); - const creditCardNumber = await page.frameLocator('[title="paypal_card_number_field"]').locator('.card-field-number'); - await creditCardNumber.fill('4005519200000004'); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( '4005519200000004' ); - const expirationDate = await page.frameLocator('[title="paypal_card_expiry_field"]').locator('.card-field-expiry'); - await expirationDate.fill('01/25'); + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '12/25' ); - const cvv = await page.frameLocator('[title="paypal_card_cvv_field"]').locator('.card-field-cvv'); - await cvv.fill('123'); + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( '123' ); - await page.waitForURL('/my-account/payment-methods'); -}); + await page.getByRole( 'button', { name: 'Add payment method' } ).click(); -test('PayPal logged-in user free trial subscription without payment token', async ({page}) => { - await loginAsCustomer(page); - - await page.goto('/shop'); - await page.click("text=Sign up now"); - await page.goto('/classic-checkout'); - - const popup = await openPaypalPopup(page); - await loginIntoPaypal(popup); - popup.locator('#consentButton').click(); - - await page.click("text=Proceed to PayPal"); - - const title = await page.locator('.entry-title'); - await expect(title).toHaveText('Order received'); -}) + await page.waitForURL( '/my-account/payment-methods' ); +} ); +test( 'PayPal logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); + await page.click( 'text=Proceed to PayPal' ); + const title = await page.locator( '.entry-title' ); + await expect( title ).toHaveText( 'Order received' ); +} ); From 96f8c5ef973f74d6fb8ffc3cab74c917590762ce Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 12:02:37 +0400 Subject: [PATCH 124/357] Automatically delete PayPal Package --- modules/ppcp-compat/resources/js/tracking-compat.js | 11 +++++++++++ .../src/Integration/DhlShipmentIntegration.php | 2 -- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js index a907ddc8f..3849249ba 100644 --- a/modules/ppcp-compat/resources/js/tracking-compat.js +++ b/modules/ppcp-compat/resources/js/tracking-compat.js @@ -92,6 +92,17 @@ document.addEventListener( 'DOMContentLoaded', () => { } ); } + jQuery( document ).on( + 'mouseover mouseout', + '#dhl_delete_label', + function ( event ) { + jQuery( '#ppcp-shipment-status' ) + .val( 'CANCELLED' ) + .trigger( 'change' ); + document.querySelector( '.update_shipment' ).click(); + } + ); + if ( wcShippingTaxSyncEnabled && typeof wcShippingTaxSyncEnabled !== 'undefined' diff --git a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php index 1e8961fcb..f51d880ef 100644 --- a/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php +++ b/modules/ppcp-order-tracking/src/Integration/DhlShipmentIntegration.php @@ -76,8 +76,6 @@ class DhlShipmentIntegration implements Integration { return; } - $foo = $tracking_details; - $paypal_order = ppcp_get_paypal_order( $wc_order ); $capture_id = $this->get_paypal_order_transaction_id( $paypal_order ); $tracking_number = $tracking_details['tracking_number']; From 548c0082eb6c0851f5cc0d32f80fbc7d246a5e73 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:11:54 +0400 Subject: [PATCH 125/357] Add China to GPay support, simplify country-currency matrix --- modules/ppcp-googlepay/services.php | 834 ++---------------- .../ppcp-googlepay/src/Helper/ApmApplies.php | 42 +- 2 files changed, 105 insertions(+), 771 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index 11cbd740b..c1ef967a1 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -29,12 +29,13 @@ return array( $apm_applies = $container->get( 'googlepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency(); + return $apm_applies->for_country() && $apm_applies->for_currency(); }, 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( - $container->get( 'googlepay.supported-country-currency-matrix' ), + $container->get( 'googlepay.supported-countries' ), + $container->get( 'googlepay.supported-currencies' ), $container->get( 'api.shop.currency' ), $container->get( 'api.shop.country' ) ); @@ -81,767 +82,84 @@ return array( ), /** - * The matrix which countries and currency combinations can be used for GooglePay. + * The list of which countries can be used for GooglePay. */ - 'googlepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'googlepay.supported-countries' => static function ( ContainerInterface $container) : array { /** - * Returns which countries and currency combinations can be used for GooglePay. + * Returns which countries can be used for GooglePay. */ return apply_filters( - 'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix', + 'woocommerce_paypal_payments_googlepay_supported_countries', array( - 'AU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'AT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CA' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'HU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LV' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GB' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'US' => array( - 'AUD', - 'CAD', - 'EUR', - 'GBP', - 'JPY', - 'USD', - ), + 'AU', // Australia + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CA', // Canada + 'CN', // China + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DK', // Denmark + 'EE', // Estonia + 'FI', // Finland + 'FR', // France + 'DE', // Germany + 'GR', // Greece + 'HU', // Hungary + 'IE', // Ireland + 'IT', // Italy + 'LV', // Latvia + 'LI', // Liechtenstein + 'LT', // Lithuania + 'LU', // Luxembourg + 'MT', // Malta + 'NL', // Netherlands + 'NO', // Norway + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SK', // Slovakia + 'SI', // Slovenia + 'ES', // Spain + 'SE', // Sweden + 'US', // United States + 'GB' // United Kingdom + ) + ); + }, + + /** + * The list of which currencies can be used for GooglePay. + */ + 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + /** + * Returns which currencies can be used for GooglePay. + */ + return apply_filters( + 'woocommerce_paypal_payments_googlepay_supported_currencies', + array( + 'AUD', // Australian Dollar + 'BRL', // Brazilian Real + 'CAD', // Canadian Dollar + 'CHF', // Swiss Franc + 'CZK', // Czech Koruna + 'DKK', // Danish Krone + 'EUR', // Euro + 'GBP', // British Pound Sterling + 'HKD', // Hong Kong Dollar + 'HUF', // Hungarian Forint + 'ILS', // Israeli New Shekel + 'JPY', // Japanese Yen + 'MXN', // Mexican Peso + 'NOK', // Norwegian Krone + 'NZD', // New Zealand Dollar + 'PHP', // Philippine Peso + 'PLN', // Polish Zloty + 'SEK', // Swedish Krona + 'SGD', // Singapore Dollar + 'THB', // Thai Baht + 'TWD', // New Taiwan Dollar + 'USD' // United States Dollar ) ); }, diff --git a/modules/ppcp-googlepay/src/Helper/ApmApplies.php b/modules/ppcp-googlepay/src/Helper/ApmApplies.php index 84ad2e057..96a16b210 100644 --- a/modules/ppcp-googlepay/src/Helper/ApmApplies.php +++ b/modules/ppcp-googlepay/src/Helper/ApmApplies.php @@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Googlepay\Helper; class ApmApplies { /** - * The matrix which countries and currency combinations can be used for GooglePay. + * The list of which countries can be used for GooglePay. * * @var array */ - private $allowed_country_currency_matrix; + private $allowed_countries; + + /** + * The list of which currencies can be used for GooglePay. + * + * @var array + */ + private $allowed_currencies; /** * 3-letter currency code of the shop. @@ -39,30 +46,39 @@ class ApmApplies { /** * DccApplies constructor. * - * @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for GooglePay. + * @param array $allowed_countries The list of which countries can be used for GooglePay. + * @param array $allowed_currencies The list of which currencies can be used for GooglePay. * @param string $currency 3-letter currency code of the shop. * @param string $country 2-letter country code of the shop. */ public function __construct( - array $allowed_country_currency_matrix, + array $allowed_countries, + array $allowed_currencies, string $currency, string $country ) { - $this->allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_countries = $allowed_countries; + $this->allowed_currencies = $allowed_currencies; + $this->currency = $currency; + $this->country = $country; } /** - * Returns whether GooglePay can be used in the current country and the current currency used. + * Returns whether GooglePay can be used in the current country used. * * @return bool */ - public function for_country_currency(): bool { - if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { - return false; - } - return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); + public function for_country(): bool { + return in_array( $this->country, $this->allowed_countries, true ); + } + + /** + * Returns whether GooglePay can be used in the current currency used. + * + * @return bool + */ + public function for_currency(): bool { + return in_array( $this->currency, $this->allowed_currencies, true ); } } From a06c01ad1f631497e12e92f7c2deffdbe33b3033 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:13:56 +0400 Subject: [PATCH 126/357] phpcs fix --- modules/ppcp-googlepay/services.php | 38 ++++++++++++++--------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index c1ef967a1..e496ae629 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -25,14 +25,14 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( // If GooglePay can be configured. - 'googlepay.eligible' => static function ( ContainerInterface $container ): bool { + 'googlepay.eligible' => static function ( ContainerInterface $container ): bool { $apm_applies = $container->get( 'googlepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); return $apm_applies->for_country() && $apm_applies->for_currency(); }, - 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { + 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( $container->get( 'googlepay.supported-countries' ), $container->get( 'googlepay.supported-currencies' ), @@ -42,7 +42,7 @@ return array( }, // If GooglePay is configured and onboarded. - 'googlepay.available' => static function ( ContainerInterface $container ): bool { + 'googlepay.available' => static function ( ContainerInterface $container ): bool { if ( apply_filters( 'woocommerce_paypal_payments_googlepay_validate_product_status', true ) ) { $status = $container->get( 'googlepay.helpers.apm-product-status' ); assert( $status instanceof ApmProductStatus ); @@ -55,14 +55,14 @@ return array( }, // We assume it's a referral if we can check product status without API request failures. - 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { + 'googlepay.is_referral' => static function ( ContainerInterface $container ): bool { $status = $container->get( 'googlepay.helpers.apm-product-status' ); assert( $status instanceof ApmProductStatus ); return ! $status->has_request_failure(); }, - 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { + 'googlepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { return new AvailabilityNotice( $container->get( 'googlepay.helpers.apm-product-status' ), $container->get( 'wcgateway.is-wc-gateways-list-page' ), @@ -70,7 +70,7 @@ return array( ); }, - 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( + 'googlepay.helpers.apm-product-status' => SingletonDecorator::make( static function( ContainerInterface $container ): ApmProductStatus { return new ApmProductStatus( $container->get( 'wcgateway.settings' ), @@ -84,7 +84,7 @@ return array( /** * The list of which countries can be used for GooglePay. */ - 'googlepay.supported-countries' => static function ( ContainerInterface $container) : array { + 'googlepay.supported-countries' => static function ( ContainerInterface $container ) : array { /** * Returns which countries can be used for GooglePay. */ @@ -123,7 +123,7 @@ return array( 'ES', // Spain 'SE', // Sweden 'US', // United States - 'GB' // United Kingdom + 'GB', // United Kingdom ) ); }, @@ -131,7 +131,7 @@ return array( /** * The list of which currencies can be used for GooglePay. */ - 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + 'googlepay.supported-currencies' => static function ( ContainerInterface $container ) : array { /** * Returns which currencies can be used for GooglePay. */ @@ -159,12 +159,12 @@ return array( 'SGD', // Singapore Dollar 'THB', // Thai Baht 'TWD', // New Taiwan Dollar - 'USD' // United States Dollar + 'USD', // United States Dollar ) ); }, - 'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface { + 'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface { return new Button( $container->get( 'googlepay.url' ), $container->get( 'googlepay.sdk_url' ), @@ -178,7 +178,7 @@ return array( ); }, - 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { + 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( 'ppcp-googlepay', $container->get( 'googlepay.url' ), @@ -188,7 +188,7 @@ return array( ); }, - 'googlepay.url' => static function ( ContainerInterface $container ): string { + 'googlepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); if ( false === $path ) { return ''; @@ -199,26 +199,26 @@ return array( ); }, - 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { + 'googlepay.sdk_url' => static function ( ContainerInterface $container ): string { return 'https://pay.google.com/gp/p/js/pay.js'; }, - 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { + 'googlepay.endpoint.update-payment-data' => static function ( ContainerInterface $container ): UpdatePaymentDataEndpoint { return new UpdatePaymentDataEndpoint( $container->get( 'button.request-data' ), $container->get( 'woocommerce.logger.woocommerce' ) ); }, - 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + 'googlepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; }, - 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { + 'googlepay.enable-url-live' => static function ( ContainerInterface $container ): string { return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY'; }, - 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); if ( $state->current_state() < State::STATE_ONBOARDED ) { return ''; @@ -256,7 +256,7 @@ return array( esc_html( $button_text ) ); }, - 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { + 'googlepay.wc-gateway' => static function ( ContainerInterface $container ): GooglePayGateway { return new GooglePayGateway( $container->get( 'wcgateway.order-processor' ), $container->get( 'api.factory.paypal-checkout-url' ), From 883a5b15bcea94c4b2ccd8cfd9966f720530b61e Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:28:33 +0400 Subject: [PATCH 127/357] Add China to ApplePay support, simplify country-currency matrix --- modules/ppcp-applepay/services.php | 877 ++---------------- .../ppcp-applepay/src/Helper/ApmApplies.php | 44 +- 2 files changed, 130 insertions(+), 791 deletions(-) diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php index 6079fcb40..e4a3297dc 100644 --- a/modules/ppcp-applepay/services.php +++ b/modules/ppcp-applepay/services.php @@ -24,32 +24,33 @@ use WooCommerce\PayPalCommerce\Onboarding\State; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; return array( - 'applepay.eligible' => static function ( ContainerInterface $container ): bool { + 'applepay.eligible' => static function ( ContainerInterface $container ): bool { $apm_applies = $container->get( 'applepay.helpers.apm-applies' ); assert( $apm_applies instanceof ApmApplies ); - return $apm_applies->for_country_currency(); + return $apm_applies->for_country() && $apm_applies->for_currency(); }, - 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { + 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies { return new ApmApplies( - $container->get( 'applepay.supported-country-currency-matrix' ), + $container->get( 'applepay.supported-countries' ), + $container->get( 'applepay.supported-currencies' ), $container->get( 'api.shop.currency' ), $container->get( 'api.shop.country' ) ); }, - 'applepay.status-cache' => static function( ContainerInterface $container ): Cache { + 'applepay.status-cache' => static function( ContainerInterface $container ): Cache { return new Cache( 'ppcp-paypal-apple-status-cache' ); }, // We assume it's a referral if we can check product status without API request failures. - 'applepay.is_referral' => static function ( ContainerInterface $container ): bool { + 'applepay.is_referral' => static function ( ContainerInterface $container ): bool { $status = $container->get( 'applepay.apple-product-status' ); assert( $status instanceof AppleProductStatus ); return ! $status->has_request_failure(); }, - 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { + 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice { $settings = $container->get( 'wcgateway.settings' ); return new AvailabilityNotice( @@ -63,17 +64,17 @@ return array( ); }, - 'applepay.has_validated' => static function ( ContainerInterface $container ): bool { + 'applepay.has_validated' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'applepay_validated' ); }, - 'applepay.is_validated' => static function ( ContainerInterface $container ): bool { + 'applepay.is_validated' => static function ( ContainerInterface $container ): bool { $settings = $container->get( 'wcgateway.settings' ); return $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false; }, - 'applepay.apple-product-status' => SingletonDecorator::make( + 'applepay.apple-product-status' => SingletonDecorator::make( static function( ContainerInterface $container ): AppleProductStatus { return new AppleProductStatus( $container->get( 'wcgateway.settings' ), @@ -83,7 +84,7 @@ return array( ); } ), - 'applepay.available' => static function ( ContainerInterface $container ): bool { + 'applepay.available' => static function ( ContainerInterface $container ): bool { if ( apply_filters( 'woocommerce_paypal_payments_applepay_validate_product_status', true ) ) { $status = $container->get( 'applepay.apple-product-status' ); assert( $status instanceof AppleProductStatus ); @@ -94,10 +95,10 @@ return array( } return true; }, - 'applepay.server_supported' => static function ( ContainerInterface $container ): bool { + 'applepay.server_supported' => static function ( ContainerInterface $container ): bool { return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off'; }, - 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { + 'applepay.is_browser_supported' => static function ( ContainerInterface $container ): bool { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $user_agent = wp_unslash( $_SERVER['HTTP_USER_AGENT'] ?? '' ); if ( $user_agent ) { @@ -127,7 +128,7 @@ return array( } return false; }, - 'applepay.url' => static function ( ContainerInterface $container ): string { + 'applepay.url' => static function ( ContainerInterface $container ): string { $path = realpath( __FILE__ ); if ( false === $path ) { return ''; @@ -137,13 +138,13 @@ return array( dirname( $path, 3 ) . '/woocommerce-paypal-payments.php' ); }, - 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { + 'applepay.sdk_script_url' => static function ( ContainerInterface $container ): string { return 'https://applepay.cdn-apple.com/jsapi/v1/apple-pay-sdk.js'; }, - 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { + 'applepay.data_to_scripts' => static function ( ContainerInterface $container ): DataToAppleButtonScripts { return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) ); }, - 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { + 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton { return new ApplePayButton( $container->get( 'wcgateway.settings' ), $container->get( 'woocommerce.logger.woocommerce' ), @@ -155,7 +156,7 @@ return array( $container->get( 'button.helper.cart-products' ) ); }, - 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { + 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface { return new BlocksPaymentMethod( 'ppcp-applepay', $container->get( 'applepay.url' ), @@ -164,781 +165,103 @@ return array( $container->get( 'blocks.method' ) ); }, + /** - * The matrix which countries and currency combinations can be used for ApplePay. + * The list of which countries can be used for ApplePay. */ - 'applepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array { + 'applepay.supported-countries' => static function ( ContainerInterface $container ) : array { /** - * Returns which countries and currency combinations can be used for ApplePay. + * Returns which countries can be used for ApplePay. */ return apply_filters( - 'woocommerce_paypal_payments_applepay_supported_country_currency_matrix', + 'woocommerce_paypal_payments_applepay_supported_countries', + // phpcs:disable Squiz.Commenting.InlineComment array( - 'AU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'AT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'BG' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CA' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CY' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'CZ' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'EE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'FR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'DE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GR' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'HU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'IT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LV' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'LU' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'MT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'NL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PL' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'PT' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'RO' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SK' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SI' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'ES' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'SE' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'GB' => array( - 'AUD', - 'BRL', - 'CAD', - 'CHF', - 'CZK', - 'DKK', - 'EUR', - 'GBP', - 'HKD', - 'HUF', - 'ILS', - 'JPY', - 'MXN', - 'NOK', - 'NZD', - 'PHP', - 'PLN', - 'SEK', - 'SGD', - 'THB', - 'TWD', - 'USD', - ), - 'US' => array( - 'AUD', - 'CAD', - 'EUR', - 'GBP', - 'JPY', - 'USD', - ), + 'AU', // Australia + 'AT', // Austria + 'BE', // Belgium + 'BG', // Bulgaria + 'CA', // Canada + 'CN', // China + 'CY', // Cyprus + 'CZ', // Czech Republic + 'DK', // Denmark + 'EE', // Estonia + 'FI', // Finland + 'FR', // France + 'DE', // Germany + 'GR', // Greece + 'HU', // Hungary + 'IE', // Ireland + 'IT', // Italy + 'LV', // Latvia + 'LI', // Liechtenstein + 'LT', // Lithuania + 'LU', // Luxembourg + 'MT', // Malta + 'NL', // Netherlands + 'NO', // Norway + 'PL', // Poland + 'PT', // Portugal + 'RO', // Romania + 'SK', // Slovakia + 'SI', // Slovenia + 'ES', // Spain + 'SE', // Sweden + 'US', // United States + 'GB', // United Kingdom ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, - 'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { + /** + * The list of which currencies can be used for ApplePay. + */ + 'applepay.supported-currencies' => static function ( ContainerInterface $container ) : array { + /** + * Returns which currencies can be used for ApplePay. + */ + return apply_filters( + 'woocommerce_paypal_payments_applepay_supported_currencies', + // phpcs:disable Squiz.Commenting.InlineComment + array( + 'AUD', // Australian Dollar + 'BRL', // Brazilian Real + 'CAD', // Canadian Dollar + 'CHF', // Swiss Franc + 'CZK', // Czech Koruna + 'DKK', // Danish Krone + 'EUR', // Euro + 'GBP', // British Pound Sterling + 'HKD', // Hong Kong Dollar + 'HUF', // Hungarian Forint + 'ILS', // Israeli New Shekel + 'JPY', // Japanese Yen + 'MXN', // Mexican Peso + 'NOK', // Norwegian Krone + 'NZD', // New Zealand Dollar + 'PHP', // Philippine Peso + 'PLN', // Polish Zloty + 'SEK', // Swedish Krona + 'SGD', // Singapore Dollar + 'THB', // Thai Baht + 'TWD', // New Taiwan Dollar + 'USD', // United States Dollar + ) + // phpcs:enable Squiz.Commenting.InlineComment + ); + }, + + 'applepay.enable-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; }, - 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { + 'applepay.enable-url-live' => static function ( ContainerInterface $container ): string { return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY'; }, - 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { + 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string { $state = $container->get( 'onboarding.state' ); if ( $state->current_state() < State::STATE_ONBOARDED ) { return ''; diff --git a/modules/ppcp-applepay/src/Helper/ApmApplies.php b/modules/ppcp-applepay/src/Helper/ApmApplies.php index 0fa12420b..4deeade14 100644 --- a/modules/ppcp-applepay/src/Helper/ApmApplies.php +++ b/modules/ppcp-applepay/src/Helper/ApmApplies.php @@ -16,11 +16,18 @@ namespace WooCommerce\PayPalCommerce\Applepay\Helper; class ApmApplies { /** - * The matrix which countries and currency combinations can be used for ApplePay. + * The list of which countries can be used for ApplePay. * * @var array */ - private $allowed_country_currency_matrix; + private $allowed_countries; + + /** + * The list of which currencies can be used for ApplePay. + * + * @var array + */ + private $allowed_currencies; /** * 3-letter currency code of the shop. @@ -37,32 +44,41 @@ class ApmApplies { private $country; /** - * ApmApplies constructor. + * DccApplies constructor. * - * @param array $allowed_country_currency_matrix The matrix which countries and currency combinations can be used for ApplePay. + * @param array $allowed_countries The list of which countries can be used for ApplePay. + * @param array $allowed_currencies The list of which currencies can be used for ApplePay. * @param string $currency 3-letter currency code of the shop. * @param string $country 2-letter country code of the shop. */ public function __construct( - array $allowed_country_currency_matrix, + array $allowed_countries, + array $allowed_currencies, string $currency, string $country ) { - $this->allowed_country_currency_matrix = $allowed_country_currency_matrix; - $this->currency = $currency; - $this->country = $country; + $this->allowed_countries = $allowed_countries; + $this->allowed_currencies = $allowed_currencies; + $this->currency = $currency; + $this->country = $country; } /** - * Returns whether ApplePay can be used in the current country and the current currency used. + * Returns whether ApplePay can be used in the current country used. * * @return bool */ - public function for_country_currency(): bool { - if ( ! in_array( $this->country, array_keys( $this->allowed_country_currency_matrix ), true ) ) { - return false; - } - return in_array( $this->currency, $this->allowed_country_currency_matrix[ $this->country ], true ); + public function for_country(): bool { + return in_array( $this->country, $this->allowed_countries, true ); + } + + /** + * Returns whether ApplePay can be used in the current currency used. + * + * @return bool + */ + public function for_currency(): bool { + return in_array( $this->currency, $this->allowed_currencies, true ); } } From 5252888721d679c997394087996ab71156cdadb1 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 13:29:48 +0400 Subject: [PATCH 128/357] phpcs fix --- modules/ppcp-googlepay/services.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php index e496ae629..110b56937 100644 --- a/modules/ppcp-googlepay/services.php +++ b/modules/ppcp-googlepay/services.php @@ -90,6 +90,7 @@ return array( */ return apply_filters( 'woocommerce_paypal_payments_googlepay_supported_countries', + // phpcs:disable Squiz.Commenting.InlineComment array( 'AU', // Australia 'AT', // Austria @@ -125,6 +126,7 @@ return array( 'US', // United States 'GB', // United Kingdom ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, @@ -137,6 +139,7 @@ return array( */ return apply_filters( 'woocommerce_paypal_payments_googlepay_supported_currencies', + // phpcs:disable Squiz.Commenting.InlineComment array( 'AUD', // Australian Dollar 'BRL', // Brazilian Real @@ -161,6 +164,7 @@ return array( 'TWD', // New Taiwan Dollar 'USD', // United States Dollar ) + // phpcs:enable Squiz.Commenting.InlineComment ); }, From 06378c34c880deeae455af728409437a01d969c2 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Mon, 29 Jul 2024 14:15:35 +0400 Subject: [PATCH 129/357] Add Amex support for ACDC China --- modules/ppcp-api-client/services.php | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index dbdf2efe3..241240ce2 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1443,6 +1443,7 @@ return array( 'CN' => array( 'mastercard' => array(), 'visa' => array(), + 'amex' => array(), ), 'CY' => array( 'mastercard' => array(), From 6f7270c35bc6c9ab3c53e02373e54b8bac2b971d Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 12:18:41 +0200 Subject: [PATCH 130/357] Fix card fields rendered multiple times --- .../ppcp-card-fields/resources/js/Render.js | 7 ++++++ tests/Playwright/playwright.config.js | 2 +- .../tests/free-trial-subscriptions.js | 22 +++++++++++++++++++ tests/Playwright/tests/place-order.spec.js | 21 +++++++++--------- .../tests/save-payment-methods.spec.js | 19 ---------------- 5 files changed, 41 insertions(+), 30 deletions(-) create mode 100644 tests/Playwright/tests/free-trial-subscriptions.js diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js index 8b46f17d7..a77874159 100644 --- a/modules/ppcp-card-fields/resources/js/Render.js +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -1,6 +1,13 @@ import { cardFieldStyles } from './CardFieldsHelper'; +let fieldsRendered = false; + export function renderFields( cardFields ) { + if ( fieldsRendered === true ) { + return; + } + fieldsRendered = true; + const nameField = document.getElementById( 'ppcp-credit-card-gateway-card-name' ); diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js index 7273db2a0..402a04bb4 100644 --- a/tests/Playwright/playwright.config.js +++ b/tests/Playwright/playwright.config.js @@ -7,7 +7,7 @@ require( 'dotenv' ).config( { path: '.env' } ); * @see https://playwright.dev/docs/test-configuration */ module.exports = defineConfig( { - timeout: 60000, + timeout: 30000, testDir: './tests', /* Run tests in files in parallel */ fullyParallel: false, diff --git a/tests/Playwright/tests/free-trial-subscriptions.js b/tests/Playwright/tests/free-trial-subscriptions.js new file mode 100644 index 000000000..c4cc68baf --- /dev/null +++ b/tests/Playwright/tests/free-trial-subscriptions.js @@ -0,0 +1,22 @@ +const { test, expect } = require( '@playwright/test' ); +const { loginAsCustomer } = require( './utils/user' ); +const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); + +test( 'PayPal logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + + const popup = await openPaypalPopup( page ); + await loginIntoPaypal( popup ); + popup.locator( '#consentButton' ).click(); + + await page.click( 'text=Proceed to PayPal' ); + + const title = await page.locator( '.entry-title' ); + await expect( title ).toHaveText( 'Order received' ); +} ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js index 086d8e9c7..d6d9a40cf 100644 --- a/tests/Playwright/tests/place-order.spec.js +++ b/tests/Playwright/tests/place-order.spec.js @@ -93,19 +93,20 @@ test.describe( 'Classic checkout', () => { await page.click( 'text=Credit Cards' ); - const creditCardNumber = page - .frameLocator( '#braintree-hosted-field-number' ) - .locator( '#credit-card-number' ); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - const expirationDate = page - .frameLocator( '#braintree-hosted-field-expirationDate' ) - .locator( '#expiration' ); - await expirationDate.fill( CREDIT_CARD_EXPIRATION ); + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( CREDIT_CARD_EXPIRATION ); - const cvv = page - .frameLocator( '#braintree-hosted-field-cvv' ) - .locator( '#cvv' ); + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); await cvv.fill( CREDIT_CARD_CVV ); await Promise.all( [ diff --git a/tests/Playwright/tests/save-payment-methods.spec.js b/tests/Playwright/tests/save-payment-methods.spec.js index 092bd8ee4..077207141 100644 --- a/tests/Playwright/tests/save-payment-methods.spec.js +++ b/tests/Playwright/tests/save-payment-methods.spec.js @@ -82,22 +82,3 @@ test( 'ACDC add payment method', async ( { page } ) => { await page.waitForURL( '/my-account/payment-methods' ); } ); - -test( 'PayPal logged-in user free trial subscription without payment token', async ( { - page, -} ) => { - await loginAsCustomer( page ); - - await page.goto( '/product/free-trial' ); - await page.click( 'text=Sign up now' ); - await page.goto( '/classic-checkout' ); - - const popup = await openPaypalPopup( page ); - await loginIntoPaypal( popup ); - popup.locator( '#consentButton' ).click(); - - await page.click( 'text=Proceed to PayPal' ); - - const title = await page.locator( '.entry-title' ); - await expect( title ).toHaveText( 'Order received' ); -} ); From a7c43ab0d125931220cf2ea1ab4220ba64e9fc29 Mon Sep 17 00:00:00 2001 From: Narek Zakarian Date: Mon, 29 Jul 2024 15:55:29 +0400 Subject: [PATCH 131/357] Do not add tex info into order meta during order creation --- modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php | 7 ------- 1 file changed, 7 deletions(-) diff --git a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php index 5ff2b02e6..888a567c5 100644 --- a/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php +++ b/modules/ppcp-button/src/Helper/WooCommerceOrderCreator.php @@ -303,13 +303,6 @@ class WooCommerceOrderCreator { $item->set_tax_class( $product->get_tax_class() ); $item->set_total_tax( (float) array_sum( $taxes ) ); - - foreach ( $taxes as $tax_rate_id => $tax_amount ) { - if ( $tax_amount > 0 ) { - $item->add_meta_data( 'tax_rate_id', $tax_rate_id, true ); - $item->add_meta_data( 'tax_amount', $tax_amount, true ); - } - } } /** From 8bda629df5d8d8c21004980adbbdf1e525db9672 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 14:20:27 +0200 Subject: [PATCH 132/357] Move pw tests into multiple files --- .../Playwright/tests/apms-place-order.spec.js | 76 ++++++ .../tests/blocks-place-order.spec.js | 109 ++++++++ .../tests/classic-place-order.spec.js | 98 +++++++ ...ns.js => free-trial-subscriptions.spec.js} | 9 +- tests/Playwright/tests/place-order.spec.js | 243 ------------------ 5 files changed, 289 insertions(+), 246 deletions(-) create mode 100644 tests/Playwright/tests/apms-place-order.spec.js create mode 100644 tests/Playwright/tests/blocks-place-order.spec.js create mode 100644 tests/Playwright/tests/classic-place-order.spec.js rename tests/Playwright/tests/{free-trial-subscriptions.js => free-trial-subscriptions.spec.js} (77%) delete mode 100644 tests/Playwright/tests/place-order.spec.js diff --git a/tests/Playwright/tests/apms-place-order.spec.js b/tests/Playwright/tests/apms-place-order.spec.js new file mode 100644 index 000000000..f12337840 --- /dev/null +++ b/tests/Playwright/tests/apms-place-order.spec.js @@ -0,0 +1,76 @@ +const { test, expect } = require( '@playwright/test' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, + acceptTerms, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + completePaypalPayment, +} = require( './utils/paypal-popup' ); + +const { PRODUCT_ID, CHECKOUT_URL, CART_URL, APM_ID } = process.env; + +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); +} + +async function completeContinuation( page ) { + await expectContinuation( page ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); +} + +test( 'PayPal APM button place order', async ( { page } ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( CHECKOUT_URL ); + + await fillCheckoutForm( page ); + + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); + + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'PayPal APM button place order when redirect fails', async ( { + page, +} ) => { + await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( CHECKOUT_URL ); + + await fillCheckoutForm( page ); + + await page.evaluate( 'PayPalCommerceGateway.ajax.approve_order = null' ); + + const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); + + await popup.getByText( 'Continue', { exact: true } ).click(); + await completePaypalPayment( popup, { + selector: '[name="Successful"]', + } ); + + await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); + + await page.reload(); + await expectContinuation( page ); + + await acceptTerms( page ); + + await completeContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); diff --git a/tests/Playwright/tests/blocks-place-order.spec.js b/tests/Playwright/tests/blocks-place-order.spec.js new file mode 100644 index 000000000..efb0cf38b --- /dev/null +++ b/tests/Playwright/tests/blocks-place-order.spec.js @@ -0,0 +1,109 @@ +const { expect, test } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, + waitForPaypalShippingList, +} = require( './utils/paypal-popup' ); +const { expectOrderReceivedPage } = require( './utils/checkout' ); + +const { + PRODUCT_ID, + BLOCK_CHECKOUT_URL, + BLOCK_CHECKOUT_PAGE_ID, + BLOCK_CART_URL, +} = process.env; + +async function completeBlockContinuation( page ) { + await expect( + page.locator( '#radio-control-wc-payment-method-options-ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); + + await Promise.all( + page.waitForNavigation(), + page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click() + ); +} + +test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp option update woocommerce_checkout_page_id ' + + BLOCK_CHECKOUT_PAGE_ID + ); + await serverExec( + 'wp pcp settings update blocks_final_review_enabled true' + ); +} ); + +test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( BLOCK_CHECKOUT_URL ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await completePaypalPayment( popup ); + + await completeBlockContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await completePaypalPayment( popup ); + + await completeBlockContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); + +test.describe( 'Without review', () => { + test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled false' + ); + } ); + + test( 'PayPal express block checkout', async ( { page } ) => { + await page.goto( '?add-to-cart=' + PRODUCT_ID ); + + await page.goto( BLOCK_CHECKOUT_URL ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await waitForPaypalShippingList( popup ); + + await completePaypalPayment( popup ); + + await expectOrderReceivedPage( page ); + } ); + + test( 'PayPal express block cart', async ( { page } ) => { + await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await waitForPaypalShippingList( popup ); + + await completePaypalPayment( popup ); + + await expectOrderReceivedPage( page ); + } ); +} ); diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js new file mode 100644 index 000000000..9a9bcae37 --- /dev/null +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -0,0 +1,98 @@ +const { test, expect } = require( '@playwright/test' ); +const { serverExec } = require( './utils/server' ); +const { + fillCheckoutForm, + expectOrderReceivedPage, +} = require( './utils/checkout' ); +const { + openPaypalPopup, + loginIntoPaypal, + completePaypalPayment, +} = require( './utils/paypal-popup' ); + +const { + CREDIT_CARD_NUMBER, + CREDIT_CARD_EXPIRATION, + CREDIT_CARD_CVV, + PRODUCT_URL, + CHECKOUT_URL, + CHECKOUT_PAGE_ID, +} = process.env; + +async function expectContinuation( page ) { + await expect( + page.locator( '#payment_method_ppcp-gateway' ) + ).toBeChecked(); + + await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); +} + +async function completeContinuation( page ) { + await expectContinuation( page ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '#place_order' ).click(), + ] ); +} + +test.beforeAll( async ( { browser } ) => { + await serverExec( + 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID + ); +} ); + +test( 'PayPal button place order from Product page', async ( { page } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled true' + ); + + await page.goto( PRODUCT_URL ); + + const popup = await openPaypalPopup( page ); + + await loginIntoPaypal( popup ); + + await completePaypalPayment( popup ); + + await fillCheckoutForm( page ); + + await completeContinuation( page ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { + page, +} ) => { + await page.goto( PRODUCT_URL ); + await page.locator( '.single_add_to_cart_button' ).click(); + + await page.goto( CHECKOUT_URL ); + await fillCheckoutForm( page ); + + await page.click( 'text=Credit Cards' ); + + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( CREDIT_CARD_EXPIRATION ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); + + await expectOrderReceivedPage( page ); +} ); diff --git a/tests/Playwright/tests/free-trial-subscriptions.js b/tests/Playwright/tests/free-trial-subscriptions.spec.js similarity index 77% rename from tests/Playwright/tests/free-trial-subscriptions.js rename to tests/Playwright/tests/free-trial-subscriptions.spec.js index c4cc68baf..8254d3c76 100644 --- a/tests/Playwright/tests/free-trial-subscriptions.js +++ b/tests/Playwright/tests/free-trial-subscriptions.spec.js @@ -1,10 +1,15 @@ const { test, expect } = require( '@playwright/test' ); const { loginAsCustomer } = require( './utils/user' ); const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); +const { serverExec } = require( './utils/server' ); -test( 'PayPal logged-in user free trial subscription without payment token', async ( { +test( 'PayPal logged-in user free trial subscription without payment token with shipping callback enabled', async ( { page, } ) => { + await serverExec( + 'wp pcp settings update blocks_final_review_enabled false' + ); + await loginAsCustomer( page ); await page.goto( '/product/free-trial' ); @@ -15,8 +20,6 @@ test( 'PayPal logged-in user free trial subscription without payment token', asy await loginIntoPaypal( popup ); popup.locator( '#consentButton' ).click(); - await page.click( 'text=Proceed to PayPal' ); - const title = await page.locator( '.entry-title' ); await expect( title ).toHaveText( 'Order received' ); } ); diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js deleted file mode 100644 index d6d9a40cf..000000000 --- a/tests/Playwright/tests/place-order.spec.js +++ /dev/null @@ -1,243 +0,0 @@ -const { test, expect } = require( '@playwright/test' ); -const { serverExec } = require( './utils/server' ); -const { - fillCheckoutForm, - expectOrderReceivedPage, - acceptTerms, -} = require( './utils/checkout' ); -const { - openPaypalPopup, - loginIntoPaypal, - waitForPaypalShippingList, - completePaypalPayment, -} = require( './utils/paypal-popup' ); - -const { - CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, - CREDIT_CARD_CVV, - PRODUCT_URL, - PRODUCT_ID, - CHECKOUT_URL, - CHECKOUT_PAGE_ID, - CART_URL, - BLOCK_CHECKOUT_URL, - BLOCK_CHECKOUT_PAGE_ID, - BLOCK_CART_URL, - APM_ID, -} = process.env; - -async function completeBlockContinuation( page ) { - await expect( - page.locator( '#radio-control-wc-payment-method-options-ppcp-gateway' ) - ).toBeChecked(); - - await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); - - await Promise.all( - page.waitForNavigation(), - page - .locator( '.wc-block-components-checkout-place-order-button' ) - .click() - ); -} - -async function expectContinuation( page ) { - await expect( - page.locator( '#payment_method_ppcp-gateway' ) - ).toBeChecked(); - - await expect( page.locator( '.component-frame' ) ).toHaveCount( 0 ); -} - -async function completeContinuation( page ) { - await expectContinuation( page ); - - await Promise.all( [ - page.waitForNavigation(), - page.locator( '#place_order' ).click(), - ] ); -} - -test.describe( 'Classic checkout', () => { - test.beforeAll( async ( { browser } ) => { - await serverExec( - 'wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID - ); - } ); - - test( 'PayPal button place order from Product page', async ( { page } ) => { - await page.goto( PRODUCT_URL ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await completePaypalPayment( popup ); - - await fillCheckoutForm( page ); - - await completeContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { - page, - } ) => { - await page.goto( PRODUCT_URL ); - await page.locator( '.single_add_to_cart_button' ).click(); - - await page.goto( CHECKOUT_URL ); - await fillCheckoutForm( page ); - - await page.click( 'text=Credit Cards' ); - - const creditCardNumber = await page - .frameLocator( '[title="paypal_card_number_field"]' ) - .locator( '.card-field-number' ); - await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - - const expirationDate = await page - .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) - .locator( 'input.card-field-expiry' ); - await expirationDate.click(); - await page.keyboard.type( CREDIT_CARD_EXPIRATION ); - - const cvv = await page - .frameLocator( '[title="paypal_card_cvv_field"]' ) - .locator( '.card-field-cvv' ); - await cvv.fill( CREDIT_CARD_CVV ); - - await Promise.all( [ - page.waitForNavigation(), - page.locator( '.ppcp-dcc-order-button' ).click(), - ] ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal APM button place order', async ( { page } ) => { - await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( CHECKOUT_URL ); - - await fillCheckoutForm( page ); - - const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - - await popup.getByText( 'Continue', { exact: true } ).click(); - await completePaypalPayment( popup, { - selector: '[name="Successful"]', - } ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal APM button place order when redirect fails', async ( { - page, - } ) => { - await page.goto( CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( CHECKOUT_URL ); - - await fillCheckoutForm( page ); - - await page.evaluate( - 'PayPalCommerceGateway.ajax.approve_order = null' - ); - - const popup = await openPaypalPopup( page, { fundingSource: APM_ID } ); - - await popup.getByText( 'Continue', { exact: true } ).click(); - await completePaypalPayment( popup, { - selector: '[name="Successful"]', - } ); - - await expect( page.locator( '.woocommerce-error' ) ).toBeVisible(); - - await page.reload(); - await expectContinuation( page ); - - await acceptTerms( page ); - - await completeContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); -} ); - -test.describe( 'Block checkout', () => { - test.beforeAll( async ( { browser } ) => { - // await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID); - // await serverExec('wp pcp settings update blocks_final_review_enabled true'); - } ); - - test( 'PayPal express block checkout', async ( { page } ) => { - await page.goto( '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( BLOCK_CHECKOUT_URL ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await completePaypalPayment( popup ); - - await completeBlockContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal express block cart', async ( { page } ) => { - await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await completePaypalPayment( popup ); - - await completeBlockContinuation( page ); - - await expectOrderReceivedPage( page ); - } ); - - test.describe( 'Without review', () => { - test.beforeAll( async ( { browser } ) => { - await serverExec( - 'wp pcp settings update blocks_final_review_enabled false' - ); - } ); - - test( 'PayPal express block checkout', async ( { page } ) => { - await page.goto( '?add-to-cart=' + PRODUCT_ID ); - - await page.goto( BLOCK_CHECKOUT_URL ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await waitForPaypalShippingList( popup ); - - await completePaypalPayment( popup ); - - await expectOrderReceivedPage( page ); - } ); - - test( 'PayPal express block cart', async ( { page } ) => { - await page.goto( BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID ); - - const popup = await openPaypalPopup( page ); - - await loginIntoPaypal( popup ); - - await waitForPaypalShippingList( popup ); - - await completePaypalPayment( popup ); - - await expectOrderReceivedPage( page ); - } ); - } ); -} ); From 5e5c99bc7d09ce034159b26b15a9e5532bd17a1f Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 15:56:45 +0200 Subject: [PATCH 133/357] Ensure field container is empty before render card field --- modules/ppcp-card-fields/resources/js/Render.js | 15 ++++----------- .../Playwright/tests/classic-place-order.spec.js | 2 +- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-card-fields/resources/js/Render.js b/modules/ppcp-card-fields/resources/js/Render.js index a77874159..9a35ff449 100644 --- a/modules/ppcp-card-fields/resources/js/Render.js +++ b/modules/ppcp-card-fields/resources/js/Render.js @@ -1,17 +1,10 @@ import { cardFieldStyles } from './CardFieldsHelper'; -let fieldsRendered = false; - export function renderFields( cardFields ) { - if ( fieldsRendered === true ) { - return; - } - fieldsRendered = true; - const nameField = document.getElementById( 'ppcp-credit-card-gateway-card-name' ); - if ( nameField ) { + if ( nameField && nameField.hidden !== true ) { const styles = cardFieldStyles( nameField ); cardFields .NameField( { style: { input: styles } } ) @@ -22,7 +15,7 @@ export function renderFields( cardFields ) { const numberField = document.getElementById( 'ppcp-credit-card-gateway-card-number' ); - if ( numberField ) { + if ( numberField && numberField.hidden !== true ) { const styles = cardFieldStyles( numberField ); cardFields .NumberField( { style: { input: styles } } ) @@ -33,7 +26,7 @@ export function renderFields( cardFields ) { const expiryField = document.getElementById( 'ppcp-credit-card-gateway-card-expiry' ); - if ( expiryField ) { + if ( expiryField && expiryField.hidden !== true ) { const styles = cardFieldStyles( expiryField ); cardFields .ExpiryField( { style: { input: styles } } ) @@ -44,7 +37,7 @@ export function renderFields( cardFields ) { const cvvField = document.getElementById( 'ppcp-credit-card-gateway-card-cvc' ); - if ( cvvField ) { + if ( cvvField && cvvField.hidden !== true ) { const styles = cardFieldStyles( cvvField ); cardFields .CVVField( { style: { input: styles } } ) diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js index 9a9bcae37..f73a8a476 100644 --- a/tests/Playwright/tests/classic-place-order.spec.js +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -82,7 +82,7 @@ test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) .locator( 'input.card-field-expiry' ); await expirationDate.click(); - await page.keyboard.type( CREDIT_CARD_EXPIRATION ); + await page.keyboard.type( '01/42' ); const cvv = await page .frameLocator( '[title="paypal_card_cvv_field"]' ) From 8afa7e34dc9342c99817afa5e08fe68329053521 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 16:20:47 +0200 Subject: [PATCH 134/357] =?UTF-8?q?=F0=9F=90=9B=20Prevent=20duplicate=20pa?= =?UTF-8?q?yment=20button=20instances?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 37 +++++++++++++++++-- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 87bc642f0..723f9dd8c 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -5,6 +5,19 @@ import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; class GooglepayButton { + /** + * Reference to the payment button created by this instance. + * + * @type {HTMLElement} + */ + #button; + + /** + * Client reference, provided by the Google Pay JS SDK. + * @see https://developers.google.com/pay/api/web/reference/client + */ + paymentsClient = null; + constructor( context, externalHandler, @@ -22,8 +35,6 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.paymentsClient = null; - this.log = function () { if ( this.buttonConfig.is_debug ) { //console.log('[GooglePayButton]', ...arguments); @@ -235,13 +246,19 @@ class GooglepayButton { const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); this.waitForWrapper( wrapper, () => { + // Prevent duplicate payment buttons. + this.removeButton(); + jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); if ( ppcpStyle.height ) { jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); } - const button = this.paymentsClient.createButton( { + /** + * @see https://developers.google.com/pay/api/web/reference/client#createButton + */ + this.#button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), allowedPaymentMethods: [ baseCardPaymentMethod ], buttonColor: buttonStyle.color || 'black', @@ -250,10 +267,22 @@ class GooglepayButton { buttonSizeMode: 'fill', } ); - jQuery( wrapper ).append( button ); + jQuery( wrapper ).append( this.#button ); } ); } + /** + * Removes the payment button that was injected via addButton() + */ + removeButton() { + if ( ! this.#button ) { + return; + } + + this.#button.remove(); + this.#button = null; + } + addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { const button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), From d0b8f4ccba261b0201bc62db4020bda655bfa9bc Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 29 Jul 2024 16:28:22 +0200 Subject: [PATCH 135/357] Add test for card block free trial without saved payments --- .../tests/classic-place-order.spec.js | 11 ++- .../tests/free-trial-subscriptions.spec.js | 78 ++++++++++++++++++- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/tests/Playwright/tests/classic-place-order.spec.js b/tests/Playwright/tests/classic-place-order.spec.js index f73a8a476..22cc5055d 100644 --- a/tests/Playwright/tests/classic-place-order.spec.js +++ b/tests/Playwright/tests/classic-place-order.spec.js @@ -12,7 +12,6 @@ const { const { CREDIT_CARD_NUMBER, - CREDIT_CARD_EXPIRATION, CREDIT_CARD_CVV, PRODUCT_URL, CHECKOUT_URL, @@ -73,17 +72,17 @@ test( 'Advanced Credit and Debit Card place order from Checkout page', async ( { await page.click( 'text=Credit Cards' ); - const creditCardNumber = await page - .frameLocator( '[title="paypal_card_number_field"]' ) - .locator( '.card-field-number' ); - await creditCardNumber.fill( CREDIT_CARD_NUMBER ); - const expirationDate = await page .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) .locator( 'input.card-field-expiry' ); await expirationDate.click(); await page.keyboard.type( '01/42' ); + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + const cvv = await page .frameLocator( '[title="paypal_card_cvv_field"]' ) .locator( '.card-field-cvv' ); diff --git a/tests/Playwright/tests/free-trial-subscriptions.spec.js b/tests/Playwright/tests/free-trial-subscriptions.spec.js index 8254d3c76..8451884fd 100644 --- a/tests/Playwright/tests/free-trial-subscriptions.spec.js +++ b/tests/Playwright/tests/free-trial-subscriptions.spec.js @@ -2,6 +2,9 @@ const { test, expect } = require( '@playwright/test' ); const { loginAsCustomer } = require( './utils/user' ); const { openPaypalPopup, loginIntoPaypal } = require( './utils/paypal-popup' ); const { serverExec } = require( './utils/server' ); +const { expectOrderReceivedPage } = require( './utils/checkout' ); + +const { CREDIT_CARD_NUMBER, CREDIT_CARD_CVV } = process.env; test( 'PayPal logged-in user free trial subscription without payment token with shipping callback enabled', async ( { page, @@ -11,7 +14,6 @@ test( 'PayPal logged-in user free trial subscription without payment token with ); await loginAsCustomer( page ); - await page.goto( '/product/free-trial' ); await page.click( 'text=Sign up now' ); await page.goto( '/classic-checkout' ); @@ -20,6 +22,76 @@ test( 'PayPal logged-in user free trial subscription without payment token with await loginIntoPaypal( popup ); popup.locator( '#consentButton' ).click(); - const title = await page.locator( '.entry-title' ); - await expect( title ).toHaveText( 'Order received' ); + await page.waitForURL( '**/order-received/**' ); +} ); + +test( 'ACDC logged-in user free trial subscription without payment token', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/classic-checkout' ); + + await page.click( 'text=Credit Cards' ); + + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '01/42' ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await Promise.all( [ + page.waitForNavigation(), + page.locator( '.ppcp-dcc-order-button' ).click(), + ] ); + + await expectOrderReceivedPage( page ); +} ); + +test( 'ACDC purchase free trial in Block checkout page as logged-in without saved card payments', async ( { + page, +} ) => { + await loginAsCustomer( page ); + await page.goto( '/product/free-trial' ); + await page.click( 'text=Sign up now' ); + await page.goto( '/checkout' ); + + await page + .locator( + '#radio-control-wc-payment-method-options-ppcp-credit-card-gateway' + ) + .click(); + + const expirationDate = await page + .frameLocator( 'iframe[title="paypal_card_expiry_field"]' ) + .locator( 'input.card-field-expiry' ); + await expirationDate.click(); + await page.keyboard.type( '01/42' ); + + const creditCardNumber = await page + .frameLocator( '[title="paypal_card_number_field"]' ) + .locator( '.card-field-number' ); + await creditCardNumber.fill( CREDIT_CARD_NUMBER ); + + const cvv = await page + .frameLocator( '[title="paypal_card_cvv_field"]' ) + .locator( '.card-field-cvv' ); + await cvv.fill( CREDIT_CARD_CVV ); + + await page + .locator( '.wc-block-components-checkout-place-order-button' ) + .click(); + + await page.waitForURL( '**/order-received/**' ); } ); From 1e5b6d5a210252b6f0f49d4d7c52e6ba0ff29337 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 18:05:08 +0200 Subject: [PATCH 136/357] =?UTF-8?q?=E2=9C=A8=20Improve=20debug-logging=20f?= =?UTF-8?q?or=20GooglePayButton=20events?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implement same logic as we use for the ApplePayButton --- .../resources/js/GooglepayButton.js | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 723f9dd8c..aa2c27921 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -25,6 +25,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { + this._initDebug( !! buttonConfig?.is_debug, context ); + apmButtonsInit( ppcpConfig ); this.isInitialized = false; @@ -34,12 +36,35 @@ class GooglepayButton { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + } - this.log = function () { - if ( this.buttonConfig.is_debug ) { - //console.log('[GooglePayButton]', ...arguments); - } + /** + * NOOP log function to avoid errors when debugging is disabled. + */ + log() {} + + /** + * Enables debugging tools, when the button's is_debug flag is set. + * + * @param {boolean} enableDebugging If debugging features should be enabled for this instance. + * @param {string} context Used to make the instance accessible via the global debug object. + * @private + */ + _initDebug( enableDebugging, context ) { + if ( ! enableDebugging ) { + return; + } + + document.ppcpGooglepayButtons = document.ppcpGooglepayButtons || {}; + document.ppcpGooglepayButtons[ context ] = this; + + this.log = ( ...args ) => { + console.log( `[GooglePayButton | ${ context }]`, ...args ); }; + + document.addEventListener( 'ppcp-googlepay-debug', () => { + this.log( this ); + } ); } init( config, transactionInfo ) { From 8814b1f6368d997a5191269ccaad171e819c094e Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 18:30:03 +0200 Subject: [PATCH 137/357] =?UTF-8?q?=F0=9F=92=A1=20Improve/adjust=20debug?= =?UTF-8?q?=20logging?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 40 ++++++++++--------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index aa2c27921..9ee34fa0f 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -36,6 +36,8 @@ class GooglepayButton { this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + + this.log( 'Create instance' ); } /** @@ -47,7 +49,8 @@ class GooglepayButton { * Enables debugging tools, when the button's is_debug flag is set. * * @param {boolean} enableDebugging If debugging features should be enabled for this instance. - * @param {string} context Used to make the instance accessible via the global debug object. + * @param {string} context Used to make the instance accessible via the global debug + * object. * @private */ _initDebug( enableDebugging, context ) { @@ -81,6 +84,8 @@ class GooglepayButton { return; } + this.log( 'Init' ); + this.googlePayConfig = config; this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; @@ -218,9 +223,13 @@ class GooglepayButton { this.onPaymentDataChanged.bind( this ); } + /** + * Consider providing merchant info here: + * + * @see https://developers.google.com/pay/api/web/reference/request-objects#PaymentOptions + */ this.paymentsClient = new google.payments.api.PaymentsClient( { environment: this.buttonConfig.environment, - // add merchant info maybe paymentDataCallbacks: callbacks, } ); } @@ -266,7 +275,7 @@ class GooglepayButton { * @param baseCardPaymentMethod */ addButton( baseCardPaymentMethod ) { - this.log( 'addButton', this.context ); + this.log( 'addButton' ); const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); @@ -344,17 +353,14 @@ class GooglepayButton { * Show Google Pay payment sheet when Google Pay payment button is clicked */ onButtonClick() { - this.log( 'onButtonClick', this.context ); + this.log( 'onButtonClick' ); const paymentDataRequest = this.paymentDataRequest(); - this.log( - 'onButtonClick: paymentDataRequest', - paymentDataRequest, - this.context - ); + this.log( 'onButtonClick: paymentDataRequest', paymentDataRequest ); - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. + // Do this on another place like on create order endpoint handler. + window.ppcpFundingSource = 'googlepay'; this.paymentsClient.loadPaymentData( paymentDataRequest ); } @@ -404,8 +410,7 @@ class GooglepayButton { } onPaymentDataChanged( paymentData ) { - this.log( 'onPaymentDataChanged', this.context ); - this.log( 'paymentData', paymentData ); + this.log( 'onPaymentDataChanged', paymentData ); return new Promise( async ( resolve, reject ) => { try { @@ -478,18 +483,18 @@ class GooglepayButton { //------------------------ onPaymentAuthorized( paymentData ) { - this.log( 'onPaymentAuthorized', this.context ); + this.log( 'onPaymentAuthorized' ); return this.processPayment( paymentData ); } async processPayment( paymentData ) { - this.log( 'processPayment', this.context ); + this.log( 'processPayment' ); return new Promise( async ( resolve, reject ) => { try { const id = await this.contextHandler.createOrder(); - this.log( 'processPayment: createOrder', id, this.context ); + this.log( 'processPayment: createOrder', id ); const confirmOrderResponse = await widgetBuilder.paypal .Googlepay() @@ -500,8 +505,7 @@ class GooglepayButton { this.log( 'processPayment: confirmOrder', - confirmOrderResponse, - this.context + confirmOrderResponse ); /** Capture the Order on the Server */ @@ -571,7 +575,7 @@ class GooglepayButton { }; } - this.log( 'processPaymentResponse', response, this.context ); + this.log( 'processPaymentResponse', response ); return response; } From 8286085f594372e4aba5b02aa4e566ddd01ce734 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 29 Jul 2024 21:16:53 +0200 Subject: [PATCH 138/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Apply=20latest=20J?= =?UTF-8?q?S=20structure=20from=20ApplePay=20Gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Partially addresses the known display bug - Simplifies maintainance between both gateways - Reduces component-internal redundancies --- .../resources/js/GooglepayButton.js | 405 ++++++++++++------ modules/ppcp-googlepay/src/Assets/Button.php | 17 + 2 files changed, 299 insertions(+), 123 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9ee34fa0f..9571e9735 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,16 +1,61 @@ +/* global google */ +/* global jQuery */ + import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +/** + * Plugin-specific styling. + * + * Note that most properties of this object do not apply to the Google Pay button. + * + * @typedef {Object} PPCPStyle + * @property {string} shape - Outline shape. + * @property {?number} height - Button height in pixel. + */ + +/** + * Style options that are defined by the Google Pay SDK and are required to render the button. + * + * @typedef {Object} GooglePayStyle + * @property {string} type - Defines the button label. + * @property {string} color - Button color + * @property {string} language - The locale; an empty string will apply the user-agent's language. + */ + +/** + * List of valid context values that the button can have. + * + * @type {Object} + */ +const CONTEXT = { + Product: 'product', + Cart: 'cart', + Checkout: 'checkout', + PayNow: 'pay-now', + MiniCart: 'mini-cart', + BlockCart: 'cart-block', + BlockCheckout: 'checkout-block', + Preview: 'preview', + // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], +}; + class GooglepayButton { + #wrapperId = ''; + #ppcpButtonWrapperId = ''; + /** - * Reference to the payment button created by this instance. + * Whether the payment button is initialized. * - * @type {HTMLElement} + * @type {boolean} */ - #button; + #isInitialized = false; /** * Client reference, provided by the Google Pay JS SDK. @@ -29,8 +74,6 @@ class GooglepayButton { apmButtonsInit( ppcpConfig ); - this.isInitialized = false; - this.context = context; this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; @@ -54,7 +97,7 @@ class GooglepayButton { * @private */ _initDebug( enableDebugging, context ) { - if ( ! enableDebugging ) { + if ( ! enableDebugging || this.#isInitialized ) { return; } @@ -70,11 +113,164 @@ class GooglepayButton { } ); } + /** + * Determines if the current payment button should be rendered as a stand-alone gateway. + * The return value `false` usually means, that the payment button is bundled with all available + * payment buttons. + * + * The decision depends on the button context (placement) and the plugin settings. + * + * @return {boolean} True, if the current button represents a stand-alone gateway. + */ + get isSeparateGateway() { + return ( + this.buttonConfig.is_wc_gateway_enabled && + CONTEXT.Gateways.includes( this.context ) + ); + } + + /** + * Returns the wrapper ID for the current button context. + * The ID varies for the MiniCart context. + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get wrapperId() { + if ( ! this.#wrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.buttonConfig.button.mini_cart_wrapper; + } else if ( this.isSeparateGateway ) { + id = 'ppc-button-ppcp-googlepay'; + } else { + id = this.buttonConfig.button.wrapper; + } + + this.#wrapperId = id.replace( /^#/, '' ); + } + + return this.#wrapperId; + } + + /** + * Returns the wrapper ID for the ppcpButton + * + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get ppcpButtonWrapperId() { + if ( ! this.#ppcpButtonWrapperId ) { + let id; + + if ( CONTEXT.MiniCart === this.context ) { + id = this.ppcpConfig.button.mini_cart_wrapper; + } else if ( CONTEXT.Blocks.includes( this.context ) ) { + id = 'express-payment-method-ppcp-gateway-paypal'; + } else { + id = this.ppcpConfig.button.wrapper; + } + + this.#ppcpButtonWrapperId = id.replace( /^#/, '' ); + } + + return this.#ppcpButtonWrapperId; + } + + /** + * Returns the context-relevant PPCP style object. + * The style for the MiniCart context can be different. + * + * The PPCP style are custom style options, that are provided by this plugin. + * + * @return {PPCPStyle} The style object. + */ + get ppcpStyle() { + if ( CONTEXT.MiniCart === this.context ) { + return this.ppcpConfig.button.mini_cart_style; + } + + return this.ppcpConfig.button.style; + } + + /** + * Returns default style options that are propagated to and rendered by the Google Pay button. + * + * These styles are the official style options provided by the Google Pay SDK. + * + * @return {GooglePayStyle} The style object. + */ + get buttonStyle() { + let style; + + if ( CONTEXT.MiniCart === this.context ) { + style = this.buttonConfig.button.mini_cart_style; + + // Handle incompatible types. + if ( style.type === 'buy' ) { + style.type = 'pay'; + } + } else { + style = this.buttonConfig.button.style; + } + + return { + type: style.type, + language: style.language, + color: style.color, + }; + } + + /** + * Returns the HTML element that wraps the current button + * + * @return {HTMLElement|null} The wrapper element, or null. + */ + get wrapperElement() { + return document.getElementById( this.wrapperId ); + } + + /** + * Returns an array of HTMLElements that belong to the payment button. + * + * @return {HTMLElement[]} List of payment button wrapper elements. + */ + get allElements() { + const selectors = []; + + // Payment button (Pay now, smart button block) + selectors.push( `#${ this.wrapperId }` ); + + // Block Checkout: Express checkout button. + if ( CONTEXT.Blocks.includes( this.context ) ) { + selectors.push( '#express-payment-method-ppcp-googlepay' ); + } + + // Classic Checkout: Google Pay gateway. + if ( CONTEXT.Gateways === this.context ) { + selectors.push( + '.wc_payment_method.payment_method_ppcp-googlepay' + ); + } + + this.log( 'Wrapper Elements:', selectors ); + return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => + Array.from( document.querySelectorAll( selector ) ) + ); + } + + /** + * Checks whether the main button-wrapper is present in the current DOM. + * + * @return {boolean} True, if the button context (wrapper element) is found. + */ + get isPresent() { + return this.wrapperElement instanceof HTMLElement; + } + init( config, transactionInfo ) { - if ( this.isInitialized ) { + if ( this.#isInitialized ) { return; } - this.isInitialized = true; if ( ! this.validateConfig() ) { return; @@ -85,6 +281,7 @@ class GooglepayButton { } this.log( 'Init' ); + this.#isInitialized = true; this.googlePayConfig = config; this.transactionInfo = transactionInfo; @@ -103,40 +300,7 @@ class GooglepayButton { ) .then( ( response ) => { if ( response.result ) { - if ( - ( this.context === 'checkout' || - this.context === 'pay-now' ) && - this.buttonConfig.is_wc_gateway_enabled === '1' - ) { - const wrapper = document.getElementById( - 'ppc-button-ppcp-googlepay' - ); - - if ( wrapper ) { - const { ppcpStyle, buttonStyle } = - this.contextConfig(); - - wrapper.classList.add( - `ppcp-button-${ ppcpStyle.shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); - - if ( ppcpStyle.height ) { - wrapper.style.height = `${ ppcpStyle.height }px`; - } - - this.addButtonCheckout( - this.baseCardPaymentMethod, - wrapper, - buttonStyle - ); - - return; - } - } - - this.addButton( this.baseCardPaymentMethod ); + this.addButton(); } } ) .catch( function ( err ) { @@ -149,7 +313,7 @@ class GooglepayButton { return; } - this.isInitialized = false; + this.#isInitialized = false; this.init( this.googlePayConfig, this.transactionInfo ); } @@ -177,39 +341,6 @@ class GooglepayButton { return true; } - /** - * Returns configurations relative to this button context. - */ - contextConfig() { - const config = { - wrapper: this.buttonConfig.button.wrapper, - ppcpStyle: this.ppcpConfig.button.style, - buttonStyle: this.buttonConfig.button.style, - ppcpButtonWrapper: this.ppcpConfig.button.wrapper, - }; - - if ( this.context === 'mini-cart' ) { - config.wrapper = this.buttonConfig.button.mini_cart_wrapper; - config.ppcpStyle = this.ppcpConfig.button.mini_cart_style; - config.buttonStyle = this.buttonConfig.button.mini_cart_style; - config.ppcpButtonWrapper = this.ppcpConfig.button.mini_cart_wrapper; - - // Handle incompatible types. - if ( config.buttonStyle.type === 'buy' ) { - config.buttonStyle.type = 'pay'; - } - } - - if ( - [ 'cart-block', 'checkout-block' ].indexOf( this.context ) !== -1 - ) { - config.ppcpButtonWrapper = - '#express-payment-method-ppcp-gateway-paypal'; - } - - return config; - } - initClient() { const callbacks = { onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), @@ -235,7 +366,8 @@ class GooglepayButton { } initEventHandlers() { - const { wrapper, ppcpButtonWrapper } = this.contextConfig(); + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; if ( wrapper === ppcpButtonWrapper ) { throw new Error( @@ -272,77 +404,104 @@ class GooglepayButton { /** * Add a Google Pay purchase button - * @param baseCardPaymentMethod */ - addButton( baseCardPaymentMethod ) { + addButton() { this.log( 'addButton' ); - const { wrapper, ppcpStyle, buttonStyle } = this.contextConfig(); + const insertButton = () => { + const wrapper = this.wrapperElement; + const baseCardPaymentMethod = this.baseCardPaymentMethod; + const { color, type, language } = this.buttonStyle; + const { shape, height } = this.ppcpStyle; - this.waitForWrapper( wrapper, () => { - // Prevent duplicate payment buttons. - this.removeButton(); + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); - jQuery( wrapper ).addClass( 'ppcp-button-' + ppcpStyle.shape ); - - if ( ppcpStyle.height ) { - jQuery( wrapper ).css( 'height', `${ ppcpStyle.height }px` ); + if ( height ) { + wrapper.style.height = `${ height }px`; } /** * @see https://developers.google.com/pay/api/web/reference/client#createButton */ - this.#button = this.paymentsClient.createButton( { + const button = this.paymentsClient.createButton( { onClick: this.onButtonClick.bind( this ), allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', + buttonColor: color || 'black', + buttonType: type || 'pay', + buttonLocale: language || 'en', buttonSizeMode: 'fill', } ); - jQuery( wrapper ).append( this.#button ); + this.log( 'Insert Button', { wrapper, button } ); + + wrapper.replaceChildren( button ); + this.show(); + }; + + this.waitForWrapper( insertButton ); + } + + waitForWrapper( callback, delay = 100, timeout = 2000 ) { + let interval = 0; + const startTime = Date.now(); + + const stop = () => { + if ( interval ) { + clearInterval( interval ); + } + interval = 0; + }; + + const checkElement = () => { + if ( this.isPresent ) { + stop(); + callback(); + return; + } + + const timeElapsed = Date.now() - startTime; + + if ( timeElapsed > timeout ) { + stop(); + this.log( '!! Wrapper not found:', this.wrapperId ); + } + }; + + interval = setInterval( checkElement, delay ); + } + + /** + * Hides all wrappers that belong to this GooglePayButton instance. + */ + hide() { + this.log( 'Hide' ); + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; } ); } /** - * Removes the payment button that was injected via addButton() + * Ensures all wrapper elements of this GooglePayButton instance are visible. */ - removeButton() { - if ( ! this.#button ) { + show() { + if ( ! this.isPresent ) { + this.log( 'Cannot show button, wrapper is not present' ); return; } + this.log( 'Show' ); - this.#button.remove(); - this.#button = null; - } + // Classic Checkout: Make the Google Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-google-pay' ) + .forEach( ( el ) => el.remove() ); - addButtonCheckout( baseCardPaymentMethod, wrapper, buttonStyle ) { - const button = this.paymentsClient.createButton( { - onClick: this.onButtonClick.bind( this ), - allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: buttonStyle.color || 'black', - buttonType: buttonStyle.type || 'pay', - buttonLocale: buttonStyle.language || 'en', - buttonSizeMode: 'fill', + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; } ); - - wrapper.appendChild( button ); - } - - waitForWrapper( selector, callback, delay = 100, timeout = 2000 ) { - const startTime = Date.now(); - const interval = setInterval( () => { - const el = document.querySelector( selector ); - const timeElapsed = Date.now() - startTime; - - if ( el ) { - clearInterval( interval ); - callback( el ); - } else if ( timeElapsed > timeout ) { - clearInterval( interval ); - } - }, delay ); } //------------------------ diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index bd6faea79..98bff2c97 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -290,6 +290,7 @@ class Button implements ButtonInterface { $render_placeholder, function () { $this->googlepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -303,6 +304,7 @@ class Button implements ButtonInterface { $render_placeholder, function () { $this->googlepay_button(); + $this->hide_gateway_until_eligible(); }, 21 ); @@ -335,6 +337,21 @@ class Button implements ButtonInterface { + + Date: Tue, 30 Jul 2024 10:24:26 +0200 Subject: [PATCH 139/357] Google Pay: Prevent field validation from being triggered on checkout page load --- .../resources/js/Context/CheckoutHandler.js | 6 ++--- .../resources/js/GooglepayButton.js | 25 ++++++++++++------- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index 251b4f3c4..82d04f578 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -4,7 +4,7 @@ import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; class CheckoutHandler extends BaseHandler { - transactionInfo() { + formValidator() { return new Promise( async ( resolve, reject ) => { try { const spinner = new Spinner(); @@ -23,7 +23,7 @@ class CheckoutHandler extends BaseHandler { : null; if ( ! formValidator ) { - resolve( super.transactionInfo() ); + resolve(); return; } @@ -42,7 +42,7 @@ class CheckoutHandler extends BaseHandler { reject(); } else { - resolve( super.transactionInfo() ); + resolve(); } } ); } catch ( error ) { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 87bc642f0..2c15571b3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -292,17 +292,24 @@ class GooglepayButton { onButtonClick() { this.log( 'onButtonClick', this.context ); - const paymentDataRequest = this.paymentDataRequest(); + this.contextHandler.formValidator().then( + () => { + window.ppcpFundingSource = 'googlepay'; - this.log( - 'onButtonClick: paymentDataRequest', - paymentDataRequest, - this.context + const paymentDataRequest = this.paymentDataRequest(); + + this.log( + 'onButtonClick: paymentDataRequest', + paymentDataRequest, + this.context + ); + + this.paymentsClient.loadPaymentData( paymentDataRequest ); + }, + () => { + console.error( '[GooglePayButton] Form validation failed.' ); + } ); - - window.ppcpFundingSource = 'googlepay'; // Do this on another place like on create order endpoint handler. - - this.paymentsClient.loadPaymentData( paymentDataRequest ); } paymentDataRequest() { From a301d901dd4a530032769e37f55f407d9e44ad37 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 30 Jul 2024 10:31:44 +0200 Subject: [PATCH 140/357] Google Pay: Update the formValidator name --- modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js | 2 +- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js index 82d04f578..b4a07709e 100644 --- a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js +++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js @@ -4,7 +4,7 @@ import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ import FormValidator from '../../../../ppcp-button/resources/js/modules/Helper/FormValidator'; class CheckoutHandler extends BaseHandler { - formValidator() { + validateForm() { return new Promise( async ( resolve, reject ) => { try { const spinner = new Spinner(); diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 2c15571b3..4363c67be 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -292,7 +292,7 @@ class GooglepayButton { onButtonClick() { this.log( 'onButtonClick', this.context ); - this.contextHandler.formValidator().then( + this.contextHandler.validateForm().then( () => { window.ppcpFundingSource = 'googlepay'; From 19fd6c806d7ae148a2f1e730c5c6402d2bdcbdab Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 30 Jul 2024 11:37:03 +0200 Subject: [PATCH 141/357] Add free trial block checkout support for card payment --- modules/ppcp-blocks/package.json | 2 +- .../resources/js/Components/card-fields.js | 24 ++++++++-- .../resources/js/card-fields-config.js | 48 +++++++++++++++++++ modules/ppcp-blocks/yarn.lock | 18 +++---- 4 files changed, 79 insertions(+), 13 deletions(-) diff --git a/modules/ppcp-blocks/package.json b/modules/ppcp-blocks/package.json index 993b115e3..3398edd5e 100644 --- a/modules/ppcp-blocks/package.json +++ b/modules/ppcp-blocks/package.json @@ -10,7 +10,7 @@ "Edge >= 14" ], "dependencies": { - "@paypal/react-paypal-js": "^8.3.0", + "@paypal/react-paypal-js": "^8.5.0", "core-js": "^3.25.0", "react": "^17.0.0", "react-dom": "^17.0.0" diff --git a/modules/ppcp-blocks/resources/js/Components/card-fields.js b/modules/ppcp-blocks/resources/js/Components/card-fields.js index 4a1f25785..2726bc12e 100644 --- a/modules/ppcp-blocks/resources/js/Components/card-fields.js +++ b/modules/ppcp-blocks/resources/js/Components/card-fields.js @@ -7,7 +7,12 @@ import { } from '@paypal/react-paypal-js'; import { CheckoutHandler } from './checkout-handler'; -import { createOrder, onApprove } from '../card-fields-config'; +import { + createOrder, + onApprove, + createVaultSetupToken, + onApproveSavePayment, +} from '../card-fields-config'; import { cartHasSubscriptionProducts } from '../Helper/Subscription'; export function CardFields( { @@ -70,8 +75,21 @@ export function CardFields( { } } > { console.error( err ); } } diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js index 5383729dc..c4accf471 100644 --- a/modules/ppcp-blocks/resources/js/card-fields-config.js +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -44,3 +44,51 @@ export async function onApprove( data ) { console.error( err ); } ); } + +export async function createVaultSetupToken() { + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); + + return fetch( config.scriptData.ajax.create_setup_token.endpoint, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: config.scriptData.ajax.create_setup_token.nonce, + payment_method: 'ppcp-credit-card-gateway', + } ), + } ) + .then( ( response ) => response.json() ) + .then( ( result ) => { + console.log( result ); + return result.data.id; + } ) + .catch( ( err ) => { + console.error( err ); + } ); +} + +export async function onApproveSavePayment( { vaultSetupToken } ) { + const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); + + const response = await fetch( + config.scriptData.ajax.create_payment_token.endpoint, + { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( { + nonce: config.scriptData.ajax.create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + is_free_trial_cart: config.scriptData.is_free_trial_cart, + } ), + } + ); + + const result = await response.json(); + if ( result.success !== true ) { + console.error( result ); + } +} diff --git a/modules/ppcp-blocks/yarn.lock b/modules/ppcp-blocks/yarn.lock index 1e812a4ae..1debd6e24 100644 --- a/modules/ppcp-blocks/yarn.lock +++ b/modules/ppcp-blocks/yarn.lock @@ -1005,19 +1005,19 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@paypal/paypal-js@^8.0.5": - version "8.0.5" - resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.0.5.tgz#77bc461b4d1e5a2c6f081269e3ef0b2e3331a68c" - integrity sha512-yQNV7rOILeaVCNU4aVDRPqEnbIlzfxgQfFsxzsBuZW1ouqRD/4kYBWJDzczCiscSr2xOeA/Pkm7e3a9fRfnuMQ== +"@paypal/paypal-js@^8.1.0": + version "8.1.0" + resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-8.1.0.tgz#4e7d10e0a0b4164985029cfdac748e5694d117e9" + integrity sha512-f64bom5xYwmxyeKPJUFS/XpM0tXojQEgjRIADPqe1R9WmK+PFqL4SEkT85cGU0ZXLVx4EGbjwREHhqEOR+OstA== dependencies: promise-polyfill "^8.3.0" -"@paypal/react-paypal-js@^8.3.0": - version "8.3.0" - resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.3.0.tgz#a103080b752766b8ff59b8620887abf802e1a01b" - integrity sha512-SX17d2h1CMNFGI+wtjb329AEDaBR8Ziy2LCV076eDcY1Q0MFKRkfQ/v0HOAvZtk3sJoydRmYez2pq47BRblwqQ== +"@paypal/react-paypal-js@^8.5.0": + version "8.5.0" + resolved "https://registry.yarnpkg.com/@paypal/react-paypal-js/-/react-paypal-js-8.5.0.tgz#cf17483202c8fa7a33dae86798d50a102705f182" + integrity sha512-YIAyLw4OiUoHHoUgXvibrBDdluzqnqVMGsJXyBcoOzlWHQIe5zhh8dgYezNNRXjXwy6t22YmPljjw7lr+eD9cw== dependencies: - "@paypal/paypal-js" "^8.0.5" + "@paypal/paypal-js" "^8.1.0" "@paypal/sdk-constants" "^1.0.122" "@paypal/sdk-constants@^1.0.122": From f3b4a317c831fb4e085bf581b4f29cec155830e6 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 30 Jul 2024 12:40:37 +0200 Subject: [PATCH 142/357] Add free trial block checkout card support for guest users --- .../resources/js/card-fields-config.js | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/modules/ppcp-blocks/resources/js/card-fields-config.js b/modules/ppcp-blocks/resources/js/card-fields-config.js index c4accf471..8932ef916 100644 --- a/modules/ppcp-blocks/resources/js/card-fields-config.js +++ b/modules/ppcp-blocks/resources/js/card-fields-config.js @@ -71,21 +71,31 @@ export async function createVaultSetupToken() { export async function onApproveSavePayment( { vaultSetupToken } ) { const config = wc.wcSettings.getSetting( 'ppcp-credit-card-gateway_data' ); - const response = await fetch( - config.scriptData.ajax.create_payment_token.endpoint, - { - method: 'POST', - credentials: 'same-origin', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify( { - nonce: config.scriptData.ajax.create_payment_token.nonce, - vault_setup_token: vaultSetupToken, - is_free_trial_cart: config.scriptData.is_free_trial_cart, - } ), - } - ); + let endpoint = + config.scriptData.ajax.create_payment_token_for_guest.endpoint; + let bodyContent = { + nonce: config.scriptData.ajax.create_payment_token_for_guest.nonce, + vault_setup_token: vaultSetupToken, + }; + + if ( config.scriptData.user.is_logged_in ) { + endpoint = config.scriptData.ajax.create_payment_token.endpoint; + + bodyContent = { + nonce: config.scriptData.ajax.create_payment_token.nonce, + vault_setup_token: vaultSetupToken, + is_free_trial_cart: config.scriptData.is_free_trial_cart, + }; + } + + const response = await fetch( endpoint, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify( bodyContent ), + } ); const result = await response.json(); if ( result.success !== true ) { From ef6632c85faac999754be89daeca2d74a2a7de21 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 12:50:41 +0200 Subject: [PATCH 143/357] =?UTF-8?q?=E2=9C=A8=20Add=20new=20isEligible=20fl?= =?UTF-8?q?ag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The flag is set once Google’s PaymentClient responds to the isReadyToPay() request and controls the rendering of the button --- .../resources/js/GooglepayButton.js | 133 +++++++++++++----- 1 file changed, 95 insertions(+), 38 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 9571e9735..364a6f4e3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -39,10 +39,8 @@ const CONTEXT = { MiniCart: 'mini-cart', BlockCart: 'cart-block', BlockCheckout: 'checkout-block', - Preview: 'preview', - // Block editor contexts. - Blocks: [ 'cart-block', 'checkout-block' ], - // Custom gateway contexts. + Preview: 'preview', // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], // Custom gateway contexts. Gateways: [ 'checkout', 'pay-now' ], }; @@ -57,6 +55,14 @@ class GooglepayButton { */ #isInitialized = false; + /** + * Whether the current client support the payment button. + * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` + * + * @type {boolean} + */ + #isEligible = false; + /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -267,6 +273,29 @@ class GooglepayButton { return this.wrapperElement instanceof HTMLElement; } + /** + * Whether the browser can accept Google Pay payments. + * + * @return {boolean} True, if payments are technically possible. + */ + get isEligible() { + return this.#isEligible; + } + + /** + * Changes the eligibility state of this button component. + * + * @param {boolean} newState Whether the browser can accept payments. + */ + set isEligible( newState ) { + if ( newState === this.#isEligible ) { + return; + } + + this.#isEligible = newState; + this.refresh(); + } + init( config, transactionInfo ) { if ( this.#isInitialized ) { return; @@ -299,12 +328,22 @@ class GooglepayButton { ) ) .then( ( response ) => { - if ( response.result ) { - this.addButton(); - } + this.log( 'PaymentsClient.isReadyToPay response:', response ); + + /** + * In case the button wrapper element is not present in the DOM yet, wait for it + * to appear. Only proceed, if a button wrapper is found on this page. + * + * Not sure if this is needed, or if we can directly test for `this.isPresent` + * without any delay. + */ + this.waitForWrapper( () => { + this.isEligible = !! response.result; + } ); } ) - .catch( function ( err ) { + .catch( ( err ) => { console.error( err ); + this.isEligible = false; } ); } @@ -397,6 +436,8 @@ class GooglepayButton { } buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { + this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods ); + return Object.assign( {}, baseRequest, { allowedPaymentMethods, } ); @@ -408,43 +449,47 @@ class GooglepayButton { addButton() { this.log( 'addButton' ); - const insertButton = () => { - const wrapper = this.wrapperElement; - const baseCardPaymentMethod = this.baseCardPaymentMethod; - const { color, type, language } = this.buttonStyle; - const { shape, height } = this.ppcpStyle; + const wrapper = this.wrapperElement; + const baseCardPaymentMethod = this.baseCardPaymentMethod; + const { color, type, language } = this.buttonStyle; + const { shape, height } = this.ppcpStyle; - wrapper.classList.add( - `ppcp-button-${ shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + 'ppcp-button-googlepay' + ); - if ( height ) { - wrapper.style.height = `${ height }px`; - } + if ( height ) { + wrapper.style.height = `${ height }px`; + } - /** - * @see https://developers.google.com/pay/api/web/reference/client#createButton - */ - const button = this.paymentsClient.createButton( { - onClick: this.onButtonClick.bind( this ), - allowedPaymentMethods: [ baseCardPaymentMethod ], - buttonColor: color || 'black', - buttonType: type || 'pay', - buttonLocale: language || 'en', - buttonSizeMode: 'fill', - } ); + /** + * @see https://developers.google.com/pay/api/web/reference/client#createButton + */ + const button = this.paymentsClient.createButton( { + onClick: this.onButtonClick.bind( this ), + allowedPaymentMethods: [ baseCardPaymentMethod ], + buttonColor: color || 'black', + buttonType: type || 'pay', + buttonLocale: language || 'en', + buttonSizeMode: 'fill', + } ); - this.log( 'Insert Button', { wrapper, button } ); + this.log( 'Insert Button', { wrapper, button } ); - wrapper.replaceChildren( button ); - this.show(); - }; - - this.waitForWrapper( insertButton ); + wrapper.replaceChildren( button ); } + /** + * Waits for the current button's wrapper element to become available in the DOM. + * + * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. + * + * @param {Function} callback Function to call when the wrapper element was detected. Only called on success. + * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec + * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec + */ waitForWrapper( callback, delay = 100, timeout = 2000 ) { let interval = 0; const startTime = Date.now(); @@ -474,6 +519,18 @@ class GooglepayButton { interval = setInterval( checkElement, delay ); } + /** + * Refreshes the payment button on the page. + */ + refresh() { + if ( this.isEligible && this.isPresent ) { + this.show(); + this.addButton(); + } else { + this.hide(); + } + } + /** * Hides all wrappers that belong to this GooglePayButton instance. */ From 490cd1958b7b64f75765862c4c7aef67b5239fa0 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 13:51:16 +0200 Subject: [PATCH 144/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20config=20ob?= =?UTF-8?q?ject=20to=20appropriate=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../js/modules/Helper/CheckoutMethodState.js | 24 +++++++++++++++++++ .../resources/js/GooglepayButton.js | 19 +-------------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js index 3e284c8ef..aecf434f4 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js +++ b/modules/ppcp-button/resources/js/modules/Helper/CheckoutMethodState.js @@ -6,6 +6,30 @@ export const PaymentMethods = { GOOGLEPAY: 'ppcp-googlepay', }; +/** + * List of valid context values that the button can have. + * + * The "context" describes the placement or page where a payment button might be displayed. + * + * @type {Object} + */ +export const PaymentContext = { + Product: 'product', + Cart: 'cart', + Checkout: 'checkout', + PayNow: 'pay-now', + MiniCart: 'mini-cart', + BlockCart: 'cart-block', + BlockCheckout: 'checkout-block', + Preview: 'preview', + + // Block editor contexts. + Blocks: [ 'cart-block', 'checkout-block' ], + + // Custom gateway contexts. + Gateways: [ 'checkout', 'pay-now' ], +}; + export const ORDER_BUTTON_SELECTOR = '#place_order'; export const getCurrentPaymentMethod = () => { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 364a6f4e3..b682ce7d3 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -6,6 +6,7 @@ import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/But import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; +import { PaymentContext as CONTEXT } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -26,24 +27,6 @@ import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper * @property {string} language - The locale; an empty string will apply the user-agent's language. */ -/** - * List of valid context values that the button can have. - * - * @type {Object} - */ -const CONTEXT = { - Product: 'product', - Cart: 'cart', - Checkout: 'checkout', - PayNow: 'pay-now', - MiniCart: 'mini-cart', - BlockCart: 'cart-block', - BlockCheckout: 'checkout-block', - Preview: 'preview', // Block editor contexts. - Blocks: [ 'cart-block', 'checkout-block' ], // Custom gateway contexts. - Gateways: [ 'checkout', 'pay-now' ], -}; - class GooglepayButton { #wrapperId = ''; #ppcpButtonWrapperId = ''; From 0888c696ff5fbaae6bb75ee66d16c6b820ca8510 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 30 Jul 2024 13:55:24 +0200 Subject: [PATCH 145/357] =?UTF-8?q?=E2=9C=A8=20Sync=20gateway=20visibility?= =?UTF-8?q?=20via=20custom=20event?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContextBootstrap/CheckoutBootstap.js | 21 +++++- .../resources/js/GooglepayButton.js | 69 ++++++++++++------- 2 files changed, 65 insertions(+), 25 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 33d1ecfd3..7a016fd9e 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -68,6 +68,7 @@ class CheckoutBootstap { jQuery( document.body ).on( 'updated_checkout payment_method_selected', () => { + this.invalidatePaymentMethods(); this.updateUi(); } ); @@ -174,6 +175,14 @@ class CheckoutBootstap { ); } + invalidatePaymentMethods() { + /** + * Custom JS event to notify other modules that the payment button on the checkout page + * has become irrelevant or invalid. + */ + document.body.dispatchEvent( new Event( 'ppcp_invalidate_methods' ) ); + } + updateUi() { const currentPaymentMethod = getCurrentPaymentMethod(); const isPaypal = currentPaymentMethod === PaymentMethods.PAYPAL; @@ -232,9 +241,17 @@ class CheckoutBootstap { } } - setVisible( '#ppc-button-ppcp-googlepay', isGooglePayMethod ); + /** + * Custom JS event that is observed by the relevant payment gateway. + * + * Dynamic part of the event name is the payment method ID, for example + * "ppcp-credit-card-gateway" or "ppcp-googlepay" + */ + document.body.dispatchEvent( + new Event( `ppcp_render_method-${ currentPaymentMethod }` ) + ); - jQuery( document.body ).trigger( 'ppcp_checkout_rendered' ); + document.body.dispatchEvent( new Event( 'ppcp_checkout_rendered' ) ); } shouldShowMessages() { diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index b682ce7d3..e6aa1851b 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -6,7 +6,10 @@ import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/But import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; -import { PaymentContext as CONTEXT } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { + PaymentMethods, + PaymentContext as CONTEXT, +} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -69,6 +72,10 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; + this.hide = this.hide.bind( this ); + this.show = this.show.bind( this ); + this.refresh = this.refresh.bind( this ); + this.log( 'Create instance' ); } @@ -388,34 +395,50 @@ class GooglepayButton { } initEventHandlers() { - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + if ( CONTEXT.Gateways.includes( this.context ) ) { + document.body.addEventListener( + 'ppcp_invalidate_methods', + this.hide ); - } - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + document.body.addEventListener( + `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, + this.refresh ); - }; + } else { + /** + * Review: The following logic appears to be unnecessary. Is it still required? + */ - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; + + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); } - ); - syncButtonVisibility(); + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + + syncButtonVisibility(); + } } buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { From 592920b20bef8a6721213dfe4430dc6796e82d57 Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 31 Jul 2024 11:24:12 +0400 Subject: [PATCH 146/357] Update ACDC signup URLs --- modules/ppcp-wc-gateway/services.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..48958ae37 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -1410,10 +1410,10 @@ return array( return $label; }, 'wcgateway.enable-dcc-url-sandbox' => static function ( ContainerInterface $container ): string { - return 'https://www.sandbox.paypal.com/bizsignup/entry/product/ppcp'; + return 'https://www.sandbox.paypal.com/bizsignup/entry?product=ppcp'; }, 'wcgateway.enable-dcc-url-live' => static function ( ContainerInterface $container ): string { - return 'https://www.paypal.com/bizsignup/entry/product/ppcp'; + return 'https://www.paypal.com/bizsignup/entry?product=ppcp'; }, 'wcgateway.enable-pui-url-sandbox' => static function ( ContainerInterface $container ): string { return 'https://www.sandbox.paypal.com/bizsignup/entry?country.x=DE&product=payment_methods&capabilities=PAY_UPON_INVOICE'; From 000cc8ed9eb6e1baee7a14731a231ff2fe3292ad Mon Sep 17 00:00:00 2001 From: George Burduli Date: Wed, 31 Jul 2024 11:53:13 +0400 Subject: [PATCH 147/357] Change Apple Pay and Google Pay default button labels to plain --- modules/ppcp-applepay/extensions.php | 2 +- modules/ppcp-googlepay/extensions.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php index daf5cf2ea..8f9d84b5a 100644 --- a/modules/ppcp-applepay/extensions.php +++ b/modules/ppcp-applepay/extensions.php @@ -269,7 +269,7 @@ return array( 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), - 'default' => 'pay', + 'default' => 'plain', 'options' => PropertiesDictionary::button_types(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'dcc', diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php index e0a01901f..df012fb8a 100644 --- a/modules/ppcp-googlepay/extensions.php +++ b/modules/ppcp-googlepay/extensions.php @@ -166,7 +166,7 @@ return array( 'classes' => array( 'ppcp-field-indent' ), 'class' => array(), 'input_class' => array( 'wc-enhanced-select' ), - 'default' => 'pay', + 'default' => 'plain', 'options' => PropertiesDictionary::button_types(), 'screens' => array( State::STATE_ONBOARDED ), 'gateway' => 'dcc', From bfef725f6e5d8e492b0dd55dccb8607adf8f8b1d Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 11:49:10 +0200 Subject: [PATCH 148/357] =?UTF-8?q?=E2=9C=A8=20Site-wide=20HTML=20tag=20wi?= =?UTF-8?q?th=20AXO=20status?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..e92e01c61 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -194,9 +194,18 @@ class AxoModule implements ModuleInterface { add_action( 'wp_head', - function () { + function () use ( $c ) { // phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript echo ''; + + // Add meta tag to allow feature-detection of the site's AXO payment state. + $settings = $c->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + printf( + '', + $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ? 'enabled' : 'disabled' + ); } ); From be59960777da38e5cd5cead53177a418ed081477 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Wed, 31 Jul 2024 12:27:33 +0200 Subject: [PATCH 149/357] AXO: Temporarily hide the Fastlane payment method in the admin until the ACDC/Fastlane-sharing refactor is complete --- modules/ppcp-axo/src/AxoModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..0ab85a106 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -66,7 +66,7 @@ class AxoModule implements ModuleInterface { // Add the gateway in admin area. if ( is_admin() ) { - $methods[] = $gateway; + // $methods[] = $gateway; - Temporarily remove Fastlane from the payment gateway list in admin area. return $methods; } From 0e24db159ff63b803edcb64917f127e09796ea67 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 12:47:07 +0200 Subject: [PATCH 150/357] =?UTF-8?q?=E2=9C=A8=20Add=20feature-detection=20t?= =?UTF-8?q?ag=20condition?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/src/AxoModule.php | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index e92e01c61..359199041 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -202,9 +202,8 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - printf( - '', - $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ? 'enabled' : 'disabled' + $this->add_feature_detection_tag( + $settings->has( 'axo_enabled' ) && $settings->get( 'axo_enabled' ) ); } ); @@ -405,4 +404,23 @@ class AxoModule implements ModuleInterface { // Exclude the Order Pay endpoint. return is_wc_endpoint_url( 'order-pay' ); } + + /** + * Outputs a meta tag to allow feature detection on certain pages. + * + * @param bool $axo_enabled Whether the gateway is enabled. + * @return void + */ + private function add_feature_detection_tag( bool $axo_enabled ) { + $show_tag = is_checkout() || is_cart() || is_shop(); + + if ( ! $show_tag ) { + return; + } + + printf( + '', + $axo_enabled ? 'enabled' : 'disabled' + ); + } } From dc2dbaed92eae93bdf2cb2209f19d8e7241c78aa Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 14:04:48 +0200 Subject: [PATCH 151/357] =?UTF-8?q?=E2=9C=A8=20Show=20Fastlane=20warning?= =?UTF-8?q?=20when=20DCC=20is=20disabled?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/ppcp-axo/extensions.php | 1 + modules/ppcp-axo/services.php | 11 ++++- .../src/Helper/SettingsNoticeGenerator.php | 49 +++++++++++++++++++ 3 files changed, 60 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 3bee1d391..5b2ac3214 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -119,6 +119,7 @@ return array( 'html' => implode( '', array( + $container->get( 'axo.settings-conflict-notice' ), $container->get( 'axo.shipping-config-notice' ), $container->get( 'axo.checkout-config-notice' ), $container->get( 'axo.incompatible-plugins-notice' ), diff --git a/modules/ppcp-axo/services.php b/modules/ppcp-axo/services.php index 31e448bfd..5b468835f 100644 --- a/modules/ppcp-axo/services.php +++ b/modules/ppcp-axo/services.php @@ -16,7 +16,6 @@ use WooCommerce\PayPalCommerce\Axo\Helper\SettingsNoticeGenerator; use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface; use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway; use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway; -use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; return array( @@ -164,6 +163,16 @@ return array( ); }, + 'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string { + $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); + assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); + + $settings = $container->get( 'wcgateway.settings' ); + assert( $settings instanceof Settings ); + + return $settings_notice_generator->generate_settings_conflict_notice( $settings ); + }, + 'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string { $settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' ); assert( $settings_notice_generator instanceof SettingsNoticeGenerator ); diff --git a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php index f375e2943..503e135cb 100644 --- a/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php +++ b/modules/ppcp-axo/src/Helper/SettingsNoticeGenerator.php @@ -11,11 +11,32 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\Axo\Helper; use WooCommerce\PayPalCommerce\WcGateway\Helper\CartCheckoutDetector; +use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** * Class SettingsNoticeGenerator */ class SettingsNoticeGenerator { + /** + * Generates the full HTML of the notification. + * + * @param string $message HTML of the inner message contents. + * @param bool $is_error Whether the provided message is an error. Affects the notice color. + * + * @return string The full HTML code of the notification, or an empty string. + */ + private function render_notice( string $message, bool $is_error = false ) : string { + if ( ! $message ) { + return ''; + } + + return sprintf( + '

    %2$s

    ', + $is_error ? 'ppcp-notice-error' : '', + $message + ); + } /** * Generates the checkout notice. @@ -127,4 +148,32 @@ class SettingsNoticeGenerator { return '

    ' . $notice_content . '

    '; } + + /** + * Generates a warning notice with instructions on conflicting plugin-internal settings. + * + * @param Settings $settings The plugin settings container, which is checked for conflicting + * values. + * @return string + */ + public function generate_settings_conflict_notice( Settings $settings ) : string { + $notice_content = ''; + $is_dcc_enabled = false; + + try { + $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ); + // phpcs:ignore Generic.CodeAnalysis.EmptyStatement.DetectedCatch + } catch ( NotFoundException $ignored ) { + // Never happens. + } + + if ( ! $is_dcc_enabled ) { + $notice_content = __( + 'Warning: To enable Fastlane and accelerate payments, the Advanced Card Processing payment method must also be enabled.', + 'woocommerce-paypal-payments' + ); + } + + return $this->render_notice( $notice_content, true ); + } } From 7526189e407bb30062027af7b481220a77276850 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 11:48:52 +0200 Subject: [PATCH 152/357] AXO: Refactor the debug script in order to remove logger calls when logging is disabled --- modules/ppcp-axo/resources/js/Helper/Debug.js | 24 ++++++++++--------- modules/ppcp-axo/src/Assets/AxoManager.php | 1 + 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/modules/ppcp-axo/resources/js/Helper/Debug.js b/modules/ppcp-axo/resources/js/Helper/Debug.js index 84cda012c..6345ee935 100644 --- a/modules/ppcp-axo/resources/js/Helper/Debug.js +++ b/modules/ppcp-axo/resources/js/Helper/Debug.js @@ -1,7 +1,19 @@ export function log( message, level = 'info' ) { const wpDebug = window.wc_ppcp_axo?.wp_debug; const endpoint = window.wc_ppcp_axo?.ajax?.frontend_logger?.endpoint; - if ( ! endpoint ) { + const loggingEnabled = window.wc_ppcp_axo?.logging_enabled; + + if ( wpDebug ) { + switch ( level ) { + case 'error': + console.error( `[AXO] ${ message }` ); + break; + default: + console.log( `[AXO] ${ message }` ); + } + } + + if ( ! endpoint || ! loggingEnabled ) { return; } @@ -15,15 +27,5 @@ export function log( message, level = 'info' ) { level, }, } ), - } ).then( () => { - if ( wpDebug ) { - switch ( level ) { - case 'error': - console.error( `[AXO] ${ message }` ); - break; - default: - console.log( `[AXO] ${ message }` ); - } - } } ); } diff --git a/modules/ppcp-axo/src/Assets/AxoManager.php b/modules/ppcp-axo/src/Assets/AxoManager.php index 363f0d092..2e7e60804 100644 --- a/modules/ppcp-axo/src/Assets/AxoManager.php +++ b/modules/ppcp-axo/src/Assets/AxoManager.php @@ -216,6 +216,7 @@ class AxoManager { 'nonce' => wp_create_nonce( FrontendLoggerEndpoint::nonce() ), ), ), + 'logging_enabled' => $this->settings->has( 'logging_enabled' ) ? $this->settings->get( 'logging_enabled' ) : '', 'wp_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG, 'billing_email_button_text' => __( 'Continue', 'woocommerce-paypal-payments' ), ); From eab4f64a467bffb626899f462075161d9173a74f Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 18:44:31 +0200 Subject: [PATCH 153/357] AXO: Fix the conditions for rendering Fastlane when shipping config is incompatible --- modules/ppcp-axo/src/AxoModule.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 6350b5adc..7491f1777 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -87,6 +87,10 @@ class AxoModule implements ModuleInterface { return $methods; } + if ( ! $this->is_compatible_shipping_config() ) { + return $methods; + } + $methods[] = $gateway; return $methods; }, @@ -150,7 +154,8 @@ class AxoModule implements ModuleInterface { // Check if the module is applicable, correct country, currency, ... etc. if ( ! $c->get( 'axo.eligible' ) || 'continuation' === $c->get( 'button.context' ) - || $subscription_helper->cart_contains_subscription() ) { + || $subscription_helper->cart_contains_subscription() + || ! $this->is_compatible_shipping_config() ) { return; } From 3488e6f6f38c0d136d3ce7db6c9f5fb72b4918f2 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 18:47:45 +0200 Subject: [PATCH 154/357] AXO: Simplify the is_compatible_shipping_config() condition --- modules/ppcp-axo/src/AxoModule.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 7491f1777..a6b9cb652 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -409,6 +409,6 @@ class AxoModule implements ModuleInterface { * @return bool */ private function is_compatible_shipping_config(): bool { - return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() ); + return ! wc_shipping_enabled() || ! wc_ship_to_billing_address_only(); } } From 6c4c31fbf01f6a9f4d3897187025d501f781eb18 Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Thu, 1 Aug 2024 22:25:46 +0200 Subject: [PATCH 155/357] AXO: Fix the main notice key in the display manager --- modules/ppcp-axo/extensions.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-axo/extensions.php b/modules/ppcp-axo/extensions.php index 5b2ac3214..636aee38e 100644 --- a/modules/ppcp-axo/extensions.php +++ b/modules/ppcp-axo/extensions.php @@ -83,7 +83,7 @@ return array( ->rule() ->condition_element( 'axo_enabled', '1' ) ->action_visible( 'axo_gateway_title' ) - ->action_visible( 'axo_checkout_config_notice' ) + ->action_visible( 'axo_main_notice' ) ->action_visible( 'axo_privacy' ) ->action_visible( 'axo_name_on_card' ) ->action_visible( 'axo_style_heading' ) From fe8101699f79320033ffd4a977781d65f9eea79d Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Fri, 2 Aug 2024 12:30:39 +0200 Subject: [PATCH 156/357] Fix the PayPal enabled settings key --- .megaignore | 1 + modules/ppcp-axo/src/AxoModule.php | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) create mode 100644 .megaignore diff --git a/.megaignore b/.megaignore new file mode 100644 index 000000000..6ad02d892 --- /dev/null +++ b/.megaignore @@ -0,0 +1 @@ +-s:* \ No newline at end of file diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 8612729a1..0c0576e55 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -77,7 +77,7 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false; $is_dcc_enabled = $settings->has( 'dcc_enabled' ) && $settings->get( 'dcc_enabled' ) ?? false; if ( ! $is_paypal_enabled || ! $is_dcc_enabled ) { @@ -152,7 +152,7 @@ class AxoModule implements ModuleInterface { $settings = $c->get( 'wcgateway.settings' ); assert( $settings instanceof Settings ); - $is_paypal_enabled = $settings->has( 'paypal_enabled' ) && $settings->get( 'paypal_enabled' ) ?? false; + $is_paypal_enabled = $settings->has( 'enabled' ) && $settings->get( 'enabled' ) ?? false; $subscription_helper = $c->get( 'wc-subscriptions.helper' ); assert( $subscription_helper instanceof SubscriptionHelper ); From 2815fcbb919dbccc9e2220034968700d538d39ef Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Fri, 2 Aug 2024 15:24:26 +0200 Subject: [PATCH 157/357] Fix phpcs --- modules/ppcp-button/src/Assets/SmartButton.php | 10 +++++----- .../src/Endpoint/CreatePaymentToken.php | 2 +- .../ppcp-wc-gateway/src/Gateway/CreditCardGateway.php | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/modules/ppcp-button/src/Assets/SmartButton.php b/modules/ppcp-button/src/Assets/SmartButton.php index 43acaa81a..b80022a5e 100644 --- a/modules/ppcp-button/src/Assets/SmartButton.php +++ b/modules/ppcp-button/src/Assets/SmartButton.php @@ -1293,8 +1293,8 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages 'early_checkout_validation_enabled' => $this->early_validation_enabled, 'funding_sources_without_redirect' => $this->funding_sources_without_redirect, 'user' => array( - 'is_logged' => is_user_logged_in(), - 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens(get_current_user_id()), + 'is_logged' => is_user_logged_in(), + 'has_wc_card_payment_tokens' => $this->user_has_wc_card_payment_tokens( get_current_user_id() ), ), 'should_handle_shipping_in_paypal' => $this->should_handle_shipping_in_paypal && ! $this->is_checkout(), 'vaultingEnabled' => $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ), @@ -2138,12 +2138,12 @@ document.querySelector("#payment").before(document.querySelector(".ppcp-messages /** * Whether the given user has WC card payment tokens. * - * @param int $user_id + * @param int $user_id The user ID. * @return bool */ - private function user_has_wc_card_payment_tokens(int $user_id): bool { + private function user_has_wc_card_payment_tokens( int $user_id ): bool { $tokens = WC_Payment_Tokens::get_customer_tokens( $user_id, CreditCardGateway::ID ); - if($tokens) { + if ( $tokens ) { return true; } diff --git a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php index acd4f988f..ae3d7dfa0 100644 --- a/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php +++ b/modules/ppcp-save-payment-methods/src/Endpoint/CreatePaymentToken.php @@ -117,7 +117,7 @@ class CreatePaymentToken implements EndpointInterface { $wc_token_id = $this->wc_payment_tokens->create_payment_token_card( $current_user_id, $result ); $is_free_trial_cart = $data['is_free_trial_cart'] ?? ''; - if($is_free_trial_cart === '1') { + if ( $is_free_trial_cart === '1' ) { WC()->session->set( 'ppcp_card_payment_token_for_free_trial', $wc_token_id ); } } diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php index 393328660..e60b9e1bd 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php @@ -436,7 +436,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { $guest_card_payment_for_free_trial = WC()->session->get( 'ppcp_guest_payment_for_free_trial' ) ?? null; WC()->session->get( 'ppcp_guest_payment_for_free_trial', null ); - if($guest_card_payment_for_free_trial) { + if ( $guest_card_payment_for_free_trial ) { $customer_id = $guest_card_payment_for_free_trial->customer->id ?? ''; if ( $customer_id ) { update_user_meta( $wc_order->get_customer_id(), '_ppcp_target_customer_id', $customer_id ); @@ -450,9 +450,9 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC { } } - $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial') ?? null; + $card_payment_token_for_free_trial = WC()->session->get( 'ppcp_card_payment_token_for_free_trial' ) ?? null; WC()->session->set( 'ppcp_card_payment_token_for_free_trial', null ); - if($card_payment_token_for_free_trial) { + if ( $card_payment_token_for_free_trial ) { $tokens = WC_Payment_Tokens::get_customer_tokens( get_current_user_id() ); foreach ( $tokens as $token ) { if ( $token->get_id() === (int) $card_payment_token_for_free_trial ) { From 9da37a2cc68d4ab0ed1326d19af80cb21d1cb453 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 31 Jul 2024 10:01:42 +0200 Subject: [PATCH 158/357] =?UTF-8?q?=E2=9C=A8=20Introduce=20new=20=E2=80=9C?= =?UTF-8?q?isVisible=E2=80=9D=20flag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 171 ++++++++++-------- 1 file changed, 97 insertions(+), 74 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index e6aa1851b..0ec6d494f 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,8 +1,5 @@ /* global google */ -/* global jQuery */ -import { setVisible } from '../../../ppcp-button/resources/js/modules/Helper/Hiding'; -import { setEnabled } from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; @@ -49,6 +46,13 @@ class GooglepayButton { */ #isEligible = false; + /** + * Whether this button is visible. Modified by `show()` and `hide()` + * + * @type {boolean} + */ + #isVisible = false; + /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -72,8 +76,6 @@ class GooglepayButton { this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.hide = this.hide.bind( this ); - this.show = this.show.bind( this ); this.refresh = this.refresh.bind( this ); this.log( 'Create instance' ); @@ -263,6 +265,34 @@ class GooglepayButton { return this.wrapperElement instanceof HTMLElement; } + /** + * The visibility state of the button. + * This flag does not reflect actual visibility on the page, but rather, if the button + * is intended/allowed to be displayed, in case all other checks pass. + * + * @return {boolean} True indicates, that the button can be displayed + */ + get isVisible() { + return this.#isVisible; + } + + /** + * Change the visibility of the button. + * + * A visible button does not always force the button to render on the page. It only means, that + * the button is allowed or not allowed to render, if certain other conditions are met. + * + * @param {boolean} newState Whether rendering the button is allowed. + */ + set isVisible( newState ) { + if ( this.#isVisible === newState ) { + return; + } + + this.#isVisible = newState; + this.refresh(); + } + /** * Whether the browser can accept Google Pay payments. * @@ -396,48 +426,46 @@ class GooglepayButton { initEventHandlers() { if ( CONTEXT.Gateways.includes( this.context ) ) { - document.body.addEventListener( - 'ppcp_invalidate_methods', - this.hide - ); + document.body.addEventListener( 'ppcp_invalidate_methods', () => { + this.isVisible = false; + } ); document.body.addEventListener( `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, - this.refresh + () => { + this.isVisible = true; + } ); } else { /** * Review: The following logic appears to be unnecessary. Is it still required? + * / + const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; + const wrapper = `#${ this.wrapperId }`; + if ( wrapper === ppcpButtonWrapper ) { + throw new Error( + `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` + ); + } + const syncButtonVisibility = () => { + const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); + setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); + setEnabled( + wrapper, + ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) + ); + }; + jQuery( document ).on( + 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', + ( ev, data ) => { + if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { + syncButtonVisibility(); + } + } + ); + syncButtonVisibility(); + // */ - - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` - ); - } - - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) - ); - }; - - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } - } - ); - - syncButtonVisibility(); } } @@ -482,7 +510,10 @@ class GooglepayButton { buttonSizeMode: 'fill', } ); - this.log( 'Insert Button', { wrapper, button } ); + this.log( 'Insert Button', { + wrapper, + button, + } ); wrapper.replaceChildren( button ); } @@ -492,7 +523,8 @@ class GooglepayButton { * * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. * - * @param {Function} callback Function to call when the wrapper element was detected. Only called on success. + * @param {Function} callback Function to call when the wrapper element was detected. Only + * called on success. * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec */ @@ -529,44 +561,35 @@ class GooglepayButton { * Refreshes the payment button on the page. */ refresh() { - if ( this.isEligible && this.isPresent ) { - this.show(); + const showButtonWrapper = () => { + this.log( 'Show' ); + + // Classic Checkout: Make the Google Pay gateway visible. + document + .querySelectorAll( 'style#ppcp-hide-google-pay' ) + .forEach( ( el ) => el.remove() ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; + } ); + }; + + const hideButtonWrapper = () => { + this.log( 'Hide' ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + }; + + if ( this.isVisible && this.isEligible && this.isPresent ) { + showButtonWrapper(); this.addButton(); } else { - this.hide(); + hideButtonWrapper(); } } - /** - * Hides all wrappers that belong to this GooglePayButton instance. - */ - hide() { - this.log( 'Hide' ); - this.allElements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - } - - /** - * Ensures all wrapper elements of this GooglePayButton instance are visible. - */ - show() { - if ( ! this.isPresent ) { - this.log( 'Cannot show button, wrapper is not present' ); - return; - } - this.log( 'Show' ); - - // Classic Checkout: Make the Google Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-google-pay' ) - .forEach( ( el ) => el.remove() ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'block'; - } ); - } - //------------------------ // Button click //------------------------ From f1f243505ce3fe2bcf2f21ebb7bf42523f987e9c Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 2 Aug 2024 16:32:27 +0200 Subject: [PATCH 159/357] =?UTF-8?q?=E2=9C=A8=20Introduce=20a=20new=20Conso?= =?UTF-8?q?leLogger=20JS=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extract debug logic to separate component --- .../js/modules/Helper/ConsoleLogger.js | 42 ++++++++++++++++++ .../resources/js/GooglepayButton.js | 43 ++++++------------- 2 files changed, 56 insertions(+), 29 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js b/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js new file mode 100644 index 000000000..689f07c0a --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js @@ -0,0 +1,42 @@ +/** + * Logs debug details to the console. + * + * A utility class that is used by payment buttons on the front-end, like the GooglePayButton. + */ +export default class ConsoleLogger { + /** + * The prefix to display before every log output. + * + * @type {string} + */ + #prefix = ''; + + /** + * Whether logging is enabled, disabled by default. + * + * @type {boolean} + */ + #enabled = false; + + constructor( ...prefixes ) { + if ( prefixes.length ) { + this.#prefix = `[${ prefixes.join( ' | ' ) }]`; + } + } + + set enabled( state ) { + this.#enabled = state; + } + + log( ...args ) { + if ( this.#enabled ) { + console.log( this.#prefix, ...args ); + } + } + + error( ...args ) { + if ( this.#enabled ) { + console.error( this.#prefix, ...args ); + } + } +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 0ec6d494f..a21f803a4 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,5 +1,6 @@ /* global google */ +import ConsoleLogger from '../../../ppcp-button/resources/js/modules/Helper/ConsoleLogger'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; @@ -28,6 +29,11 @@ import { */ class GooglepayButton { + /** + * @type {ConsoleLogger} + */ + #logger; + #wrapperId = ''; #ppcpButtonWrapperId = ''; @@ -66,7 +72,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { - this._initDebug( !! buttonConfig?.is_debug, context ); + this.#logger = new ConsoleLogger( 'GooglePayButton', context ); + this.#logger.enabled = !! buttonConfig?.is_debug; apmButtonsInit( ppcpConfig ); @@ -81,34 +88,12 @@ class GooglepayButton { this.log( 'Create instance' ); } - /** - * NOOP log function to avoid errors when debugging is disabled. - */ - log() {} + log( ...args ) { + this.#logger.log( ...args ); + } - /** - * Enables debugging tools, when the button's is_debug flag is set. - * - * @param {boolean} enableDebugging If debugging features should be enabled for this instance. - * @param {string} context Used to make the instance accessible via the global debug - * object. - * @private - */ - _initDebug( enableDebugging, context ) { - if ( ! enableDebugging || this.#isInitialized ) { - return; - } - - document.ppcpGooglepayButtons = document.ppcpGooglepayButtons || {}; - document.ppcpGooglepayButtons[ context ] = this; - - this.log = ( ...args ) => { - console.log( `[GooglePayButton | ${ context }]`, ...args ); - }; - - document.addEventListener( 'ppcp-googlepay-debug', () => { - this.log( this ); - } ); + error( ...args ) { + this.#logger.error( ...args ); } /** @@ -550,7 +535,7 @@ class GooglepayButton { if ( timeElapsed > timeout ) { stop(); - this.log( '!! Wrapper not found:', this.wrapperId ); + this.error( 'Wrapper not found:', this.wrapperId ); } }; From f69209b91ce6f4bd50bc5a17019e3bb21b84d1c6 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Fri, 2 Aug 2024 17:12:07 +0200 Subject: [PATCH 160/357] =?UTF-8?q?=E2=9C=A8=20New=20PaymentButton=20base?= =?UTF-8?q?=20class=20for=20APM=20buttons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This class is used to render buttons in the front end, and encapsulates logic that is shared between ApplePay and GooglePay buttons --- .../js/modules/Renderer/PaymentButton.js | 101 ++++++++++++++++++ .../resources/js/GooglepayButton.js | 59 ++++------ 2 files changed, 123 insertions(+), 37 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js new file mode 100644 index 000000000..8843ca662 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -0,0 +1,101 @@ +import ConsoleLogger from '../Helper/ConsoleLogger'; +import { apmButtonsInit } from '../Helper/ApmButtons'; + +/** + * Base class for APM payment buttons, like GooglePay and ApplePay. + * + * This class is not intended for the PayPal button. + */ +export default class PaymentButton { + /** + * @type {ConsoleLogger} + */ + #logger; + + /** + * Whether the payment button is initialized. + * + * @type {boolean} + */ + #isInitialized = false; + + /** + * The button's context. + */ + #context; + + #buttonConfig; + + #ppcpConfig; + + constructor( gatewayName, context, buttonConfig, ppcpConfig ) { + this.#logger = new ConsoleLogger( gatewayName, context ); + this.#logger.enabled = !! buttonConfig?.is_debug; + + this.#context = context; + this.#buttonConfig = buttonConfig; + this.#ppcpConfig = ppcpConfig; + + apmButtonsInit( ppcpConfig ); + } + + /** + * Whether the payment button was fully initialized. Read-only. + * + * @return {boolean} True indicates, that the button was fully initialized. + */ + get isInitialized() { + return this.#isInitialized; + } + + /** + * The button's context. Read-only. + * + * TODO: Convert the string to a context-object (primitive obsession smell) + * + * @return {string} The button context. + */ + get context() { + return this.#context; + } + + /** + * Log a debug detail to the browser console. + * + * @param {any} args + */ + log( ...args ) { + this.#logger.log( ...args ); + } + + /** + * Log an error message to the browser console. + * + * @param {any} args + */ + error( ...args ) { + this.#logger.error( ...args ); + } + + /** + * Must be named `init()` to simulate "protected" visibility: + * Since the derived class also implements a method with the same name, this method can only + * be called by the derived class, but not from any other code. + * + * @protected + */ + init() { + this.#isInitialized = true; + } + + /** + * Must be named `reinit()` to simulate "protected" visibility: + * Since the derived class also implements a method with the same name, this method can only + * be called by the derived class, but not from any other code. + * + * @protected + */ + reinit() { + this.#isInitialized = false; + } +} diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index a21f803a4..f778ff8e2 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,9 +1,8 @@ /* global google */ -import ConsoleLogger from '../../../ppcp-button/resources/js/modules/Helper/ConsoleLogger'; +import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; -import { apmButtonsInit } from '../../../ppcp-button/resources/js/modules/Helper/ApmButtons'; import { PaymentMethods, PaymentContext as CONTEXT, @@ -28,22 +27,10 @@ import { * @property {string} language - The locale; an empty string will apply the user-agent's language. */ -class GooglepayButton { - /** - * @type {ConsoleLogger} - */ - #logger; - +class GooglepayButton extends PaymentButton { #wrapperId = ''; #ppcpButtonWrapperId = ''; - /** - * Whether the payment button is initialized. - * - * @type {boolean} - */ - #isInitialized = false; - /** * Whether the current client support the payment button. * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` @@ -72,12 +59,8 @@ class GooglepayButton { ppcpConfig, contextHandler ) { - this.#logger = new ConsoleLogger( 'GooglePayButton', context ); - this.#logger.enabled = !! buttonConfig?.is_debug; + super( 'GooglePayButton', context, buttonConfig, ppcpConfig ); - apmButtonsInit( ppcpConfig ); - - this.context = context; this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; this.ppcpConfig = ppcpConfig; @@ -88,14 +71,6 @@ class GooglepayButton { this.log( 'Create instance' ); } - log( ...args ) { - this.#logger.log( ...args ); - } - - error( ...args ) { - this.#logger.error( ...args ); - } - /** * Determines if the current payment button should be rendered as a stand-alone gateway. * The return value `false` usually means, that the payment button is bundled with all available @@ -301,8 +276,21 @@ class GooglepayButton { this.refresh(); } - init( config, transactionInfo ) { - if ( this.#isInitialized ) { + init( config = null, transactionInfo = null ) { + if ( this.isInitialized ) { + return; + } + if ( config ) { + this.googlePayConfig = config; + } + if ( transactionInfo ) { + this.transactionInfo = transactionInfo; + } + + if ( ! this.googlePayConfig || ! this.transactionInfo ) { + this.error( + 'Init called without providing config or transactionInfo' + ); return; } @@ -314,11 +302,8 @@ class GooglepayButton { return; } - this.log( 'Init' ); - this.#isInitialized = true; + super.init(); - this.googlePayConfig = config; - this.transactionInfo = transactionInfo; this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; @@ -353,12 +338,12 @@ class GooglepayButton { } reinit() { - if ( ! this.googlePayConfig ) { + if ( ! this.isInitialized ) { return; } - this.#isInitialized = false; - this.init( this.googlePayConfig, this.transactionInfo ); + super.reinit(); + this.init(); } validateConfig() { From 3c200e408a8d957983046afa9870698f3f32f1fd Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Mon, 5 Aug 2024 12:54:24 +0200 Subject: [PATCH 161/357] =?UTF-8?q?=F0=9F=9A=9A=20Move=20ConsoleLogger=20t?= =?UTF-8?q?o=20wc-gateway=20module?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/modules/Renderer/PaymentButton.js | 2 +- .../resources/js/helper}/ConsoleLogger.js | 3 ++- .../resources/js/helper/preview-button.js | 10 ++++++---- 3 files changed, 9 insertions(+), 6 deletions(-) rename modules/{ppcp-button/resources/js/modules/Helper => ppcp-wc-gateway/resources/js/helper}/ConsoleLogger.js (88%) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 8843ca662..20459f52e 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -1,4 +1,4 @@ -import ConsoleLogger from '../Helper/ConsoleLogger'; +import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger'; import { apmButtonsInit } from '../Helper/ApmButtons'; /** diff --git a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js similarity index 88% rename from modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js rename to modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js index 689f07c0a..4b8891247 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ConsoleLogger.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js @@ -1,5 +1,5 @@ /** - * Logs debug details to the console. + * Helper component to log debug details to the browser console. * * A utility class that is used by payment buttons on the front-end, like the GooglePayButton. */ @@ -30,6 +30,7 @@ export default class ConsoleLogger { log( ...args ) { if ( this.#enabled ) { + // eslint-disable-next-line console.log( this.#prefix, ...args ); } } diff --git a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js index 30b71f511..d9c4f8264 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/preview-button.js @@ -1,9 +1,11 @@ +/* global jQuery */ + /** * Returns a Map with all input fields that are relevant to render the preview of the * given payment button. * * @param {string} apmName - Value of the custom attribute `data-ppcp-apm-name`. - * @return {Map} + * @return {Map} List of input elements found on the current admin page. */ export function getButtonFormFields( apmName ) { const inputFields = document.querySelectorAll( @@ -28,9 +30,9 @@ export function getButtonFormFields( apmName ) { /** * Returns a function that triggers an update of the specified preview button, when invoked. - + * * @param {string} apmName - * @return {((object) => void)} + * @return {((object) => void)} Trigger-function; updates preview buttons when invoked. */ export function buttonRefreshTriggerFactory( apmName ) { const eventName = `ppcp_paypal_render_preview_${ apmName }`; @@ -44,7 +46,7 @@ export function buttonRefreshTriggerFactory( apmName ) { * Returns a function that gets the current form values of the specified preview button. * * @param {string} apmName - * @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} + * @return {() => {button: {wrapper:string, is_enabled:boolean, style:{}}}} Getter-function; returns preview config details when invoked. */ export function buttonSettingsGetterFactory( apmName ) { const fields = getButtonFormFields( apmName ); From 2edeac55442a91353a6b4a8ee6d759635f060ecb Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Mon, 5 Aug 2024 17:36:56 +0200 Subject: [PATCH 162/357] Add new create order endpoint (WIP) --- modules/ppcp-api-client/services.php | 7 ++ .../ppcp-api-client/src/Endpoint/Orders.php | 76 +++++++++++++++++++ .../ppcp-wc-gateway/src/WCGatewayModule.php | 1 + .../PHPUnit/ApiClient/Endpoint/OrdersTest.php | 26 +++++++ tests/e2e/PHPUnit/OrdersTest.php | 49 ++++++++++++ 5 files changed, 159 insertions(+) create mode 100644 modules/ppcp-api-client/src/Endpoint/Orders.php create mode 100644 tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php create mode 100644 tests/e2e/PHPUnit/OrdersTest.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 241240ce2..5b60c7720 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokensEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult; @@ -239,6 +240,12 @@ return array( $bn_code ); }, + 'api.endpoint.orders' => static function (ContainerInterface $container): Orders { + return new Orders( + $container->get( 'api.host' ), + $container->get( 'api.bearer' ) + ); + }, 'api.endpoint.billing-agreements' => static function ( ContainerInterface $container ): BillingAgreementsEndpoint { return new BillingAgreementsEndpoint( $container->get( 'api.host' ), diff --git a/modules/ppcp-api-client/src/Endpoint/Orders.php b/modules/ppcp-api-client/src/Endpoint/Orders.php new file mode 100644 index 000000000..51d01a24d --- /dev/null +++ b/modules/ppcp-api-client/src/Endpoint/Orders.php @@ -0,0 +1,76 @@ +host = $host; + $this->bearer = $bearer; + } + + public function create(array $request_body, array $headers = array()): array { + $bearer = $this->bearer->bearer(); + $url = trailingslashit( $this->host ) . 'v2/checkout/orders'; + + $default_headers = array( + 'Authorization' => 'Bearer ' . $bearer->token(), + 'Content-Type' => 'application/json', + ); + $headers = array_merge( + $default_headers, + $headers + ); + + $args = array( + 'method' => 'POST', + 'headers' => $headers, + 'body' => wp_json_encode( $request_body ), + ); + + $response = wp_remote_get( $url, $args ); + if ( $response instanceof WP_Error ) { + throw new RuntimeException( $response->get_error_message() ); + } + + return $response; + } +} diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php index 0b1315376..f01084da9 100644 --- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php +++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php @@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway; use Psr\Log\LoggerInterface; use Throwable; use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message; +use WooCommerce\PayPalCommerce\ApiClient\Endpoint\Orders; use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php new file mode 100644 index 000000000..1033a015b --- /dev/null +++ b/tests/PHPUnit/ApiClient/Endpoint/OrdersTest.php @@ -0,0 +1,26 @@ +shouldReceive('token')->andReturn(''); + $bearer->shouldReceive('bearer')->andReturn($token); + + $sut = new Orders('', $bearer); + + expect('wp_remote_get')->andReturn([]); + + $this->assertEquals([], $sut->create([])); + } +} diff --git a/tests/e2e/PHPUnit/OrdersTest.php b/tests/e2e/PHPUnit/OrdersTest.php new file mode 100644 index 000000000..da9dc990e --- /dev/null +++ b/tests/e2e/PHPUnit/OrdersTest.php @@ -0,0 +1,49 @@ +getContainer(); + + $orders = new Orders($host, $container->get('api.bearer')); + + $requestBody = [ + "intent" => "CAPTURE", + "payment_source" => [ + "bancontact" => [ + "country_code" => "BE", + "name" => "John Doe" + ] + ], + "processing_instruction" => "ORDER_COMPLETE_ON_PAYMENT_APPROVAL", + "purchase_units" => [ + [ + "reference_id" => "d9f80740-38f0-11e8-b467-0ed5f89f718b", + "amount" => [ + "currency_code" => "EUR", + "value" => "1.00" + ], + ] + ], + "application_context" => [ + "locale" => "en-BE", + "return_url" => "https://example.com/returnUrl", + "cancel_url" => "https://example.com/cancelUrl" + ] + ]; + + $headers = array( + 'PayPal-Request-Id' => uniqid( 'ppcp-', true ), + ); + + $result = $orders->create($requestBody, $headers); + + $this->assertEquals(200, $result['response']['code']); + } +} From bea17db5881dbc80c058fe7eca812f0d82ff4cdc Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 09:25:28 +0200 Subject: [PATCH 163/357] Use client credentials --- modules/ppcp-api-client/services.php | 14 +++++-- .../src/Authentication/ClientCredentials.php | 41 +++++++++++++++++++ .../src/Authentication/SdkClientToken.php | 26 ++++++------ .../src/Authentication/UserIdToken.php | 26 ++++++------ 4 files changed, 75 insertions(+), 32 deletions(-) create mode 100644 modules/ppcp-api-client/src/Authentication/ClientCredentials.php diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 241240ce2..7cb890c18 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\ClientCredentials; use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentMethodTokensEndpoint; @@ -1656,18 +1657,23 @@ return array( return new PurchaseUnitSanitizer( $behavior, $line_name ); } ), + 'api.client-credentials' => static function(ContainerInterface $container): ClientCredentials { + return new ClientCredentials( + $container->get( 'wcgateway.settings' ) + ); + }, 'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken { return new UserIdToken( $container->get( 'api.host' ), - $container->get( 'api.bearer' ), - $container->get( 'woocommerce.logger.woocommerce' ) + $container->get( 'woocommerce.logger.woocommerce' ), + $container->get( 'api.client-credentials' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { return new SdkClientToken( $container->get( 'api.host' ), - $container->get( 'api.bearer' ), - $container->get( 'woocommerce.logger.woocommerce' ) + $container->get( 'woocommerce.logger.woocommerce' ), + $container->get( 'api.client-credentials' ) ); }, ); diff --git a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php new file mode 100644 index 000000000..7ec4628a5 --- /dev/null +++ b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php @@ -0,0 +1,41 @@ +settings = $settings; + } + + public function credentials(): string { + $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; + $client_secret = $this->settings->has( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : ''; + + return 'Basic ' . base64_encode($client_id . ':' . $client_secret); + } +} diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index ba0f3a373..c55847d30 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -27,13 +27,6 @@ class SdkClientToken { */ private $host; - /** - * The bearer. - * - * @var Bearer - */ - private $bearer; - /** * The logger. * @@ -41,21 +34,28 @@ class SdkClientToken { */ private $logger; + /** + * The client credentials. + * + * @var ClientCredentials + */ + private $client_credentials; + /** * SdkClientToken constructor. * * @param string $host The host. - * @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger. + * @param ClientCredentials $client_credentials The client credentials. */ public function __construct( string $host, - Bearer $bearer, - LoggerInterface $logger + LoggerInterface $logger, + ClientCredentials $client_credentials ) { $this->host = $host; - $this->bearer = $bearer; $this->logger = $logger; + $this->client_credentials = $client_credentials; } /** @@ -69,8 +69,6 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token( string $target_customer_id = '' ): string { - $bearer = $this->bearer->bearer(); - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); @@ -89,7 +87,7 @@ class SdkClientToken { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), + 'Authorization' => $this->client_credentials->credentials(), 'Content-Type' => 'application/x-www-form-urlencoded', ), ); diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index cae8cb58a..05dc62282 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -27,13 +27,6 @@ class UserIdToken { */ private $host; - /** - * The bearer. - * - * @var Bearer - */ - private $bearer; - /** * The logger. * @@ -41,21 +34,28 @@ class UserIdToken { */ private $logger; + /** + * The client credentials. + * + * @var ClientCredentials + */ + private $client_credentials; + /** * UserIdToken constructor. * * @param string $host The host. - * @param Bearer $bearer The bearer. * @param LoggerInterface $logger The logger. + * @param ClientCredentials $client_credentials The client credentials. */ public function __construct( string $host, - Bearer $bearer, - LoggerInterface $logger + LoggerInterface $logger, + ClientCredentials $client_credentials ) { $this->host = $host; - $this->bearer = $bearer; $this->logger = $logger; + $this->client_credentials = $client_credentials; } /** @@ -69,8 +69,6 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { - $bearer = $this->bearer->bearer(); - $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; if ( $target_customer_id ) { $url = add_query_arg( @@ -84,7 +82,7 @@ class UserIdToken { $args = array( 'method' => 'POST', 'headers' => array( - 'Authorization' => 'Bearer ' . $bearer->token(), + 'Authorization' => $this->client_credentials->credentials(), 'Content-Type' => 'application/x-www-form-urlencoded', ), ); From 54657ed649352f3d5b8172c245bc4e9499bd9104 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 10:44:54 +0200 Subject: [PATCH 164/357] Add tokens cache --- modules/ppcp-api-client/services.php | 8 +++-- .../src/Authentication/SdkClientToken.php | 32 +++++++++++++---- .../src/Authentication/UserIdToken.php | 32 +++++++++++++---- modules/ppcp-onboarding/services.php | 6 ++-- .../src/Endpoint/LoginSellerEndpoint.php | 34 ++++++++++++++----- modules/ppcp-wc-gateway/services.php | 3 +- .../src/Settings/SettingsListener.php | 20 ++++++++++- 7 files changed, 107 insertions(+), 28 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index 7cb890c18..dd2c786a1 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1657,7 +1657,7 @@ return array( return new PurchaseUnitSanitizer( $behavior, $line_name ); } ), - 'api.client-credentials' => static function(ContainerInterface $container): ClientCredentials { + 'api.client-credentials' => static function( ContainerInterface $container ): ClientCredentials { return new ClientCredentials( $container->get( 'wcgateway.settings' ) ); @@ -1666,14 +1666,16 @@ return array( return new UserIdToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.client-credentials' ) + $container->get( 'api.client-credentials' ), + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { return new SdkClientToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), - $container->get( 'api.client-credentials' ) + $container->get( 'api.client-credentials' ), + new Cache( 'ppcp-client-credentials-cache' ) ); }, ); diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index c55847d30..2e1f3a7ca 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WP_Error; /** @@ -20,6 +21,8 @@ class SdkClientToken { use RequestTrait; + const CACHE_KEY = 'sdk-client-token-key'; + /** * The host. * @@ -41,21 +44,31 @@ class SdkClientToken { */ private $client_credentials; + /** + * The cache. + * + * @var Cache + */ + private $cache; + /** * SdkClientToken constructor. * - * @param string $host The host. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials. + * @param Cache $cache The cache. */ public function __construct( string $host, LoggerInterface $logger, - ClientCredentials $client_credentials + ClientCredentials $client_credentials, + Cache $cache ) { - $this->host = $host; - $this->logger = $logger; + $this->host = $host; + $this->logger = $logger; $this->client_credentials = $client_credentials; + $this->cache = $cache; } /** @@ -69,6 +82,10 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token( string $target_customer_id = '' ): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); @@ -103,6 +120,9 @@ class SdkClientToken { throw new PayPalApiException( $json, $status_code ); } - return $json->access_token; + $access_token = $json->access_token; + $this->cache->set( self::CACHE_KEY, $access_token ); + + return $access_token; } } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 05dc62282..2d18d0507 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -11,6 +11,7 @@ use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait; use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WP_Error; /** @@ -20,6 +21,8 @@ class UserIdToken { use RequestTrait; + const CACHE_KEY = 'user-id-token-key'; + /** * The host. * @@ -41,21 +44,31 @@ class UserIdToken { */ private $client_credentials; + /** + * The cache. + * + * @var Cache + */ + private $cache; + /** * UserIdToken constructor. * - * @param string $host The host. - * @param LoggerInterface $logger The logger. + * @param string $host The host. + * @param LoggerInterface $logger The logger. * @param ClientCredentials $client_credentials The client credentials. + * @param Cache $cache The cache. */ public function __construct( string $host, LoggerInterface $logger, - ClientCredentials $client_credentials + ClientCredentials $client_credentials, + Cache $cache ) { - $this->host = $host; - $this->logger = $logger; + $this->host = $host; + $this->logger = $logger; $this->client_credentials = $client_credentials; + $this->cache = $cache; } /** @@ -69,6 +82,10 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; if ( $target_customer_id ) { $url = add_query_arg( @@ -98,6 +115,9 @@ class UserIdToken { throw new PayPalApiException( $json, $status_code ); } - return $json->id_token; + $id_token = $json->id_token; + $this->cache->set( self::CACHE_KEY, $id_token ); + + return $id_token; } } diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php index b76519f62..e7f493999 100644 --- a/modules/ppcp-onboarding/services.php +++ b/modules/ppcp-onboarding/services.php @@ -189,9 +189,8 @@ return array( $login_seller_sandbox = $container->get( 'api.endpoint.login-seller-sandbox' ); $partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' ); $settings = $container->get( 'wcgateway.settings' ); - $logger = $container->get( 'woocommerce.logger.woocommerce' ); - $cache = new Cache( 'ppcp-paypal-bearer' ); + $logger = $container->get( 'woocommerce.logger.woocommerce' ); return new LoginSellerEndpoint( $request_data, $login_seller_production, @@ -199,7 +198,8 @@ return array( $partner_referrals_data, $settings, $cache, - $logger + $logger, + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint { diff --git a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php index 20ab7b95a..2a30ad6b1 100644 --- a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\Onboarding\Endpoint; use Exception; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\LoginSeller; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData; @@ -76,6 +78,13 @@ class LoginSellerEndpoint implements EndpointInterface { */ protected $logger; + /** + * The client credentials cache. + * + * @var Cache + */ + private $client_credentials_cache; + /** * LoginSellerEndpoint constructor. * @@ -86,6 +95,7 @@ class LoginSellerEndpoint implements EndpointInterface { * @param Settings $settings The Settings. * @param Cache $cache The Cache. * @param LoggerInterface $logger The logger. + * @param Cache $client_credentials_cache The client credentials cache. */ public function __construct( RequestData $request_data, @@ -94,16 +104,18 @@ class LoginSellerEndpoint implements EndpointInterface { PartnerReferralsData $partner_referrals_data, Settings $settings, Cache $cache, - LoggerInterface $logger + LoggerInterface $logger, + Cache $client_credentials_cache ) { - $this->request_data = $request_data; - $this->login_seller_production = $login_seller_production; - $this->login_seller_sandbox = $login_seller_sandbox; - $this->partner_referrals_data = $partner_referrals_data; - $this->settings = $settings; - $this->cache = $cache; - $this->logger = $logger; + $this->request_data = $request_data; + $this->login_seller_production = $login_seller_production; + $this->login_seller_sandbox = $login_seller_sandbox; + $this->partner_referrals_data = $partner_referrals_data; + $this->settings = $settings; + $this->cache = $cache; + $this->logger = $logger; + $this->client_credentials_cache = $client_credentials_cache; } /** @@ -175,6 +187,12 @@ class LoginSellerEndpoint implements EndpointInterface { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } + if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } wp_schedule_single_event( time() + 5, diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php index ce41619ca..52a41576e 100644 --- a/modules/ppcp-wc-gateway/services.php +++ b/modules/ppcp-wc-gateway/services.php @@ -366,7 +366,8 @@ return array( $container->get( 'api.partner_merchant_id-production' ), $container->get( 'api.partner_merchant_id-sandbox' ), $container->get( 'api.endpoint.billing-agreements' ), - $logger + $logger, + new Cache( 'ppcp-client-credentials-cache' ) ); }, 'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor { diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index 9986d5edc..e7e39a9e6 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings; use Psr\Log\LoggerInterface; use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer; use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint; use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; @@ -164,6 +166,13 @@ class SettingsListener { */ private $logger; + /** + * The client credentials cache. + * + * @var Cache + */ + private $client_credentials_cache; + /** * SettingsListener constructor. * @@ -183,6 +192,7 @@ class SettingsListener { * @param string $partner_merchant_id_sandbox Partner merchant ID sandbox. * @param BillingAgreementsEndpoint $billing_agreements_endpoint Billing Agreements endpoint. * @param ?LoggerInterface $logger The logger. + * @param Cache $client_credentials_cache The client credentials cache. */ public function __construct( Settings $settings, @@ -200,7 +210,8 @@ class SettingsListener { string $partner_merchant_id_production, string $partner_merchant_id_sandbox, BillingAgreementsEndpoint $billing_agreements_endpoint, - LoggerInterface $logger = null + LoggerInterface $logger = null, + Cache $client_credentials_cache ) { $this->settings = $settings; @@ -219,6 +230,7 @@ class SettingsListener { $this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox; $this->billing_agreements_endpoint = $billing_agreements_endpoint; $this->logger = $logger ?: new NullLogger(); + $this->client_credentials_cache = $client_credentials_cache; } /** @@ -490,6 +502,12 @@ class SettingsListener { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } + if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } if ( $this->pui_status_cache->has( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ) ) { $this->pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY ); From bc58fce8a0853178fa04bf9398f3d89320e4af1b Mon Sep 17 00:00:00 2001 From: Daniel Dudzic Date: Tue, 6 Aug 2024 10:56:37 +0200 Subject: [PATCH 165/357] Apple Pay: Fix the shipping callback --- .../ppcp-applepay/resources/js/Context/BaseHandler.js | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js index f763ac5d9..00d7bd463 100644 --- a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js +++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js @@ -1,6 +1,5 @@ import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler'; import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler'; -import { isPayPalSubscription } from '../../../../ppcp-blocks/resources/js/Helper/Subscription'; class BaseHandler { constructor( buttonConfig, ppcpConfig ) { @@ -24,7 +23,7 @@ class BaseHandler { } shippingAllowed() { - return this.buttonConfig.product.needsShipping; + return this.buttonConfig.product.needShipping; } transactionInfo() { @@ -76,13 +75,6 @@ class BaseHandler { document.querySelector( '.woocommerce-notices-wrapper' ) ); } - - errorHandler() { - return new ErrorHandler( - this.ppcpConfig.labels.error.generic, - document.querySelector( '.woocommerce-notices-wrapper' ) - ); - } } export default BaseHandler; From 9c380917c55ac75c4d394264da9c9dd2f705c8b0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:14:46 +0200 Subject: [PATCH 166/357] Fix phpunit --- tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php index c34146f3e..c747b19b8 100644 --- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php +++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php @@ -43,6 +43,7 @@ class SettingsListenerTest extends ModularTestCase $billing_agreement_endpoint = Mockery::mock(BillingAgreementsEndpoint::class); $subscription_helper = Mockery::mock(SubscriptionHelper::class); $logger = Mockery::mock(LoggerInterface::class); + $client_credentials_cache = Mockery::mock(Cache::class); $testee = new SettingsListener( $settings, @@ -60,7 +61,8 @@ class SettingsListenerTest extends ModularTestCase '', '', $billing_agreement_endpoint, - $logger + $logger, + $client_credentials_cache ); $_GET['section'] = PayPalGateway::ID; From 86860d4fd6c285d2497cc0b6f5458a89516b24c3 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:23:50 +0200 Subject: [PATCH 167/357] Fix phpunit --- tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php index c747b19b8..efa1b7de6 100644 --- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php +++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php @@ -96,6 +96,9 @@ class SettingsListenerTest extends ModularTestCase ->andReturn(false); $dcc_status_cache->shouldReceive('has') ->andReturn(false); + $client_credentials_cache->shouldReceive('has')->andReturn(true); + $client_credentials_cache->shouldReceive('delete'); + $testee->listen(); } From c68db5c1a11f961ad7890fb9695e58e33edc594c Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 11:35:28 +0200 Subject: [PATCH 168/357] Fix phpcs --- .../src/Authentication/ClientCredentials.php | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php index 7ec4628a5..08d37bead 100644 --- a/modules/ppcp-api-client/src/Authentication/ClientCredentials.php +++ b/modules/ppcp-api-client/src/Authentication/ClientCredentials.php @@ -10,6 +10,7 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient\Authentication; use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings; +use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException; /** * Class ClientCredentials @@ -28,14 +29,21 @@ class ClientCredentials { * * @param Settings $settings The settings. */ - public function __construct(Settings $settings) { + public function __construct( Settings $settings ) { $this->settings = $settings; } + /** + * Returns encoded client credentials. + * + * @return string + * @throws NotFoundException If setting does not found. + */ public function credentials(): string { - $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; + $client_id = $this->settings->has( 'client_id' ) ? $this->settings->get( 'client_id' ) : ''; $client_secret = $this->settings->has( 'client_secret' ) ? $this->settings->get( 'client_secret' ) : ''; - return 'Basic ' . base64_encode($client_id . ':' . $client_secret); + // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode + return 'Basic ' . base64_encode( $client_id . ':' . $client_secret ); } } From 22c2b5afd4703ae02f602dcf99d18b72cacf61fb Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 12:04:58 +0200 Subject: [PATCH 169/357] Delete transients stored while user is logged in --- modules/ppcp-api-client/services.php | 7 +++++-- modules/ppcp-api-client/src/ApiModule.php | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php index dd2c786a1..7f62da5f6 100644 --- a/modules/ppcp-api-client/services.php +++ b/modules/ppcp-api-client/services.php @@ -1662,12 +1662,15 @@ return array( $container->get( 'wcgateway.settings' ) ); }, + 'api.client-credentials-cache' => static function( ContainerInterface $container ): Cache { + return new Cache( 'ppcp-client-credentials-cache' ); + }, 'api.user-id-token' => static function( ContainerInterface $container ): UserIdToken { return new UserIdToken( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.client-credentials' ), - new Cache( 'ppcp-client-credentials-cache' ) + $container->get( 'api.client-credentials-cache' ) ); }, 'api.sdk-client-token' => static function( ContainerInterface $container ): SdkClientToken { @@ -1675,7 +1678,7 @@ return array( $container->get( 'api.host' ), $container->get( 'woocommerce.logger.woocommerce' ), $container->get( 'api.client-credentials' ), - new Cache( 'ppcp-client-credentials-cache' ) + $container->get( 'api.client-credentials-cache' ) ); }, ); diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 42bc1f117..5c3ec56bd 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -10,6 +10,9 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; +use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; +use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient; use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider; @@ -94,6 +97,21 @@ class ApiModule implements ModuleInterface { 10, 2 ); + + add_action( + 'wp_logout', + function() use ( $c ) { + $client_credentials_cache = $c->get( 'api.client-credentials-cache' ); + assert( $client_credentials_cache instanceof Cache ); + + if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { + $client_credentials_cache->delete( UserIdToken::CACHE_KEY ); + } + if ( $client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { + $client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + } + } + ); } /** From a1413782e98dd78ccd2cecb66d056d7f3917a311 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 12:32:40 +0200 Subject: [PATCH 170/357] Invalidate cache when switching user --- .../src/Authentication/SdkClientToken.php | 15 +++++++++++++-- .../src/Authentication/UserIdToken.php | 15 +++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 2e1f3a7ca..85a447176 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -83,7 +83,12 @@ class SdkClientToken { */ public function sdk_client_token( string $target_customer_id = '' ): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); + $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; + $access_token = $this->cache->get( self::CACHE_KEY )['access_token'] ?? ''; + + if ( $user_id === get_current_user_id() && $access_token ) { + return $access_token; + } } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized @@ -121,7 +126,13 @@ class SdkClientToken { } $access_token = $json->access_token; - $this->cache->set( self::CACHE_KEY, $access_token ); + + $data = array( + 'access_token' => $access_token, + 'user_id' => get_current_user_id(), + ); + + $this->cache->set( self::CACHE_KEY, $data ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 2d18d0507..c60801dd5 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -83,7 +83,12 @@ class UserIdToken { */ public function id_token( string $target_customer_id = '' ): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); + $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; + $id_token = $this->cache->get( self::CACHE_KEY )['id_token'] ?? ''; + + if ( $user_id === get_current_user_id() && $id_token ) { + return $id_token; + } } $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; @@ -116,7 +121,13 @@ class UserIdToken { } $id_token = $json->id_token; - $this->cache->set( self::CACHE_KEY, $id_token ); + + $data = array( + 'id_token' => $id_token, + 'user_id' => get_current_user_id(), + ); + + $this->cache->set( self::CACHE_KEY, $data ); return $id_token; } From b42e9436ae88ed159b645b65f07cfdd4cccc8320 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Tue, 6 Aug 2024 15:30:50 +0200 Subject: [PATCH 171/357] Add id token cache key for each user --- modules/ppcp-api-client/src/ApiModule.php | 10 ++----- .../src/Authentication/SdkClientToken.php | 30 +++---------------- .../src/Authentication/UserIdToken.php | 16 ++-------- modules/ppcp-axo/src/AxoModule.php | 10 +------ .../js/modules/Helper/ScriptLoading.js | 5 +++- .../src/Endpoint/LoginSellerEndpoint.php | 3 -- .../src/Settings/SettingsListener.php | 3 -- 7 files changed, 15 insertions(+), 62 deletions(-) diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php index 5c3ec56bd..2d63bd80f 100644 --- a/modules/ppcp-api-client/src/ApiModule.php +++ b/modules/ppcp-api-client/src/ApiModule.php @@ -10,7 +10,6 @@ declare(strict_types=1); namespace WooCommerce\PayPalCommerce\ApiClient; use WC_Order; -use WooCommerce\PayPalCommerce\ApiClient\Authentication\SdkClientToken; use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken; use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache; use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry; @@ -100,15 +99,12 @@ class ApiModule implements ModuleInterface { add_action( 'wp_logout', - function() use ( $c ) { + function( int $user_id ) use ( $c ) { $client_credentials_cache = $c->get( 'api.client-credentials-cache' ); assert( $client_credentials_cache instanceof Cache ); - if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } - if ( $client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { - $client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); + if ( $client_credentials_cache->has( UserIdToken::CACHE_KEY . '-' . (string) $user_id ) ) { + $client_credentials_cache->delete( UserIdToken::CACHE_KEY . '-' . (string) $user_id ); } } ); diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 85a447176..6236869e2 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -72,23 +72,16 @@ class SdkClientToken { } /** - * Returns `sdk_client_token` which uniquely identifies the payer. - * - * @param string $target_customer_id Vaulted customer id. + * Returns the client token for SDK `data-sdk-client-token`. * * @return string * * @throws PayPalApiException If the request fails. * @throws RuntimeException If something unexpected happens. */ - public function sdk_client_token( string $target_customer_id = '' ): string { + public function sdk_client_token(): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; - $access_token = $this->cache->get( self::CACHE_KEY )['access_token'] ?? ''; - - if ( $user_id === get_current_user_id() && $access_token ) { - return $access_token; - } + $this->cache->get( self::CACHE_KEY ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized @@ -97,15 +90,6 @@ class SdkClientToken { $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=client_token&intent=sdk_init&domains[]=' . $domain; - if ( $target_customer_id ) { - $url = add_query_arg( - array( - 'target_customer_id' => $target_customer_id, - ), - $url - ); - } - $args = array( 'method' => 'POST', 'headers' => array( @@ -126,13 +110,7 @@ class SdkClientToken { } $access_token = $json->access_token; - - $data = array( - 'access_token' => $access_token, - 'user_id' => get_current_user_id(), - ); - - $this->cache->set( self::CACHE_KEY, $data ); + $this->cache->set( self::CACHE_KEY, $access_token ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index c60801dd5..3c215a11f 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -82,13 +82,8 @@ class UserIdToken { * @throws RuntimeException If something unexpected happens. */ public function id_token( string $target_customer_id = '' ): string { - if ( $this->cache->has( self::CACHE_KEY ) ) { - $user_id = $this->cache->get( self::CACHE_KEY )['user_id'] ?? 0; - $id_token = $this->cache->get( self::CACHE_KEY )['id_token'] ?? ''; - - if ( $user_id === get_current_user_id() && $id_token ) { - return $id_token; - } + if ( $this->cache->has( self::CACHE_KEY . '-' . (string) get_current_user_id() ) ) { + return $this->cache->get( self::CACHE_KEY . '-' . (string) get_current_user_id() ); } $url = trailingslashit( $this->host ) . 'v1/oauth2/token?grant_type=client_credentials&response_type=id_token'; @@ -122,12 +117,7 @@ class UserIdToken { $id_token = $json->id_token; - $data = array( - 'id_token' => $id_token, - 'user_id' => get_current_user_id(), - ); - - $this->cache->set( self::CACHE_KEY, $data ); + $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token ); return $id_token; } diff --git a/modules/ppcp-axo/src/AxoModule.php b/modules/ppcp-axo/src/AxoModule.php index 3505ff555..c0fc34b24 100644 --- a/modules/ppcp-axo/src/AxoModule.php +++ b/modules/ppcp-axo/src/AxoModule.php @@ -280,15 +280,7 @@ class AxoModule implements ModuleInterface { array $localized_script_data ): array { try { - $target_customer_id = ''; - if ( is_user_logged_in() ) { - $target_customer_id = get_user_meta( get_current_user_id(), '_ppcp_target_customer_id', true ); - if ( ! $target_customer_id ) { - $target_customer_id = get_user_meta( get_current_user_id(), 'ppcp_customer_id', true ); - } - } - - $sdk_client_token = $api->sdk_client_token( $target_customer_id ); + $sdk_client_token = $api->sdk_client_token(); $localized_script_data['axo'] = array( 'sdk_client_token' => $sdk_client_token, ); diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js index 0aa70f793..588e14cd6 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js +++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js @@ -71,7 +71,10 @@ export const loadPaypalScript = ( config, onLoaded, onError = null ) => { } // Load PayPal script for special case with data-client-token - if ( config.data_client_id?.set_attribute ) { + if ( + config.data_client_id?.set_attribute && + config.vault_v3_enabled !== '1' + ) { dataClientIdAttributeHandler( scriptOptions, config.data_client_id, diff --git a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php index 2a30ad6b1..e1d23234f 100644 --- a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php +++ b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php @@ -187,9 +187,6 @@ class LoginSellerEndpoint implements EndpointInterface { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } - if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); } diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php index e7e39a9e6..6c01830d6 100644 --- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php +++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php @@ -502,9 +502,6 @@ class SettingsListener { if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) { $this->cache->delete( PayPalBearer::CACHE_KEY ); } - if ( $this->client_credentials_cache->has( UserIdToken::CACHE_KEY ) ) { - $this->client_credentials_cache->delete( UserIdToken::CACHE_KEY ); - } if ( $this->client_credentials_cache->has( SdkClientToken::CACHE_KEY ) ) { $this->client_credentials_cache->delete( SdkClientToken::CACHE_KEY ); } From b85a16abda79b2db715f8ac0b78e43e6a261f9b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 15:59:54 +0200 Subject: [PATCH 172/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20button=20ev?= =?UTF-8?q?ent=20dispatcher=20to=20helper=20file?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ContextBootstrap/CheckoutBootstap.js | 13 +++-- .../js/modules/Helper/PaymentButtonHelpers.js | 48 +++++++++++++++++++ 2 files changed, 57 insertions(+), 4 deletions(-) create mode 100644 modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js index 7a016fd9e..d679a7f21 100644 --- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js +++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js @@ -7,6 +7,10 @@ import { PaymentMethods, } from '../Helper/CheckoutMethodState'; import BootstrapHelper from '../Helper/BootstrapHelper'; +import { + ButtonEvents, + dispatchButtonEvent, +} from '../Helper/PaymentButtonHelpers'; class CheckoutBootstap { constructor( gateway, renderer, spinner, errorHandler ) { @@ -180,7 +184,7 @@ class CheckoutBootstap { * Custom JS event to notify other modules that the payment button on the checkout page * has become irrelevant or invalid. */ - document.body.dispatchEvent( new Event( 'ppcp_invalidate_methods' ) ); + dispatchButtonEvent( { event: ButtonEvents.INVALIDATE } ); } updateUi() { @@ -247,9 +251,10 @@ class CheckoutBootstap { * Dynamic part of the event name is the payment method ID, for example * "ppcp-credit-card-gateway" or "ppcp-googlepay" */ - document.body.dispatchEvent( - new Event( `ppcp_render_method-${ currentPaymentMethod }` ) - ); + dispatchButtonEvent( { + event: ButtonEvents.RENDER, + paymentMethod: currentPaymentMethod, + } ); document.body.dispatchEvent( new Event( 'ppcp_checkout_rendered' ) ); } diff --git a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js new file mode 100644 index 000000000..19ecfc001 --- /dev/null +++ b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js @@ -0,0 +1,48 @@ +/** + * Helper function used by PaymentButton instances. + * + * @file + */ + +/** + * Collection of recognized event names for payment button events. + * + * @type {Object} + */ +export const ButtonEvents = Object.freeze( { + INVALIDATE: 'ppcp_invalidate_methods', + RENDER: 'ppcp_render_method', + REDRAW: 'ppcp_redraw_method', +} ); + +/** + * Verifies if the given event name is a valid Payment Button event. + * + * @param {string} event - The event name to verify. + * @return {boolean} True, if the event name is valid. + */ +export function isValidButtonEvent( event ) { + const buttonEventValues = Object.values( ButtonEvents ); + + return buttonEventValues.includes( event ); +} + +/** + * Dispatches a payment button event. + * + * @param {Object} options - The options for dispatching the event. + * @param {string} options.event - Event to dispatch. + * @param {string} [options.paymentMethod] - Optional. Name of payment method, to target a specific button only. + * @throws {Error} Throws an error if the event is invalid. + */ +export function dispatchButtonEvent( { event, paymentMethod = '' } ) { + if ( ! isValidButtonEvent( event ) ) { + throw new Error( `Invalid event: ${ event }` ); + } + + const fullEventName = paymentMethod + ? `${ event }-${ paymentMethod }` + : event; + + document.body.dispatchEvent( new Event( fullEventName ) ); +} From fc805a4369c5e4bb35a133af765a7730e74af8d2 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Tue, 6 Aug 2024 17:45:53 +0200 Subject: [PATCH 173/357] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Move=20most=20of?= =?UTF-8?q?=20the=20display=20logic=20to=20base=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The PaymentButton base class now handles display logic that is shared between different APMs --- .../js/modules/Helper/PaymentButtonHelpers.js | 69 +++ .../js/modules/Renderer/PaymentButton.js | 418 +++++++++++++++- .../resources/js/GooglepayButton.js | 459 ++---------------- modules/ppcp-googlepay/src/Assets/Button.php | 7 +- 4 files changed, 539 insertions(+), 414 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js index 19ecfc001..f9a066a23 100644 --- a/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js +++ b/modules/ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers.js @@ -15,6 +15,54 @@ export const ButtonEvents = Object.freeze( { REDRAW: 'ppcp_redraw_method', } ); +/** + * + * @param {string} defaultId - Default wrapper ID. + * @param {string} miniCartId - Wrapper inside the mini-cart. + * @param {string} smartButtonId - ID of the smart button wrapper. + * @param {string} blockId - Block wrapper ID (express checkout, block cart). + * @param {string} gatewayId - Gateway wrapper ID (classic checkout). + * @return {{MiniCart, Gateway, Block, SmartButton, Default}} List of all wrapper IDs, by context. + */ +export function combineWrapperIds( + defaultId = '', + miniCartId = '', + smartButtonId = '', + blockId = '', + gatewayId = '' +) { + const sanitize = ( id ) => id.replace( /^#/, '' ); + + return { + Default: sanitize( defaultId ), + SmartButton: sanitize( smartButtonId ), + Block: sanitize( blockId ), + Gateway: sanitize( gatewayId ), + MiniCart: sanitize( miniCartId ), + }; +} + +/** + * Returns full payment button styles by combining the global ppcpConfig with + * payment-method-specific styling provided via buttonConfig. + * + * @param {Object} ppcpConfig - Global plugin configuration. + * @param {Object} buttonConfig - Payment method specific configuration. + * @return {{MiniCart: (*), Default: (*)}} Combined styles, separated by context. + */ +export function combineStyles( ppcpConfig, buttonConfig ) { + return { + Default: { + ...ppcpConfig.style, + ...buttonConfig.style, + }, + MiniCart: { + ...ppcpConfig.mini_cart_style, + ...buttonConfig.mini_cart_style, + }, + }; +} + /** * Verifies if the given event name is a valid Payment Button event. * @@ -46,3 +94,24 @@ export function dispatchButtonEvent( { event, paymentMethod = '' } ) { document.body.dispatchEvent( new Event( fullEventName ) ); } + +/** + * Adds an event listener for the provided button event. + * + * @param {Object} options - The options for the event listener. + * @param {string} options.event - Event to observe. + * @param {string} [options.paymentMethod] - The payment method name (optional). + * @param {Function} options.callback - The callback function to execute when the event is triggered. + * @throws {Error} Throws an error if the event is invalid. + */ +export function observeButtonEvent( { event, paymentMethod = '', callback } ) { + if ( ! isValidButtonEvent( event ) ) { + throw new Error( `Invalid event: ${ event }` ); + } + + const fullEventName = paymentMethod + ? `${ event }-${ paymentMethod }` + : event; + + document.body.addEventListener( fullEventName, callback ); +} diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 20459f52e..8275d1ee4 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -1,5 +1,30 @@ import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger'; import { apmButtonsInit } from '../Helper/ApmButtons'; +import { PaymentContext } from '../Helper/CheckoutMethodState'; +import { + ButtonEvents, + dispatchButtonEvent, + observeButtonEvent, +} from '../Helper/PaymentButtonHelpers'; + +/** + * Collection of all available styling options for this button. + * + * @typedef {Object} StylesCollection + * @property {string} Default - Default button styling. + * @property {string} MiniCart - Styles for mini-cart button. + */ + +/** + * Collection of all available wrapper IDs that are possible for the button. + * + * @typedef {Object} WrapperCollection + * @property {string} Default - Default button wrapper. + * @property {string} Gateway - Wrapper for separate gateway. + * @property {string} Block - Wrapper for block checkout button. + * @property {string} MiniCart - Wrapper for mini-cart button. + * @property {string} SmartButton - Wrapper for smart button container. + */ /** * Base class for APM payment buttons, like GooglePay and ApplePay. @@ -12,6 +37,11 @@ export default class PaymentButton { */ #logger; + /** + * @type {string} + */ + #methodId; + /** * Whether the payment button is initialized. * @@ -21,27 +51,105 @@ export default class PaymentButton { /** * The button's context. + * + * @type {string} */ #context; + /** + * Object containing the IDs of all possible wrapper elements that might contain this + * button; only one wrapper is relevant, depending on the value of the context. + * + * @type {Object} + */ + #wrappers; + + /** + * @type {StylesCollection} + */ + #styles; + + /** + * APM relevant configuration; e.g., configuration of the GooglePay button + */ #buttonConfig; + /** + * Plugin-wide configuration; i.e., PayPal client ID, shop currency, etc. + */ #ppcpConfig; - constructor( gatewayName, context, buttonConfig, ppcpConfig ) { - this.#logger = new ConsoleLogger( gatewayName, context ); + /** + * Whether the current browser/website support the payment method. + * + * @type {boolean} + */ + #isEligible = false; + + /** + * Whether this button is visible. Modified by `show()` and `hide()` + * + * @type {boolean} + */ + #isVisible = true; + + /** + * The currently visible payment button. + * + * @see {PaymentButton.insertButton} + * @type {HTMLElement|null} + */ + #button = null; + + /** + * Initialize the payment button instance. + * + * @param {string} methodId - Payment method ID (slug, e.g., "ppcp-googlepay"). + * @param {string} context - Button context name. + * @param {WrapperCollection} wrappers - Button wrapper IDs, by context. + * @param {StylesCollection} styles - Button styles, by context. + * @param {Object} buttonConfig - Payment button specific configuration. + * @param {Object} ppcpConfig - Plugin wide configuration object. + */ + constructor( + methodId, + context, + wrappers, + styles, + buttonConfig, + ppcpConfig + ) { + const methodName = methodId.replace( /^ppcp?-/, '' ); + + this.#methodId = methodId; + + this.#logger = new ConsoleLogger( methodName, context ); this.#logger.enabled = !! buttonConfig?.is_debug; this.#context = context; + this.#wrappers = wrappers; + this.#styles = styles; this.#buttonConfig = buttonConfig; this.#ppcpConfig = ppcpConfig; apmButtonsInit( ppcpConfig ); + this.initEventListeners(); } /** - * Whether the payment button was fully initialized. Read-only. + * Internal ID of the payment gateway. * + * @readonly + * @return {string} The internal gateway ID. + */ + get methodId() { + return this.#methodId; + } + + /** + * Whether the payment button was fully initialized. + * + * @readonly * @return {boolean} True indicates, that the button was fully initialized. */ get isInitialized() { @@ -49,16 +157,188 @@ export default class PaymentButton { } /** - * The button's context. Read-only. + * The button's context. * * TODO: Convert the string to a context-object (primitive obsession smell) * + * @readonly * @return {string} The button context. */ get context() { return this.#context; } + /** + * Button wrapper details. + * + * @readonly + * @return {WrapperCollection} Wrapper IDs. + */ + get wrappers() { + return this.#wrappers; + } + + /** + * Returns the context-relevant button style object. + * + * @readonly + * @return {string} Styling options. + */ + get style() { + if ( PaymentContext.MiniCart === this.context ) { + return this.#styles.MiniCart; + } + + return this.#styles.Default; + } + + /** + * Returns the context-relevant wrapper ID. + * + * @readonly + * @return {string} The wrapper-element's ID (without the `#` prefix). + */ + get wrapperId() { + if ( PaymentContext.MiniCart === this.context ) { + return this.wrappers.MiniCart; + } else if ( this.isSeparateGateway ) { + return this.wrappers.Gateway; + } else if ( PaymentContext.Blocks.includes( this.context ) ) { + return this.wrappers.Block; + } + + return this.wrappers.Default; + } + + /** + * Determines if the current payment button should be rendered as a stand-alone gateway. + * The return value `false` usually means, that the payment button is bundled with all available + * payment buttons. + * + * The decision depends on the button context (placement) and the plugin settings. + * + * @return {boolean} True, if the current button represents a stand-alone gateway. + */ + get isSeparateGateway() { + return ( + this.#buttonConfig.is_wc_gateway_enabled && + PaymentContext.Gateways.includes( this.context ) + ); + } + + /** + * Determines if the current button instance has valid and complete configuration details. + * Used during initialization to decide if the button can be initialized or should be skipped. + * + * Can be implemented by the derived class. + * + * @return {boolean} True indicates the config is valid and initialization can continue. + */ + get isConfigValid() { + return true; + } + + /** + * Whether the browser can accept this payment method. + * + * @return {boolean} True, if payments are technically possible. + */ + get isEligible() { + return this.#isEligible; + } + + /** + * Changes the eligibility state of this button component. + * + * @param {boolean} newState Whether the browser can accept payments. + */ + set isEligible( newState ) { + if ( newState === this.#isEligible ) { + return; + } + + this.#isEligible = newState; + this.triggerRedraw(); + } + + /** + * The visibility state of the button. + * This flag does not reflect actual visibility on the page, but rather, if the button + * is intended/allowed to be displayed, in case all other checks pass. + * + * @return {boolean} True indicates, that the button can be displayed. + */ + get isVisible() { + return this.#isVisible; + } + + /** + * Change the visibility of the button. + * + * A visible button does not always force the button to render on the page. It only means, that + * the button is allowed or not allowed to render, if certain other conditions are met. + * + * @param {boolean} newState Whether rendering the button is allowed. + */ + set isVisible( newState ) { + if ( this.#isVisible === newState ) { + return; + } + + this.#isVisible = newState; + this.triggerRedraw(); + } + + /** + * Returns the HTML element that wraps the current button + * + * @readonly + * @return {HTMLElement|null} The wrapper element, or null. + */ + get wrapperElement() { + return document.getElementById( this.wrapperId ); + } + + /** + * Checks whether the main button-wrapper is present in the current DOM. + * + * @readonly + * @return {boolean} True, if the button context (wrapper element) is found. + */ + get isPresent() { + return this.wrapperElement instanceof HTMLElement; + } + + /** + * Returns an array of HTMLElements that belong to the payment button. + * + * @readonly + * @return {HTMLElement[]} List of payment button wrapper elements. + */ + get allElements() { + const selectors = []; + + // Payment button (Pay now, smart button block) + selectors.push( `#${ this.wrapperId }` ); + + // Block Checkout: Express checkout button. + if ( PaymentContext.Blocks.includes( this.context ) ) { + selectors.push( `#${ this.wrappers.Block }` ); + } + + // Classic Checkout: Separate gateway. + if ( this.isSeparateGateway ) { + selectors.push( + `.wc_payment_method.payment_method_${ this.methodId }` + ); + } + + this.log( 'Wrapper Elements:', selectors ); + return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => + Array.from( document.querySelectorAll( selector ) ) + ); + } + /** * Log a debug detail to the browser console. * @@ -98,4 +378,134 @@ export default class PaymentButton { reinit() { this.#isInitialized = false; } + + triggerRedraw() { + dispatchButtonEvent( { + event: ButtonEvents.REDRAW, + paymentMethod: this.methodId, + } ); + } + + /** + * Attaches event listeners to show or hide the payment button when needed. + */ + initEventListeners() { + // Refresh the button - this might show, hide or re-create the payment button. + observeButtonEvent( { + event: ButtonEvents.REDRAW, + paymentMethod: this.methodId, + callback: () => this.refresh(), + } ); + + // Events relevant for buttons inside a payment gateway. + if ( PaymentContext.Gateways.includes( this.context ) ) { + // Hide the button right after the user selected _any_ gateway. + observeButtonEvent( { + event: ButtonEvents.INVALIDATE, + callback: () => ( this.isVisible = false ), + } ); + + // Show the button (again) when the user selected the current gateway. + observeButtonEvent( { + event: ButtonEvents.RENDER, + paymentMethod: this.methodId, + callback: () => ( this.isVisible = true ), + } ); + } + } + + /** + * Refreshes the payment button on the page. + */ + refresh() { + const showButtonWrapper = () => { + this.log( 'Show' ); + + const styleSelectors = `style[data-hide-gateway="${ this.methodId }"]`; + + document + .querySelectorAll( styleSelectors ) + .forEach( ( el ) => el.remove() ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'block'; + } ); + }; + + const hideButtonWrapper = () => { + this.log( 'Hide' ); + + this.allElements.forEach( ( element ) => { + element.style.display = 'none'; + } ); + }; + + // Refresh or hide the actual payment button. + if ( this.isVisible ) { + this.addButton(); + } else { + this.removeButton(); + } + + // Show the wrapper or gateway entry, i.e. add space for the button. + if ( this.isEligible && this.isPresent ) { + showButtonWrapper(); + } else { + hideButtonWrapper(); + } + } + + /** + * Prepares the button wrapper element and inserts the provided payment button into the DOM. + * + * @param {HTMLElement} button - The button element to inject. + */ + insertButton( button ) { + if ( ! this.isPresent ) { + return; + } + + if ( this.#button ) { + this.#button.remove(); + } + + this.#button = button; + this.log( 'addButton', button ); + + const wrapper = this.wrapperElement; + const { shape, height } = this.style; + const methodSlug = this.methodId.replace( /^ppcp?-/, '' ); + + wrapper.classList.add( + `ppcp-button-${ shape }`, + 'ppcp-button-apm', + `ppcp-button-${ methodSlug }` + ); + + if ( height ) { + wrapper.style.height = `${ height }px`; + } + + wrapper.appendChild( button ); + } + + /** + * Removes the payment button from the DOM. + */ + removeButton() { + if ( ! this.isPresent ) { + return; + } + + this.log( 'removeButton' ); + + if ( this.#button ) { + this.#button.remove(); + } + this.#button = null; + + const wrapper = this.wrapperElement; + + wrapper.innerHTML = ''; + } } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index f778ff8e2..347eb819b 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -1,12 +1,13 @@ /* global google */ +import { + combineStyles, + combineWrapperIds, +} from '../../../ppcp-button/resources/js/modules/Helper/PaymentButtonHelpers'; import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton'; import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder'; import UpdatePaymentData from './Helper/UpdatePaymentData'; -import { - PaymentMethods, - PaymentContext as CONTEXT, -} from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; +import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState'; /** * Plugin-specific styling. @@ -28,24 +29,6 @@ import { */ class GooglepayButton extends PaymentButton { - #wrapperId = ''; - #ppcpButtonWrapperId = ''; - - /** - * Whether the current client support the payment button. - * This state is mainly dependent on the response of `PaymentClient.isReadyToPay()` - * - * @type {boolean} - */ - #isEligible = false; - - /** - * Whether this button is visible. Modified by `show()` and `hide()` - * - * @type {boolean} - */ - #isVisible = false; - /** * Client reference, provided by the Google Pay JS SDK. * @see https://developers.google.com/pay/api/web/reference/client @@ -59,221 +42,54 @@ class GooglepayButton extends PaymentButton { ppcpConfig, contextHandler ) { - super( 'GooglePayButton', context, buttonConfig, ppcpConfig ); + const wrappers = combineWrapperIds( + buttonConfig.button.wrapper, + buttonConfig.button.mini_cart_wrapper, + ppcpConfig.button.wrapper, + 'express-payment-method-ppcp-googlepay', + 'ppc-button-ppcp-googlepay' + ); + + console.log( ppcpConfig.button, buttonConfig.button ); + + const styles = combineStyles( ppcpConfig.button, buttonConfig.button ); + + if ( 'buy' === styles.MiniCart.type ) { + styles.MiniCart.type = 'pay'; + } + + super( + PaymentMethods.GOOGLEPAY, + context, + wrappers, + styles, + buttonConfig, + ppcpConfig + ); - this.externalHandler = externalHandler; this.buttonConfig = buttonConfig; - this.ppcpConfig = ppcpConfig; this.contextHandler = contextHandler; - this.refresh = this.refresh.bind( this ); - this.log( 'Create instance' ); } /** - * Determines if the current payment button should be rendered as a stand-alone gateway. - * The return value `false` usually means, that the payment button is bundled with all available - * payment buttons. - * - * The decision depends on the button context (placement) and the plugin settings. - * - * @return {boolean} True, if the current button represents a stand-alone gateway. + * @inheritDoc */ - get isSeparateGateway() { - return ( - this.buttonConfig.is_wc_gateway_enabled && - CONTEXT.Gateways.includes( this.context ) - ); - } + get isConfigValid() { + const validEnvs = [ 'PRODUCTION', 'TEST' ]; - /** - * Returns the wrapper ID for the current button context. - * The ID varies for the MiniCart context. - * - * @return {string} The wrapper-element's ID (without the `#` prefix). - */ - get wrapperId() { - if ( ! this.#wrapperId ) { - let id; - - if ( CONTEXT.MiniCart === this.context ) { - id = this.buttonConfig.button.mini_cart_wrapper; - } else if ( this.isSeparateGateway ) { - id = 'ppc-button-ppcp-googlepay'; - } else { - id = this.buttonConfig.button.wrapper; - } - - this.#wrapperId = id.replace( /^#/, '' ); + if ( ! validEnvs.includes( this.buttonConfig.environment ) ) { + this.error( 'Invalid environment.', this.buttonConfig.environment ); + return false; } - return this.#wrapperId; - } - - /** - * Returns the wrapper ID for the ppcpButton - * - * @return {string} The wrapper-element's ID (without the `#` prefix). - */ - get ppcpButtonWrapperId() { - if ( ! this.#ppcpButtonWrapperId ) { - let id; - - if ( CONTEXT.MiniCart === this.context ) { - id = this.ppcpConfig.button.mini_cart_wrapper; - } else if ( CONTEXT.Blocks.includes( this.context ) ) { - id = 'express-payment-method-ppcp-gateway-paypal'; - } else { - id = this.ppcpConfig.button.wrapper; - } - - this.#ppcpButtonWrapperId = id.replace( /^#/, '' ); + if ( ! typeof this.contextHandler?.validateContext() ) { + this.error( 'Invalid context handler.', this.contextHandler ); + return false; } - return this.#ppcpButtonWrapperId; - } - - /** - * Returns the context-relevant PPCP style object. - * The style for the MiniCart context can be different. - * - * The PPCP style are custom style options, that are provided by this plugin. - * - * @return {PPCPStyle} The style object. - */ - get ppcpStyle() { - if ( CONTEXT.MiniCart === this.context ) { - return this.ppcpConfig.button.mini_cart_style; - } - - return this.ppcpConfig.button.style; - } - - /** - * Returns default style options that are propagated to and rendered by the Google Pay button. - * - * These styles are the official style options provided by the Google Pay SDK. - * - * @return {GooglePayStyle} The style object. - */ - get buttonStyle() { - let style; - - if ( CONTEXT.MiniCart === this.context ) { - style = this.buttonConfig.button.mini_cart_style; - - // Handle incompatible types. - if ( style.type === 'buy' ) { - style.type = 'pay'; - } - } else { - style = this.buttonConfig.button.style; - } - - return { - type: style.type, - language: style.language, - color: style.color, - }; - } - - /** - * Returns the HTML element that wraps the current button - * - * @return {HTMLElement|null} The wrapper element, or null. - */ - get wrapperElement() { - return document.getElementById( this.wrapperId ); - } - - /** - * Returns an array of HTMLElements that belong to the payment button. - * - * @return {HTMLElement[]} List of payment button wrapper elements. - */ - get allElements() { - const selectors = []; - - // Payment button (Pay now, smart button block) - selectors.push( `#${ this.wrapperId }` ); - - // Block Checkout: Express checkout button. - if ( CONTEXT.Blocks.includes( this.context ) ) { - selectors.push( '#express-payment-method-ppcp-googlepay' ); - } - - // Classic Checkout: Google Pay gateway. - if ( CONTEXT.Gateways === this.context ) { - selectors.push( - '.wc_payment_method.payment_method_ppcp-googlepay' - ); - } - - this.log( 'Wrapper Elements:', selectors ); - return /** @type {HTMLElement[]} */ selectors.flatMap( ( selector ) => - Array.from( document.querySelectorAll( selector ) ) - ); - } - - /** - * Checks whether the main button-wrapper is present in the current DOM. - * - * @return {boolean} True, if the button context (wrapper element) is found. - */ - get isPresent() { - return this.wrapperElement instanceof HTMLElement; - } - - /** - * The visibility state of the button. - * This flag does not reflect actual visibility on the page, but rather, if the button - * is intended/allowed to be displayed, in case all other checks pass. - * - * @return {boolean} True indicates, that the button can be displayed - */ - get isVisible() { - return this.#isVisible; - } - - /** - * Change the visibility of the button. - * - * A visible button does not always force the button to render on the page. It only means, that - * the button is allowed or not allowed to render, if certain other conditions are met. - * - * @param {boolean} newState Whether rendering the button is allowed. - */ - set isVisible( newState ) { - if ( this.#isVisible === newState ) { - return; - } - - this.#isVisible = newState; - this.refresh(); - } - - /** - * Whether the browser can accept Google Pay payments. - * - * @return {boolean} True, if payments are technically possible. - */ - get isEligible() { - return this.#isEligible; - } - - /** - * Changes the eligibility state of this button component. - * - * @param {boolean} newState Whether the browser can accept payments. - */ - set isEligible( newState ) { - if ( newState === this.#isEligible ) { - return; - } - - this.#isEligible = newState; - this.refresh(); + return true; } init( config = null, transactionInfo = null ) { @@ -288,27 +104,24 @@ class GooglepayButton extends PaymentButton { } if ( ! this.googlePayConfig || ! this.transactionInfo ) { - this.error( - 'Init called without providing config or transactionInfo' - ); + this.error( 'Missing config or transactionInfo during init.' ); return; } - if ( ! this.validateConfig() ) { + if ( ! this.isConfigValid ) { return; } - if ( ! this.contextHandler.validateContext() ) { - return; - } - - super.init(); - this.allowedPaymentMethods = config.allowedPaymentMethods; this.baseCardPaymentMethod = this.allowedPaymentMethods[ 0 ]; + super.init(); this.initClient(); - this.initEventHandlers(); + + if ( ! this.isPresent ) { + this.log( 'Payment wrapper not found', this.wrapperId ); + return; + } this.paymentsClient .isReadyToPay( @@ -319,17 +132,7 @@ class GooglepayButton extends PaymentButton { ) .then( ( response ) => { this.log( 'PaymentsClient.isReadyToPay response:', response ); - - /** - * In case the button wrapper element is not present in the DOM yet, wait for it - * to appear. Only proceed, if a button wrapper is found on this page. - * - * Not sure if this is needed, or if we can directly test for `this.isPresent` - * without any delay. - */ - this.waitForWrapper( () => { - this.isEligible = !! response.result; - } ); + this.isEligible = !! response.result; } ) .catch( ( err ) => { console.error( err ); @@ -346,30 +149,6 @@ class GooglepayButton extends PaymentButton { this.init(); } - validateConfig() { - if ( - [ 'PRODUCTION', 'TEST' ].indexOf( - this.buttonConfig.environment - ) === -1 - ) { - console.error( - '[GooglePayButton] Invalid environment.', - this.buttonConfig.environment - ); - return false; - } - - if ( ! this.contextHandler ) { - console.error( - '[GooglePayButton] Invalid context handler.', - this.contextHandler - ); - return false; - } - - return true; - } - initClient() { const callbacks = { onPaymentAuthorized: this.onPaymentAuthorized.bind( this ), @@ -394,51 +173,6 @@ class GooglepayButton extends PaymentButton { } ); } - initEventHandlers() { - if ( CONTEXT.Gateways.includes( this.context ) ) { - document.body.addEventListener( 'ppcp_invalidate_methods', () => { - this.isVisible = false; - } ); - - document.body.addEventListener( - `ppcp_render_method-${ PaymentMethods.GOOGLEPAY }`, - () => { - this.isVisible = true; - } - ); - } else { - /** - * Review: The following logic appears to be unnecessary. Is it still required? - * / - const ppcpButtonWrapper = `#${ this.ppcpButtonWrapperId }`; - const wrapper = `#${ this.wrapperId }`; - if ( wrapper === ppcpButtonWrapper ) { - throw new Error( - `[GooglePayButton] "wrapper" and "ppcpButtonWrapper" values must differ to avoid infinite loop. Current value: "${ wrapper }"` - ); - } - const syncButtonVisibility = () => { - const $ppcpButtonWrapper = jQuery( ppcpButtonWrapper ); - setVisible( wrapper, $ppcpButtonWrapper.is( ':visible' ) ); - setEnabled( - wrapper, - ! $ppcpButtonWrapper.hasClass( 'ppcp-disabled' ) - ); - }; - jQuery( document ).on( - 'ppcp-shown ppcp-hidden ppcp-enabled ppcp-disabled', - ( ev, data ) => { - if ( jQuery( data.selector ).is( ppcpButtonWrapper ) ) { - syncButtonVisibility(); - } - } - ); - syncButtonVisibility(); - // - */ - } - } - buildReadyToPayRequest( allowedPaymentMethods, baseRequest ) { this.log( 'Ready To Pay request', baseRequest, allowedPaymentMethods ); @@ -448,25 +182,11 @@ class GooglepayButton extends PaymentButton { } /** - * Add a Google Pay purchase button + * Add a Google Pay purchase button. */ addButton() { - this.log( 'addButton' ); - - const wrapper = this.wrapperElement; const baseCardPaymentMethod = this.baseCardPaymentMethod; - const { color, type, language } = this.buttonStyle; - const { shape, height } = this.ppcpStyle; - - wrapper.classList.add( - `ppcp-button-${ shape }`, - 'ppcp-button-apm', - 'ppcp-button-googlepay' - ); - - if ( height ) { - wrapper.style.height = `${ height }px`; - } + const { color, type, language } = this.style; /** * @see https://developers.google.com/pay/api/web/reference/client#createButton @@ -480,84 +200,7 @@ class GooglepayButton extends PaymentButton { buttonSizeMode: 'fill', } ); - this.log( 'Insert Button', { - wrapper, - button, - } ); - - wrapper.replaceChildren( button ); - } - - /** - * Waits for the current button's wrapper element to become available in the DOM. - * - * Not sure if still needed, or if a simple `this.isPresent` check is sufficient. - * - * @param {Function} callback Function to call when the wrapper element was detected. Only - * called on success. - * @param {number} delay Optional. Polling interval to inspect the DOM. Default to 0.1 sec - * @param {number} timeout Optional. Max timeout in ms. Defaults to 2 sec - */ - waitForWrapper( callback, delay = 100, timeout = 2000 ) { - let interval = 0; - const startTime = Date.now(); - - const stop = () => { - if ( interval ) { - clearInterval( interval ); - } - interval = 0; - }; - - const checkElement = () => { - if ( this.isPresent ) { - stop(); - callback(); - return; - } - - const timeElapsed = Date.now() - startTime; - - if ( timeElapsed > timeout ) { - stop(); - this.error( 'Wrapper not found:', this.wrapperId ); - } - }; - - interval = setInterval( checkElement, delay ); - } - - /** - * Refreshes the payment button on the page. - */ - refresh() { - const showButtonWrapper = () => { - this.log( 'Show' ); - - // Classic Checkout: Make the Google Pay gateway visible. - document - .querySelectorAll( 'style#ppcp-hide-google-pay' ) - .forEach( ( el ) => el.remove() ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'block'; - } ); - }; - - const hideButtonWrapper = () => { - this.log( 'Hide' ); - - this.allElements.forEach( ( element ) => { - element.style.display = 'none'; - } ); - }; - - if ( this.isVisible && this.isEligible && this.isPresent ) { - showButtonWrapper(); - this.addButton(); - } else { - hideButtonWrapper(); - } + this.insertButton( button ); } //------------------------ diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 98bff2c97..2f87a487d 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -345,9 +345,12 @@ class Button implements ButtonInterface { * @return void */ protected function hide_gateway_until_eligible() : void { + ?> - Date: Wed, 7 Aug 2024 09:51:12 +0200 Subject: [PATCH 174/357] Fix not returning the cached token --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 6236869e2..10b1eb0eb 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -81,7 +81,7 @@ class SdkClientToken { */ public function sdk_client_token(): string { if ( $this->cache->has( self::CACHE_KEY ) ) { - $this->cache->get( self::CACHE_KEY ); + return $this->cache->get( self::CACHE_KEY ); } // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized From 4f47b382c571ead74c5f5cf768e37ecae79389ea Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 10:36:05 +0200 Subject: [PATCH 175/357] Add token expiration --- .../ppcp-api-client/src/Authentication/SdkClientToken.php | 4 +++- modules/ppcp-api-client/src/Authentication/UserIdToken.php | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 10b1eb0eb..5031f1291 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -110,7 +110,9 @@ class SdkClientToken { } $access_token = $json->access_token; - $this->cache->set( self::CACHE_KEY, $access_token ); + $expires_in = (int) $json->expires_in; + + $this->cache->set( self::CACHE_KEY, $access_token, $expires_in ); return $access_token; } diff --git a/modules/ppcp-api-client/src/Authentication/UserIdToken.php b/modules/ppcp-api-client/src/Authentication/UserIdToken.php index 3c215a11f..9487a7238 100644 --- a/modules/ppcp-api-client/src/Authentication/UserIdToken.php +++ b/modules/ppcp-api-client/src/Authentication/UserIdToken.php @@ -115,9 +115,10 @@ class UserIdToken { throw new PayPalApiException( $json, $status_code ); } - $id_token = $json->id_token; + $id_token = $json->id_token; + $expires_in = (int) $json->expires_in; - $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token ); + $this->cache->set( self::CACHE_KEY . '-' . (string) get_current_user_id(), $id_token, $expires_in ); return $id_token; } From 7778cfafed613b6286d08e38ed9eb9b2ec1f48c0 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 12:45:44 +0200 Subject: [PATCH 176/357] Revert #2283 --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index 5031f1291..b85490acf 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -80,10 +80,6 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token(): string { - if ( $this->cache->has( self::CACHE_KEY ) ) { - return $this->cache->get( self::CACHE_KEY ); - } - // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); From 939991c42f794ff26813c6d811e412fe355793b4 Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 12:47:11 +0200 Subject: [PATCH 177/357] Revert #2283 --- modules/ppcp-api-client/src/Authentication/SdkClientToken.php | 4 ++++ .../src/SavePaymentMethodsModule.php | 4 ---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php index b85490acf..5031f1291 100644 --- a/modules/ppcp-api-client/src/Authentication/SdkClientToken.php +++ b/modules/ppcp-api-client/src/Authentication/SdkClientToken.php @@ -80,6 +80,10 @@ class SdkClientToken { * @throws RuntimeException If something unexpected happens. */ public function sdk_client_token(): string { + if ( $this->cache->has( self::CACHE_KEY ) ) { + return $this->cache->get( self::CACHE_KEY ); + } + // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $domain = wp_unslash( $_SERVER['HTTP_HOST'] ?? '' ); $domain = preg_replace( '/^www\./', '', $domain ); diff --git a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php index db7335cf5..b667fb10b 100644 --- a/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php +++ b/modules/ppcp-save-payment-methods/src/SavePaymentMethodsModule.php @@ -84,10 +84,6 @@ class SavePaymentMethodsModule implements ModuleInterface { add_filter( 'woocommerce_paypal_payments_localized_script_data', function( array $localized_script_data ) use ( $c ) { - if ( ! is_user_logged_in() ) { - return $localized_script_data; - } - $api = $c->get( 'api.user-id-token' ); assert( $api instanceof UserIdToken ); From c0bd7a5c46be17e589f90a2cc73a272c9feb091e Mon Sep 17 00:00:00 2001 From: Emili Castells Guasch Date: Wed, 7 Aug 2024 14:24:44 +0200 Subject: [PATCH 178/357] Release 2.8.3-rc1 --- changelog.txt | 17 +++++++++++++++++ package.json | 2 +- readme.txt | 19 ++++++++++++++++++- woocommerce-paypal-payments.php | 4 ++-- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/changelog.txt b/changelog.txt index aaf167948..e28d7a258 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,22 @@ *** Changelog *** += 2.8.3 - xxxx-xx-xx = +* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474 +* Fix - Do not add tax info into order meta during order creation #2471 +* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425 +* Fix - PayPal js files loaded on non PayPal pages #2411 +* Fix - Google Pay: Fix the incorrect popup triggering #2414 +* Fix - Add tax configurator when programmatically creating WC orders #2431 +* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402 +* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334 +* Fix - Prevent displaying smart button multiple times on variable product page #2420 +* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404 +* Fix - Use client credentials for user tokens #2491 +* Fix - Apple Pay: Fix the shipping callback #2492 +* Enhancement - Separate Google Pay button for Classic Checkout #2430 +* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468 +* Enhancement - Add AMEX support for Advanced Card Processing in China #2469 + = 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 diff --git a/package.json b/package.json index 61c625ca9..72aad99db 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "woocommerce-paypal-payments", - "version": "2.8.2", + "version": "2.8.3", "description": "WooCommerce PayPal Payments", "repository": "https://github.com/woocommerce/woocommerce-paypal-payments", "license": "GPL-2.0", diff --git a/readme.txt b/readme.txt index e4404042e..fc1976e15 100644 --- a/readme.txt +++ b/readme.txt @@ -4,7 +4,7 @@ Tags: woocommerce, paypal, payments, ecommerce, checkout, cart, pay later, apple Requires at least: 5.3 Tested up to: 6.6 Requires PHP: 7.2 -Stable tag: 2.8.2 +Stable tag: 2.8.3 License: GPLv2 License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -179,6 +179,23 @@ If you encounter issues with the PayPal buttons not appearing after an update, p == Changelog == += 2.8.3 - xxxx-xx-xx = +* Fix - Google Pay: Prevent field validation from being triggered on checkout page load #2474 +* Fix - Do not add tax info into order meta during order creation #2471 +* Fix - PayPal declares subscription support when for Subscription mode is set Disable PayPal for subscription #2425 +* Fix - PayPal js files loaded on non PayPal pages #2411 +* Fix - Google Pay: Fix the incorrect popup triggering #2414 +* Fix - Add tax configurator when programmatically creating WC orders #2431 +* Fix - Shipping callback compatibility with WC Name Your Price plugin #2402 +* Fix - Uncaught Error: Cannot use object of type ...\Settings as array in .../AbstractPaymentMethodType.php (3253) #2334 +* Fix - Prevent displaying smart button multiple times on variable product page #2420 +* Fix - Prevent enabling Standard Card Button when ACDC is enabled #2404 +* Fix - Use client credentials for user tokens #2491 +* Fix - Apple Pay: Fix the shipping callback #2492 +* Enhancement - Separate Google Pay button for Classic Checkout #2430 +* Enhancement - Add Apple Pay and Google Pay support for China, simplify country-currency matrix #2468 +* Enhancement - Add AMEX support for Advanced Card Processing in China #2469 + = 2.8.2 - 2024-07-22 = * Fix - Sold individually checkbox automatically disabled after adding product to the cart more than once #2415 * Fix - All products "Sold individually" when PayPal Subscriptions selected as Subscriptions Mode #2400 diff --git a/woocommerce-paypal-payments.php b/woocommerce-paypal-payments.php index 2cdf4f8ed..fcf66ae12 100644 --- a/woocommerce-paypal-payments.php +++ b/woocommerce-paypal-payments.php @@ -3,7 +3,7 @@ * Plugin Name: WooCommerce PayPal Payments * Plugin URI: https://woocommerce.com/products/woocommerce-paypal-payments/ * Description: PayPal's latest complete payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets local payment types and bank accounts. Turn on only PayPal options or process a full suite of payment methods. Enable global transaction with extensive currency and country coverage. - * Version: 2.8.2 + * Version: 2.8.3 * Author: WooCommerce * Author URI: https://woocommerce.com/ * License: GPL-2.0 @@ -26,7 +26,7 @@ define( 'PAYPAL_API_URL', 'https://api-m.paypal.com' ); define( 'PAYPAL_URL', 'https://www.paypal.com' ); define( 'PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com' ); define( 'PAYPAL_SANDBOX_URL', 'https://www.sandbox.paypal.com' ); -define( 'PAYPAL_INTEGRATION_DATE', '2024-07-17' ); +define( 'PAYPAL_INTEGRATION_DATE', '2024-08-07' ); define( 'PPCP_PAYPAL_BN_CODE', 'Woo_PPCP' ); ! defined( 'CONNECT_WOO_CLIENT_ID' ) && define( 'CONNECT_WOO_CLIENT_ID', 'AcCAsWta_JTL__OfpjspNyH7c1GGHH332fLwonA5CwX4Y10mhybRZmHLA0GdRbwKwjQIhpDQy0pluX_P' ); From 4b30449ddb4ce7145a4e69ace7f93d6bb38c25b1 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 7 Aug 2024 14:36:51 +0200 Subject: [PATCH 179/357] =?UTF-8?q?=F0=9F=A9=B9=20ConsoleLogger=20will=20a?= =?UTF-8?q?lways=20output=20error=20messages?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/GooglepayButton.js | 4 ++-- .../resources/js/helper/ConsoleLogger.js | 21 ++++++++++++++++--- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index 832768401..ec0f4bd7a 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -227,8 +227,8 @@ class GooglepayButton extends PaymentButton { this.paymentsClient.loadPaymentData( paymentDataRequest ); }, - () => { - console.error( '[GooglePayButton] Form validation failed.' ); + ( reason ) => { + this.error( 'Form validation failed.', reason ); } ); } diff --git a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js index 4b8891247..c76aa8960 100644 --- a/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js +++ b/modules/ppcp-wc-gateway/resources/js/helper/ConsoleLogger.js @@ -24,10 +24,20 @@ export default class ConsoleLogger { } } + /** + * Enable or disable logging. Only impacts `log()` output. + * + * @param {boolean} state True to enable log output. + */ set enabled( state ) { this.#enabled = state; } + /** + * Output log-level details to the browser console, if logging is enabled. + * + * @param {...any} args - All provided values are output to the browser console. + */ log( ...args ) { if ( this.#enabled ) { // eslint-disable-next-line @@ -35,9 +45,14 @@ export default class ConsoleLogger { } } + /** + * Generate an error message in the browser's console. + * + * Error messages are always output, even when logging is disabled. + * + * @param {...any} args - All provided values are output to the browser console. + */ error( ...args ) { - if ( this.#enabled ) { - console.error( this.#prefix, ...args ); - } + console.error( this.#prefix, ...args ); } } From 429568fbd9a1024399fe22d7369f0bb5726a4a83 Mon Sep 17 00:00:00 2001 From: Philipp Stracker Date: Wed, 7 Aug 2024 14:45:08 +0200 Subject: [PATCH 180/357] =?UTF-8?q?=F0=9F=94=A5=20Minor=20cleanup?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/js/modules/Renderer/PaymentButton.js | 4 ---- modules/ppcp-googlepay/resources/js/GooglepayButton.js | 2 -- modules/ppcp-googlepay/src/Assets/Button.php | 3 +-- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js index 8275d1ee4..9e1def508 100644 --- a/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js +++ b/modules/ppcp-button/resources/js/modules/Renderer/PaymentButton.js @@ -503,9 +503,5 @@ export default class PaymentButton { this.#button.remove(); } this.#button = null; - - const wrapper = this.wrapperElement; - - wrapper.innerHTML = ''; } } diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js index ec0f4bd7a..af4d977d5 100644 --- a/modules/ppcp-googlepay/resources/js/GooglepayButton.js +++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js @@ -50,8 +50,6 @@ class GooglepayButton extends PaymentButton { 'ppc-button-ppcp-googlepay' ); - console.log( ppcpConfig.button, buttonConfig.button ); - const styles = combineStyles( ppcpConfig.button, buttonConfig.button ); if ( 'buy' === styles.MiniCart.type ) { diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php index 2f87a487d..575def21f 100644 --- a/modules/ppcp-googlepay/src/Assets/Button.php +++ b/modules/ppcp-googlepay/src/Assets/Button.php @@ -339,13 +339,12 @@ class Button implements ButtonInterface { /** * Outputs an inline CSS style that hides the Google Pay gateway (on Classic Checkout). - * The style is removed by `GooglepayButton.js` once the eligibility of the payment method + * The style is removed by `PaymentButton.js` once the eligibility of the payment method * is confirmed. * * @return void */ protected function hide_gateway_until_eligible() : void { - ?>