diff --git a/.ddev/addon-metadata/phpmyadmin/manifest.yaml b/.ddev/addon-metadata/phpmyadmin/manifest.yaml
new file mode 100644
index 000000000..f69a17df9
--- /dev/null
+++ b/.ddev/addon-metadata/phpmyadmin/manifest.yaml
@@ -0,0 +1,10 @@
+name: phpmyadmin
+repository: ddev/ddev-phpmyadmin
+version: v0.3.0
+install_date: "2023-08-02T12:20:36+02:00"
+project_files:
+ - docker-compose.phpmyadmin.yaml
+ - docker-compose.phpmyadmin-norouter.yaml
+ - commands/host/phpmyadmin
+global_files: []
+removal_actions: []
diff --git a/.ddev/addon-metadata/playwright/manifest.yaml b/.ddev/addon-metadata/playwright/manifest.yaml
new file mode 100644
index 000000000..a687c9f08
--- /dev/null
+++ b/.ddev/addon-metadata/playwright/manifest.yaml
@@ -0,0 +1,14 @@
+name: playwright
+repository: julienloizelet/ddev-playwright
+version: v2.0.1
+install_date: "2023-07-28T14:52:54+02:00"
+project_files:
+ - commands/playwright/playwright
+ - commands/playwright/playwright-install
+ - playwright-build/Dockerfile
+ - playwright-build/kasmvnc.yaml
+ - playwright-build/xstartup
+ - playwright-build/entrypoint.sh
+ - docker-compose.playwright.yaml
+global_files: []
+removal_actions: []
diff --git a/.ddev/commands/host/phpmyadmin b/.ddev/commands/host/phpmyadmin
new file mode 100755
index 000000000..e0c9bc602
--- /dev/null
+++ b/.ddev/commands/host/phpmyadmin
@@ -0,0 +1,52 @@
+#!/bin/bash
+
+## #ddev-generated: If you want to edit and own this file, remove this line.
+## Description: Launch a browser with PhpMyAdmin
+## Usage: phpmyadmin
+## Example: "ddev phpmyadmin"
+
+DDEV_PHPMYADMIN_PORT=8036
+DDEV_PHPMYADMIN_HTTPS_PORT=8037
+
+FULLURL=${DDEV_PRIMARY_URL}
+HTTPS=""
+if [ ${DDEV_PRIMARY_URL%://*} = "https" ]; then HTTPS=true; fi
+
+if [[ ! -z "${GITPOD_INSTANCE_ID}" ]] || [[ "${CODESPACES}" == "true" ]]; then
+ FULLURL="${FULLURL/-${DDEV_HOST_WEBSERVER_PORT}/-${DDEV_PHPMYADMIN_PORT}}"
+else
+ if [ "${HTTPS}" = "" ]; then
+ FULLURL="${FULLURL%:[0-9]*}:${DDEV_PHPMYADMIN_PORT}"
+ else
+ FULLURL="${FULLURL%:[0-9]*}:${DDEV_PHPMYADMIN_HTTPS_PORT}"
+ fi
+fi
+
+if [ -n "${1:-}" ] ; then
+ if [[ ${1::1} != "/" ]] ; then
+ FULLURL="${FULLURL}/";
+ fi
+
+ FULLURL="${FULLURL}${1}";
+fi
+
+if [ "${DDEV_DEBUG:-}" = "true" ]; then
+ printf "FULLURL $FULLURL\n" && exit 0
+fi
+
+case $OSTYPE in
+ linux-gnu)
+ if [[ ! -z "${GITPOD_INSTANCE_ID}" ]]; then
+ gp preview ${FULLURL}
+ else
+ xdg-open ${FULLURL}
+ fi
+ ;;
+ "darwin"*)
+ open ${FULLURL}
+ ;;
+ "win*"* | "msys"*)
+ start ${FULLURL}
+ ;;
+esac
+
diff --git a/.ddev/commands/playwright/playwright b/.ddev/commands/playwright/playwright
new file mode 100755
index 000000000..b2e9dcc64
--- /dev/null
+++ b/.ddev/commands/playwright/playwright
@@ -0,0 +1,14 @@
+#!/bin/bash
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+cd /var/www/html || exit 1
+cd "${PLAYWRIGHT_TEST_DIR}" || exit 1
+
+export PLAYWRIGHT_BROWSERS_PATH=0
+PRE="sudo -u pwuser PLAYWRIGHT_BROWSERS_PATH=0 "
+
+$PRE yarn playwright "$@"
diff --git a/.ddev/commands/playwright/playwright-install b/.ddev/commands/playwright/playwright-install
new file mode 100755
index 000000000..b4165b9a4
--- /dev/null
+++ b/.ddev/commands/playwright/playwright-install
@@ -0,0 +1,17 @@
+#!/bin/bash
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+cd /var/www/html || exit 1
+cd "${PLAYWRIGHT_TEST_DIR}" || exit 1
+
+export PLAYWRIGHT_BROWSERS_PATH=0
+PRE="sudo -u pwuser PLAYWRIGHT_BROWSERS_PATH=0 "
+
+$PRE yarn install
+$PRE yarn playwright install --with-deps
+# Conditionally copy an .env file if an example file exists
+[ -f .env.example ] && [ ! -f .env ] && $PRE cp -n .env.example .env; exit 0
diff --git a/.ddev/commands/web/wp-cleanup b/.ddev/commands/web/wp-cleanup
new file mode 100755
index 000000000..c6b3172d1
--- /dev/null
+++ b/.ddev/commands/web/wp-cleanup
@@ -0,0 +1,38 @@
+#!/bin/bash
+
+show-help() {
+ echo -e "\nDelete all posts -p [post type]"
+ echo -e "\tExample: ddev wp-cleanup -p shop_order,product"
+ echo -e "\nDelete all logs -l [wp-content path]"
+ echo -e "\tExample: ddev wp-cleanup -l uploads/wc-logs\n"
+}
+
+delete-posts() {
+ for post in $(wp post list --post_type=$1 --format=ids --path=.ddev/wordpress); do
+ wp post delete $post --force --path=.ddev/wordpress
+ done
+}
+
+delete-logs() {
+ rm .ddev/wordpress/wp-content/$1/*.log
+}
+
+declare -i param_counter=0
+
+while getopts "p:l:h" arg; do
+ case $arg in
+ p)
+ delete-posts $OPTARG
+ param_counter+=1
+ ;;
+ l)
+ delete-logs $OPTARG
+ param_counter+=1
+ ;;
+ h) show-help ;;
+ esac
+done
+
+if [ $param_counter -eq 0 ]; then
+ show-help
+fi
diff --git a/.ddev/config.yaml b/.ddev/config.yaml
index 251638bc4..d65593b7c 100644
--- a/.ddev/config.yaml
+++ b/.ddev/config.yaml
@@ -18,7 +18,7 @@ hooks:
pre-start:
- exec-host: "mkdir -p .ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}"
web_environment:
- - WP_VERSION=5.9.3
+ - WP_VERSION=6.2.2
- WP_LOCALE=en_US
- WP_TITLE=WooCommerce PayPal Payments
- WP_MULTISITE=true
@@ -26,7 +26,7 @@ web_environment:
- ADMIN_USER=admin
- ADMIN_PASS=admin
- ADMIN_EMAIL=admin@example.com
- - WC_VERSION=6.1.0
+ - WC_VERSION=7.7.2
# Key features of ddev's config.yaml:
diff --git a/.ddev/docker-compose.phpmyadmin-norouter.yaml b/.ddev/docker-compose.phpmyadmin-norouter.yaml
new file mode 100644
index 000000000..f369b6952
--- /dev/null
+++ b/.ddev/docker-compose.phpmyadmin-norouter.yaml
@@ -0,0 +1,4 @@
+#ddev-generated
+# If omit_containers[ddev-router] then this file will be replaced
+# with another with a `ports` statement to directly expose port 80 to 8036
+services: {}
diff --git a/.ddev/docker-compose.phpmyadmin.yaml b/.ddev/docker-compose.phpmyadmin.yaml
new file mode 100644
index 000000000..7e5b381d9
--- /dev/null
+++ b/.ddev/docker-compose.phpmyadmin.yaml
@@ -0,0 +1,27 @@
+#ddev-generated
+services:
+ phpmyadmin:
+ container_name: ddev-${DDEV_SITENAME}-phpmyadmin
+ image: phpmyadmin:5
+ working_dir: "/root"
+ restart: "no"
+ labels:
+ com.ddev.site-name: ${DDEV_SITENAME}
+ com.ddev.approot: $DDEV_APPROOT
+ expose:
+ - "80"
+ environment:
+ - PMA_USER=root
+ - PMA_PASSWORD=root
+ - PMA_HOST=db
+ - PMA_PORT=3306
+ - VIRTUAL_HOST=$DDEV_HOSTNAME
+ - UPLOAD_LIMIT=4000M
+ - HTTP_EXPOSE=8036:80
+ - HTTPS_EXPOSE=8037:80
+ healthcheck:
+ interval: 120s
+ timeout: 2s
+ retries: 1
+ depends_on:
+ - db
diff --git a/.ddev/docker-compose.playwright.yaml b/.ddev/docker-compose.playwright.yaml
new file mode 100644
index 000000000..05906faba
--- /dev/null
+++ b/.ddev/docker-compose.playwright.yaml
@@ -0,0 +1,38 @@
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+services:
+ playwright:
+ build:
+ context: playwright-build
+ container_name: ddev-${DDEV_SITENAME}-playwright
+ hostname: ${DDEV_SITENAME}-playwright
+ # These labels ensure this service is discoverable by ddev.
+ labels:
+ com.ddev.site-name: ${DDEV_SITENAME}
+ com.ddev.approot: $DDEV_APPROOT
+ environment:
+ # Modify the PLAYWRIGHT_TEST_DIR folder path to suit your needs
+ - PLAYWRIGHT_TEST_DIR=tests/Playwright
+ - NETWORK_IFACE=eth0
+ - DISPLAY=:1
+ - VIRTUAL_HOST=$DDEV_HOSTNAME
+ - HTTP_EXPOSE=8443:8444,9322:9323
+ - HTTPS_EXPOSE=8444:8444,9323:9323
+ - DDEV_UID=${DDEV_UID}
+ - DDEV_GID=${DDEV_GID}
+ expose:
+ - "8444"
+ - "9323"
+ depends_on:
+ - web
+ volumes:
+ - .:/mnt/ddev_config
+ - ddev-global-cache:/mnt/ddev-global-cache
+ - ../:/var/www/html:rw
+ external_links:
+ - ddev-router:${DDEV_HOSTNAME}
+ working_dir: /var/www/html
diff --git a/.ddev/playwright-build/Dockerfile b/.ddev/playwright-build/Dockerfile
new file mode 100644
index 000000000..f03c7df7a
--- /dev/null
+++ b/.ddev/playwright-build/Dockerfile
@@ -0,0 +1,57 @@
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+# If on arm64 machine, edit to use mcr.microsoft.com/playwright:focal-arm64
+FROM mcr.microsoft.com/playwright:focal
+
+# Debian images by default disable apt caching, so turn it on until we finish
+# the build.
+RUN mv /etc/apt/apt.conf.d/docker-clean /etc/apt/docker-clean-disabled
+
+USER root
+
+RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ apt-get update \
+ && apt-get install -y sudo
+
+# Give the pwuser user full `sudo` privileges
+RUN echo "pwuser ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers.d/pwuser \
+ && chmod 0440 /etc/sudoers.d/pwuser
+
+# CAROOT for `mkcert` to use, has the CA config
+ENV CAROOT=/mnt/ddev-global-cache/mkcert
+
+# Install the correct architecture binary of `mkcert`
+RUN export TARGETPLATFORM=linux/$(uname -m | sed 's/x86_64/amd64/;s/aarch64/arm64/') && mkdir -p /usr/local/bin && curl --fail -JL -s -o /usr/local/bin/mkcert "https://dl.filippo.io/mkcert/latest?for=${TARGETPLATFORM}"
+RUN chmod +x /usr/local/bin/mkcert
+
+
+# Install a window manager.
+RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ apt-get update \
+ && apt-get install -y icewm xauth
+
+# Install kasmvnc for remote access.
+RUN /bin/bash -c 'if [ $(arch) == "aarch64" ]; then KASM_ARCH=arm64; else KASM_ARCH=amd64; fi; wget https://github.com/kasmtech/KasmVNC/releases/download/v1.1.0/kasmvncserver_bullseye_1.1.0_${KASM_ARCH}.deb'
+RUN --mount=type=cache,target=/var/cache/apt,sharing=locked \
+ --mount=type=cache,target=/var/lib/apt,sharing=locked \
+ apt-get install -y ./kasmvncserver*.deb
+
+# We're done with apt so disable caching again for the final image.
+RUN mv /etc/apt/docker-clean-disabled /etc/apt/apt.conf.d/docker-clean
+
+# prepare KasmVNC
+RUN sudo -u pwuser mkdir /home/pwuser/.vnc
+COPY kasmvnc.yaml xstartup /home/pwuser/.vnc/
+RUN chown pwuser:pwuser /home/pwuser/.vnc/*
+RUN sudo -u pwuser touch /home/pwuser/.vnc/.de-was-selected
+RUN sudo -u pwuser /bin/bash -c 'echo -e "secret\nsecret\n" | kasmvncpasswd -wo -u pwuser' # We actually disable auth, but KASM complains without it
+
+
+COPY entrypoint.sh /root/entrypoint.sh
+ENTRYPOINT "/root/entrypoint.sh"
diff --git a/.ddev/playwright-build/entrypoint.sh b/.ddev/playwright-build/entrypoint.sh
new file mode 100755
index 000000000..1ff25d127
--- /dev/null
+++ b/.ddev/playwright-build/entrypoint.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+
+# Change pwuser IDs to the host IDs supplied by DDEV
+usermod -u ${DDEV_UID} pwuser
+groupmod -g ${DDEV_GID} pwuser
+usermod -a -G ssl-cert pwuser
+
+# Install DDEV certificate
+mkcert -install
+
+# Run CMD from parameters as pwuser
+sudo -u pwuser vncserver -fg -disableBasicAuth
diff --git a/.ddev/playwright-build/kasmvnc.yaml b/.ddev/playwright-build/kasmvnc.yaml
new file mode 100644
index 000000000..c42c849b2
--- /dev/null
+++ b/.ddev/playwright-build/kasmvnc.yaml
@@ -0,0 +1,14 @@
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+logging:
+ log_writer_name: all
+ log_dest: syslog
+ level: 100
+
+network:
+ ssl:
+ require_ssl: false
diff --git a/.ddev/playwright-build/xstartup b/.ddev/playwright-build/xstartup
new file mode 100755
index 000000000..7d5896d3f
--- /dev/null
+++ b/.ddev/playwright-build/xstartup
@@ -0,0 +1,32 @@
+#!/bin/sh
+#ddev-generated
+# Remove the line above if you don't want this file to be overwritten when you run
+# ddev get julienloizelet/ddev-playwright
+#
+# This file comes from https://github.com/julienloizelet/ddev-playwright
+#
+
+export DISPLAY=:1
+
+unset SESSION_MANAGER
+unset DBUS_SESSION_BUS_ADDRESS
+OS=`uname -s`
+if [ $OS = 'Linux' ]; then
+ case "$WINDOWMANAGER" in
+ *gnome*)
+ if [ -e /etc/SuSE-release ]; then
+ PATH=$PATH:/opt/gnome/bin
+ export PATH
+ fi
+ ;;
+ esac
+fi
+if [ -x /etc/X11/xinit/xinitrc ]; then
+ exec /etc/X11/xinit/xinitrc
+fi
+if [ -f /etc/X11/xinit/xinitrc ]; then
+ exec sh /etc/X11/xinit/xinitrc
+fi
+[ -r $HOME/.Xresources ] && xrdb $HOME/.Xresources
+xterm -geometry 80x24+10+10 -ls -title "$VNCDESKTOP Desktop" &
+icewm-session
diff --git a/.env.e2e.example b/.env.e2e.example
index 02ffdb0bc..98ad27c23 100644
--- a/.env.e2e.example
+++ b/.env.e2e.example
@@ -1 +1,38 @@
PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress
+
+BASEURL="https://woocommerce-paypal-payments.ddev.site"
+AUTHORIZATION="Bearer ABC123"
+
+CHECKOUT_URL="/checkout"
+CHECKOUT_PAGE_ID=7
+CART_URL="/cart"
+BLOCK_CHECKOUT_URL="/checkout-block"
+BLOCK_CHECKOUT_PAGE_ID=22
+BLOCK_CART_URL="/cart-block"
+
+PRODUCT_URL="/product/prod"
+PRODUCT_ID=123
+
+SUBSCRIPTION_URL="/product/sub"
+
+APM_ID="sofort"
+
+WP_MERCHANT_USER="admin"
+WP_MERCHANT_PASSWORD="admin"
+
+WP_CUSTOMER_USER="customer"
+WP_CUSTOMER_PASSWORD="password"
+
+CUSTOMER_EMAIL="customer@example.com"
+CUSTOMER_PASSWORD="password"
+CUSTOMER_FIRST_NAME="John"
+CUSTOMER_LAST_NAME="Doe"
+CUSTOMER_COUNTRY="DE"
+CUSTOMER_ADDRESS="street 1"
+CUSTOMER_POSTCODE="12345"
+CUSTOMER_CITY="city"
+CUSTOMER_PHONE="1234567890"
+
+CREDIT_CARD_NUMBER="1234567890"
+CREDIT_CARD_EXPIRATION="01/2042"
+CREDIT_CARD_CVV="123"
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index 791fe3a18..0bf80c13b 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -7,25 +7,21 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- php-versions: ['7.2', '7.4', '8.1']
- wc-versions: ['5.9.5', '7.1.0']
- exclude:
- - php-versions: 7.2
- wc-versions: 7.1.0
+ php-versions: ['7.3', '7.4', '8.1']
+ wc-versions: ['5.9.5', '7.7.2']
name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }}
steps:
- - uses: satackey/action-docker-layer-caching@v0.0.11
- continue-on-error: true
-
- - uses: jonaseberle/github-action-setup-ddev@v1
+ - uses: ddev/github-action-setup-ddev@v1
with:
autostart: false
- uses: actions/checkout@v1
- - name: Configure DDEV
- run: ddev config --php-version ${{ matrix.php-versions }} --web-environment-add="WC_VERSION=${{ matrix.wc-versions }}"
+ - name: Configure DDEV PHP
+ run: ddev config --php-version ${{ matrix.php-versions }}
+ - name: Configure DDEV WC
+ run: ddev config --web-environment-add="WC_VERSION=${{ matrix.wc-versions }}"
- name: Start DDEV
run: ddev start
diff --git a/.gitignore b/.gitignore
index 8fa78b2c6..ff5c7fa98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,5 @@ modules/ppcp-wc-gateway/assets/css
.env
.env.e2e
auth.json
+.DS_Store
+tests/.DS_Store
diff --git a/.psalm/gzd.php b/.psalm/gzd.php
index 6ececb60d..1b2b02256 100644
--- a/.psalm/gzd.php
+++ b/.psalm/gzd.php
@@ -50,6 +50,27 @@ namespace Vendidero\Germanized\Shipments {
public function add_note( $note, $added_by_user = false ) {
}
+
+ /**
+ * Return an array of items within this shipment.
+ *
+ * @return ShipmentItem[]
+ */
+ public function get_items() {
+ }
+ }
+
+ class ShipmentItem extends WC_Data {
+
+ /**
+ * Get order ID this meta belongs to.
+ *
+ * @param string $context What the value is for. Valid values are 'view' and 'edit'.
+ * @return int
+ */
+ public function get_order_item_id( $context = 'view' ) {
+ }
+
}
}
diff --git a/.psalm/stubs.php b/.psalm/stubs.php
index bdf5e45c5..7282b6907 100644
--- a/.psalm/stubs.php
+++ b/.psalm/stubs.php
@@ -1,4 +1,7 @@
$instance` pairs.
+ *
+ * @var IntegrationInterface[]
+ */
+ protected $registered_integrations = [];
+
+ /**
+ * Initializes all registered integrations.
+ *
+ * Integration identifier is used to construct hook names and is given when the integration registry is initialized.
+ *
+ * @param string $registry_identifier Identifier for this registry.
+ */
+ public function initialize($registry_identifier = '')
+ {
+ }
+
+ /**
+ * Registers an integration.
+ *
+ * @param IntegrationInterface $integration An instance of IntegrationInterface.
+ *
+ * @return boolean True means registered successfully.
+ */
+ public function register(IntegrationInterface $integration)
+ {
+ }
+
+ /**
+ * Checks if an integration is already registered.
+ *
+ * @param string $name Integration name.
+ * @return bool True if the integration is registered, false otherwise.
+ */
+ public function is_registered($name)
+ {
+ }
+
+ /**
+ * Un-register an integration.
+ *
+ * @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance.
+ * @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully.
+ */
+ public function unregister($name)
+ {
+ }
+
+ /**
+ * Retrieves a registered Integration by name.
+ *
+ * @param string $name Integration name.
+ * @return IntegrationInterface|null The registered integration, or null if it is not registered.
+ */
+ public function get_registered($name)
+ {
+ }
+
+ /**
+ * Retrieves all registered integrations.
+ *
+ * @return IntegrationInterface[]
+ */
+ public function get_all_registered()
+ {
+ }
+
+ /**
+ * Gets an array of all registered integration's script handles for the editor.
+ *
+ * @return string[]
+ */
+ public function get_all_registered_editor_script_handles()
+ {
+ }
+
+ /**
+ * Gets an array of all registered integration's script handles.
+ *
+ * @return string[]
+ */
+ public function get_all_registered_script_handles()
+ {
+ }
+
+ /**
+ * Gets an array of all registered integration's script data.
+ *
+ * @return array
+ */
+ public function get_all_registered_script_data()
+ {
+ }
+ }
+}
+
+namespace Automattic\WooCommerce\Blocks\Payments {
+ use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
+
+ /**
+ * Class used for interacting with payment method types.
+ *
+ * @since 2.6.0
+ */
+ final class PaymentMethodRegistry extends IntegrationRegistry
+ {
+ /**
+ * Integration identifier is used to construct hook names and is given when the integration registry is initialized.
+ *
+ * @var string
+ */
+ protected $registry_identifier = 'payment_method_type';
+
+ /**
+ * Retrieves all registered payment methods that are also active.
+ *
+ * @return PaymentMethodTypeInterface[]
+ */
+ public function get_all_active_registered()
+ {
+ }
+
+ /**
+ * Gets an array of all registered payment method script handles, but only for active payment methods.
+ *
+ * @return string[]
+ */
+ public function get_all_active_payment_method_script_dependencies()
+ {
+ }
+
+ /**
+ * Gets an array of all registered payment method script data, but only for active payment methods.
+ *
+ * @return array
+ */
+ public function get_all_registered_script_data()
+ {
+ }
+ }
+}
+
+/**
+ * Registers and validates payment requirements callbacks.
+ *
+ * @see Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::register_payment_requirements()
+ *
+ * @param array $args Args to pass to register_payment_requirements.
+ * @returns boolean|\WP_Error True on success, WP_Error on fail.
+ */
+function woocommerce_store_api_register_payment_requirements( $args ) {
+}
diff --git a/.psalm/wcs.php b/.psalm/wcs.php
index 2106378e2..1698c98c0 100644
--- a/.psalm/wcs.php
+++ b/.psalm/wcs.php
@@ -1631,7 +1631,7 @@ function wcs_get_order_items_product_id($item_id)
*
* When acting on cart items or order items, Subscriptions often needs to use an item's canonical product ID. For
* items representing a variation, that means the 'variation_id' value, if the item is not a variation, that means
- * the 'product_id value. This function helps save keystrokes on the idiom to check if an item is to a variation or not.
+ * the product_id value. This function helps save keystrokes on the idiom to check if an item is to a variation or not.
*
* @param array or object $item Either a cart item, order/subscription line item, or a product.
*/
@@ -2084,3 +2084,39 @@ function wcs_find_matching_line_item($order, $subscription_item, $match_type = '
function wcs_order_contains_product($order, $product)
{
}
+
+/**
+ * Get page ID for a specific WC resource.
+ *
+ * @param string $for Name of the resource.
+ *
+ * @return string Page ID. Empty string if resource not found.
+ */
+function wc_get_page_screen_id( $for ) {}
+
+/**
+ * Subscription Product Variation Class
+ *
+ * The subscription product variation class extends the WC_Product_Variation product class
+ * to create subscription product variations.
+ *
+ * @class WC_Product_Subscription
+ * @package WooCommerce Subscriptions
+ * @category Class
+ * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
+ *
+ */
+class WC_Product_Subscription_Variation extends WC_Product_Variation {}
+
+/**
+ * Variable Subscription Product Class
+ *
+ * This class extends the WC Variable product class to create variable products with recurring payments.
+ *
+ * @class WC_Product_Variable_Subscription
+ * @package WooCommerce Subscriptions
+ * @category Class
+ * @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
+ *
+ */
+class WC_Product_Variable_Subscription extends WC_Product_Variable {}
diff --git a/.psalm/wpcli.php b/.psalm/wpcli.php
new file mode 100644
index 000000000..4b2ea7ed8
--- /dev/null
+++ b/.psalm/wpcli.php
@@ -0,0 +1,651 @@
+ ['color' => 'yellow'],
+ * * %g => ['color' => 'green'],
+ * * %b => ['color' => 'blue'],
+ * * %r => ['color' => 'red'],
+ * * %p => ['color' => 'magenta'],
+ * * %m => ['color' => 'magenta'],
+ * * %c => ['color' => 'cyan'],
+ * * %w => ['color' => 'grey'],
+ * * %k => ['color' => 'black'],
+ * * %n => ['color' => 'reset'],
+ * * %Y => ['color' => 'yellow', 'style' => 'bright'],
+ * * %G => ['color' => 'green', 'style' => 'bright'],
+ * * %B => ['color' => 'blue', 'style' => 'bright'],
+ * * %R => ['color' => 'red', 'style' => 'bright'],
+ * * %P => ['color' => 'magenta', 'style' => 'bright'],
+ * * %M => ['color' => 'magenta', 'style' => 'bright'],
+ * * %C => ['color' => 'cyan', 'style' => 'bright'],
+ * * %W => ['color' => 'grey', 'style' => 'bright'],
+ * * %K => ['color' => 'black', 'style' => 'bright'],
+ * * %N => ['color' => 'reset', 'style' => 'bright'],
+ * * %3 => ['background' => 'yellow'],
+ * * %2 => ['background' => 'green'],
+ * * %4 => ['background' => 'blue'],
+ * * %1 => ['background' => 'red'],
+ * * %5 => ['background' => 'magenta'],
+ * * %6 => ['background' => 'cyan'],
+ * * %7 => ['background' => 'grey'],
+ * * %0 => ['background' => 'black'],
+ * * %F => ['style' => 'blink'],
+ * * %U => ['style' => 'underline'],
+ * * %8 => ['style' => 'inverse'],
+ * * %9 => ['style' => 'bright'],
+ * * %_ => ['style' => 'bright']
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string $string String to colorize for output, with color tokens.
+ * @return string Colorized string.
+ */
+ public static function colorize( $string ) {
+ }
+
+ /**
+ * Schedule a callback to be executed at a certain point.
+ *
+ * Hooks conceptually are very similar to WordPress actions. WP-CLI hooks
+ * are typically called before WordPress is loaded.
+ *
+ * WP-CLI hooks include:
+ *
+ * * `before_add_command:` - Before the command is added.
+ * * `after_add_command:` - After the command was added.
+ * * `before_invoke:` (1) - Just before a command is invoked.
+ * * `after_invoke:` (1) - Just after a command is invoked.
+ * * `find_command_to_run_pre` - Just before WP-CLI finds the command to run.
+ * * `before_registering_contexts` (1) - Before the contexts are registered.
+ * * `before_wp_load` - Just before the WP load process begins.
+ * * `before_wp_config_load` - After wp-config.php has been located.
+ * * `after_wp_config_load` - After wp-config.php has been loaded into scope.
+ * * `after_wp_load` - Just after the WP load process has completed.
+ * * `before_run_command` (3) - Just before the command is executed.
+ *
+ * The parentheses behind the hook name denote the number of arguments
+ * being passed into the hook. For such hooks, the callback should return
+ * the first argument again, making them work like a WP filter.
+ *
+ * WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`.
+ *
+ * If additional arguments are passed through the `WP_CLI::do_hook()` call,
+ * these will be passed on to the callback provided by `WP_CLI::add_hook()`.
+ *
+ * ```
+ * # `wp network meta` confirms command is executing in multisite context.
+ * WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array(
+ * 'before_invoke' => function ( $name ) {
+ * if ( !is_multisite() ) {
+ * WP_CLI::error( 'This is not a multisite installation.' );
+ * }
+ * }
+ * ) );
+ * ```
+ *
+ * @access public
+ * @category Registration
+ *
+ * @param string $when Identifier for the hook.
+ * @param mixed $callback Callback to execute when hook is called.
+ * @return null
+ */
+ public static function add_hook( $when, $callback ) {
+ }
+
+ /**
+ * Execute callbacks registered to a given hook.
+ *
+ * See `WP_CLI::add_hook()` for details on WP-CLI's internal hook system.
+ * Commands can provide and call their own hooks.
+ *
+ * @access public
+ * @category Registration
+ *
+ * @param string $when Identifier for the hook.
+ * @param mixed ...$args Optional. Arguments that will be passed onto the
+ * callback provided by `WP_CLI::add_hook()`.
+ * @return null|mixed Returns the first optional argument if optional
+ * arguments were passed, otherwise returns null.
+ */
+ public static function do_hook( $when, ...$args ) {
+ }
+
+ /**
+ * Add a callback to a WordPress action or filter.
+ *
+ * `add_action()` without needing access to `add_action()`. If WordPress is
+ * already loaded though, you should use `add_action()` (and `add_filter()`)
+ * instead.
+ *
+ * @access public
+ * @category Registration
+ *
+ * @param string $tag Named WordPress action or filter.
+ * @param mixed $function_to_add Callable to execute when the action or filter is evaluated.
+ * @param integer $priority Priority to add the callback as.
+ * @param integer $accepted_args Number of arguments to pass to callback.
+ * @return true
+ */
+ public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
+ }
+
+ /**
+ * Register a command to WP-CLI.
+ *
+ * WP-CLI supports using any callable class, function, or closure as a
+ * command. `WP_CLI::add_command()` is used for both internal and
+ * third-party command registration.
+ *
+ * Command arguments are parsed from PHPDoc by default, but also can be
+ * supplied as an optional third argument during registration.
+ *
+ * ```
+ * # Register a custom 'foo' command to output a supplied positional param.
+ * #
+ * # $ wp foo bar --append=qux
+ * # Success: bar qux
+ *
+ * /**
+ * * My awesome closure command
+ * *
+ * *
+ * * : An awesome message to display
+ * *
+ * * --append=
+ * * : An awesome message to append to the original message.
+ * *
+ * * @when before_wp_load
+ * *\/
+ * $foo = function( $args, $assoc_args ) {
+ * WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] );
+ * };
+ * WP_CLI::add_command( 'foo', $foo );
+ * ```
+ *
+ * @access public
+ * @category Registration
+ *
+ * @param string $name Name for the command (e.g. "post list" or "site empty").
+ * @param callable $callable Command implementation as a class, function or closure.
+ * @param array $args {
+ * Optional. An associative array with additional registration parameters.
+ *
+ * @type callable $before_invoke Callback to execute before invoking the command.
+ * @type callable $after_invoke Callback to execute after invoking the command.
+ * @type string $shortdesc Short description (80 char or less) for the command.
+ * @type string $longdesc Description of arbitrary length for examples, etc.
+ * @type string $synopsis The synopsis for the command (string or array).
+ * @type string $when Execute callback on a named WP-CLI hook (e.g. before_wp_load).
+ * @type bool $is_deferred Whether the command addition had already been deferred.
+ * }
+ * @return bool True on success, false if deferred, hard error if registration failed.
+ */
+ public static function add_command( $name, $callable, $args = [] ) {
+ }
+
+ /**
+ * Get the list of outstanding deferred command additions.
+ *
+ * @return array Array of outstanding command additions.
+ */
+ public static function get_deferred_additions() {
+ }
+
+ /**
+ * Remove a command addition from the list of outstanding deferred additions.
+ */
+ public static function remove_deferred_addition( $name ) {
+ }
+
+ /**
+ * Display informational message without prefix, and ignore `--quiet`.
+ *
+ * Message is written to STDOUT. `WP_CLI::log()` is typically recommended;
+ * `WP_CLI::line()` is included for historical compat.
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string $message Message to display to the end user.
+ * @return null
+ */
+ public static function line( $message = '' ) {
+ }
+
+ /**
+ * Display informational message without prefix.
+ *
+ * Message is written to STDOUT, or discarded when `--quiet` flag is supplied.
+ *
+ * ```
+ * # `wp cli update` lets user know of each step in the update process.
+ * WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) );
+ * ```
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string $message Message to write to STDOUT.
+ */
+ public static function log( $message ) {
+ }
+
+ /**
+ * Display success message prefixed with "Success: ".
+ *
+ * Success message is written to STDOUT.
+ *
+ * Typically recommended to inform user of successful script conclusion.
+ *
+ * ```
+ * # wp rewrite flush expects 'rewrite_rules' option to be set after flush.
+ * flush_rewrite_rules( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) );
+ * if ( ! get_option( 'rewrite_rules' ) ) {
+ * WP_CLI::warning( "Rewrite rules are empty." );
+ * } else {
+ * WP_CLI::success( 'Rewrite rules flushed.' );
+ * }
+ * ```
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string $message Message to write to STDOUT.
+ * @return null
+ */
+ public static function success( $message ) {
+ }
+
+ /**
+ * Display debug message prefixed with "Debug: " when `--debug` is used.
+ *
+ * Debug message is written to STDERR, and includes script execution time.
+ *
+ * Helpful for optionally showing greater detail when needed. Used throughout
+ * WP-CLI bootstrap process for easier debugging and profiling.
+ *
+ * ```
+ * # Called in `WP_CLI\Runner::set_wp_root()`.
+ * private static function set_wp_root( $path ) {
+ * define( 'ABSPATH', Utils\trailingslashit( $path ) );
+ * WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH );
+ * $_SERVER['DOCUMENT_ROOT'] = realpath( $path );
+ * }
+ *
+ * # Debug details only appear when `--debug` is used.
+ * # $ wp --debug
+ * # [...]
+ * # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s)
+ * ```
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
+ * @param string|bool $group Organize debug message to a specific group.
+ * Use `false` to not group the message.
+ * @return null
+ */
+ public static function debug( $message, $group = false ) {
+ }
+
+ /**
+ * Display warning message prefixed with "Warning: ".
+ *
+ * Warning message is written to STDERR.
+ *
+ * Use instead of `WP_CLI::debug()` when script execution should be permitted
+ * to continue.
+ *
+ * ```
+ * # `wp plugin activate` skips activation when plugin is network active.
+ * $status = $this->get_status( $plugin->file );
+ * // Network-active is the highest level of activation status
+ * if ( 'active-network' === $status ) {
+ * WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
+ * continue;
+ * }
+ * ```
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
+ * @return null
+ */
+ public static function warning( $message ) {
+ }
+
+ /**
+ * Display error message prefixed with "Error: " and exit script.
+ *
+ * Error message is written to STDERR. Defaults to halting script execution
+ * with return code 1.
+ *
+ * Use `WP_CLI::warning()` instead when script execution should be permitted
+ * to continue.
+ *
+ * ```
+ * # `wp cache flush` considers flush failure to be a fatal error.
+ * if ( false === wp_cache_flush() ) {
+ * WP_CLI::error( 'The object cache could not be flushed.' );
+ * }
+ * ```
+ *
+ * @access public
+ * @category Output
+ *
+ * @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
+ * @param boolean|integer $exit True defaults to exit(1).
+ * @return null
+ */
+ public static function error( $message, $exit = true ) {
+ }
+
+ /**
+ * Halt script execution with a specific return code.
+ *
+ * Permits script execution to be overloaded by `WP_CLI::runcommand()`
+ *
+ * @access public
+ * @category Output
+ *
+ * @param integer $return_code
+ * @return never
+ */
+ public static function halt( $return_code ) {
+ }
+
+ /**
+ * Display a multi-line error message in a red box. Doesn't exit script.
+ *
+ * Error message is written to STDERR.
+ *
+ * @access public
+ * @category Output
+ *
+ * @param array $message_lines Multi-line error message to be displayed.
+ */
+ public static function error_multi_line( $message_lines ) {
+ }
+
+ /**
+ * Ask for confirmation before running a destructive operation.
+ *
+ * If 'y' is provided to the question, the script execution continues. If
+ * 'n' or any other response is provided to the question, script exits.
+ *
+ * ```
+ * # `wp db drop` asks for confirmation before dropping the database.
+ *
+ * WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args );
+ * ```
+ *
+ * @access public
+ * @category Input
+ *
+ * @param string $question Question to display before the prompt.
+ * @param array $assoc_args Skips prompt if 'yes' is provided.
+ */
+ public static function confirm( $question, $assoc_args = [] ) {
+ }
+
+ /**
+ * Read value from a positional argument or from STDIN.
+ *
+ * @param array $args The list of positional arguments.
+ * @param int $index At which position to check for the value.
+ *
+ * @return string
+ */
+ public static function get_value_from_arg_or_stdin( $args, $index ) {
+ }
+
+ /**
+ * Read a value, from various formats.
+ *
+ * @access public
+ * @category Input
+ *
+ * @param mixed $raw_value
+ * @param array $assoc_args
+ */
+ public static function read_value( $raw_value, $assoc_args = [] ) {
+ }
+
+ /**
+ * Display a value, in various formats
+ *
+ * @param mixed $value Value to display.
+ * @param array $assoc_args Arguments passed to the command, determining format.
+ */
+ public static function print_value( $value, $assoc_args = [] ) {
+ }
+
+ /**
+ * Convert a WP_Error or Exception into a string
+ *
+ * @param string|WP_Error|Exception|Throwable $errors
+ * @throws InvalidArgumentException
+ *
+ * @return string
+ */
+ public static function error_to_string( $errors ) {
+ }
+
+ /**
+ * Launch an arbitrary external process that takes over I/O.
+ *
+ * ```
+ * # `wp core download` falls back to the `tar` binary when PharData isn't available
+ * if ( ! class_exists( 'PharData' ) ) {
+ * $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball";
+ * WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) );
+ * return;
+ * }
+ * ```
+ *
+ * @access public
+ * @category Execution
+ *
+ * @param string $command External process to launch.
+ * @param boolean $exit_on_error Whether to exit if the command returns an elevated return code.
+ * @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results.
+ * @return int|ProcessRun The command exit status, or a ProcessRun object for full details.
+ */
+ public static function launch( $command, $exit_on_error = true, $return_detailed = false ) {
+ }
+
+ /**
+ * Run a WP-CLI command in a new process reusing the current runtime arguments.
+ *
+ * Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
+ *
+ * Note: While this command does persist a limited set of runtime arguments,
+ * it *does not* persist environment variables. Practically speaking, WP-CLI
+ * packages won't be loaded when using WP_CLI::launch_self() because the
+ * launched process doesn't have access to the current process $HOME.
+ *
+ * @access public
+ * @category Execution
+ *
+ * @param string $command WP-CLI command to call.
+ * @param array $args Positional arguments to include when calling the command.
+ * @param array $assoc_args Associative arguments to include when calling the command.
+ * @param bool $exit_on_error Whether to exit if the command returns an elevated return code.
+ * @param bool $return_detailed Whether to return an exit status (default) or detailed execution results.
+ * @param array $runtime_args Override one or more global args (path,url,user,allow-root)
+ * @return int|ProcessRun The command exit status, or a ProcessRun instance
+ */
+ public static function launch_self( $command, $args = [], $assoc_args = [], $exit_on_error = true, $return_detailed = false, $runtime_args = [] ) {
+ }
+
+ /**
+ * Get the path to the PHP binary used when executing WP-CLI.
+ *
+ * Environment values permit specific binaries to be indicated.
+ *
+ * Note: moved to Utils, left for BC.
+ *
+ * @access public
+ * @category System
+ *
+ * @return string
+ */
+ public static function get_php_binary() {
+ }
+
+ /**
+ * Confirm that a global configuration parameter does exist.
+ *
+ * @access public
+ * @category Input
+ *
+ * @param string $key Config parameter key to check.
+ *
+ * @return bool
+ */
+ public static function has_config( $key ) {
+ }
+
+ /**
+ * Get values of global configuration parameters.
+ *
+ * Provides access to `--path=`, `--url=`, and other values of
+ * the [global configuration parameters](https://wp-cli.org/config/).
+ *
+ * ```
+ * WP_CLI::log( 'The --url= value is: ' . WP_CLI::get_config( 'url' ) );
+ * ```
+ *
+ * @access public
+ * @category Input
+ *
+ * @param string $key Get value for a specific global configuration parameter.
+ * @return mixed
+ */
+ public static function get_config( $key = null ) {
+ }
+
+ /**
+ * Run a WP-CLI command.
+ *
+ * Launches a new child process to run a specified WP-CLI command.
+ * Optionally:
+ *
+ * * Run the command in an existing process.
+ * * Prevent halting script execution on error.
+ * * Capture and return STDOUT, or full details about command execution.
+ * * Parse JSON output if the command rendered it.
+ *
+ * ```
+ * $options = array(
+ * 'return' => true, // Return 'STDOUT'; use 'all' for full object.
+ * 'parse' => 'json', // Parse captured STDOUT to JSON array.
+ * 'launch' => false, // Reuse the current process.
+ * 'exit_error' => true, // Halt script execution on error.
+ * );
+ * $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options );
+ * ```
+ *
+ * @access public
+ * @category Execution
+ *
+ * @param string $command WP-CLI command to run, including arguments.
+ * @param array $options Configuration options for command execution.
+ * @return mixed
+ */
+ public static function runcommand( $command, $options = [] ) {
+ }
+
+ /**
+ * Run a given command within the current process using the same global
+ * parameters.
+ *
+ * Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
+ *
+ * To run a command using a new process with the same global parameters,
+ * use WP_CLI::launch_self(). To run a command using a new process with
+ * different global parameters, use WP_CLI::launch().
+ *
+ * ```
+ * ob_start();
+ * WP_CLI::run_command( array( 'cli', 'cmd-dump' ) );
+ * $ret = ob_get_clean();
+ * ```
+ *
+ * @access public
+ * @category Execution
+ *
+ * @param array $args Positional arguments including command name.
+ * @param array $assoc_args
+ */
+ public static function run_command( $args, $assoc_args = [] ) {
+ }
+}
diff --git a/README.md b/README.md
index 676d60ffe..d640960c9 100644
--- a/README.md
+++ b/README.md
@@ -73,6 +73,8 @@ Enable xdebug via `$ ddev xdebug`, and press `Start Listening for PHP Debug Conn
After creating the server in the PHPStorm dialog, you need to set the local project path for the server plugin path.
It should look [like this](https://i.imgur.com/ofsF1Mc.png).
+See [tests/playwright](tests/playwright) for e2e (browser-based) tests.
+
## Test account setup
You will need a PayPal sandbox merchant and customer accounts to configure the plugin and make test purchases with it.
diff --git a/api/order-functions.php b/api/order-functions.php
new file mode 100644
index 000000000..701b064fb
--- /dev/null
+++ b/api/order-functions.php
@@ -0,0 +1,121 @@
+get_meta( PayPalGateway::ORDER_ID_META_KEY );
+ if ( ! $paypal_id_or_wc_order ) {
+ throw new InvalidArgumentException( 'PayPal order ID not found in meta.' );
+ }
+ }
+ if ( ! is_string( $paypal_id_or_wc_order ) ) {
+ throw new InvalidArgumentException( 'Invalid PayPal order ID, string expected.' );
+ }
+
+ $order_endpoint = PPCP::container()->get( 'api.endpoint.order' );
+ assert( $order_endpoint instanceof OrderEndpoint );
+
+ return $order_endpoint->order( $paypal_id_or_wc_order );
+}
+
+/**
+ * Captures the PayPal order.
+ *
+ * @param WC_Order $wc_order The WC order.
+ * @throws InvalidArgumentException When the order cannot be captured.
+ * @throws Exception When the operation fails.
+ */
+function ppcp_capture_order( WC_Order $wc_order ): void {
+ $intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) );
+
+ if ( $intent !== 'AUTHORIZE' ) {
+ throw new InvalidArgumentException( 'Only orders with "authorize" intent can be captured.' );
+ }
+ $captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) );
+ if ( $captured ) {
+ throw new InvalidArgumentException( 'The order is already captured.' );
+ }
+
+ $authorized_payment_processor = PPCP::container()->get( 'wcgateway.processor.authorized-payments' );
+ assert( $authorized_payment_processor instanceof AuthorizedPaymentsProcessor );
+
+ if ( ! $authorized_payment_processor->capture_authorized_payment( $wc_order ) ) {
+ throw new RuntimeException( 'Capture failed.' );
+ }
+}
+
+/**
+ * Refunds the PayPal order.
+ * Note that you can use wc_refund_payment() to trigger the refund in WC and PayPal.
+ *
+ * @param WC_Order $wc_order The WC order.
+ * @param float $amount The refund amount.
+ * @param string $reason The reason for the refund.
+ * @return string The PayPal refund ID.
+ * @throws InvalidArgumentException When the order cannot be refunded.
+ * @throws Exception When the operation fails.
+ */
+function ppcp_refund_order( WC_Order $wc_order, float $amount, string $reason = '' ): string {
+ $order = ppcp_get_paypal_order( $wc_order );
+
+ $refund_processor = PPCP::container()->get( 'wcgateway.processor.refunds' );
+ assert( $refund_processor instanceof RefundProcessor );
+
+ return $refund_processor->refund( $order, $wc_order, $amount, $reason );
+}
+
+/**
+ * Voids the authorization.
+ *
+ * @param WC_Order $wc_order The WC order.
+ * @throws InvalidArgumentException When the order cannot be voided.
+ * @throws Exception When the operation fails.
+ */
+function ppcp_void_order( WC_Order $wc_order ): void {
+ $order = ppcp_get_paypal_order( $wc_order );
+
+ $refund_processor = PPCP::container()->get( 'wcgateway.processor.refunds' );
+ assert( $refund_processor instanceof RefundProcessor );
+
+ $refund_processor->void( $order );
+}
+
+/**
+ * Updates the PayPal refund fees totals on an order.
+ *
+ * @param WC_Order $wc_order The WC order.
+ */
+function ppcp_update_order_refund_fees( WC_Order $wc_order ): void {
+ $updater = PPCP::container()->get( 'wcgateway.helper.refund-fees-updater' );
+ assert( $updater instanceof RefundFeesUpdater );
+ $updater->update( $wc_order );
+}
diff --git a/bootstrap.php b/bootstrap.php
index 27c8f36a3..fe4552110 100644
--- a/bootstrap.php
+++ b/bootstrap.php
@@ -16,10 +16,18 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
return function (
string $root_dir,
- ContainerInterface ...$additional_containers
+ array $additional_containers = array(),
+ array $additional_modules = array()
): ContainerInterface {
+ /**
+ * Skip path check.
+ *
+ * @psalm-suppress UnresolvableInclude
+ */
$modules = ( require "$root_dir/modules.php" )( $root_dir );
+ $modules = array_merge( $modules, $additional_modules );
+
/**
* Use this filter to add custom module or remove some of existing ones.
* Modules able to access container, add services and modify existing ones.
@@ -38,7 +46,12 @@ return function (
// 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 );
+ $container = new DelegatingContainer( $provider );
+ /**
+ * Skip iterable vs array check.
+ *
+ * @psalm-suppress PossiblyInvalidArgument
+ */
$app_container = new CachingContainer(
new CompositeContainer(
array_merge(
diff --git a/changelog.txt b/changelog.txt
index 1bf182d82..a840377a9 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,192 @@
*** Changelog ***
+= 2.4.0 - 2023-10-31 =
+* Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735
+* Fix - ACDC disappearing after plugin updates #1751
+* Fix - Subscription module hooks #1748
+* Fix - Ensure PayPal Subscriptions API products description is 1-127 characters #1738
+* Fix - Add validation on the Plan Name field to not accept a blank value #1754
+* Enhancement - Improve Pay Later messages and add Shop, Home locations #1770
+* Enhancement - Use api-m PayPal API URLs #1740
+* Enhancement - Google Pay Settings improvements #1719
+* Enhancement - Apple Pay transaction improvements #1767
+* Enhancement - Change default ACDC title #1750
+* Enhancement - Cart simulation improvements #1753
+* Enhancement - Billing schedule fields not greyed out when PayPal Subscriptions product is connected #1755
+* Enhancement - Check validation errors when submitting in block #1528
+* Enhancement - Improve handling of server error when submitting block #1785
+* Enhancement - Extend Apple Pay country eligibility #1781
+* Enhancement - Apple Pay validation notice improvements #1783
+* Enhancement - Apple Pay payment process issues #1789
+* Enhancement - Disable the tracking if payment is not captured #1780
+* Enhancement - Place order button remains - Could not retrieve order #1786
+* Enhancement - Google Pay for variable product greyed out but clickable #1788
+* Enhancement - Merchant credential validation & remove PAYEE object #1795
+
+= 2.3.1 - 2023-09-26 =
+* Fix - Fatal error when saving product while WooCommerce Subscriptions plugin is not active #1731
+* Fix - Validate tracking data only for add/update Package Tracking #1729
+* Fix - Disable Package Tracking for order if transaction ID doesn't exist #1727
+
+= 2.3.0 - 2023-09-26 =
+* Fix - Plus sign in PayPal account email address gets converted to space #771
+* Fix - Payment method dropdown option label on edit order screen for ppcp-gateway option displaying wrong name #1639
+* Fix - WooCommerce Bookings products don't remain in Cart as a guest when PayPal button active on single product #1645
+* Fix - Since version > 2.2.0 the PayPal Checkout button on single product pages does not redirect anymore #1664
+* Fix - PayPal fee and PayPal Payout do not change on order if we do partial refund #1578
+* Fix - Order does not contain intent error when using ACDC payment token while buyer is not present #1506
+* Fix - Error when product description linked with a PayPal subscription exceeds 127 characters #1700
+* Fix - $_POST uses the wrong key to hold the shipping method #1652
+* Fix - WC Payment Token created multiple times when webhook is received #1663
+* Fix - Subtotal mismatch line name shows on Account settings page when merchant is disconnected #1702
+* Fix - Warning prevents payments on Pay for Order page when debugging is enabled #1703
+* Fix - paypal-overlay-uid_ blocks page after closing PayPal popup on Pay for Order page | Terms checkbox validation fails on Pay for Order page #1704
+* Enhancement - Add support for HPOS for tracking module #1676
+* Enhancement - Billing agreements endpoint called too frequently for Reference Transactions check #1646
+* Enhancement - Do not declare subscription support for PayPal when only ACDC vaulting #1669
+* Enhancement - Apply Capture On Status Change only when order contains PayPal payment method #1595
+* Enhancement - Do not use transient expiration longer than one month to support memcached #1448
+* Enhancement - By disconnecting or disabling the plugin the connection should clear the Onboarding links from cache #1668
+* Enhancement - Upgrade tracking integration #1562
+* Enhancement - Include url & image_url in create order call #1649
+* Enhancement - Add compat layer for Yith tracking #1656
+* Enhancement - Improve invalid currency backend notice (1926) #1588
+* Enhancement - Hide ACDC footer frame via CSS to avoid empty space #1613
+* Enhancement - Compatibility with WooCommerce Product Add-Ons plugin #1586
+* Enhancement - Remove "no shipment" message after adding tracking #1674
+* Enhancement - Improve error & success validation messages #1675
+* Enhancement - Compatibility with third-party "Product Add-Ons" plugins #1601
+* Enhancement - PayPal logo flashes when switching between tabs #1345
+* Enhancement - Include url & image_url in create order call #1649
+* Enhancement - Include item_url & image_url to tracking call #1712
+* Enhancement - Update strings for tracking metabox #1714
+* Enhancement - Validate email address API credentials field #1691
+* Enhancement - Set payment method title for order edit page only if our gateway #1661
+* Enhancement - Fix missing Pay Later messages in cart + refactoring #1683
+* Enhancement - Product page PP button keep loading popup - "wc_add_to_cart_params is not defined" error in WooCommerce #1655
+* Enhancement - Remove PayPal Subscriptions API feature flag #1690
+* Enhancement - Don't send image_url when it is empty #1678
+* Enhancement - Subscription support depending on Vaulting setting instead of subscription mode setting #1697
+* Enhancement - Wrong PayPal subscription id on vaulted subscriptions #1699
+* Enhancement - Remove payment vaulted checker functionality (2030) #1711
+* Feature preview - Apple Pay integration #1514
+* Feature preview - Google Pay integration #1654
+
+= 2.2.2 - 2023-08-29 =
+* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529
+* Enhancement - HPOS compatibility issues #1594
+* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607
+
+= 2.2.1 - 2023-08-24 =
+* Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536
+* Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565
+* Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533
+* Fix - Send payee_preferred correctly for instant payments #1489
+* Fix - Auto-disabled ACDC vaulting after updating to 2.1.0 #1490
+* Fix - PayPal Payments serializing formData of array inputs #1501
+* Fix - Buttons not working on single product page for WooCommerce Bookings product #1478
+* Enhancement - PayPal Later message price amount doesn't update dynamically #1585
+* Enhancement - Improve WC order creation in webhook #1530
+* Enhancement - Refactor hosted fields for early card detection #1554
+* Enhancement - Pay Later button and message get hidden when product/cart/checkout value is outside of range #1511
+* Enhancement - Add link to manual credential docs #1430
+* Enhancement - Validate Merchant ID field format when saving settings #1509
+* Enhancement - Include soft descriptor for card's activity #1427
+* Enhancement - Update Pay Later amount on the cart page and checkout when total changes #1441
+* Enhancement - Log Subscription Mode configuration in system report #1507
+* Enhancement - HPOS compatibility issues #1555
+* Feature preview - PayPal Subscriptions API fixes and improvements #1443
+
+= 2.2.0 - 2023-07-17 =
+* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
+* Fix - Use order currency instead of shop currency on order-pay page #1363
+* Fix - Do not show broken card button gateway when no checkout location #1358
+* Fix - Smart buttons not greyed out/removed on single product when deselecting product variation #1469
+* Fix - Type error with advanced columns pro #1367
+* Fix - Undefined array key 0 when checking $retry_errors in process_payment method #1375
+* Fix - Advanced Card Processing gateway becomes invisible post-plugin update unless admin pages are accessed once #1432
+* Fix - Incompatibility with WooCommerce One Page Checkout (or similar use cases) in Version 2.1.0 #1473
+* Fix - Prevent Repetitive Token Migration and Database Overload After 2.1.0 Update #1461
+* Fix - Onboarding from connection page with CSRF parameter manipulates email and merchant id fields #1502
+* Fix - Do not complete non-checkout button orders via webhooks #1513
+* Enhancement - Remove feature flag requirement for express cart/checkout block integration #1483
+* Enhancement - Add notice when shop currency is unsupported #1433
+* Enhancement - Improve ACDC error message when empty fields #1360
+* Enhancement - Do not exclude free items #1362
+* Enhancement - Trigger WC checkout_error event #1384
+* Enhancement - Update wording in buttons previews #1408
+* Enhancement - Filter to conditionally block the PayPal buttons #1485
+* Enhancement - Display funding source on the admin order page #1450
+* Enhancement - Update system report plugin status for Vaulting #1471
+* Enhancement - Revert Elementor Pro Checkout hook compatibility #1482
+
+= 2.1.0 - 2023-06-13 =
+* Fix - Performance issue #1182
+* Fix - Webhooks not registered when onboarding with manual credentials #1223
+* Fix - Boolean false type sent as empty value when setting cache #1313
+* Fix - Ajax vulnerabilities #1411
+* Enhancement - Save and display vaulted payment methods in WC Payment Token API #1059
+* Enhancement - Cache webhook verification results #1379
+* Enhancement - Refresh checkout totals after validation if needed #1294
+* Enhancement - Improve Divi and Elementor Pro compatibility #1254
+* Enhancement - Add MX and JP to ACDC #1415
+* Enhancement - Add fraudnet script to SGO filter #1366
+* Feature preview - Add express cart/checkout block #1346
+* Feature preview - Integrate PayPal Subscriptions API #1217
+
+= 2.0.5 - 2023-05-31 =
+* Fix - Potential invalidation of merchant credentials #1339
+
+= 2.0.4 - 2023-04-03 =
+* Fix - Allow Pay Later in mini-cart #1221
+* Fix - Duplicated auth error when credentials become wrong #1229
+* Fix - Webhook issues when switching sandbox, and delete all webhooks when unsubscribing #1239
+* Fix - High volume of traffic from merchant-integrations endpoint #1273
+* Fix - Add content type json to all fetch ajax endpoints #1275
+* Enhancement - Remove shortcodes from description #1226
+* Enhancement - Handle price suffix with price for product button check #1234
+* Enhancement - Show funding source as payment method #1220
+* Enhancement - Change "Enabled" to "Available" in status text #1237
+* Enhancement - Programmatically capturing/voiding authorized payments #590
+
+= 2.0.3 - 2023-03-14 =
+* Fix - `DEVICE_DATA_NOT_AVAILABLE` error message when FraudNet is enabled #1177
+* Fix - Redirect to connection tab after manual credentials input #1201
+* Fix - Asking for address fields in checkout when not using them #1089
+* Fix - Validate before free trial #1170
+* Fix - Validate new user creation #1131
+* Fix - After Updating to 2.0.2, Site Health reports REST API error #1195
+* Fix - Do not send buyer-country for previews in live mode to avoid error #1186
+* Fix - PPEC compatibility layer does not take over subscriptions #1193
+* Fix - Checkout conflict with "All products for subscriptions" plugin #629
+* Fix - Pay Later on order pay page #1214
+* Fix - High volume of traffic from merchant-integrations endpoint #1241
+* Enhancement - Save checkout form before free trial redirect #1135
+* Enhancement - Add filter for controlling the ditching of items/breakdown #1146
+* Enhancement - Add patch order data filter #1147
+* Enhancement - Add filter for disabling fees on wc order admin pages #1153
+* Enhancement - Use wp_loaded for fraudnet loading to avoid warnings #1172
+* Enhancement - reCaptcha for WooCommerce support #1093
+* Enhancement - Make it possible to hide missing funding resource Trustly #1155
+* Enhancement - Add white color option #1167
+* Enhancement - Checkout validation for other fields #861
+* Enhancement - Mention PUI only for German shops and add line breaks #1169
+* Enhancement - Add filter to fallback tracking_data['carrier'] #1188
+* Enhancement - Error notices in checkout do not update / or are shown twice #1168
+* Enhancement - capture authorized payment by changing order status (or programmatically) #587
+
+= 2.0.2 - 2023-01-31 =
+* Fix - Do not call PayPal get order by ID if it does not exist #1029
+* Fix - Type check error conflict with German Market #1056
+* Fix - Backend Storage for the PayPalRequestIdRepository does not scale #983
+* Fix - Ensure WC()->payment_gateways is not null #1128
+* Enhancement - Remove plugin data after uninstalling #1075
+* Enhancement - Add FraudNet to all payments #1040
+* Enhancement - Update "Standard Payments" tab settings #1065
+* Enhancement - Update PHP 7.2 requirement in all relevant files #1084
+* Enhancement - When PUI is enabled FraudNet should be also enabled #1129
+* Enhancement - Add PayPal-Request-Id if payment source exist #1132
+
= 2.0.1 - 2022-12-13 =
* Fix - Error while syncing tracking data to PayPal -> Sync GZD Tracking #1020
* Fix - Fix product price retrieval for variable product buttons #1000
diff --git a/composer.json b/composer.json
index e149ce2ed..bb2ccbf7c 100644
--- a/composer.json
+++ b/composer.json
@@ -29,8 +29,12 @@
"autoload": {
"psr-4": {
"WooCommerce\\PayPalCommerce\\": "src",
+ "WooCommerce\\PayPalCommerce\\Common\\": "lib/common/",
"WooCommerce\\PayPalCommerce\\Vendor\\": "lib/packages/"
- }
+ },
+ "files": [
+ "api/order-functions.php"
+ ]
},
"autoload-dev": {
"psr-4": {
diff --git a/composer.lock b/composer.lock
index e377dddce..450c96e96 100644
--- a/composer.lock
+++ b/composer.lock
@@ -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",
@@ -794,16 +797,16 @@
},
{
"name": "antecedent/patchwork",
- "version": "2.1.21",
+ "version": "2.1.25",
"source": {
"type": "git",
"url": "https://github.com/antecedent/patchwork.git",
- "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d"
+ "reference": "17314e042d45e0dacb0a494c2d1ef50e7621136a"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/antecedent/patchwork/zipball/25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d",
- "reference": "25c1fa0cd9a6e6d0d13863d8df8f050b6733f16d",
+ "url": "https://api.github.com/repos/antecedent/patchwork/zipball/17314e042d45e0dacb0a494c2d1ef50e7621136a",
+ "reference": "17314e042d45e0dacb0a494c2d1ef50e7621136a",
"shasum": ""
},
"require": {
@@ -836,9 +839,9 @@
],
"support": {
"issues": "https://github.com/antecedent/patchwork/issues",
- "source": "https://github.com/antecedent/patchwork/tree/2.1.21"
+ "source": "https://github.com/antecedent/patchwork/tree/2.1.25"
},
- "time": "2022-02-07T07:28:34+00:00"
+ "time": "2023-02-19T12:51:24+00:00"
},
{
"name": "brain/monkey",
@@ -1430,30 +1433,30 @@
},
{
"name": "doctrine/instantiator",
- "version": "1.4.1",
+ "version": "1.5.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
- "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
- "reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
+ "url": "https://api.github.com/repos/doctrine/instantiator/zipball/0a0fa9780f5d4e507415a065172d26a98d02047b",
+ "reference": "0a0fa9780f5d4e507415a065172d26a98d02047b",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
- "doctrine/coding-standard": "^9",
+ "doctrine/coding-standard": "^9 || ^11",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
- "vimeo/psalm": "^4.22"
+ "vimeo/psalm": "^4.30 || ^5.4"
},
"type": "library",
"autoload": {
@@ -1480,7 +1483,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
- "source": "https://github.com/doctrine/instantiator/tree/1.4.1"
+ "source": "https://github.com/doctrine/instantiator/tree/1.5.0"
},
"funding": [
{
@@ -1496,7 +1499,7 @@
"type": "tidelift"
}
],
- "time": "2022-03-03T08:28:38+00:00"
+ "time": "2022-12-30T00:15:36+00:00"
},
{
"name": "felixfbecker/advanced-json-rpc",
@@ -1601,24 +1604,24 @@
},
{
"name": "graham-campbell/result-type",
- "version": "v1.1.0",
+ "version": "v1.1.1",
"source": {
"type": "git",
"url": "https://github.com/GrahamCampbell/Result-Type.git",
- "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8"
+ "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/a878d45c1914464426dc94da61c9e1d36ae262a8",
- "reference": "a878d45c1914464426dc94da61c9e1d36ae262a8",
+ "url": "https://api.github.com/repos/GrahamCampbell/Result-Type/zipball/672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
+ "reference": "672eff8cf1d6fe1ef09ca0f89c4b287d6a3eb831",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0",
- "phpoption/phpoption": "^1.9"
+ "phpoption/phpoption": "^1.9.1"
},
"require-dev": {
- "phpunit/phpunit": "^8.5.28 || ^9.5.21"
+ "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"autoload": {
@@ -1647,7 +1650,7 @@
],
"support": {
"issues": "https://github.com/GrahamCampbell/Result-Type/issues",
- "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.0"
+ "source": "https://github.com/GrahamCampbell/Result-Type/tree/v1.1.1"
},
"funding": [
{
@@ -1659,7 +1662,7 @@
"type": "tidelift"
}
],
- "time": "2022-07-30T15:56:11+00:00"
+ "time": "2023-02-25T20:23:15+00:00"
},
{
"name": "hamcrest/hamcrest-php",
@@ -1783,16 +1786,16 @@
},
{
"name": "myclabs/deep-copy",
- "version": "1.11.0",
+ "version": "1.11.1",
"source": {
"type": "git",
"url": "https://github.com/myclabs/DeepCopy.git",
- "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614"
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/14daed4296fae74d9e3201d2c4925d1acb7aa614",
- "reference": "14daed4296fae74d9e3201d2c4925d1acb7aa614",
+ "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
+ "reference": "7284c22080590fb39f2ffa3e9057f10a4ddd0e0c",
"shasum": ""
},
"require": {
@@ -1830,7 +1833,7 @@
],
"support": {
"issues": "https://github.com/myclabs/DeepCopy/issues",
- "source": "https://github.com/myclabs/DeepCopy/tree/1.11.0"
+ "source": "https://github.com/myclabs/DeepCopy/tree/1.11.1"
},
"funding": [
{
@@ -1838,20 +1841,20 @@
"type": "tidelift"
}
],
- "time": "2022-03-03T13:19:32+00:00"
+ "time": "2023-03-08T13:26:56+00:00"
},
{
"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 +1890,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.2",
+ "version": "v4.16.0",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
- "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
+ "reference": "19526a33fb561ef417e822e85f08a00db4059c17"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
- "reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
+ "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/19526a33fb561ef417e822e85f08a00db4059c17",
+ "reference": "19526a33fb561ef417e822e85f08a00db4059c17",
"shasum": ""
},
"require": {
@@ -1943,9 +1946,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
- "source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
+ "source": "https://github.com/nikic/PHP-Parser/tree/v4.16.0"
},
- "time": "2022-11-12T15:38:23+00:00"
+ "time": "2023-06-25T14:52:30+00:00"
},
{
"name": "openlss/lib-array2xml",
@@ -2157,27 +2160,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 +2199,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",
@@ -2538,24 +2539,24 @@
},
{
"name": "phpoption/phpoption",
- "version": "1.9.0",
+ "version": "1.9.1",
"source": {
"type": "git",
"url": "https://github.com/schmittjoh/php-option.git",
- "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab"
+ "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
- "reference": "dc5ff11e274a90cc1c743f66c9ad700ce50db9ab",
+ "url": "https://api.github.com/repos/schmittjoh/php-option/zipball/dd3a383e599f49777d8b628dadbb90cae435b87e",
+ "reference": "dd3a383e599f49777d8b628dadbb90cae435b87e",
"shasum": ""
},
"require": {
"php": "^7.2.5 || ^8.0"
},
"require-dev": {
- "bamarni/composer-bin-plugin": "^1.8",
- "phpunit/phpunit": "^8.5.28 || ^9.5.21"
+ "bamarni/composer-bin-plugin": "^1.8.2",
+ "phpunit/phpunit": "^8.5.32 || ^9.6.3 || ^10.0.12"
},
"type": "library",
"extra": {
@@ -2597,7 +2598,7 @@
],
"support": {
"issues": "https://github.com/schmittjoh/php-option/issues",
- "source": "https://github.com/schmittjoh/php-option/tree/1.9.0"
+ "source": "https://github.com/schmittjoh/php-option/tree/1.9.1"
},
"funding": [
{
@@ -2609,7 +2610,7 @@
"type": "tidelift"
}
],
- "time": "2022-07-30T15:51:26+00:00"
+ "time": "2023-02-25T19:38:58+00:00"
},
{
"name": "phpunit/php-code-coverage",
@@ -2910,16 +2911,16 @@
},
{
"name": "phpunit/phpunit",
- "version": "8.5.31",
+ "version": "8.5.33",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
- "reference": "33c126b09a42de5c99e5e8032b54e8221264a74e"
+ "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/33c126b09a42de5c99e5e8032b54e8221264a74e",
- "reference": "33c126b09a42de5c99e5e8032b54e8221264a74e",
+ "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e",
+ "reference": "7d1ff0e8c6b35db78ff13e3e05517d7cbf7aa32e",
"shasum": ""
},
"require": {
@@ -2987,7 +2988,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
- "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.31"
+ "source": "https://github.com/sebastianbergmann/phpunit/tree/8.5.33"
},
"funding": [
{
@@ -3003,7 +3004,7 @@
"type": "tidelift"
}
],
- "time": "2022-10-28T05:57:37+00:00"
+ "time": "2023-02-27T13:04:50+00:00"
},
{
"name": "sebastian/code-unit-reverse-lookup",
@@ -3136,16 +3137,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 +3191,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 +3199,7 @@
"type": "github"
}
],
- "time": "2020-11-30T07:59:04+00:00"
+ "time": "2023-05-07T05:30:20+00:00"
},
{
"name": "sebastian/environment",
@@ -3736,16 +3737,16 @@
},
{
"name": "squizlabs/php_codesniffer",
- "version": "3.7.1",
+ "version": "3.7.2",
"source": {
"type": "git",
"url": "https://github.com/squizlabs/PHP_CodeSniffer.git",
- "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619"
+ "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/1359e176e9307e906dc3d890bcc9603ff6d90619",
- "reference": "1359e176e9307e906dc3d890bcc9603ff6d90619",
+ "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879",
+ "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879",
"shasum": ""
},
"require": {
@@ -3781,27 +3782,28 @@
"homepage": "https://github.com/squizlabs/PHP_CodeSniffer",
"keywords": [
"phpcs",
- "standards"
+ "standards",
+ "static analysis"
],
"support": {
"issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues",
"source": "https://github.com/squizlabs/PHP_CodeSniffer",
"wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki"
},
- "time": "2022-06-18T07:21:10+00:00"
+ "time": "2023-02-22T23:07:41+00:00"
},
{
"name": "symfony/console",
- "version": "v5.4.16",
+ "version": "v5.4.26",
"source": {
"type": "git",
"url": "https://github.com/symfony/console.git",
- "reference": "8e9b9c8dfb33af6057c94e1b44846bee700dc5ef"
+ "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/console/zipball/8e9b9c8dfb33af6057c94e1b44846bee700dc5ef",
- "reference": "8e9b9c8dfb33af6057c94e1b44846bee700dc5ef",
+ "url": "https://api.github.com/repos/symfony/console/zipball/b504a3d266ad2bb632f196c0936ef2af5ff6e273",
+ "reference": "b504a3d266ad2bb632f196c0936ef2af5ff6e273",
"shasum": ""
},
"require": {
@@ -3866,12 +3868,12 @@
"homepage": "https://symfony.com",
"keywords": [
"cli",
- "command line",
+ "command-line",
"console",
"terminal"
],
"support": {
- "source": "https://github.com/symfony/console/tree/v5.4.16"
+ "source": "https://github.com/symfony/console/tree/v5.4.26"
},
"funding": [
{
@@ -3887,7 +3889,7 @@
"type": "tidelift"
}
],
- "time": "2022-11-25T14:09:27+00:00"
+ "time": "2023-07-19T20:11:33+00:00"
},
{
"name": "symfony/deprecation-contracts",
@@ -4450,16 +4452,16 @@
},
{
"name": "symfony/string",
- "version": "v5.4.15",
+ "version": "v5.4.26",
"source": {
"type": "git",
"url": "https://github.com/symfony/string.git",
- "reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed"
+ "reference": "1181fe9270e373537475e826873b5867b863883c"
},
"dist": {
"type": "zip",
- "url": "https://api.github.com/repos/symfony/string/zipball/571334ce9f687e3e6af72db4d3b2a9431e4fd9ed",
- "reference": "571334ce9f687e3e6af72db4d3b2a9431e4fd9ed",
+ "url": "https://api.github.com/repos/symfony/string/zipball/1181fe9270e373537475e826873b5867b863883c",
+ "reference": "1181fe9270e373537475e826873b5867b863883c",
"shasum": ""
},
"require": {
@@ -4516,7 +4518,7 @@
"utf8"
],
"support": {
- "source": "https://github.com/symfony/string/tree/v5.4.15"
+ "source": "https://github.com/symfony/string/tree/v5.4.26"
},
"funding": [
{
@@ -4532,7 +4534,7 @@
"type": "tidelift"
}
],
- "time": "2022-10-05T15:16:54+00:00"
+ "time": "2023-06-28T12:46:07+00:00"
},
{
"name": "theseer/tokenizer",
diff --git a/lib/README.md b/lib/README.md
index 9114c43ef..d38e3edd4 100644
--- a/lib/README.md
+++ b/lib/README.md
@@ -1,5 +1,10 @@
+## packages
The packages that are likely to cause conflicts with other plugins (by loading multiple incompatible versions).
Their namespaces are isolated by [Mozart](https://github.com/coenjacobs/mozart).
Currently, the packages are simply added in the repo to avoid making the build process more complex (Mozart has different PHP requirements).
We need to isolate only PSR-11 containers and Dhii modularity packages, which are not supposed to change often.
+
+## common
+This folder contains reusable classes or components that do not fit into any specific module.
+They are designed to be versatile and can be used by any module within the plugin.
diff --git a/lib/common/Pattern/SingletonDecorator.php b/lib/common/Pattern/SingletonDecorator.php
new file mode 100644
index 000000000..54282dc91
--- /dev/null
+++ b/lib/common/Pattern/SingletonDecorator.php
@@ -0,0 +1,68 @@
+callable = $callable;
+ }
+
+ /**
+ * The make constructor.
+ *
+ * @param callable $callable
+ * @return self
+ */
+ public static function make( callable $callable ): self {
+ return new static( $callable );
+ }
+
+ /**
+ * Invokes a callable once and returns the same result on subsequent invokes.
+ *
+ * @param mixed ...$args Arguments to be passed to the callable.
+ * @return mixed
+ */
+ public function __invoke( ...$args ) {
+ if ( ! $this->executed ) {
+ $this->result = call_user_func_array( $this->callable, $args );
+ $this->executed = true;
+ }
+
+ return $this->result;
+ }
+}
diff --git a/lib/common/Pattern/SingletonTrait.php b/lib/common/Pattern/SingletonTrait.php
new file mode 100644
index 000000000..cf09570c7
--- /dev/null
+++ b/lib/common/Pattern/SingletonTrait.php
@@ -0,0 +1,41 @@
+type = $type;
$this->message = $message;
$this->dismissable = $dismissable;
+ $this->wrapper = $wrapper;
}
/**
@@ -74,4 +83,13 @@ class Message {
public function is_dismissable(): bool {
return $this->dismissable;
}
+
+ /**
+ * Returns the wrapper selector that will contain the notice.
+ *
+ * @return string
+ */
+ public function wrapper(): string {
+ return $this->wrapper;
+ }
}
diff --git a/modules/ppcp-admin-notices/src/Renderer/Renderer.php b/modules/ppcp-admin-notices/src/Renderer/Renderer.php
index b67da1c30..1202d2ab4 100644
--- a/modules/ppcp-admin-notices/src/Renderer/Renderer.php
+++ b/modules/ppcp-admin-notices/src/Renderer/Renderer.php
@@ -41,9 +41,10 @@ class Renderer implements RendererInterface {
$messages = $this->repository->current_message();
foreach ( $messages as $message ) {
printf(
- '',
+ '',
$message->type(),
( $message->is_dismissable() ) ? 'is-dismissible' : '',
+ ( $message->wrapper() ? sprintf( 'data-ppcp-wrapper="%s"', esc_attr( $message->wrapper() ) ) : '' ),
wp_kses_post( $message->message() )
);
}
diff --git a/modules/ppcp-api-client/services.php b/modules/ppcp-api-client/services.php
index 9dfc86ca2..bad667b78 100644
--- a/modules/ppcp-api-client/services.php
+++ b/modules/ppcp-api-client/services.php
@@ -9,6 +9,21 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
+use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerPayableBreakdown;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundPayerFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerPayableBreakdownFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
+use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
@@ -49,12 +64,13 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
return array(
@@ -105,7 +121,8 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'api.factory.sellerstatus' ),
$container->get( 'api.partner_merchant_id' ),
- $container->get( 'api.merchant_id' )
+ $container->get( 'api.merchant_id' ),
+ $container->get( 'api.helper.failure-registry' )
);
},
'api.factory.sellerstatus' => static function ( ContainerInterface $container ) : SellerStatusFactory {
@@ -118,8 +135,7 @@ return array(
$container->get( 'api.factory.payment-token' ),
$container->get( 'api.factory.payment-token-action-links' ),
$container->get( 'woocommerce.logger.woocommerce' ),
- $container->get( 'api.repository.customer' ),
- $container->get( 'api.repository.paypal-request-id' )
+ $container->get( 'api.repository.customer' )
);
},
'api.endpoint.webhook' => static function ( ContainerInterface $container ) : WebhookEndpoint {
@@ -179,15 +195,15 @@ return array(
$patch_collection_factory = $container->get( 'api.factory.patch-collection-factory' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
- /**
- * The settings.
- *
- * @var Settings $settings
- */
- $settings = $container->get( 'wcgateway.settings' );
+ $session_handler = $container->get( 'session.handler' );
+ assert( $session_handler instanceof SessionHandler );
+ $bn_code = $session_handler->bn_code();
+
+ $settings = $container->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+
$intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$application_context_repository = $container->get( 'api.repository.application-context' );
- $paypal_request_id = $container->get( 'api.repository.paypal-request-id' );
$subscription_helper = $container->get( 'subscription.helper' );
return new OrderEndpoint(
$container->get( 'api.host' ),
@@ -197,10 +213,10 @@ return array(
$intent,
$logger,
$application_context_repository,
- $paypal_request_id,
$subscription_helper,
$container->get( 'wcgateway.is-fraudnet-enabled' ),
- $container->get( 'wcgateway.fraudnet' )
+ $container->get( 'wcgateway.fraudnet' ),
+ $bn_code
);
},
'api.endpoint.billing-agreements' => static function ( ContainerInterface $container ): BillingAgreementsEndpoint {
@@ -210,8 +226,29 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
- 'api.repository.paypal-request-id' => static function( ContainerInterface $container ) : PayPalRequestIdRepository {
- return new PayPalRequestIdRepository();
+ 'api.endpoint.catalog-products' => static function ( ContainerInterface $container ): CatalogProducts {
+ return new CatalogProducts(
+ $container->get( 'api.host' ),
+ $container->get( 'api.bearer' ),
+ $container->get( 'api.factory.product' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'api.endpoint.billing-plans' => static function( ContainerInterface $container ): BillingPlans {
+ return new BillingPlans(
+ $container->get( 'api.host' ),
+ $container->get( 'api.bearer' ),
+ $container->get( 'api.factory.billing-cycle' ),
+ $container->get( 'api.factory.plan' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'api.endpoint.billing-subscriptions' => static function( ContainerInterface $container ): BillingSubscriptions {
+ return new BillingSubscriptions(
+ $container->get( 'api.host' ),
+ $container->get( 'api.bearer' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
},
'api.repository.application-context' => static function( ContainerInterface $container ) : ApplicationContextRepository {
@@ -261,24 +298,32 @@ return array(
$container->get( 'api.factory.fraud-processor-response' )
);
},
+ 'api.factory.refund' => static function ( ContainerInterface $container ): RefundFactory {
+ $amount_factory = $container->get( 'api.factory.amount' );
+ return new RefundFactory(
+ $amount_factory,
+ $container->get( 'api.factory.seller-payable-breakdown' ),
+ $container->get( 'api.factory.refund_payer' )
+ );
+ },
'api.factory.purchase-unit' => static function ( ContainerInterface $container ): PurchaseUnitFactory {
$amount_factory = $container->get( 'api.factory.amount' );
- $payee_repository = $container->get( 'api.repository.payee' );
- $payee_factory = $container->get( 'api.factory.payee' );
$item_factory = $container->get( 'api.factory.item' );
$shipping_factory = $container->get( 'api.factory.shipping' );
$payments_factory = $container->get( 'api.factory.payments' );
$prefix = $container->get( 'api.prefix' );
+ $soft_descriptor = $container->get( 'wcgateway.soft-descriptor' );
+ $sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' );
return new PurchaseUnitFactory(
$amount_factory,
- $payee_repository,
- $payee_factory,
$item_factory,
$shipping_factory,
$payments_factory,
- $prefix
+ $prefix,
+ $soft_descriptor,
+ $sanitizer
);
},
'api.factory.patch-collection-factory' => static function ( ContainerInterface $container ): PatchCollectionFactory {
@@ -293,12 +338,19 @@ return array(
);
},
'api.factory.shipping' => static function ( ContainerInterface $container ): ShippingFactory {
- $address_factory = $container->get( 'api.factory.address' );
- return new ShippingFactory( $address_factory );
+ return new ShippingFactory(
+ $container->get( 'api.factory.address' ),
+ $container->get( 'api.factory.shipping-option' )
+ );
},
'api.factory.shipping-preference' => static function ( ContainerInterface $container ): ShippingPreferenceFactory {
return new ShippingPreferenceFactory();
},
+ 'api.factory.shipping-option' => static function ( ContainerInterface $container ): ShippingOptionFactory {
+ return new ShippingOptionFactory(
+ $container->get( 'api.factory.money' )
+ );
+ },
'api.factory.amount' => static function ( ContainerInterface $container ): AmountFactory {
$item_factory = $container->get( 'api.factory.item' );
return new AmountFactory(
@@ -314,6 +366,9 @@ return array(
$address_factory = $container->get( 'api.factory.address' );
return new PayerFactory( $address_factory );
},
+ 'api.factory.refund_payer' => static function ( ContainerInterface $container ): RefundPayerFactory {
+ return new RefundPayerFactory();
+ },
'api.factory.address' => static function ( ContainerInterface $container ): AddressFactory {
return new AddressFactory();
},
@@ -337,7 +392,8 @@ return array(
'api.factory.payments' => static function ( ContainerInterface $container ): PaymentsFactory {
$authorizations_factory = $container->get( 'api.factory.authorization' );
$capture_factory = $container->get( 'api.factory.capture' );
- return new PaymentsFactory( $authorizations_factory, $capture_factory );
+ $refund_factory = $container->get( 'api.factory.refund' );
+ return new PaymentsFactory( $authorizations_factory, $capture_factory, $refund_factory );
},
'api.factory.authorization' => static function ( ContainerInterface $container ): AuthorizationFactory {
return new AuthorizationFactory();
@@ -358,9 +414,30 @@ return array(
$container->get( 'api.factory.platform-fee' )
);
},
+ 'api.factory.seller-payable-breakdown' => static function ( ContainerInterface $container ): SellerPayableBreakdownFactory {
+ return new SellerPayableBreakdownFactory(
+ $container->get( 'api.factory.money' ),
+ $container->get( 'api.factory.platform-fee' )
+ );
+ },
'api.factory.fraud-processor-response' => static function ( ContainerInterface $container ): FraudProcessorResponseFactory {
return new FraudProcessorResponseFactory();
},
+ 'api.factory.product' => static function( ContainerInterface $container ): ProductFactory {
+ return new ProductFactory();
+ },
+ 'api.factory.billing-cycle' => static function( ContainerInterface $container ): BillingCycleFactory {
+ return new BillingCycleFactory( $container->get( 'api.shop.currency' ) );
+ },
+ 'api.factory.payment-preferences' => static function( ContainerInterface $container ):PaymentPreferencesFactory {
+ return new PaymentPreferencesFactory( $container->get( 'api.shop.currency' ) );
+ },
+ 'api.factory.plan' => static function( ContainerInterface $container ): PlanFactory {
+ return new PlanFactory(
+ $container->get( 'api.factory.billing-cycle' ),
+ $container->get( 'api.factory.payment-preferences' )
+ );
+ },
'api.helpers.dccapplies' => static function ( ContainerInterface $container ) : DccApplies {
return new DccApplies(
$container->get( 'api.dcc-supported-country-currency-matrix' ),
@@ -635,6 +712,27 @@ return array(
'SGD',
'USD',
),
+ 'MX' => array(
+ 'MXN',
+ ),
+ 'JP' => array(
+ 'AUD',
+ 'CAD',
+ 'CHF',
+ 'CZK',
+ 'DKK',
+ 'EUR',
+ 'GBP',
+ 'HKD',
+ 'HUF',
+ 'JPY',
+ 'NOK',
+ 'NZD',
+ 'PLN',
+ 'SEK',
+ 'SGD',
+ 'USD',
+ ),
)
);
},
@@ -691,6 +789,17 @@ return array(
'amex' => array( 'CAD' ),
'jcb' => array( 'CAD' ),
),
+ 'MX' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array(),
+ ),
+ 'JP' => array(
+ 'mastercard' => array(),
+ 'visa' => array(),
+ 'amex' => array( 'JPY' ),
+ 'jcb' => array( 'JPY' ),
+ ),
)
);
},
@@ -730,4 +839,23 @@ return array(
'api.order-helper' => static function( ContainerInterface $container ): OrderHelper {
return new OrderHelper();
},
+ 'api.helper.order-transient' => static function( ContainerInterface $container ): OrderTransient {
+ $cache = new Cache( 'ppcp-paypal-bearer' );
+ $purchase_unit_sanitizer = $container->get( 'api.helper.purchase-unit-sanitizer' );
+ return new OrderTransient( $cache, $purchase_unit_sanitizer );
+ },
+ 'api.helper.failure-registry' => static function( ContainerInterface $container ): FailureRegistry {
+ $cache = new Cache( 'ppcp-paypal-api-status-cache' );
+ return new FailureRegistry( $cache );
+ },
+ 'api.helper.purchase-unit-sanitizer' => SingletonDecorator::make(
+ static function( ContainerInterface $container ): PurchaseUnitSanitizer {
+ $settings = $container->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+
+ $behavior = $settings->has( 'subtotal_mismatch_behavior' ) ? $settings->get( 'subtotal_mismatch_behavior' ) : null;
+ $line_name = $settings->has( 'subtotal_mismatch_line_name' ) ? $settings->get( 'subtotal_mismatch_line_name' ) : null;
+ return new PurchaseUnitSanitizer( $behavior, $line_name );
+ }
+ ),
);
diff --git a/modules/ppcp-api-client/src/ApiModule.php b/modules/ppcp-api-client/src/ApiModule.php
index 95a0efd14..ed09ecddc 100644
--- a/modules/ppcp-api-client/src/ApiModule.php
+++ b/modules/ppcp-api-client/src/ApiModule.php
@@ -9,10 +9,14 @@ declare(strict_types=1);
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\Psr\Container\ContainerInterface;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
/**
* Class ApiModule
@@ -40,6 +44,56 @@ class ApiModule implements ModuleInterface {
WC()->session->set( 'ppcp_fees', $fees );
}
);
+ add_filter(
+ 'ppcp_create_order_request_body_data',
+ function( array $data ) use ( $c ) {
+
+ foreach ( $data['purchase_units'] as $purchase_unit_index => $purchase_unit ) {
+ foreach ( $purchase_unit['items'] as $item_index => $item ) {
+ $data['purchase_units'][ $purchase_unit_index ]['items'][ $item_index ]['name'] =
+ apply_filters( 'woocommerce_paypal_payments_cart_line_item_name', $item['name'], $item['cart_item_key'] ?? null );
+ }
+ }
+
+ return $data;
+ }
+ );
+ add_action(
+ 'woocommerce_paypal_payments_paypal_order_created',
+ function ( Order $order ) use ( $c ) {
+ $transient = $c->has( 'api.helper.order-transient' ) ? $c->get( 'api.helper.order-transient' ) : null;
+
+ if ( $transient instanceof OrderTransient ) {
+ $transient->on_order_created( $order );
+ }
+ },
+ 10,
+ 1
+ );
+ add_action(
+ 'woocommerce_paypal_payments_woocommerce_order_created',
+ function ( WC_Order $wc_order, Order $order ) use ( $c ) {
+ $transient = $c->has( 'api.helper.order-transient' ) ? $c->get( 'api.helper.order-transient' ) : null;
+
+ if ( $transient instanceof OrderTransient ) {
+ $transient->on_woocommerce_order_created( $wc_order, $order );
+ }
+ },
+ 10,
+ 2
+ );
+ add_action(
+ 'woocommerce_paypal_payments_clear_apm_product_status',
+ function () use ( $c ) {
+ $failure_registry = $c->has( 'api.helper.failure-registry' ) ? $c->get( 'api.helper.failure-registry' ) : null;
+
+ if ( $failure_registry instanceof FailureRegistry ) {
+ $failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY );
+ }
+ },
+ 10,
+ 2
+ );
}
/**
diff --git a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php
index e226253a2..954be01b5 100644
--- a/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/BillingAgreementsEndpoint.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Endpoint;
+use Exception;
use stdClass;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
@@ -120,6 +121,10 @@ class BillingAgreementsEndpoint {
*/
public function reference_transaction_enabled(): bool {
try {
+ if ( wc_string_to_bool( get_transient( 'ppcp_reference_transaction_enabled' ) ) === true ) {
+ return true;
+ }
+
$this->is_request_logging_enabled = false;
try {
@@ -130,10 +135,12 @@ class BillingAgreementsEndpoint {
);
} finally {
$this->is_request_logging_enabled = true;
+ set_transient( 'ppcp_reference_transaction_enabled', true, MONTH_IN_SECONDS );
}
return true;
- } catch ( PayPalApiException $exception ) {
+ } catch ( Exception $exception ) {
+ delete_transient( 'ppcp_reference_transaction_enabled' );
return false;
}
}
diff --git a/modules/ppcp-api-client/src/Endpoint/BillingPlans.php b/modules/ppcp-api-client/src/Endpoint/BillingPlans.php
new file mode 100644
index 000000000..4792421c2
--- /dev/null
+++ b/modules/ppcp-api-client/src/Endpoint/BillingPlans.php
@@ -0,0 +1,263 @@
+host = $host;
+ $this->bearer = $bearer;
+ $this->billing_cycle_factory = $billing_cycle_factory;
+ $this->plan_factory = $plan_factory;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Creates a subscription plan.
+ *
+ * @param string $name Product name.
+ * @param string $product_id Product ID.
+ * @param array $billing_cycles Billing cycles.
+ * @param array $payment_preferences Payment preferences.
+ *
+ * @return Plan
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function create(
+ string $name,
+ string $product_id,
+ array $billing_cycles,
+ array $payment_preferences
+ ): Plan {
+
+ $data = array(
+ 'name' => $name,
+ 'product_id' => $product_id,
+ 'billing_cycles' => $billing_cycles,
+ 'payment_preferences' => $payment_preferences,
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/plans';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to create plan.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 201 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $this->plan_factory->from_paypal_response( $json );
+ }
+
+ /**
+ * Returns a plan,
+ *
+ * @param string $id Plan ID.
+ *
+ * @return Plan
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function plan( string $id ): Plan {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $id;
+ $args = array(
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to get plan.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 200 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $this->plan_factory->from_paypal_response( $json );
+ }
+
+ /**
+ * Updates pricing.
+ *
+ * @param string $id Plan ID.
+ * @param BillingCycle $billing_cycle Billing cycle.
+ *
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function update_pricing( string $id, BillingCycle $billing_cycle ): void {
+ $data = array(
+ 'pricing_schemes' => array(
+ (object) array(
+ 'billing_cycle_sequence' => 1,
+ 'pricing_scheme' => $billing_cycle->pricing_scheme(),
+ ),
+ ),
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $id . '/update-pricing-schemes';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Could not update pricing.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+
+ /**
+ * Deactivates a Subscription Plan.
+ *
+ * @param string $billing_plan_id The Plan ID.
+ *
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function deactivate_plan( string $billing_plan_id ) {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $billing_plan_id . '/deactivate';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Could not deactivate plan.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+}
diff --git a/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php
new file mode 100644
index 000000000..8a46506b6
--- /dev/null
+++ b/modules/ppcp-api-client/src/Endpoint/BillingSubscriptions.php
@@ -0,0 +1,219 @@
+host = $host;
+ $this->bearer = $bearer;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Suspends a subscription.
+ *
+ * @param string $id Subscription ID.
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function suspend( string $id ):void {
+ $data = array(
+ 'reason' => 'Suspended by customer',
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id . '/suspend';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to suspend subscription.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+
+ /**
+ * Activates a subscription.
+ *
+ * @param string $id Subscription ID.
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function activate( string $id ): void {
+ $data = array(
+ 'reason' => 'Reactivated by customer',
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id . '/activate';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to reactivate subscription.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+
+ /**
+ * Cancels a Subscription.
+ *
+ * @param string $id Subscription ID.
+ *
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function cancel( string $id ): void {
+ $data = array(
+ 'reason' => 'Cancelled by customer',
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id . '/cancel';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to cancel subscription.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+
+ /**
+ * Returns a Subscription object from the given ID.
+ *
+ * @param string $id Subscription ID.
+ *
+ * @return stdClass
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function subscription( string $id ): stdClass {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/billing/subscriptions/' . $id;
+ $args = array(
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to get subscription.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 200 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $json;
+ }
+
+
+}
diff --git a/modules/ppcp-api-client/src/Endpoint/CatalogProducts.php b/modules/ppcp-api-client/src/Endpoint/CatalogProducts.php
new file mode 100644
index 000000000..8a04e693e
--- /dev/null
+++ b/modules/ppcp-api-client/src/Endpoint/CatalogProducts.php
@@ -0,0 +1,196 @@
+host = $host;
+ $this->bearer = $bearer;
+ $this->product_factory = $product_factory;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Creates a product.
+ *
+ * @param string $name Product name.
+ * @param string $description Product description.
+ *
+ * @return Product
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function create( string $name, string $description ): Product {
+ $data = array(
+ 'name' => $name,
+ 'description' => $description ?: $name,
+ );
+
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/catalogs/products';
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to create product.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 201 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $this->product_factory->from_paypal_response( $json );
+ }
+
+ /**
+ * Updates a Product.
+ *
+ * @param string $id Product ID.
+ * @param array $data Data to update.
+ *
+ * @return void
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function update( string $id, array $data ): void {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/catalogs/products/' . $id;
+ $args = array(
+ 'method' => 'PATCH',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ 'body' => wp_json_encode( $data ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to update product.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+ }
+
+ /**
+ * Return a Product from the given ID.
+ *
+ * @param string $id Product ID.
+ *
+ * @return Product
+ *
+ * @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
+ */
+ public function product( string $id ): Product {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v1/catalogs/products/' . $id;
+ $args = array(
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( is_wp_error( $response ) || ! is_array( $response ) ) {
+ throw new RuntimeException( 'Not able to get product.' );
+ }
+
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 200 !== $status_code ) {
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
+
+ return $this->product_factory->from_paypal_response( $json );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php
index 0c27e3fe4..42265e26c 100644
--- a/modules/ppcp-api-client/src/Endpoint/IdentityToken.php
+++ b/modules/ppcp-api-client/src/Endpoint/IdentityToken.php
@@ -103,7 +103,8 @@ class IdentityToken {
);
if (
( $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) )
- && defined( 'PPCP_FLAG_SUBSCRIPTION' ) && PPCP_FLAG_SUBSCRIPTION
+ || ( $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ) )
+ || ( $this->settings->has( 'subscriptions_mode' ) && $this->settings->get( 'subscriptions_mode' ) === 'vaulting_api' )
) {
$customer_id = $this->customer_repository->customer_id_for_user( ( $user_id ) );
update_user_meta( $user_id, 'ppcp_customer_id', $customer_id );
diff --git a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php
index 55c16d774..abba2c182 100644
--- a/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/OrderEndpoint.php
@@ -16,8 +16,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
@@ -26,7 +26,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ErrorResponse;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
@@ -116,13 +115,6 @@ class OrderEndpoint {
*/
private $bn_code;
- /**
- * The paypal request id repository.
- *
- * @var PayPalRequestIdRepository
- */
- private $paypal_request_id_repository;
-
/**
* OrderEndpoint constructor.
*
@@ -133,7 +125,6 @@ class OrderEndpoint {
* @param string $intent The intent.
* @param LoggerInterface $logger The logger.
* @param ApplicationContextRepository $application_context_repository The application context repository.
- * @param PayPalRequestIdRepository $paypal_request_id_repository The paypal request id repository.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param bool $is_fraudnet_enabled true if FraudNet support is enabled in settings, otherwise false.
* @param FraudNet $fraudnet The FraudNet entity.
@@ -147,7 +138,6 @@ class OrderEndpoint {
string $intent,
LoggerInterface $logger,
ApplicationContextRepository $application_context_repository,
- PayPalRequestIdRepository $paypal_request_id_repository,
SubscriptionHelper $subscription_helper,
bool $is_fraudnet_enabled,
FraudNet $fraudnet,
@@ -162,7 +152,6 @@ class OrderEndpoint {
$this->logger = $logger;
$this->application_context_repository = $application_context_repository;
$this->bn_code = $bn_code;
- $this->paypal_request_id_repository = $paypal_request_id_repository;
$this->is_fraudnet_enabled = $is_fraudnet_enabled;
$this->subscription_helper = $subscription_helper;
$this->fraudnet = $fraudnet;
@@ -185,12 +174,12 @@ class OrderEndpoint {
/**
* Creates an order.
*
- * @param PurchaseUnit[] $items The purchase unit items for the order.
- * @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values.
- * @param Payer|null $payer The payer off the order.
- * @param PaymentToken|null $payment_token The payment token.
- * @param PaymentMethod|null $payment_method The payment method.
- * @param string $paypal_request_id The paypal request id.
+ * @param PurchaseUnit[] $items The purchase unit items for the order.
+ * @param string $shipping_preference One of ApplicationContext::SHIPPING_PREFERENCE_ values.
+ * @param Payer|null $payer The payer off the order.
+ * @param PaymentToken|null $payment_token The payment token.
+ * @param string $paypal_request_id The paypal request id.
+ * @param string $user_action The user action.
*
* @return Order
* @throws RuntimeException If the request fails.
@@ -200,20 +189,27 @@ class OrderEndpoint {
string $shipping_preference,
Payer $payer = null,
PaymentToken $payment_token = null,
- PaymentMethod $payment_method = null,
- string $paypal_request_id = ''
+ string $paypal_request_id = '',
+ string $user_action = ApplicationContext::USER_ACTION_CONTINUE
): Order {
$bearer = $this->bearer->bearer();
$data = array(
- 'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent,
+ 'intent' => apply_filters( 'woocommerce_paypal_payments_order_intent', $this->intent ),
'purchase_units' => array_map(
- static function ( PurchaseUnit $item ): array {
- return $item->to_array();
+ static function ( PurchaseUnit $item ) use ( $shipping_preference ): array {
+ $data = $item->to_array();
+
+ if ( $shipping_preference !== ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) {
+ // Shipping options are not allowed to be sent when not getting the address from PayPal.
+ unset( $data['shipping']['options'] );
+ }
+
+ return $data;
},
$items
),
'application_context' => $this->application_context_repository
- ->current_context( $shipping_preference )->to_array(),
+ ->current_context( $shipping_preference, $user_action )->to_array(),
);
if ( $payer && ! empty( $payer->email_address() ) ) {
$data['payer'] = $payer->to_array();
@@ -221,9 +217,6 @@ class OrderEndpoint {
if ( $payment_token ) {
$data['payment_source']['token'] = $payment_token->to_array();
}
- if ( $payment_method ) {
- $data['payment_method'] = $payment_method->to_array();
- }
/**
* The filter can be used to modify the order creation request body data.
@@ -240,8 +233,6 @@ class OrderEndpoint {
'body' => wp_json_encode( $data ),
);
- $paypal_request_id = $paypal_request_id ? $paypal_request_id : uniqid( 'ppcp-', true );
- $args['headers']['PayPal-Request-Id'] = $paypal_request_id;
if ( $this->bn_code ) {
$args['headers']['PayPal-Partner-Attribution-Id'] = $this->bn_code;
}
@@ -250,6 +241,10 @@ class OrderEndpoint {
$args['headers']['PayPal-Client-Metadata-Id'] = $this->fraudnet->session_id();
}
+ if ( isset( $data['payment_source'] ) ) {
+ $args['headers']['PayPal-Request-Id'] = uniqid( 'ppcp-', true );
+ }
+
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = new RuntimeException(
@@ -286,7 +281,9 @@ class OrderEndpoint {
throw $error;
}
$order = $this->order_factory->from_paypal_response( $json );
- $this->paypal_request_id_repository->set_for_order( $order, $paypal_request_id );
+
+ do_action( 'woocommerce_paypal_payments_paypal_order_created', $order );
+
return $order;
}
@@ -307,10 +304,9 @@ class OrderEndpoint {
$args = array(
'method' => 'POST',
'headers' => array(
- 'Authorization' => 'Bearer ' . $bearer->token(),
- 'Content-Type' => 'application/json',
- 'Prefer' => 'return=representation',
- 'PayPal-Request-Id' => $this->paypal_request_id_repository->get_for_order( $order ),
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
),
);
if ( $this->bn_code ) {
@@ -382,10 +378,9 @@ class OrderEndpoint {
$args = array(
'method' => 'POST',
'headers' => array(
- 'Authorization' => 'Bearer ' . $bearer->token(),
- 'Content-Type' => 'application/json',
- 'Prefer' => 'return=representation',
- 'PayPal-Request-Id' => $this->paypal_request_id_repository->get_for_order( $order ),
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
),
);
if ( $this->bn_code ) {
@@ -456,9 +451,8 @@ class OrderEndpoint {
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $id;
$args = array(
'headers' => array(
- 'Authorization' => 'Bearer ' . $bearer->token(),
- 'Content-Type' => 'application/json',
- 'PayPal-Request-Id' => $this->paypal_request_id_repository->get_for_order_id( $id ),
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
),
);
if ( $this->bn_code ) {
@@ -469,14 +463,8 @@ class OrderEndpoint {
$error = new RuntimeException(
__( 'Could not retrieve order.', 'woocommerce-paypal-payments' )
);
- $this->logger->log(
- 'warning',
- $error->getMessage(),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
+ $this->logger->warning( $error->getMessage() );
+
throw $error;
}
$json = json_decode( $response['body'] );
@@ -511,8 +499,8 @@ class OrderEndpoint {
);
throw $error;
}
- $order = $this->order_factory->from_paypal_response( $json );
- return $order;
+
+ return $this->order_factory->from_paypal_response( $json );
}
/**
@@ -530,25 +518,36 @@ class OrderEndpoint {
return $order_to_update;
}
+ $this->patch( $order_to_update->id(), $patches );
+
+ $new_order = $this->order( $order_to_update->id() );
+ return $new_order;
+ }
+
+ /**
+ * Patches an order.
+ *
+ * @param string $order_id The PayPal order ID.
+ * @param PatchCollection $patches The patches.
+ *
+ * @throws RuntimeException If the request fails.
+ */
+ public function patch( string $order_id, PatchCollection $patches ): void {
$patches_array = $patches->to_array();
- if ( ! isset( $patches_array[0]['value']['shipping'] ) ) {
- $shipping = isset( $order_to_update->purchase_units()[0] ) && null !== $order_to_update->purchase_units()[0]->shipping() ? $order_to_update->purchase_units()[0]->shipping() : null;
- if ( $shipping ) {
- $patches_array[0]['value']['shipping'] = $shipping->to_array();
- }
- }
+
+ /**
+ * The filter can be used to modify the order patching request body data (the final prices, items).
+ */
+ $patches_array = apply_filters( 'ppcp_patch_order_request_body_data', $patches_array );
$bearer = $this->bearer->bearer();
- $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id();
+ $url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_id;
$args = array(
'method' => 'PATCH',
'headers' => array(
- 'Authorization' => 'Bearer ' . $bearer->token(),
- 'Content-Type' => 'application/json',
- 'Prefer' => 'return=representation',
- 'PayPal-Request-Id' => $this->paypal_request_id_repository->get_for_order(
- $order_to_update
- ),
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ 'Prefer' => 'return=representation',
),
'body' => wp_json_encode( $patches_array ),
);
@@ -558,11 +557,8 @@ class OrderEndpoint {
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
- $error = new RuntimeException(
- __( 'Could not retrieve order.', 'woocommerce-paypal-payments' )
- );
- $this->logger->log(
- 'warning',
+ $error = new RuntimeException( 'Could not patch order.' );
+ $this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@@ -578,8 +574,7 @@ class OrderEndpoint {
$json,
$status_code
);
- $this->logger->log(
- 'warning',
+ $this->logger->warning(
$error->getMessage(),
array(
'args' => $args,
@@ -588,9 +583,6 @@ class OrderEndpoint {
);
throw $error;
}
-
- $new_order = $this->order( $order_to_update->id() );
- return $new_order;
}
/**
diff --git a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php
index 275f94c97..b7624d2de 100644
--- a/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PartnersEndpoint.php
@@ -15,6 +15,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatus;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerStatusFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
/**
* Class PartnersEndpoint
@@ -65,6 +66,13 @@ class PartnersEndpoint {
*/
private $merchant_id;
+ /**
+ * The failure registry.
+ *
+ * @var FailureRegistry
+ */
+ private $failure_registry;
+
/**
* PartnersEndpoint constructor.
*
@@ -74,6 +82,7 @@ class PartnersEndpoint {
* @param SellerStatusFactory $seller_status_factory The seller status factory.
* @param string $partner_id The partner ID.
* @param string $merchant_id The merchant ID.
+ * @param FailureRegistry $failure_registry The API failure registry.
*/
public function __construct(
string $host,
@@ -81,7 +90,8 @@ class PartnersEndpoint {
LoggerInterface $logger,
SellerStatusFactory $seller_status_factory,
string $partner_id,
- string $merchant_id
+ string $merchant_id,
+ FailureRegistry $failure_registry
) {
$this->host = $host;
$this->bearer = $bearer;
@@ -89,6 +99,7 @@ class PartnersEndpoint {
$this->seller_status_factory = $seller_status_factory;
$this->partner_id = $partner_id;
$this->merchant_id = $merchant_id;
+ $this->failure_registry = $failure_registry;
}
/**
@@ -140,9 +151,15 @@ class PartnersEndpoint {
'response' => $response,
)
);
+
+ // Register the failure on api failure registry.
+ $this->failure_registry->add_failure( FailureRegistry::SELLER_STATUS_KEY );
+
throw $error;
}
+ $this->failure_registry->clear_failures( FailureRegistry::SELLER_STATUS_KEY );
+
$status = $this->seller_status_factory->from_paypal_reponse( $json );
return $status;
}
diff --git a/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php
index 47d92d224..49bccf7a7 100644
--- a/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PayUponInvoiceOrderEndpoint.php
@@ -112,7 +112,7 @@ class PayUponInvoiceOrderEndpoint {
'processing_instruction' => 'ORDER_COMPLETE_ON_PAYMENT_APPROVAL',
'purchase_units' => array_map(
static function ( PurchaseUnit $item ): array {
- return $item->to_array( false );
+ return $item->to_array( true, false );
},
$items
),
@@ -133,7 +133,7 @@ class PayUponInvoiceOrderEndpoint {
'Content-Type' => 'application/json',
'Prefer' => 'return=representation',
'PayPal-Client-Metadata-Id' => $this->fraudnet->session_id(),
- 'PayPal-Request-Id' => uniqid( 'ppcp-', true ),
+ 'PayPal-Request-Id' => uniqid( 'ppcp-', true ), // Request-Id header is required.
),
'body' => wp_json_encode( $data ),
);
@@ -166,8 +166,11 @@ class PayUponInvoiceOrderEndpoint {
throw new PayPalApiException( $json, $status_code );
}
+ $order = $this->order_factory->from_paypal_response( $json );
- return $this->order_factory->from_paypal_response( $json );
+ do_action( 'woocommerce_paypal_payments_paypal_order_created', $order );
+
+ return $order;
}
/**
@@ -233,9 +236,15 @@ class PayUponInvoiceOrderEndpoint {
* @return array
*/
private function ensure_taxes( WC_Order $wc_order, array $data ): array {
- $tax_total = $data['purchase_units'][0]['amount']['breakdown']['tax_total']['value'];
- $item_total = $data['purchase_units'][0]['amount']['breakdown']['item_total']['value'];
- $shipping = $data['purchase_units'][0]['amount']['breakdown']['shipping']['value'];
+ $tax_total = $data['purchase_units'][0]['amount']['breakdown']['tax_total']['value'];
+ $item_total = $data['purchase_units'][0]['amount']['breakdown']['item_total']['value'];
+ $shipping = $data['purchase_units'][0]['amount']['breakdown']['shipping']['value'];
+
+ $handling = isset( $data['purchase_units'][0]['amount']['breakdown']['handling'] ) ? $data['purchase_units'][0]['amount']['breakdown']['handling']['value'] : 0;
+ $insurance = isset( $data['purchase_units'][0]['amount']['breakdown']['insurance'] ) ? $data['purchase_units'][0]['amount']['breakdown']['insurance']['value'] : 0;
+ $shipping_discount = isset( $data['purchase_units'][0]['amount']['breakdown']['shipping_discount'] ) ? $data['purchase_units'][0]['amount']['breakdown']['shipping_discount']['value'] : 0;
+ $discount = isset( $data['purchase_units'][0]['amount']['breakdown']['discount'] ) ? $data['purchase_units'][0]['amount']['breakdown']['discount']['value'] : 0;
+
$order_tax_total = $wc_order->get_total_tax();
$tax_rate = round( ( $order_tax_total / $item_total ) * 100, 1 );
@@ -263,7 +272,7 @@ class PayUponInvoiceOrderEndpoint {
);
$total_amount = $data['purchase_units'][0]['amount']['value'];
- $breakdown_total = $item_total + $tax_total + $shipping;
+ $breakdown_total = $item_total + $tax_total + $shipping + $handling + $insurance - $shipping_discount - $discount;
$diff = round( $total_amount - $breakdown_total, 2 );
if ( $diff === -0.01 || $diff === 0.01 ) {
$data['purchase_units'][0]['amount']['value'] = number_format( $breakdown_total, 2, '.', '' );
diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php
index 9e1b3a74b..0ae611e8c 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentTokenEndpoint.php
@@ -19,7 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenActionLinksFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
+use WP_Error;
/**
* Class PaymentTokenEndpoint
@@ -70,13 +70,6 @@ class PaymentTokenEndpoint {
*/
protected $customer_repository;
- /**
- * The request id repository.
- *
- * @var PayPalRequestIdRepository
- */
- private $request_id_repository;
-
/**
* PaymentTokenEndpoint constructor.
*
@@ -86,7 +79,6 @@ class PaymentTokenEndpoint {
* @param PaymentTokenActionLinksFactory $payment_token_action_links_factory The PaymentTokenActionLinks factory.
* @param LoggerInterface $logger The logger.
* @param CustomerRepository $customer_repository The customer repository.
- * @param PayPalRequestIdRepository $request_id_repository The request id repository.
*/
public function __construct(
string $host,
@@ -94,8 +86,7 @@ class PaymentTokenEndpoint {
PaymentTokenFactory $factory,
PaymentTokenActionLinksFactory $payment_token_action_links_factory,
LoggerInterface $logger,
- CustomerRepository $customer_repository,
- PayPalRequestIdRepository $request_id_repository
+ CustomerRepository $customer_repository
) {
$this->host = $host;
@@ -104,11 +95,10 @@ class PaymentTokenEndpoint {
$this->payment_token_action_links_factory = $payment_token_action_links_factory;
$this->logger = $logger;
$this->customer_repository = $customer_repository;
- $this->request_id_repository = $request_id_repository;
}
/**
- * Returns the payment tokens for a user.
+ * Returns the payment tokens for the given user id.
*
* @param int $id The user id.
*
@@ -129,7 +119,67 @@ class PaymentTokenEndpoint {
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
$error = new RuntimeException(
- __( 'Could not fetch payment token.', 'woocommerce-paypal-payments' )
+ __( 'Could not fetch payment token for customer id.', 'woocommerce-paypal-payments' )
+ );
+ $this->logger->log(
+ 'warning',
+ $error->getMessage(),
+ array(
+ 'args' => $args,
+ 'response' => $response,
+ )
+ );
+ throw $error;
+ }
+ $json = json_decode( $response['body'] );
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 200 !== $status_code ) {
+ $error = new PayPalApiException(
+ $json,
+ $status_code
+ );
+ $this->logger->log(
+ 'warning',
+ $error->getMessage(),
+ array(
+ 'args' => $args,
+ 'response' => $response,
+ )
+ );
+ throw $error;
+ }
+
+ $tokens = array();
+ foreach ( $json->payment_tokens as $token_value ) {
+ $tokens[] = $this->factory->from_paypal_response( $token_value );
+ }
+
+ return $tokens;
+ }
+
+ /**
+ * Returns the payment tokens for the given guest customer id.
+ *
+ * @param string $customer_id The guest customer id.
+ *
+ * @return PaymentToken[]
+ * @throws RuntimeException If the request fails.
+ */
+ public function for_guest( string $customer_id ): array {
+ $bearer = $this->bearer->bearer();
+ $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id;
+ $args = array(
+ 'method' => 'GET',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+ if ( $response instanceof WP_Error ) {
+ $error = new RuntimeException(
+ __( 'Could not fetch payment token for guest customer id.', 'woocommerce-paypal-payments' )
);
$this->logger->log(
'warning',
@@ -208,6 +258,40 @@ class PaymentTokenEndpoint {
return wp_remote_retrieve_response_code( $response ) === 204;
}
+ /**
+ * Deletes payment token by the given id.
+ *
+ * @param string $token_id Token id.
+ * @return bool
+ *
+ * @throws RuntimeException If something goes wrong while deleting the token.
+ */
+ public function delete_token_by_id( string $token_id ): bool {
+ $bearer = $this->bearer->bearer();
+
+ $url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/' . $token_id;
+ $args = array(
+ 'method' => 'DELETE',
+ 'headers' => array(
+ 'Authorization' => 'Bearer ' . $bearer->token(),
+ 'Content-Type' => 'application/json',
+ ),
+ );
+
+ $response = $this->request( $url, $args );
+
+ if ( is_wp_error( $response ) ) {
+ $error = new RuntimeException(
+ __( 'Could not delete payment token.', 'woocommerce-paypal-payments' )
+ );
+ $this->logger->warning( $error->getMessage() );
+
+ throw $error;
+ }
+
+ return wp_remote_retrieve_response_code( $response ) === 204;
+ }
+
/**
* Starts the process of PayPal account vaulting (without payment), returns the links for further actions.
*
@@ -243,14 +327,11 @@ class PaymentTokenEndpoint {
),
);
- $request_id = uniqid( 'ppcp-vault', true );
-
$args = array(
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
'Content-Type' => 'application/json',
- 'Request-Id' => $request_id,
),
'body' => wp_json_encode( $data ),
);
@@ -277,8 +358,6 @@ class PaymentTokenEndpoint {
$links = $this->payment_token_action_links_factory->from_paypal_response( $json );
- $this->request_id_repository->set( "ppcp-vault-{$user_id}", $request_id );
-
return $links;
}
@@ -302,7 +381,6 @@ class PaymentTokenEndpoint {
'method' => 'POST',
'headers' => array(
'Authorization' => 'Bearer ' . $bearer->token(),
- 'Request-Id' => $this->request_id_repository->get( "ppcp-vault-{$user_id}" ),
'Content-Type' => 'application/json',
),
);
diff --git a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
index 2a39a4b9b..210beae16 100644
--- a/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/PaymentsEndpoint.php
@@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundCapture;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
@@ -196,13 +196,13 @@ class PaymentsEndpoint {
/**
* Refunds a payment.
*
- * @param Refund $refund The refund to be processed.
+ * @param RefundCapture $refund The refund to be processed.
*
* @return string Refund ID.
* @throws RuntimeException If the request fails.
* @throws PayPalApiException If the request fails.
*/
- public function refund( Refund $refund ) : string {
+ public function refund( RefundCapture $refund ) : string {
$bearer = $this->bearer->bearer();
$url = trailingslashit( $this->host ) . 'v2/payments/captures/' . $refund->for_capture()->id() . '/refund';
$args = array(
diff --git a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php
index 252c1a294..3df4c3def 100644
--- a/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php
+++ b/modules/ppcp-api-client/src/Endpoint/WebhookEndpoint.php
@@ -17,6 +17,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
use Psr\Log\LoggerInterface;
+use WP_Error;
/**
* Class WebhookEndpoint
@@ -175,12 +176,12 @@ class WebhookEndpoint {
*
* @param Webhook $hook The webhook to delete.
*
- * @return bool
* @throws RuntimeException If the request fails.
+ * @throws PayPalApiException If the request fails.
*/
- public function delete( Webhook $hook ): bool {
+ public function delete( Webhook $hook ): void {
if ( ! $hook->id() ) {
- return false;
+ return;
}
$bearer = $this->bearer->bearer();
@@ -193,12 +194,28 @@ class WebhookEndpoint {
);
$response = $this->request( $url, $args );
- if ( is_wp_error( $response ) ) {
+ if ( $response instanceof WP_Error ) {
throw new RuntimeException(
__( 'Not able to delete the webhook.', 'woocommerce-paypal-payments' )
);
}
- return wp_remote_retrieve_response_code( $response ) === 204;
+
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code ) {
+ $json = null;
+ /**
+ * Use in array as consistency check.
+ *
+ * @psalm-suppress RedundantConditionGivenDocblockType
+ */
+ if ( is_array( $response ) ) {
+ $json = json_decode( $response['body'] );
+ }
+ throw new PayPalApiException(
+ $json,
+ $status_code
+ );
+ }
}
/**
diff --git a/modules/ppcp-api-client/src/Entity/ApplicationContext.php b/modules/ppcp-api-client/src/Entity/ApplicationContext.php
index 51a3ad005..c70bbf850 100644
--- a/modules/ppcp-api-client/src/Entity/ApplicationContext.php
+++ b/modules/ppcp-api-client/src/Entity/ApplicationContext.php
@@ -41,6 +41,9 @@ class ApplicationContext {
self::USER_ACTION_PAY_NOW,
);
+ const PAYMENT_METHOD_UNRESTRICTED = 'UNRESTRICTED';
+ const PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED = 'IMMEDIATE_PAYMENT_REQUIRED';
+
/**
* The brand name.
*
@@ -91,11 +94,11 @@ class ApplicationContext {
private $cancel_url;
/**
- * The payment method.
+ * The payment method preference.
*
- * @var null
+ * @var string
*/
- private $payment_method;
+ private $payment_method_preference;
/**
* ApplicationContext constructor.
@@ -107,6 +110,7 @@ class ApplicationContext {
* @param string $landing_page The landing page.
* @param string $shipping_preference The shipping preference.
* @param string $user_action The user action.
+ * @param string $payment_method_preference The payment method preference.
*
* @throws RuntimeException When values are not valid.
*/
@@ -117,7 +121,8 @@ class ApplicationContext {
string $locale = '',
string $landing_page = self::LANDING_PAGE_NO_PREFERENCE,
string $shipping_preference = self::SHIPPING_PREFERENCE_NO_SHIPPING,
- string $user_action = self::USER_ACTION_CONTINUE
+ string $user_action = self::USER_ACTION_CONTINUE,
+ string $payment_method_preference = self::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED
) {
if ( ! in_array( $landing_page, self::VALID_LANDING_PAGE_VALUES, true ) ) {
@@ -129,16 +134,14 @@ class ApplicationContext {
if ( ! in_array( $user_action, self::VALID_USER_ACTION_VALUES, true ) ) {
throw new RuntimeException( 'User action preference not correct' );
}
- $this->return_url = $return_url;
- $this->cancel_url = $cancel_url;
- $this->brand_name = $brand_name;
- $this->locale = $locale;
- $this->landing_page = $landing_page;
- $this->shipping_preference = $shipping_preference;
- $this->user_action = $user_action;
-
- // Currently we have not implemented the payment method.
- $this->payment_method = null;
+ $this->return_url = $return_url;
+ $this->cancel_url = $cancel_url;
+ $this->brand_name = $brand_name;
+ $this->locale = $locale;
+ $this->landing_page = $landing_page;
+ $this->shipping_preference = $shipping_preference;
+ $this->user_action = $user_action;
+ $this->payment_method_preference = $payment_method_preference;
}
/**
@@ -205,12 +208,10 @@ class ApplicationContext {
}
/**
- * Returns the payment method.
- *
- * @return PaymentMethod|null
+ * Returns the payment method preference.
*/
- public function payment_method() {
- return $this->payment_method;
+ public function payment_method_preference(): string {
+ return $this->payment_method_preference;
}
/**
@@ -223,9 +224,6 @@ class ApplicationContext {
if ( $this->user_action() ) {
$data['user_action'] = $this->user_action();
}
- if ( $this->payment_method() ) {
- $data['payment_method'] = $this->payment_method();
- }
if ( $this->shipping_preference() ) {
$data['shipping_preference'] = $this->shipping_preference();
}
@@ -244,6 +242,11 @@ class ApplicationContext {
if ( $this->cancel_url() ) {
$data['cancel_url'] = $this->cancel_url();
}
+ if ( $this->payment_method_preference ) {
+ $data['payment_method'] = array(
+ 'payee_preferred' => $this->payment_method_preference,
+ );
+ }
return $data;
}
}
diff --git a/modules/ppcp-api-client/src/Entity/BillingCycle.php b/modules/ppcp-api-client/src/Entity/BillingCycle.php
new file mode 100644
index 000000000..76f1c25fa
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/BillingCycle.php
@@ -0,0 +1,134 @@
+frequency = $frequency;
+ $this->sequence = $sequence;
+ $this->tenure_type = $tenure_type;
+ $this->pricing_scheme = $pricing_scheme;
+ $this->total_cycles = $total_cycles;
+ }
+
+ /**
+ * Returns frequency.
+ *
+ * @return array
+ */
+ public function frequency(): array {
+ return $this->frequency;
+ }
+
+ /**
+ * Returns sequence.
+ *
+ * @return int
+ */
+ public function sequence(): int {
+ return $this->sequence;
+ }
+
+ /**
+ * Returns tenure type.
+ *
+ * @return string
+ */
+ public function tenure_type(): string {
+ return $this->tenure_type;
+ }
+
+ /**
+ * Returns pricing scheme.
+ *
+ * @return array
+ */
+ public function pricing_scheme(): array {
+ return $this->pricing_scheme;
+ }
+
+ /**
+ * Return total cycles.
+ *
+ * @return int
+ */
+ public function total_cycles(): int {
+ return $this->total_cycles;
+ }
+
+ /**
+ * Returns Billing Cycle as array.
+ *
+ * @return array
+ */
+ public function to_array(): array {
+ return array(
+ 'frequency' => $this->frequency(),
+ 'sequence' => $this->sequence(),
+ 'tenure_type' => $this->tenure_type(),
+ 'pricing_scheme' => $this->pricing_scheme(),
+ 'total_cycles' => $this->total_cycles(),
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/Item.php b/modules/ppcp-api-client/src/Entity/Item.php
index 374c2efcb..25478d5b0 100644
--- a/modules/ppcp-api-client/src/Entity/Item.php
+++ b/modules/ppcp-api-client/src/Entity/Item.php
@@ -66,6 +66,20 @@ class Item {
*/
private $category;
+ /**
+ * The product url.
+ *
+ * @var string
+ */
+ protected $url;
+
+ /**
+ * The product image url.
+ *
+ * @var string
+ */
+ protected $image_url;
+
/**
* The tax rate.
*
@@ -73,6 +87,13 @@ class Item {
*/
protected $tax_rate;
+ /**
+ * The cart item key.
+ *
+ * @var string|null
+ */
+ protected $cart_item_key;
+
/**
* Item constructor.
*
@@ -83,7 +104,10 @@ class Item {
* @param Money|null $tax The tax.
* @param string $sku The SKU.
* @param string $category The category.
+ * @param string $url The product url.
+ * @param string $image_url The product image url.
* @param float $tax_rate The tax rate.
+ * @param ?string $cart_item_key The cart key for this item.
*/
public function __construct(
string $name,
@@ -93,18 +117,23 @@ class Item {
Money $tax = null,
string $sku = '',
string $category = 'PHYSICAL_GOODS',
- float $tax_rate = 0
+ string $url = '',
+ string $image_url = '',
+ float $tax_rate = 0,
+ string $cart_item_key = null
) {
- $this->name = $name;
- $this->unit_amount = $unit_amount;
- $this->quantity = $quantity;
- $this->description = $description;
- $this->tax = $tax;
- $this->sku = $sku;
- $this->category = ( self::DIGITAL_GOODS === $category ) ? self::DIGITAL_GOODS : self::PHYSICAL_GOODS;
- $this->category = $category;
- $this->tax_rate = $tax_rate;
+ $this->name = $name;
+ $this->unit_amount = $unit_amount;
+ $this->quantity = $quantity;
+ $this->description = $description;
+ $this->tax = $tax;
+ $this->sku = $sku;
+ $this->category = $category;
+ $this->url = $url;
+ $this->image_url = $image_url;
+ $this->tax_rate = $tax_rate;
+ $this->cart_item_key = $cart_item_key;
}
/**
@@ -170,6 +199,24 @@ class Item {
return $this->category;
}
+ /**
+ * Returns the url.
+ *
+ * @return string
+ */
+ public function url():string {
+ return $this->url;
+ }
+
+ /**
+ * Returns the image url.
+ *
+ * @return string
+ */
+ public function image_url():string {
+ return $this->validate_image_url() ? $this->image_url : '';
+ }
+
/**
* Returns the tax rate.
*
@@ -179,12 +226,21 @@ class Item {
return round( (float) $this->tax_rate, 2 );
}
+ /**
+ * Returns the cart key for this item.
+ *
+ * @return string|null
+ */
+ public function cart_item_key():?string {
+ return $this->cart_item_key;
+ }
+
/**
* Returns the object as array.
*
* @return array
*/
- public function to_array() {
+ public function to_array(): array {
$item = array(
'name' => $this->name(),
'unit_amount' => $this->unit_amount()->to_array(),
@@ -192,8 +248,13 @@ class Item {
'description' => $this->description(),
'sku' => $this->sku(),
'category' => $this->category(),
+ 'url' => $this->url(),
);
+ if ( $this->image_url() ) {
+ $item['image_url'] = $this->image_url();
+ }
+
if ( $this->tax() ) {
$item['tax'] = $this->tax()->to_array();
}
@@ -202,6 +263,20 @@ class Item {
$item['tax_rate'] = (string) $this->tax_rate();
}
+ if ( $this->cart_item_key() ) {
+ $item['cart_item_key'] = (string) $this->cart_item_key();
+ }
+
return $item;
}
+
+ /**
+ * Validates the image url for PayPal request.
+ *
+ * @return bool true if valid, otherwise false.
+ */
+ protected function validate_image_url(): bool {
+ $pattern = '/^(https:)([\/|\.|\w|\s|-])*\.(?:jpg|gif|png|jpeg|JPG|GIF|PNG|JPEG)$/';
+ return (bool) preg_match( $pattern, $this->image_url );
+ }
}
diff --git a/modules/ppcp-api-client/src/Entity/Patch.php b/modules/ppcp-api-client/src/Entity/Patch.php
index 4350565cf..8814532ea 100644
--- a/modules/ppcp-api-client/src/Entity/Patch.php
+++ b/modules/ppcp-api-client/src/Entity/Patch.php
@@ -83,8 +83,8 @@ class Patch {
public function to_array(): array {
return array(
'op' => $this->op(),
- 'value' => $this->value(),
'path' => $this->path(),
+ 'value' => $this->value(),
);
}
diff --git a/modules/ppcp-api-client/src/Entity/PaymentMethod.php b/modules/ppcp-api-client/src/Entity/PaymentMethod.php
deleted file mode 100644
index 0362e0093..000000000
--- a/modules/ppcp-api-client/src/Entity/PaymentMethod.php
+++ /dev/null
@@ -1,81 +0,0 @@
-preferred = $preferred;
- $this->selected = $selected;
- }
-
- /**
- * Returns the payer preferred value.
- *
- * @return string
- */
- public function payee_preferred(): string {
- return $this->preferred;
- }
-
- /**
- * Returns the payer selected value.
- *
- * @return string
- */
- public function payer_selected(): string {
- return $this->selected;
- }
-
- /**
- * Returns the object as array.
- *
- * @return array
- */
- public function to_array(): array {
- return array(
- 'payee_preferred' => $this->payee_preferred(),
- 'payer_selected' => $this->payer_selected(),
- );
- }
-}
diff --git a/modules/ppcp-api-client/src/Entity/PaymentPreferences.php b/modules/ppcp-api-client/src/Entity/PaymentPreferences.php
new file mode 100644
index 000000000..b257c2389
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/PaymentPreferences.php
@@ -0,0 +1,115 @@
+setup_fee = $setup_fee;
+ $this->auto_bill_outstanding = $auto_bill_outstanding;
+ $this->setup_fee_failure_action = $setup_fee_failure_action;
+ $this->payment_failure_threshold = $payment_failure_threshold;
+ }
+
+ /**
+ * Setup fee.
+ *
+ * @return array
+ */
+ public function setup_fee(): array {
+ return $this->setup_fee;
+ }
+
+ /**
+ * Auto bill outstanding.
+ *
+ * @return bool
+ */
+ public function auto_bill_outstanding(): bool {
+ return $this->auto_bill_outstanding;
+ }
+
+ /**
+ * Setup fee failure action.
+ *
+ * @return string
+ */
+ public function setup_fee_failure_action(): string {
+ return $this->setup_fee_failure_action;
+ }
+
+ /**
+ * Payment failure threshold.
+ *
+ * @return int
+ */
+ public function payment_failure_threshold(): int {
+ return $this->payment_failure_threshold;
+ }
+
+ /**
+ * Returns Payment Preferences as array.
+ *
+ * @return array
+ */
+ public function to_array():array {
+ return array(
+ 'setup_fee' => $this->setup_fee(),
+ 'auto_bill_outstanding' => $this->auto_bill_outstanding(),
+ 'setup_fee_failure_action' => $this->setup_fee_failure_action(),
+ 'payment_failure_threshold' => $this->payment_failure_threshold(),
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/Payments.php b/modules/ppcp-api-client/src/Entity/Payments.php
index d5e051f88..2dbcf9582 100644
--- a/modules/ppcp-api-client/src/Entity/Payments.php
+++ b/modules/ppcp-api-client/src/Entity/Payments.php
@@ -28,13 +28,21 @@ class Payments {
*/
private $captures;
+ /**
+ * The Refunds.
+ *
+ * @var Refund[]
+ */
+ private $refunds;
+
/**
* Payments constructor.
*
* @param array $authorizations The Authorizations.
* @param array $captures The Captures.
+ * @param array $refunds The Refunds.
*/
- public function __construct( array $authorizations, array $captures ) {
+ public function __construct( array $authorizations, array $captures, array $refunds = array() ) {
foreach ( $authorizations as $key => $authorization ) {
if ( is_a( $authorization, Authorization::class ) ) {
continue;
@@ -47,8 +55,15 @@ class Payments {
}
unset( $captures[ $key ] );
}
+ foreach ( $refunds as $key => $refund ) {
+ if ( is_a( $refund, Refund::class ) ) {
+ continue;
+ }
+ unset( $refunds[ $key ] );
+ }
$this->authorizations = $authorizations;
$this->captures = $captures;
+ $this->refunds = $refunds;
}
/**
@@ -70,6 +85,12 @@ class Payments {
},
$this->captures()
),
+ 'refunds' => array_map(
+ static function ( Refund $refund ): array {
+ return $refund->to_array();
+ },
+ $this->refunds()
+ ),
);
}
@@ -90,4 +111,13 @@ class Payments {
public function captures(): array {
return $this->captures;
}
+
+ /**
+ * Returns the Refunds.
+ *
+ * @return Refund[]
+ **/
+ public function refunds(): array {
+ return $this->refunds;
+ }
}
diff --git a/modules/ppcp-api-client/src/Entity/Plan.php b/modules/ppcp-api-client/src/Entity/Plan.php
new file mode 100644
index 000000000..06747776b
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/Plan.php
@@ -0,0 +1,154 @@
+id = $id;
+ $this->name = $name;
+ $this->product_id = $product_id;
+ $this->billing_cycles = $billing_cycles;
+ $this->payment_preferences = $payment_preferences;
+ $this->status = $status;
+ }
+
+ /**
+ * Returns Plan ID.
+ *
+ * @return string
+ */
+ public function id(): string {
+ return $this->id;
+ }
+
+ /**
+ * Returns Plan name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return $this->name;
+ }
+
+ /**
+ * Returns Product ID.
+ *
+ * @return string
+ */
+ public function product_id(): string {
+ return $this->product_id;
+ }
+
+ /**
+ * Returns Billing cycles.
+ *
+ * @return array
+ */
+ public function billing_cycles(): array {
+ return $this->billing_cycles;
+ }
+
+ /**
+ * Returns Payment preferences.
+ *
+ * @return PaymentPreferences
+ */
+ public function payment_preferences(): PaymentPreferences {
+ return $this->payment_preferences;
+ }
+
+ /**
+ * Returns Plan status.
+ *
+ * @return string
+ */
+ public function status(): string {
+ return $this->status;
+ }
+
+ /**
+ * Returns Plan as array.
+ *
+ * @return array
+ */
+ public function to_array():array {
+ return array(
+ 'id' => $this->id(),
+ 'name' => $this->name(),
+ 'product_id' => $this->product_id(),
+ 'billing_cycles' => $this->billing_cycles(),
+ 'payment_preferences' => $this->payment_preferences(),
+ 'status' => $this->status(),
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/Product.php b/modules/ppcp-api-client/src/Entity/Product.php
new file mode 100644
index 000000000..63acecc80
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/Product.php
@@ -0,0 +1,90 @@
+id = $id;
+ $this->name = $name;
+ $this->description = $description;
+ }
+
+ /**
+ * Returns the product ID.
+ *
+ * @return string
+ */
+ public function id(): string {
+ return $this->id;
+ }
+
+ /**
+ * Returns the product name.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return $this->name;
+ }
+
+ /**
+ * Returns the product description.
+ *
+ * @return string
+ */
+ public function description(): string {
+ return $this->description;
+ }
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array() {
+ return array(
+ 'id' => $this->id(),
+ 'name' => $this->name(),
+ 'description' => $this->description(),
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php
index b2e642fb4..1f0c5572d 100644
--- a/modules/ppcp-api-client/src/Entity/PurchaseUnit.php
+++ b/modules/ppcp-api-client/src/Entity/PurchaseUnit.php
@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
+
/**
* Class PurchaseUnit
*/
@@ -49,13 +51,6 @@ class PurchaseUnit {
*/
private $description;
- /**
- * The Payee.
- *
- * @var Payee|null
- */
- private $payee;
-
/**
* The custom id.
*
@@ -91,6 +86,13 @@ class PurchaseUnit {
*/
private $contains_physical_goods = false;
+ /**
+ * The sanitizer for this purchase unit output.
+ *
+ * @var PurchaseUnitSanitizer|null
+ */
+ private $sanitizer;
+
/**
* PurchaseUnit constructor.
*
@@ -99,7 +101,6 @@ class PurchaseUnit {
* @param Shipping|null $shipping The Shipping.
* @param string $reference_id The reference ID.
* @param string $description The description.
- * @param Payee|null $payee The Payee.
* @param string $custom_id The custom ID.
* @param string $invoice_id The invoice ID.
* @param string $soft_descriptor The soft descriptor.
@@ -111,7 +112,6 @@ class PurchaseUnit {
Shipping $shipping = null,
string $reference_id = 'default',
string $description = '',
- Payee $payee = null,
string $custom_id = '',
string $invoice_id = '',
string $soft_descriptor = '',
@@ -141,7 +141,6 @@ class PurchaseUnit {
}
)
);
- $this->payee = $payee;
$this->custom_id = $custom_id;
$this->invoice_id = $invoice_id;
$this->soft_descriptor = $soft_descriptor;
@@ -211,6 +210,25 @@ class PurchaseUnit {
return $this->custom_id;
}
+ /**
+ * Sets the custom ID.
+ *
+ * @param string $custom_id The value to set.
+ */
+ public function set_custom_id( string $custom_id ): void {
+ $this->custom_id = $custom_id;
+ }
+
+ /**
+ * Sets the sanitizer for this purchase unit output.
+ *
+ * @param PurchaseUnitSanitizer|null $sanitizer The sanitizer.
+ * @return void
+ */
+ public function set_sanitizer( ?PurchaseUnitSanitizer $sanitizer ) {
+ $this->sanitizer = $sanitizer;
+ }
+
/**
* Returns the invoice id.
*
@@ -229,15 +247,6 @@ class PurchaseUnit {
return $this->soft_descriptor;
}
- /**
- * Returns the Payee.
- *
- * @return Payee|null
- */
- public function payee() {
- return $this->payee;
- }
-
/**
* Returns the Payments.
*
@@ -268,11 +277,12 @@ class PurchaseUnit {
/**
* Returns the object as array.
*
- * @param bool $ditch_items_when_mismatch Whether ditch items when mismatch or not.
+ * @param bool $sanitize_output Whether output should be sanitized for PayPal consumption.
+ * @param bool $allow_ditch_items Whether to allow items to be ditched.
*
* @return array
*/
- public function to_array( bool $ditch_items_when_mismatch = true ): array {
+ public function to_array( bool $sanitize_output = true, bool $allow_ditch_items = true ): array {
$purchase_unit = array(
'reference_id' => $this->reference_id(),
'amount' => $this->amount()->to_array(),
@@ -284,14 +294,6 @@ class PurchaseUnit {
$this->items()
),
);
- if ( $ditch_items_when_mismatch && $this->ditch_items_when_mismatch( $this->amount(), ...$this->items() ) ) {
- unset( $purchase_unit['items'] );
- unset( $purchase_unit['amount']['breakdown'] );
- }
-
- if ( $this->payee() ) {
- $purchase_unit['payee'] = $this->payee()->to_array();
- }
if ( $this->payments() ) {
$purchase_unit['payments'] = $this->payments()->to_array();
@@ -309,101 +311,45 @@ class PurchaseUnit {
if ( $this->soft_descriptor() ) {
$purchase_unit['soft_descriptor'] = $this->soft_descriptor();
}
- return $purchase_unit;
+
+ $has_ditched_items_breakdown = false;
+
+ if ( $sanitize_output && isset( $this->sanitizer ) ) {
+ $purchase_unit = ( $this->sanitizer->sanitize( $purchase_unit, $allow_ditch_items ) );
+ $has_ditched_items_breakdown = $this->sanitizer->has_ditched_items_breakdown();
+ }
+
+ return $this->apply_ditch_items_mismatch_filter(
+ $has_ditched_items_breakdown,
+ $purchase_unit
+ );
}
/**
- * All money values send to PayPal can only have 2 decimal points. WooCommerce internally does
- * not have this restriction. Therefore the totals of the cart in WooCommerce and the totals
- * of the rounded money values of the items, we send to PayPal, can differ. In those cases,
- * we can not send the line items.
+ * Applies the ppcp_ditch_items_breakdown filter.
+ * If true purchase_unit items and breakdown are ditched from PayPal.
*
- * @param Amount $amount The amount.
- * @param Item ...$items The items.
- * @return bool
+ * @param bool $ditched_items_breakdown If the breakdown and items were already ditched.
+ * @param array $purchase_unit The purchase_unit array.
+ * @return array
*/
- private function ditch_items_when_mismatch( Amount $amount, Item ...$items ): bool {
- $breakdown = $amount->breakdown();
- if ( ! $breakdown ) {
- return false;
- }
+ public function apply_ditch_items_mismatch_filter( bool $ditched_items_breakdown, array $purchase_unit ): array {
+ /**
+ * The filter can be used to control when the items and totals breakdown are removed from PayPal order info.
+ */
+ $ditch = apply_filters( 'ppcp_ditch_items_breakdown', $ditched_items_breakdown, $this );
- $item_total = $breakdown->item_total();
- if ( $item_total ) {
- $remaining_item_total = array_reduce(
- $items,
- function ( float $total, Item $item ): float {
- return $total - (float) $item->unit_amount()->value_str() * (float) $item->quantity();
- },
- (float) $item_total->value_str()
- );
+ if ( $ditch ) {
+ unset( $purchase_unit['items'] );
+ unset( $purchase_unit['amount']['breakdown'] );
- $remaining_item_total = round( $remaining_item_total, 2 );
-
- if ( 0.0 !== $remaining_item_total ) {
- return true;
+ if ( isset( $this->sanitizer ) && ( $ditch !== $ditched_items_breakdown ) ) {
+ $this->sanitizer->set_last_message(
+ __( 'Ditch items breakdown filter. Items and breakdown ditched.', 'woocommerce-paypal-payments' )
+ );
}
}
- $tax_total = $breakdown->tax_total();
- $items_with_tax = array_filter(
- $this->items,
- function ( Item $item ): bool {
- return null !== $item->tax();
- }
- );
- if ( $tax_total && ! empty( $items_with_tax ) ) {
- $remaining_tax_total = array_reduce(
- $items,
- function ( float $total, Item $item ): float {
- $tax = $item->tax();
- if ( $tax ) {
- $total -= (float) $tax->value_str() * (float) $item->quantity();
- }
- return $total;
- },
- (float) $tax_total->value_str()
- );
-
- $remaining_tax_total = round( $remaining_tax_total, 2 );
-
- if ( 0.0 !== $remaining_tax_total ) {
- return true;
- }
- }
-
- $shipping = $breakdown->shipping();
- $discount = $breakdown->discount();
- $shipping_discount = $breakdown->shipping_discount();
- $handling = $breakdown->handling();
- $insurance = $breakdown->insurance();
-
- $amount_total = 0.0;
- if ( $shipping ) {
- $amount_total += (float) $shipping->value_str();
- }
- if ( $item_total ) {
- $amount_total += (float) $item_total->value_str();
- }
- if ( $discount ) {
- $amount_total -= (float) $discount->value_str();
- }
- if ( $tax_total ) {
- $amount_total += (float) $tax_total->value_str();
- }
- if ( $shipping_discount ) {
- $amount_total -= (float) $shipping_discount->value_str();
- }
- if ( $handling ) {
- $amount_total += (float) $handling->value_str();
- }
- if ( $insurance ) {
- $amount_total += (float) $insurance->value_str();
- }
-
- $amount_str = $amount->value_str();
- $amount_total_str = ( new Money( $amount_total, $amount->currency_code() ) )->value_str();
- $needs_to_ditch = $amount_str !== $amount_total_str;
- return $needs_to_ditch;
+ return $purchase_unit;
}
}
diff --git a/modules/ppcp-api-client/src/Entity/Refund.php b/modules/ppcp-api-client/src/Entity/Refund.php
index 71c2a2f56..eb44fe00a 100644
--- a/modules/ppcp-api-client/src/Entity/Refund.php
+++ b/modules/ppcp-api-client/src/Entity/Refund.php
@@ -1,6 +1,8 @@
capture = $capture;
- $this->invoice_id = $invoice_id;
- $this->note_to_payer = $note_to_payer;
- $this->amount = $amount;
+ $this->id = $id;
+ $this->status = $status;
+ $this->amount = $amount;
+ $this->invoice_id = $invoice_id;
+ $this->custom_id = $custom_id;
+ $this->seller_payable_breakdown = $seller_payable_breakdown;
+ $this->acquirer_reference_number = $acquirer_reference_number;
+ $this->note_to_payer = $note_to_payer;
+ $this->payer = $payer;
}
/**
- * Returns the capture for the refund.
+ * Returns the ID.
*
- * @return Capture
+ * @return string
*/
- public function for_capture() : Capture {
- return $this->capture;
+ public function id() : string {
+ return $this->id;
}
/**
- * Return the invoice id.
+ * Returns the status.
+ *
+ * @return RefundStatus
+ */
+ public function status() : RefundStatus {
+ return $this->status;
+ }
+
+ /**
+ * Returns the amount.
+ *
+ * @return Amount
+ */
+ public function amount() : Amount {
+ return $this->amount;
+ }
+
+ /**
+ * Returns the invoice id.
*
* @return string
*/
@@ -81,7 +151,34 @@ class Refund {
}
/**
- * Returns the note to the payer.
+ * Returns the custom id.
+ *
+ * @return string
+ */
+ public function custom_id() : string {
+ return $this->custom_id;
+ }
+
+ /**
+ * Returns the detailed breakdown of the refund activity (fees, ...).
+ *
+ * @return SellerPayableBreakdown|null
+ */
+ public function seller_payable_breakdown() : ?SellerPayableBreakdown {
+ return $this->seller_payable_breakdown;
+ }
+
+ /**
+ * The acquirer reference number.
+ *
+ * @return string
+ */
+ public function acquirer_reference_number() : string {
+ return $this->acquirer_reference_number;
+ }
+
+ /**
+ * The note to payer.
*
* @return string
*/
@@ -90,28 +187,38 @@ class Refund {
}
/**
- * Returns the Amount.
+ * Returns the refund payer.
*
- * @return Amount|null
+ * @return RefundPayer|null
*/
- public function amount() {
- return $this->amount;
+ public function payer() : ?RefundPayer {
+ return $this->payer;
}
/**
- * Returns the object as array.
+ * Returns the entity as array.
*
* @return array
*/
public function to_array() : array {
- $data = array(
- 'invoice_id' => $this->invoice_id(),
+ $data = array(
+ 'id' => $this->id(),
+ 'status' => $this->status()->name(),
+ 'amount' => $this->amount()->to_array(),
+ 'invoice_id' => $this->invoice_id(),
+ 'custom_id' => $this->custom_id(),
+ 'acquirer_reference_number' => $this->acquirer_reference_number(),
+ 'note_to_payer' => (array) $this->note_to_payer(),
);
- if ( $this->note_to_payer() ) {
- $data['note_to_payer'] = $this->note_to_payer();
+ $details = $this->status()->details();
+ if ( $details ) {
+ $data['status_details'] = array( 'reason' => $details->reason() );
}
- if ( $this->amount() ) {
- $data['amount'] = $this->amount()->to_array();
+ if ( $this->seller_payable_breakdown ) {
+ $data['seller_payable_breakdown'] = $this->seller_payable_breakdown->to_array();
+ }
+ if ( $this->payer ) {
+ $data['payer'] = $this->payer->to_array();
}
return $data;
}
diff --git a/modules/ppcp-api-client/src/Entity/RefundCapture.php b/modules/ppcp-api-client/src/Entity/RefundCapture.php
new file mode 100644
index 000000000..bca9b663f
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/RefundCapture.php
@@ -0,0 +1,118 @@
+capture = $capture;
+ $this->invoice_id = $invoice_id;
+ $this->note_to_payer = $note_to_payer;
+ $this->amount = $amount;
+ }
+
+ /**
+ * Returns the capture for the refund.
+ *
+ * @return Capture
+ */
+ public function for_capture() : Capture {
+ return $this->capture;
+ }
+
+ /**
+ * Return the invoice id.
+ *
+ * @return string
+ */
+ public function invoice_id() : string {
+ return $this->invoice_id;
+ }
+
+ /**
+ * Returns the note to the payer.
+ *
+ * @return string
+ */
+ public function note_to_payer() : string {
+ return $this->note_to_payer;
+ }
+
+ /**
+ * Returns the Amount.
+ *
+ * @return Amount|null
+ */
+ public function amount() {
+ return $this->amount;
+ }
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array() : array {
+ $data = array(
+ 'invoice_id' => $this->invoice_id(),
+ );
+ if ( $this->note_to_payer() ) {
+ $data['note_to_payer'] = $this->note_to_payer();
+ }
+ if ( $this->amount ) {
+ $data['amount'] = $this->amount->to_array();
+ }
+ return $data;
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/RefundPayer.php b/modules/ppcp-api-client/src/Entity/RefundPayer.php
new file mode 100644
index 000000000..febc1fad0
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/RefundPayer.php
@@ -0,0 +1,79 @@
+email_address = $email_address;
+ $this->merchant_id = $merchant_id;
+ }
+
+ /**
+ * Returns the email address.
+ *
+ * @return string
+ */
+ public function email_address(): string {
+ return $this->email_address;
+ }
+
+ /**
+ * Returns the merchant id.
+ *
+ * @return string
+ */
+ public function merchant_id(): string {
+ return $this->merchant_id;
+ }
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array() {
+ $payer = array(
+ 'email_address' => $this->email_address(),
+ );
+ if ( $this->merchant_id ) {
+ $payer['merchant_id'] = $this->merchant_id();
+ }
+ return $payer;
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/RefundStatus.php b/modules/ppcp-api-client/src/Entity/RefundStatus.php
new file mode 100644
index 000000000..0f3abc3b2
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/RefundStatus.php
@@ -0,0 +1,77 @@
+status = $status;
+ $this->details = $details;
+ }
+
+ /**
+ * Compares the current status with a given one.
+ *
+ * @param string $status The status to compare with.
+ *
+ * @return bool
+ */
+ public function is( string $status ): bool {
+ return $this->status === $status;
+ }
+
+ /**
+ * Returns the status.
+ *
+ * @return string
+ */
+ public function name(): string {
+ return $this->status;
+ }
+
+ /**
+ * Returns the details.
+ *
+ * @return RefundStatusDetails|null
+ */
+ public function details(): ?RefundStatusDetails {
+ return $this->details;
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/RefundStatusDetails.php b/modules/ppcp-api-client/src/Entity/RefundStatusDetails.php
new file mode 100644
index 000000000..fac1c4e88
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/RefundStatusDetails.php
@@ -0,0 +1,71 @@
+reason = $reason;
+ }
+
+ /**
+ * Compares the current reason with a given one.
+ *
+ * @param string $reason The reason to compare with.
+ *
+ * @return bool
+ */
+ public function is( string $reason ): bool {
+ return $this->reason === $reason;
+ }
+
+ /**
+ * Returns the reason explaining refund status.
+ * One of RefundStatusDetails constants.
+ *
+ * @return string
+ */
+ public function reason(): string {
+ return $this->reason;
+ }
+
+ /**
+ * Returns the human-readable reason text explaining refund status.
+ *
+ * @return string
+ */
+ public function text(): string {
+ switch ( $this->reason ) {
+ case self::ECHECK:
+ return __( 'The payer paid by an eCheck that has not yet cleared.', 'woocommerce-paypal-payments' );
+ default:
+ return $this->reason;
+ }
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/SellerPayableBreakdown.php b/modules/ppcp-api-client/src/Entity/SellerPayableBreakdown.php
new file mode 100644
index 000000000..9c6bafdde
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/SellerPayableBreakdown.php
@@ -0,0 +1,202 @@
+gross_amount = $gross_amount;
+ $this->paypal_fee = $paypal_fee;
+ $this->paypal_fee_in_receivable_currency = $paypal_fee_in_receivable_currency;
+ $this->net_amount = $net_amount;
+ $this->net_amount_in_receivable_currency = $net_amount_in_receivable_currency;
+ $this->total_refunded_amount = $total_refunded_amount;
+ $this->platform_fees = $platform_fees;
+ }
+
+ /**
+ * The amount for this refunded payment in the currency of the transaction.
+ *
+ * @return Money|null
+ */
+ public function gross_amount(): ?Money {
+ return $this->gross_amount;
+ }
+
+ /**
+ * The applicable fee for this refunded payment in the currency of the transaction.
+ *
+ * @return Money|null
+ */
+ public function paypal_fee(): ?Money {
+ return $this->paypal_fee;
+ }
+
+ /**
+ * The applicable fee for this refunded payment in the receivable currency.
+ *
+ * Present only in cases the fee is charged in the receivable currency.
+ *
+ * @return Money|null
+ */
+ public function paypal_fee_in_receivable_currency(): ?Money {
+ return $this->paypal_fee_in_receivable_currency;
+ }
+
+ /**
+ * The net amount that the payee receives for this refunded payment in their PayPal account.
+ *
+ * Computed as gross_amount minus the paypal_fee minus the platform_fees.
+ *
+ * @return Money|null
+ */
+ public function net_amount(): ?Money {
+ return $this->net_amount;
+ }
+
+ /**
+ * The net amount for this refunded payment in the receivable currency.
+ *
+ * @return Money|null
+ */
+ public function net_amount_in_receivable_currency(): ?Money {
+ return $this->net_amount_in_receivable_currency;
+ }
+
+ /**
+ * The total amount for this refund.
+ *
+ * @return Money|null
+ */
+ public function total_refunded_amount(): ?Money {
+ return $this->total_refunded_amount;
+ }
+
+ /**
+ * An array of platform or partner fees, commissions, or brokerage fees that associated with the refunded payment.
+ *
+ * @return PlatformFee[]
+ */
+ public function platform_fees(): array {
+ return $this->platform_fees;
+ }
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array(): array {
+ $data = array();
+ if ( $this->gross_amount ) {
+ $data['gross_amount'] = $this->gross_amount->to_array();
+ }
+ if ( $this->paypal_fee ) {
+ $data['paypal_fee'] = $this->paypal_fee->to_array();
+ }
+ if ( $this->paypal_fee_in_receivable_currency ) {
+ $data['paypal_fee_in_receivable_currency'] = $this->paypal_fee_in_receivable_currency->to_array();
+ }
+ if ( $this->net_amount ) {
+ $data['net_amount'] = $this->net_amount->to_array();
+ }
+ if ( $this->net_amount_in_receivable_currency ) {
+ $data['net_amount_in_receivable_currency'] = $this->net_amount_in_receivable_currency->to_array();
+ }
+ if ( $this->total_refunded_amount ) {
+ $data['total_refunded_amount'] = $this->total_refunded_amount->to_array();
+ }
+ if ( $this->platform_fees ) {
+ $data['platform_fees'] = array_map(
+ function ( PlatformFee $fee ) {
+ return $fee->to_array();
+ },
+ $this->platform_fees
+ );
+ }
+
+ return $data;
+ }
+}
diff --git a/modules/ppcp-api-client/src/Entity/Shipping.php b/modules/ppcp-api-client/src/Entity/Shipping.php
index 244d876f3..eeb4001dc 100644
--- a/modules/ppcp-api-client/src/Entity/Shipping.php
+++ b/modules/ppcp-api-client/src/Entity/Shipping.php
@@ -28,15 +28,24 @@ class Shipping {
*/
private $address;
+ /**
+ * Shipping methods.
+ *
+ * @var ShippingOption[]
+ */
+ private $options;
+
/**
* Shipping constructor.
*
- * @param string $name The name.
- * @param Address $address The address.
+ * @param string $name The name.
+ * @param Address $address The address.
+ * @param ShippingOption[] $options Shipping methods.
*/
- public function __construct( string $name, Address $address ) {
+ public function __construct( string $name, Address $address, array $options = array() ) {
$this->name = $name;
$this->address = $address;
+ $this->options = $options;
}
/**
@@ -57,17 +66,35 @@ class Shipping {
return $this->address;
}
+ /**
+ * Returns the shipping methods.
+ *
+ * @return ShippingOption[]
+ */
+ public function options(): array {
+ return $this->options;
+ }
+
/**
* Returns the object as array.
*
* @return array
*/
public function to_array(): array {
- return array(
+ $result = array(
'name' => array(
'full_name' => $this->name(),
),
'address' => $this->address()->to_array(),
);
+ if ( $this->options ) {
+ $result['options'] = array_map(
+ function ( ShippingOption $opt ): array {
+ return $opt->to_array();
+ },
+ $this->options
+ );
+ }
+ return $result;
}
}
diff --git a/modules/ppcp-api-client/src/Entity/ShippingOption.php b/modules/ppcp-api-client/src/Entity/ShippingOption.php
new file mode 100644
index 000000000..40a7b5ef7
--- /dev/null
+++ b/modules/ppcp-api-client/src/Entity/ShippingOption.php
@@ -0,0 +1,139 @@
+id = $id;
+ $this->label = $label;
+ $this->selected = $selected;
+ $this->amount = $amount;
+ $this->type = $type;
+ }
+
+ /**
+ * The name.
+ *
+ * @return string
+ */
+ public function id(): string {
+ return $this->id;
+ }
+
+ /**
+ * The label.
+ *
+ * @return string
+ */
+ public function label(): string {
+ return $this->label;
+ }
+
+ /**
+ * Whether the method is selected by default.
+ *
+ * @return bool
+ */
+ public function selected(): bool {
+ return $this->selected;
+ }
+
+ /**
+ * Sets whether the method is selected by default.
+ *
+ * @param bool $selected The value to be set.
+ */
+ public function set_selected( bool $selected ): void {
+ $this->selected = $selected;
+ }
+
+ /**
+ * The price.
+ *
+ * @return Money
+ */
+ public function amount(): Money {
+ return $this->amount;
+ }
+
+ /**
+ * SHIPPING or PICKUP.
+ *
+ * @return string
+ */
+ public function type(): string {
+ return $this->type;
+ }
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array
+ */
+ public function to_array(): array {
+ return array(
+ 'id' => $this->id,
+ 'label' => $this->label,
+ 'selected' => $this->selected,
+ 'amount' => $this->amount->to_array(),
+ 'type' => $this->type,
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/BillingCycleFactory.php b/modules/ppcp-api-client/src/Factory/BillingCycleFactory.php
new file mode 100644
index 000000000..6b00ff0ac
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/BillingCycleFactory.php
@@ -0,0 +1,84 @@
+currency = $currency;
+ }
+
+ /**
+ * Returns a BillingCycle object from the given WC product.
+ *
+ * @param WC_Product $product WC product.
+ * @return BillingCycle
+ */
+ public function from_wc_product( WC_Product $product ): BillingCycle {
+ return new BillingCycle(
+ array(
+ 'interval_unit' => $product->get_meta( '_subscription_period' ),
+ 'interval_count' => $product->get_meta( '_subscription_period_interval' ),
+ ),
+ 1,
+ 'REGULAR',
+ array(
+ 'fixed_price' => array(
+ 'value' => $product->get_meta( '_subscription_price' ),
+ 'currency_code' => $this->currency,
+ ),
+ ),
+ (int) $product->get_meta( '_subscription_length' )
+ );
+ }
+
+ /**
+ * Returns a BillingCycle object based off a PayPal response.
+ *
+ * @param stdClass $data the data.
+ * @return BillingCycle
+ */
+ public function from_paypal_response( stdClass $data ): BillingCycle {
+ return new BillingCycle(
+ array(
+ 'interval_unit' => $data->frequency->interval_unit,
+ 'interval_count' => $data->frequency->interval_count,
+ ),
+ $data->sequence,
+ $data->tenure_type,
+ array(
+ 'fixed_price' => array(
+ 'value' => $data->pricing_scheme->fixed_price->value,
+ 'currency_code' => $data->pricing_scheme->fixed_price->currency_code,
+ ),
+ ),
+ $data->total_cycles
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/ItemFactory.php b/modules/ppcp-api-client/src/Factory/ItemFactory.php
index c71e7ddfa..ff44e6014 100644
--- a/modules/ppcp-api-client/src/Factory/ItemFactory.php
+++ b/modules/ppcp-api-client/src/Factory/ItemFactory.php
@@ -13,11 +13,15 @@ use WC_Product;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\ItemTrait;
/**
* Class ItemFactory
*/
class ItemFactory {
+
+ use ItemTrait;
+
/**
* 3-letter currency code of the shop.
*
@@ -44,7 +48,8 @@ class ItemFactory {
public function from_wc_cart( \WC_Cart $cart ): array {
$items = array_map(
function ( array $item ): Item {
- $product = $item['data'];
+ $product = $item['data'];
+ $cart_item_key = $item['key'] ?? null;
/**
* The WooCommerce product.
@@ -52,16 +57,21 @@ class ItemFactory {
* @var \WC_Product $product
*/
$quantity = (int) $item['quantity'];
+ $image = wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' );
$price = (float) $item['line_subtotal'] / (float) $item['quantity'];
return new Item(
mb_substr( $product->get_name(), 0, 127 ),
new Money( $price, $this->currency ),
$quantity,
- substr( wp_strip_all_tags( $product->get_description() ), 0, 127 ) ?: '',
+ $this->prepare_description( $product->get_description() ),
null,
$product->get_sku(),
- ( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
+ ( $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
+ $product->get_permalink(),
+ $image[0] ?? '',
+ 0,
+ $cart_item_key
);
},
$cart->get_cart_contents()
@@ -125,15 +135,18 @@ class ItemFactory {
$quantity = (int) $item->get_quantity();
$price_without_tax = (float) $order->get_item_subtotal( $item, false );
$price_without_tax_rounded = round( $price_without_tax, 2 );
+ $image = $product instanceof WC_Product ? wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' ) : '';
return new Item(
mb_substr( $item->get_name(), 0, 127 ),
new Money( $price_without_tax_rounded, $currency ),
$quantity,
- substr( wp_strip_all_tags( $product instanceof WC_Product ? $product->get_description() : '' ), 0, 127 ) ?: '',
+ $product instanceof WC_Product ? $this->prepare_description( $product->get_description() ) : '',
null,
$product instanceof WC_Product ? $product->get_sku() : '',
- ( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS
+ ( $product instanceof WC_Product && $product->is_virtual() ) ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
+ $product instanceof WC_Product ? $product->get_permalink() : '',
+ $image[0] ?? ''
);
}
@@ -187,6 +200,8 @@ class ItemFactory {
: null;
$sku = ( isset( $data->sku ) ) ? $data->sku : '';
$category = ( isset( $data->category ) ) ? $data->category : 'PHYSICAL_GOODS';
+ $url = ( isset( $data->url ) ) ? $data->url : '';
+ $image_url = ( isset( $data->image_url ) ) ? $data->image_url : '';
return new Item(
$data->name,
@@ -195,7 +210,9 @@ class ItemFactory {
$description,
$tax,
$sku,
- $category
+ $category,
+ $url,
+ $image_url
);
}
}
diff --git a/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php b/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php
index 3be240393..d6ae8b7fc 100644
--- a/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php
+++ b/modules/ppcp-api-client/src/Factory/PatchCollectionFactory.php
@@ -71,7 +71,15 @@ class PatchCollectionFactory {
);
$operation = $purchase_unit_from ? 'replace' : 'add';
$value = $purchase_unit_to->to_array();
- $patches[] = new Patch(
+
+ if ( ! isset( $value['shipping'] ) ) {
+ $shipping = $purchase_unit_from && null !== $purchase_unit_from->shipping() ? $purchase_unit_from->shipping() : null;
+ if ( $shipping ) {
+ $value['shipping'] = $shipping->to_array();
+ }
+ }
+
+ $patches[] = new Patch(
$operation,
$path . "/@reference_id=='" . $purchase_unit_to->reference_id() . "'",
$value
diff --git a/modules/ppcp-api-client/src/Factory/PaymentPreferencesFactory.php b/modules/ppcp-api-client/src/Factory/PaymentPreferencesFactory.php
new file mode 100644
index 000000000..44c0d0cef
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/PaymentPreferencesFactory.php
@@ -0,0 +1,69 @@
+currency = $currency;
+ }
+
+ /**
+ * Returns a PaymentPreferences object from the given WC product.
+ *
+ * @param WC_Product $product WC product.
+ * @return PaymentPreferences
+ */
+ public function from_wc_product( WC_Product $product ):PaymentPreferences {
+ return new PaymentPreferences(
+ array(
+ 'value' => $product->get_meta( '_subscription_sign_up_fee' ) ?: '0',
+ 'currency_code' => $this->currency,
+ )
+ );
+ }
+
+ /**
+ * Returns a PaymentPreferences object based off a PayPal response.
+ *
+ * @param stdClass $data The data.
+ * @return PaymentPreferences
+ */
+ public function from_paypal_response( stdClass $data ) {
+ return new PaymentPreferences(
+ array(
+ 'value' => $data->setup_fee->value,
+ 'currency_code' => $data->setup_fee->currency_code,
+ ),
+ $data->auto_bill_outstanding,
+ $data->setup_fee_failure_action,
+ $data->payment_failure_threshold
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/PaymentsFactory.php b/modules/ppcp-api-client/src/Factory/PaymentsFactory.php
index 81f99e53f..6f2731cf9 100644
--- a/modules/ppcp-api-client/src/Factory/PaymentsFactory.php
+++ b/modules/ppcp-api-client/src/Factory/PaymentsFactory.php
@@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
/**
@@ -32,19 +33,29 @@ class PaymentsFactory {
*/
private $capture_factory;
+ /**
+ * The Refund factory.
+ *
+ * @var RefundFactory
+ */
+ private $refund_factory;
+
/**
* PaymentsFactory constructor.
*
* @param AuthorizationFactory $authorization_factory The Authorization factory.
* @param CaptureFactory $capture_factory The Capture factory.
+ * @param RefundFactory $refund_factory The Refund factory.
*/
public function __construct(
AuthorizationFactory $authorization_factory,
- CaptureFactory $capture_factory
+ CaptureFactory $capture_factory,
+ RefundFactory $refund_factory
) {
$this->authorization_factory = $authorization_factory;
$this->capture_factory = $capture_factory;
+ $this->refund_factory = $refund_factory;
}
/**
@@ -62,12 +73,18 @@ class PaymentsFactory {
isset( $data->authorizations ) ? $data->authorizations : array()
);
$captures = array_map(
- function ( \stdClass $authorization ): Capture {
- return $this->capture_factory->from_paypal_response( $authorization );
+ function ( \stdClass $capture ): Capture {
+ return $this->capture_factory->from_paypal_response( $capture );
},
isset( $data->captures ) ? $data->captures : array()
);
- $payments = new Payments( $authorizations, $captures );
+ $refunds = array_map(
+ function ( \stdClass $refund ): Refund {
+ return $this->refund_factory->from_paypal_response( $refund );
+ },
+ isset( $data->refunds ) ? $data->refunds : array()
+ );
+ $payments = new Payments( $authorizations, $captures, $refunds );
return $payments;
}
}
diff --git a/modules/ppcp-api-client/src/Factory/PlanFactory.php b/modules/ppcp-api-client/src/Factory/PlanFactory.php
new file mode 100644
index 000000000..bbf332d10
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/PlanFactory.php
@@ -0,0 +1,96 @@
+billing_cycle_factory = $billing_cycle_factory;
+ $this->payment_preferences_factory = $payment_preferences_factory;
+ }
+
+ /**
+ * Returns a Plan from PayPal response.
+ *
+ * @param stdClass $data The data.
+ *
+ * @return Plan
+ *
+ * @throws RuntimeException If it could not create Plan.
+ */
+ public function from_paypal_response( stdClass $data ): Plan {
+ if ( ! isset( $data->id ) ) {
+ throw new RuntimeException(
+ __( 'No id for given plan', 'woocommerce-paypal-payments' )
+ );
+ }
+ if ( ! isset( $data->name ) ) {
+ throw new RuntimeException(
+ __( 'No name for plan given', 'woocommerce-paypal-payments' )
+ );
+ }
+ if ( ! isset( $data->product_id ) ) {
+ throw new RuntimeException(
+ __( 'No product id for given plan', 'woocommerce-paypal-payments' )
+ );
+ }
+ if ( ! isset( $data->billing_cycles ) ) {
+ throw new RuntimeException(
+ __( 'No billing cycles for given plan', 'woocommerce-paypal-payments' )
+ );
+ }
+
+ $billing_cycles = array();
+ foreach ( $data->billing_cycles as $billing_cycle ) {
+ $billing_cycles[] = $this->billing_cycle_factory->from_paypal_response( $billing_cycle );
+ }
+
+ $payment_preferences = $this->payment_preferences_factory->from_paypal_response( $data->payment_preferences );
+
+ return new Plan(
+ $data->id,
+ $data->name,
+ $data->product_id,
+ $billing_cycles,
+ $payment_preferences,
+ $data->status ?? ''
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/ProductFactory.php b/modules/ppcp-api-client/src/Factory/ProductFactory.php
new file mode 100644
index 000000000..a34602a21
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/ProductFactory.php
@@ -0,0 +1,47 @@
+id ) ) {
+ throw new RuntimeException(
+ __( 'No id for product given', 'woocommerce-paypal-payments' )
+ );
+ }
+ if ( ! isset( $data->name ) ) {
+ throw new RuntimeException(
+ __( 'No name for product given', 'woocommerce-paypal-payments' )
+ );
+ }
+
+ return new Product(
+ $data->id,
+ $data->name,
+ $data->description ?? ''
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php
index c2667fc92..7642401b1 100644
--- a/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php
+++ b/modules/ppcp-api-client/src/Factory/PurchaseUnitFactory.php
@@ -9,10 +9,12 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
+use WC_Session_Handler;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
+use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
/**
* Class PurchaseUnitFactory
@@ -26,20 +28,6 @@ class PurchaseUnitFactory {
*/
private $amount_factory;
- /**
- * The payee repository.
- *
- * @var PayeeRepository
- */
- private $payee_repository;
-
- /**
- * The payee factory.
- *
- * @var PayeeFactory
- */
- private $payee_factory;
-
/**
* The item factory.
*
@@ -68,34 +56,48 @@ class PurchaseUnitFactory {
*/
private $prefix;
+ /**
+ * The Soft Descriptor.
+ *
+ * @var string
+ */
+ private $soft_descriptor;
+
+ /**
+ * The sanitizer for purchase unit output data.
+ *
+ * @var PurchaseUnitSanitizer|null
+ */
+ private $sanitizer;
+
/**
* PurchaseUnitFactory constructor.
*
- * @param AmountFactory $amount_factory The amount factory.
- * @param PayeeRepository $payee_repository The Payee repository.
- * @param PayeeFactory $payee_factory The Payee factory.
- * @param ItemFactory $item_factory The item factory.
- * @param ShippingFactory $shipping_factory The shipping factory.
- * @param PaymentsFactory $payments_factory The payments factory.
- * @param string $prefix The prefix.
+ * @param AmountFactory $amount_factory The amount factory.
+ * @param ItemFactory $item_factory The item factory.
+ * @param ShippingFactory $shipping_factory The shipping factory.
+ * @param PaymentsFactory $payments_factory The payments factory.
+ * @param string $prefix The prefix.
+ * @param string $soft_descriptor The soft descriptor.
+ * @param ?PurchaseUnitSanitizer $sanitizer The purchase unit to_array sanitizer.
*/
public function __construct(
AmountFactory $amount_factory,
- PayeeRepository $payee_repository,
- PayeeFactory $payee_factory,
ItemFactory $item_factory,
ShippingFactory $shipping_factory,
PaymentsFactory $payments_factory,
- string $prefix = 'WC-'
+ string $prefix = 'WC-',
+ string $soft_descriptor = '',
+ PurchaseUnitSanitizer $sanitizer = null
) {
$this->amount_factory = $amount_factory;
- $this->payee_repository = $payee_repository;
- $this->payee_factory = $payee_factory;
$this->item_factory = $item_factory;
$this->shipping_factory = $shipping_factory;
$this->payments_factory = $payments_factory;
$this->prefix = $prefix;
+ $this->soft_descriptor = $soft_descriptor;
+ $this->sanitizer = $sanitizer;
}
/**
@@ -110,7 +112,7 @@ class PurchaseUnitFactory {
$items = array_filter(
$this->item_factory->from_wc_order( $order ),
function ( Item $item ): bool {
- return $item->unit_amount()->value() > 0;
+ return $item->unit_amount()->value() >= 0;
}
);
$shipping = $this->shipping_factory->from_wc_order( $order );
@@ -123,10 +125,9 @@ class PurchaseUnitFactory {
}
$reference_id = 'default';
$description = '';
- $payee = $this->payee_repository->payee();
$custom_id = (string) $order->get_id();
$invoice_id = $this->prefix . $order->get_order_number();
- $soft_descriptor = '';
+ $soft_descriptor = $this->soft_descriptor;
$purchase_unit = new PurchaseUnit(
$amount,
@@ -134,11 +135,13 @@ class PurchaseUnitFactory {
$shipping,
$reference_id,
$description,
- $payee,
$custom_id,
$invoice_id,
$soft_descriptor
);
+
+ $this->init_purchase_unit( $purchase_unit );
+
/**
* Returns PurchaseUnit for the WC order.
*/
@@ -153,10 +156,11 @@ class PurchaseUnitFactory {
* Creates a PurchaseUnit based off a WooCommerce cart.
*
* @param \WC_Cart|null $cart The cart.
+ * @param bool $with_shipping_options Include WC shipping methods.
*
* @return PurchaseUnit
*/
- public function from_wc_cart( ?\WC_Cart $cart = null ): PurchaseUnit {
+ public function from_wc_cart( ?\WC_Cart $cart = null, bool $with_shipping_options = false ): PurchaseUnit {
if ( ! $cart ) {
$cart = WC()->cart ?? new \WC_Cart();
}
@@ -165,14 +169,14 @@ class PurchaseUnitFactory {
$items = array_filter(
$this->item_factory->from_wc_cart( $cart ),
function ( Item $item ): bool {
- return $item->unit_amount()->value() > 0;
+ return $item->unit_amount()->value() >= 0;
}
);
$shipping = null;
$customer = \WC()->customer;
if ( $this->shipping_needed( ... array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) {
- $shipping = $this->shipping_factory->from_wc_customer( \WC()->customer );
+ $shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options );
if (
2 !== strlen( $shipping->address()->country_code() ) ||
( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) )
@@ -184,23 +188,29 @@ class PurchaseUnitFactory {
$reference_id = 'default';
$description = '';
- $payee = $this->payee_repository->payee();
-
- $custom_id = '';
+ $custom_id = '';
+ $session = WC()->session;
+ if ( $session instanceof WC_Session_Handler ) {
+ $session_id = $session->get_customer_unique_id();
+ if ( $session_id ) {
+ $custom_id = CustomIds::CUSTOMER_ID_PREFIX . $session_id;
+ }
+ }
$invoice_id = '';
- $soft_descriptor = '';
+ $soft_descriptor = $this->soft_descriptor;
$purchase_unit = new PurchaseUnit(
$amount,
$items,
$shipping,
$reference_id,
$description,
- $payee,
$custom_id,
$invoice_id,
$soft_descriptor
);
+ $this->init_purchase_unit( $purchase_unit );
+
return $purchase_unit;
}
@@ -223,7 +233,7 @@ class PurchaseUnitFactory {
$description = ( isset( $data->description ) ) ? $data->description : '';
$custom_id = ( isset( $data->custom_id ) ) ? $data->custom_id : '';
$invoice_id = ( isset( $data->invoice_id ) ) ? $data->invoice_id : '';
- $soft_descriptor = ( isset( $data->soft_descriptor ) ) ? $data->soft_descriptor : '';
+ $soft_descriptor = ( isset( $data->soft_descriptor ) ) ? $data->soft_descriptor : $this->soft_descriptor;
$items = array();
if ( isset( $data->items ) && is_array( $data->items ) ) {
$items = array_map(
@@ -233,7 +243,6 @@ class PurchaseUnitFactory {
$data->items
);
}
- $payee = isset( $data->payee ) ? $this->payee_factory->from_paypal_response( $data->payee ) : null;
$shipping = null;
try {
if ( isset( $data->shipping ) ) {
@@ -257,12 +266,14 @@ class PurchaseUnitFactory {
$shipping,
$data->reference_id,
$description,
- $payee,
$custom_id,
$invoice_id,
$soft_descriptor,
$payments
);
+
+ $this->init_purchase_unit( $purchase_unit );
+
return $purchase_unit;
}
@@ -293,4 +304,16 @@ class PurchaseUnitFactory {
$countries = array( 'AE', 'AF', 'AG', 'AI', 'AL', 'AN', 'AO', 'AW', 'BB', 'BF', 'BH', 'BI', 'BJ', 'BM', 'BO', 'BS', 'BT', 'BW', 'BZ', 'CD', 'CF', 'CG', 'CI', 'CK', 'CL', 'CM', 'CO', 'CR', 'CV', 'DJ', 'DM', 'DO', 'EC', 'EG', 'ER', 'ET', 'FJ', 'FK', 'GA', 'GD', 'GH', 'GI', 'GM', 'GN', 'GQ', 'GT', 'GW', 'GY', 'HK', 'HN', 'HT', 'IE', 'IQ', 'IR', 'JM', 'JO', 'KE', 'KH', 'KI', 'KM', 'KN', 'KP', 'KW', 'KY', 'LA', 'LB', 'LC', 'LK', 'LR', 'LS', 'LY', 'ML', 'MM', 'MO', 'MR', 'MS', 'MT', 'MU', 'MW', 'MZ', 'NA', 'NE', 'NG', 'NI', 'NP', 'NR', 'NU', 'OM', 'PA', 'PE', 'PF', 'PY', 'QA', 'RW', 'SA', 'SB', 'SC', 'SD', 'SL', 'SN', 'SO', 'SR', 'SS', 'ST', 'SV', 'SY', 'TC', 'TD', 'TG', 'TL', 'TO', 'TT', 'TV', 'TZ', 'UG', 'UY', 'VC', 'VE', 'VG', 'VN', 'VU', 'WS', 'XA', 'XB', 'XC', 'XE', 'XL', 'XM', 'XN', 'XS', 'YE', 'ZM', 'ZW' );
return in_array( $country_code, $countries, true );
}
+
+ /**
+ * Initializes a purchase unit object.
+ *
+ * @param PurchaseUnit $purchase_unit The purchase unit.
+ * @return void
+ */
+ private function init_purchase_unit( PurchaseUnit $purchase_unit ): void {
+ if ( $this->sanitizer instanceof PurchaseUnitSanitizer ) {
+ $purchase_unit->set_sanitizer( $this->sanitizer );
+ }
+ }
}
diff --git a/modules/ppcp-api-client/src/Factory/RefundFactory.php b/modules/ppcp-api-client/src/Factory/RefundFactory.php
new file mode 100644
index 000000000..63530feaf
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/RefundFactory.php
@@ -0,0 +1,91 @@
+amount_factory = $amount_factory;
+ $this->seller_payable_breakdown_factory = $seller_payable_breakdown_factory;
+ $this->refund_payer_factory = $refund_payer_factory;
+ }
+
+ /**
+ * Returns the refund object based off the PayPal response.
+ *
+ * @param \stdClass $data The PayPal response.
+ *
+ * @return Refund
+ */
+ public function from_paypal_response( \stdClass $data ) : Refund {
+ $reason = $data->status_details->reason ?? null;
+ $seller_payable_breakdown = isset( $data->seller_payable_breakdown ) ?
+ $this->seller_payable_breakdown_factory->from_paypal_response( $data->seller_payable_breakdown )
+ : null;
+
+ $payer = isset( $data->payer ) ?
+ $this->refund_payer_factory->from_paypal_response( $data->payer )
+ : null;
+
+ return new Refund(
+ (string) $data->id,
+ new RefundStatus(
+ (string) $data->status,
+ $reason ? new RefundStatusDetails( $reason ) : null
+ ),
+ $this->amount_factory->from_paypal_response( $data->amount ),
+ (string) ( $data->invoice_id ?? '' ),
+ (string) ( $data->custom_id ?? '' ),
+ $seller_payable_breakdown,
+ (string) ( $data->acquirer_reference_number ?? '' ),
+ (string) ( $data->note_to_payer ?? '' ),
+ $payer
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/RefundPayerFactory.php b/modules/ppcp-api-client/src/Factory/RefundPayerFactory.php
new file mode 100644
index 000000000..a24251bed
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/RefundPayerFactory.php
@@ -0,0 +1,39 @@
+email_address ) ? $data->email_address : '',
+ isset( $data->merchant_id ) ? $data->merchant_id : ''
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/SellerPayableBreakdownFactory.php b/modules/ppcp-api-client/src/Factory/SellerPayableBreakdownFactory.php
new file mode 100644
index 000000000..8c10d0427
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/SellerPayableBreakdownFactory.php
@@ -0,0 +1,81 @@
+money_factory = $money_factory;
+ $this->platform_fee_factory = $platform_fee_factory;
+ }
+
+ /**
+ * Returns a SellerPayableBreakdownFactory object based off a PayPal Response.
+ *
+ * @param stdClass $data The JSON object.
+ *
+ * @return SellerPayableBreakdown
+ */
+ public function from_paypal_response( stdClass $data ): SellerPayableBreakdown {
+
+ $gross_amount = ( isset( $data->gross_amount ) ) ? $this->money_factory->from_paypal_response( $data->gross_amount ) : null;
+ $paypal_fee = ( isset( $data->paypal_fee ) ) ? $this->money_factory->from_paypal_response( $data->paypal_fee ) : null;
+ $paypal_fee_in_receivable_currency = ( isset( $data->paypal_fee_in_receivable_currency ) ) ? $this->money_factory->from_paypal_response( $data->paypal_fee_in_receivable_currency ) : null;
+ $net_amount = ( isset( $data->net_amount ) ) ? $this->money_factory->from_paypal_response( $data->net_amount ) : null;
+ $net_amount_in_receivable_currency = ( isset( $data->net_amount_in_receivable_currency ) ) ? $this->money_factory->from_paypal_response( $data->net_amount_in_receivable_currency ) : null;
+ $total_refunded_amount = ( isset( $data->total_refunded_amount ) ) ? $this->money_factory->from_paypal_response( $data->total_refunded_amount ) : null;
+ $platform_fees = ( isset( $data->platform_fees ) ) ? array_map(
+ function ( stdClass $fee_data ): PlatformFee {
+ return $this->platform_fee_factory->from_paypal_response( $fee_data );
+ },
+ $data->platform_fees
+ ) : array();
+
+ return new SellerPayableBreakdown(
+ $gross_amount,
+ $paypal_fee,
+ $paypal_fee_in_receivable_currency,
+ $net_amount,
+ $net_amount_in_receivable_currency,
+ $total_refunded_amount,
+ $platform_fees
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Factory/ShippingFactory.php b/modules/ppcp-api-client/src/Factory/ShippingFactory.php
index dcebb9b9f..b85259394 100644
--- a/modules/ppcp-api-client/src/Factory/ShippingFactory.php
+++ b/modules/ppcp-api-client/src/Factory/ShippingFactory.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
@@ -24,23 +25,33 @@ class ShippingFactory {
*/
private $address_factory;
+ /**
+ * The shipping option factory.
+ *
+ * @var ShippingOptionFactory
+ */
+ private $shipping_option_factory;
+
/**
* ShippingFactory constructor.
*
- * @param AddressFactory $address_factory The address factory.
+ * @param AddressFactory $address_factory The address factory.
+ * @param ShippingOptionFactory $shipping_option_factory The shipping option factory.
*/
- public function __construct( AddressFactory $address_factory ) {
- $this->address_factory = $address_factory;
+ public function __construct( AddressFactory $address_factory, ShippingOptionFactory $shipping_option_factory ) {
+ $this->address_factory = $address_factory;
+ $this->shipping_option_factory = $shipping_option_factory;
}
/**
* Creates a shipping object based off a WooCommerce customer.
*
* @param \WC_Customer $customer The WooCommerce customer.
+ * @param bool $with_shipping_options Include WC shipping methods.
*
* @return Shipping
*/
- public function from_wc_customer( \WC_Customer $customer ): Shipping {
+ public function from_wc_customer( \WC_Customer $customer, bool $with_shipping_options = false ): Shipping {
// Replicates the Behavior of \WC_Order::get_formatted_shipping_full_name().
$full_name = sprintf(
// translators: %1$s is the first name and %2$s is the second name. wc translation.
@@ -51,7 +62,8 @@ class ShippingFactory {
$address = $this->address_factory->from_wc_customer( $customer );
return new Shipping(
$full_name,
- $address
+ $address,
+ $with_shipping_options ? $this->shipping_option_factory->from_wc_cart() : array()
);
}
@@ -91,9 +103,14 @@ class ShippingFactory {
);
}
$address = $this->address_factory->from_paypal_response( $data->address );
+ $options = array_map(
+ array( $this->shipping_option_factory, 'from_paypal_response' ),
+ $data->options ?? array()
+ );
return new Shipping(
$data->name->full_name,
- $address
+ $address,
+ $options
);
}
}
diff --git a/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php b/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php
new file mode 100644
index 000000000..08af8c79e
--- /dev/null
+++ b/modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php
@@ -0,0 +1,111 @@
+money_factory = $money_factory;
+ }
+
+ /**
+ * Creates an array of ShippingOption objects for the shipping methods available in the cart.
+ *
+ * @param WC_Cart|null $cart The cart.
+ * @return ShippingOption[]
+ */
+ public function from_wc_cart( ?WC_Cart $cart = null ): array {
+ if ( ! $cart ) {
+ $cart = WC()->cart ?? new WC_Cart();
+ }
+
+ $cart->calculate_shipping();
+
+ $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
+ if ( ! is_array( $chosen_shipping_methods ) ) {
+ $chosen_shipping_methods = array();
+ }
+
+ $packages = WC()->shipping()->get_packages();
+ $options = array();
+ foreach ( $packages as $package ) {
+ $rates = $package['rates'] ?? array();
+ foreach ( $rates as $rate ) {
+ if ( ! $rate instanceof \WC_Shipping_Rate ) {
+ continue;
+ }
+
+ $options[] = new ShippingOption(
+ $rate->get_id(),
+ $rate->get_label(),
+ in_array( $rate->get_id(), $chosen_shipping_methods, true ),
+ new Money(
+ (float) $rate->get_cost(),
+ get_woocommerce_currency()
+ ),
+ ShippingOption::TYPE_SHIPPING
+ );
+ }
+ }
+
+ if ( ! $chosen_shipping_methods && $options ) {
+ $options[0]->set_selected( true );
+ }
+
+ return $options;
+ }
+
+ /**
+ * Creates a ShippingOption object from the PayPal JSON object.
+ *
+ * @param stdClass $data The JSON object.
+ *
+ * @return ShippingOption
+ * @throws RuntimeException When JSON object is malformed.
+ */
+ public function from_paypal_response( stdClass $data ): ShippingOption {
+ if ( ! isset( $data->id ) ) {
+ throw new RuntimeException( 'No id was given for shipping option.' );
+ }
+ if ( ! isset( $data->amount ) ) {
+ throw new RuntimeException( 'Shipping option amount not found' );
+ }
+
+ $amount = $this->money_factory->from_paypal_response( $data->amount );
+ return new ShippingOption(
+ $data->id,
+ $data->label ?? '',
+ isset( $data->selected ) ? (bool) $data->selected : false,
+ $amount,
+ $data->type ?? ShippingOption::TYPE_SHIPPING
+ );
+ }
+}
diff --git a/modules/ppcp-api-client/src/Helper/FailureRegistry.php b/modules/ppcp-api-client/src/Helper/FailureRegistry.php
new file mode 100644
index 000000000..68889b13c
--- /dev/null
+++ b/modules/ppcp-api-client/src/Helper/FailureRegistry.php
@@ -0,0 +1,94 @@
+cache = $cache;
+ }
+
+ /**
+ * Returns if there was a failure within a given timeframe.
+ *
+ * @param string $key The cache key.
+ * @param int $seconds The timeframe in seconds.
+ * @return bool
+ */
+ public function has_failure_in_timeframe( string $key, int $seconds ): bool {
+ $cache_key = $this->cache_key( $key );
+ $failure_time = $this->cache->get( $cache_key );
+
+ if ( ! $failure_time ) {
+ return false;
+ }
+
+ $expiration = $failure_time + $seconds;
+ return $expiration > time();
+ }
+
+ /**
+ * Registers a failure.
+ *
+ * @param string $key The cache key.
+ * @return void
+ */
+ public function add_failure( string $key ) {
+ $cache_key = $this->cache_key( $key );
+ $this->cache->set( $cache_key, time(), self::CACHE_TIMEOUT );
+ }
+
+ /**
+ * Clear a given failure.
+ *
+ * @param string $key The cache key.
+ * @return void
+ */
+ public function clear_failures( string $key ) {
+ $cache_key = $this->cache_key( $key );
+ if ( $this->cache->has( $cache_key ) ) {
+ $this->cache->delete( $cache_key );
+ }
+ }
+
+ /**
+ * Build cache key.
+ *
+ * @param string $key The cache key.
+ * @return string
+ */
+ private function cache_key( string $key ): string {
+ return implode( '_', array( self::CACHE_KEY, $key ) );
+ }
+
+}
diff --git a/modules/ppcp-api-client/src/Helper/ItemTrait.php b/modules/ppcp-api-client/src/Helper/ItemTrait.php
new file mode 100644
index 000000000..09f687328
--- /dev/null
+++ b/modules/ppcp-api-client/src/Helper/ItemTrait.php
@@ -0,0 +1,24 @@
+currencies_without_decimals, true )
+ ? 1.00
+ : 0.01;
+ }
}
diff --git a/modules/ppcp-api-client/src/Helper/OrderTransient.php b/modules/ppcp-api-client/src/Helper/OrderTransient.php
new file mode 100644
index 000000000..b6b7a0d99
--- /dev/null
+++ b/modules/ppcp-api-client/src/Helper/OrderTransient.php
@@ -0,0 +1,160 @@
+cache = $cache;
+ $this->purchase_unit_sanitizer = $purchase_unit_sanitizer;
+ }
+
+ /**
+ * Processes the created PayPal order.
+ *
+ * @param Order $order The PayPal order.
+ * @return void
+ */
+ public function on_order_created( Order $order ): void {
+ $message = $this->purchase_unit_sanitizer->get_last_message();
+ $this->add_order_note( $order, $message );
+ }
+
+ /**
+ * Processes the created WooCommerce order.
+ *
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @param Order $order The PayPal order.
+ * @return void
+ */
+ public function on_woocommerce_order_created( WC_Order $wc_order, Order $order ): void {
+ $cache_key = $this->cache_key( $order );
+
+ if ( ! $cache_key ) {
+ return;
+ }
+
+ $this->apply_order_notes( $order, $wc_order );
+ $this->cache->delete( $cache_key );
+ }
+
+ /**
+ * Adds an order note associated with a PayPal order.
+ * It can be added to a WooCommerce order associated with this PayPal order in the future.
+ *
+ * @param Order $order The PayPal order.
+ * @param string $message The message to be added to order notes.
+ * @return void
+ */
+ private function add_order_note( Order $order, string $message ): void {
+ if ( ! $message ) {
+ return;
+ }
+
+ $cache_key = $this->cache_key( $order );
+
+ if ( ! $cache_key ) {
+ return;
+ }
+
+ $transient = $this->cache->get( $cache_key );
+
+ if ( ! is_array( $transient ) ) {
+ $transient = array();
+ }
+
+ if ( ! is_array( $transient['notes'] ) ) {
+ $transient['notes'] = array();
+ }
+
+ $transient['notes'][] = $message;
+
+ $this->cache->set( $cache_key, $transient, self::CACHE_TIMEOUT );
+ }
+
+ /**
+ * Adds an order note associated with a PayPal order.
+ * It can be added to a WooCommerce order associated with this PayPal order in the future.
+ *
+ * @param Order $order The PayPal order.
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @return void
+ */
+ private function apply_order_notes( Order $order, WC_Order $wc_order ): void {
+ $cache_key = $this->cache_key( $order );
+
+ if ( ! $cache_key ) {
+ return;
+ }
+
+ $transient = $this->cache->get( $cache_key );
+
+ if ( ! is_array( $transient ) ) {
+ return;
+ }
+
+ if ( ! is_array( $transient['notes'] ) ) {
+ return;
+ }
+
+ foreach ( $transient['notes'] as $note ) {
+ if ( ! is_string( $note ) ) {
+ continue;
+ }
+ $wc_order->add_order_note( $note );
+ }
+ }
+
+ /**
+ * Build cache key.
+ *
+ * @param Order $order The PayPal order.
+ * @return string|null
+ */
+ private function cache_key( Order $order ): ?string {
+ if ( ! $order->id() ) {
+ return null;
+ }
+ return implode( '_', array( self::CACHE_KEY . $order->id() ) );
+ }
+
+}
diff --git a/modules/ppcp-api-client/src/Helper/PurchaseUnitSanitizer.php b/modules/ppcp-api-client/src/Helper/PurchaseUnitSanitizer.php
new file mode 100644
index 000000000..7cb0c048f
--- /dev/null
+++ b/modules/ppcp-api-client/src/Helper/PurchaseUnitSanitizer.php
@@ -0,0 +1,368 @@
+mode = $mode;
+ $this->extra_line_name = $extra_line_name;
+ }
+
+ /**
+ * The purchase_unit amount.
+ *
+ * @return array
+ */
+ private function amount(): array {
+ return $this->purchase_unit['amount'] ?? array();
+ }
+
+ /**
+ * The purchase_unit currency code.
+ *
+ * @return string
+ */
+ private function currency_code(): string {
+ return (string) ( $this->amount()['currency_code'] ?? '' );
+ }
+
+ /**
+ * The purchase_unit breakdown.
+ *
+ * @return array
+ */
+ private function breakdown(): array {
+ return $this->amount()['breakdown'] ?? array();
+ }
+
+ /**
+ * The purchase_unit breakdown.
+ *
+ * @param string $key The breakdown element to get the value from.
+ * @return float
+ */
+ private function breakdown_value( string $key ): float {
+ if ( ! isset( $this->breakdown()[ $key ] ) ) {
+ return 0.0;
+ }
+ return (float) ( $this->breakdown()[ $key ]['value'] ?? 0.0 );
+ }
+
+ /**
+ * The purchase_unit items array.
+ *
+ * @return array
+ */
+ private function items(): array {
+ return $this->purchase_unit['items'] ?? array();
+ }
+
+ /**
+ * The sanitizes the purchase_unit array.
+ *
+ * @param array $purchase_unit The purchase_unit array that should be sanitized.
+ * @param bool $allow_ditch_items Whether to allow items to be ditched.
+ * @return array
+ */
+ public function sanitize( array $purchase_unit, bool $allow_ditch_items = true ): array {
+ $this->purchase_unit = $purchase_unit;
+ $this->allow_ditch_items = $allow_ditch_items;
+ $this->has_ditched_items_breakdown = false;
+
+ $this->sanitize_item_amount_mismatch();
+ $this->sanitize_item_tax_mismatch();
+ $this->sanitize_breakdown_mismatch();
+ return $this->purchase_unit;
+ }
+
+ /**
+ * The sanitizes the purchase_unit items amount.
+ *
+ * @return void
+ */
+ private function sanitize_item_amount_mismatch(): void {
+ $item_mismatch = $this->calculate_item_mismatch();
+
+ if ( $this->mode === self::MODE_EXTRA_LINE ) {
+ if ( $item_mismatch < 0 ) {
+
+ // Do floors on item amounts so item_mismatch is a positive value.
+ foreach ( $this->purchase_unit['items'] as $index => $item ) {
+ // Get a more intelligent adjustment mechanism.
+ $increment = ( new MoneyFormatter() )->minimum_increment( $item['unit_amount']['currency_code'] );
+
+ $this->purchase_unit['items'][ $index ]['unit_amount'] = ( new Money(
+ ( (float) $item['unit_amount']['value'] ) - $increment,
+ $item['unit_amount']['currency_code']
+ ) )->to_array();
+ }
+ }
+
+ $item_mismatch = $this->calculate_item_mismatch();
+
+ if ( $item_mismatch > 0 ) {
+ // Add extra line item with roundings.
+ $line_name = $this->extra_line_name;
+ $roundings_money = new Money( $item_mismatch, $this->currency_code() );
+ $this->purchase_unit['items'][] = ( new Item( $line_name, $roundings_money, 1 ) )->to_array();
+
+ $this->set_last_message(
+ __( 'Item amount mismatch. Extra line added.', 'woocommerce-paypal-payments' )
+ );
+ }
+
+ $item_mismatch = $this->calculate_item_mismatch();
+ }
+
+ if ( $item_mismatch !== 0.0 ) {
+ // Ditch items.
+ if ( $this->allow_ditch_items && isset( $this->purchase_unit['items'] ) ) {
+ unset( $this->purchase_unit['items'] );
+ $this->set_last_message(
+ __( 'Item amount mismatch. Items ditched.', 'woocommerce-paypal-payments' )
+ );
+ }
+ }
+ }
+
+ /**
+ * The sanitizes the purchase_unit items tax.
+ *
+ * @return void
+ */
+ private function sanitize_item_tax_mismatch(): void {
+ $tax_mismatch = $this->calculate_tax_mismatch();
+
+ if ( $this->allow_ditch_items && $tax_mismatch !== 0.0 ) {
+ // Unset tax in items.
+ foreach ( $this->purchase_unit['items'] as $index => $item ) {
+ if ( isset( $this->purchase_unit['items'][ $index ]['tax'] ) ) {
+ unset( $this->purchase_unit['items'][ $index ]['tax'] );
+ }
+ if ( isset( $this->purchase_unit['items'][ $index ]['tax_rate'] ) ) {
+ unset( $this->purchase_unit['items'][ $index ]['tax_rate'] );
+ }
+ }
+ }
+ }
+
+ /**
+ * The sanitizes the purchase_unit breakdown.
+ *
+ * @return void
+ */
+ private function sanitize_breakdown_mismatch(): void {
+ $breakdown_mismatch = $this->calculate_breakdown_mismatch();
+
+ if ( $this->allow_ditch_items && $breakdown_mismatch !== 0.0 ) {
+ // Ditch breakdowns and items.
+ if ( isset( $this->purchase_unit['items'] ) ) {
+ unset( $this->purchase_unit['items'] );
+ }
+ if ( isset( $this->purchase_unit['amount']['breakdown'] ) ) {
+ unset( $this->purchase_unit['amount']['breakdown'] );
+ }
+
+ $this->has_ditched_items_breakdown = true;
+ $this->set_last_message(
+ __( 'Breakdown mismatch. Items and breakdown ditched.', 'woocommerce-paypal-payments' )
+ );
+ }
+ }
+
+ /**
+ * The calculates amount mismatch of items sums with breakdown.
+ *
+ * @return float
+ */
+ private function calculate_item_mismatch(): float {
+ $item_total = $this->breakdown_value( 'item_total' );
+ if ( ! $item_total ) {
+ return 0;
+ }
+
+ $remaining_item_total = array_reduce(
+ $this->items(),
+ function ( float $total, array $item ): float {
+ return $total - (float) $item['unit_amount']['value'] * (float) $item['quantity'];
+ },
+ $item_total
+ );
+
+ return round( $remaining_item_total, 2 );
+ }
+
+ /**
+ * The calculates tax mismatch of items sums with breakdown.
+ *
+ * @return float
+ */
+ private function calculate_tax_mismatch(): float {
+ $tax_total = $this->breakdown_value( 'tax_total' );
+ $items_with_tax = array_filter(
+ $this->items(),
+ function ( array $item ): bool {
+ return isset( $item['tax'] );
+ }
+ );
+
+ if ( ! $tax_total || empty( $items_with_tax ) ) {
+ return 0;
+ }
+
+ $remaining_tax_total = array_reduce(
+ $this->items(),
+ function ( float $total, array $item ): float {
+ $tax = $item['tax'] ?? false;
+ if ( $tax ) {
+ $total -= (float) $tax['value'] * (float) $item['quantity'];
+ }
+ return $total;
+ },
+ $tax_total
+ );
+
+ return round( $remaining_tax_total, 2 );
+ }
+
+ /**
+ * The calculates mismatch of breakdown sums with total amount.
+ *
+ * @return float
+ */
+ private function calculate_breakdown_mismatch(): float {
+ $breakdown = $this->breakdown();
+ if ( ! $breakdown ) {
+ return 0;
+ }
+
+ $amount_total = 0.0;
+ $amount_total += $this->breakdown_value( 'item_total' );
+ $amount_total += $this->breakdown_value( 'tax_total' );
+ $amount_total += $this->breakdown_value( 'shipping' );
+ $amount_total -= $this->breakdown_value( 'discount' );
+ $amount_total -= $this->breakdown_value( 'shipping_discount' );
+ $amount_total += $this->breakdown_value( 'handling' );
+ $amount_total += $this->breakdown_value( 'insurance' );
+
+ $amount_str = $this->amount()['value'] ?? 0;
+ $amount_total_str = ( new Money( $amount_total, $this->currency_code() ) )->value_str();
+
+ return $amount_str - $amount_total_str;
+ }
+
+ /**
+ * Indicates if the items and breakdown were ditched.
+ *
+ * @return bool
+ */
+ public function has_ditched_items_breakdown(): bool {
+ return $this->has_ditched_items_breakdown;
+ }
+
+ /**
+ * Returns the last sanitization message.
+ *
+ * @return string
+ */
+ public function get_last_message(): string {
+ return $this->last_message;
+ }
+
+ /**
+ * Set the last sanitization message.
+ *
+ * @param string $message The message.
+ */
+ public function set_last_message( string $message ): void {
+ $this->last_message = $message;
+ }
+
+}
diff --git a/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php b/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php
index 03c619495..244b955a0 100644
--- a/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php
+++ b/modules/ppcp-api-client/src/Repository/ApplicationContextRepository.php
@@ -38,24 +38,30 @@ class ApplicationContextRepository {
* Returns the current application context.
*
* @param string $shipping_preferences The shipping preferences.
+ * @param string $user_action The user action.
*
* @return ApplicationContext
*/
public function current_context(
- string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING
+ string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
+ string $user_action = ApplicationContext::USER_ACTION_CONTINUE
): ApplicationContext {
- $brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : '';
- $locale = $this->valid_bcp47_code();
- $landingpage = $this->settings->has( 'landing_page' ) ?
+ $brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : '';
+ $locale = $this->valid_bcp47_code();
+ $landingpage = $this->settings->has( 'landing_page' ) ?
$this->settings->get( 'landing_page' ) : ApplicationContext::LANDING_PAGE_NO_PREFERENCE;
- $context = new ApplicationContext(
+ $payment_preference = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ?
+ ApplicationContext::PAYMENT_METHOD_IMMEDIATE_PAYMENT_REQUIRED : ApplicationContext::PAYMENT_METHOD_UNRESTRICTED;
+ $context = new ApplicationContext(
network_home_url( \WC_AJAX::get_endpoint( ReturnUrlEndpoint::ENDPOINT ) ),
(string) wc_get_checkout_url(),
(string) $brand_name,
$locale,
(string) $landingpage,
- $shipping_preferences
+ $shipping_preferences,
+ $user_action,
+ $payment_preference
);
return $context;
}
diff --git a/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php b/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php
index a955dcf23..af8bae174 100644
--- a/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php
+++ b/modules/ppcp-api-client/src/Repository/PartnerReferralsData.php
@@ -127,4 +127,17 @@ class PartnerReferralsData {
)
);
}
+
+ /**
+ * Append the validation token to the return_url
+ *
+ * @param array $data The referral data.
+ * @param string $token The token to be appended.
+ * @return array
+ */
+ public function append_onboarding_token( array $data, string $token ): array {
+ $data['partner_config_override']['return_url'] =
+ add_query_arg( 'ppcpToken', $token, $data['partner_config_override']['return_url'] );
+ return $data;
+ }
}
diff --git a/modules/ppcp-api-client/src/Repository/PayPalRequestIdRepository.php b/modules/ppcp-api-client/src/Repository/PayPalRequestIdRepository.php
deleted file mode 100644
index de0f7e650..000000000
--- a/modules/ppcp-api-client/src/Repository/PayPalRequestIdRepository.php
+++ /dev/null
@@ -1,112 +0,0 @@
-get( $order_id );
- }
-
- /**
- * Returns the request ID for an order.
- *
- * @param Order $order The order.
- *
- * @return string
- */
- public function get_for_order( Order $order ): string {
- return $this->get_for_order_id( $order->id() );
- }
-
- /**
- * Sets a request ID for a specific order.
- *
- * @param Order $order The order.
- * @param string $request_id The ID.
- *
- * @return bool
- */
- public function set_for_order( Order $order, string $request_id ): bool {
- $this->set( $order->id(), $request_id );
- return true;
- }
-
- /**
- * Sets a request ID for the given key.
- *
- * @param string $key The key in the request ID storage.
- * @param string $request_id The ID.
- */
- public function set( string $key, string $request_id ): void {
- $all = $this->all();
- $day_in_seconds = 86400;
- $all[ $key ] = array(
- 'id' => $request_id,
- 'expiration' => time() + 10 * $day_in_seconds,
- );
- $all = $this->cleanup( $all );
- update_option( self::KEY, $all );
- }
-
- /**
- * Returns a request ID.
- *
- * @param string $key The key in the request ID storage.
- *
- * @return string
- */
- public function get( string $key ): string {
- $all = $this->all();
- return isset( $all[ $key ] ) ? (string) $all[ $key ]['id'] : '';
- }
-
- /**
- * Return all IDs.
- *
- * @return array
- */
- private function all(): array {
-
- return (array) get_option( 'ppcp-request-ids', array() );
- }
-
- /**
- * Clean up outdated request IDs.
- *
- * @param array $all All request IDs.
- *
- * @return array
- */
- private function cleanup( array $all ): array {
-
- foreach ( $all as $order_id => $value ) {
- if ( time() < $value['expiration'] ) {
- continue;
- }
- unset( $all[ $order_id ] );
- }
- return $all;
- }
-}
diff --git a/modules/ppcp-applepay/.babelrc b/modules/ppcp-applepay/.babelrc
new file mode 100644
index 000000000..822778e6c
--- /dev/null
+++ b/modules/ppcp-applepay/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "usage",
+ "corejs": "3.25.0"
+ }
+ ],
+ [
+ "@babel/preset-react"
+ ]
+ ]
+}
diff --git a/modules/ppcp-applepay/.gitignore b/modules/ppcp-applepay/.gitignore
new file mode 100644
index 000000000..0bd2b9f58
--- /dev/null
+++ b/modules/ppcp-applepay/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+assets/js
+assets/css
diff --git a/modules/ppcp-applepay/composer.json b/modules/ppcp-applepay/composer.json
new file mode 100644
index 000000000..d22092e7d
--- /dev/null
+++ b/modules/ppcp-applepay/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "woocommerce/ppcp-applepay",
+ "type": "dhii-mod",
+ "description": "Applepay module for PPCP",
+ "license": "GPL-2.0",
+ "require": {
+ "php": "^7.2 | ^8.0",
+ "dhii/module-interface": "^0.3.0-alpha1"
+ },
+ "autoload": {
+ "psr-4": {
+ "WooCommerce\\PayPalCommerce\\Applepay\\": "src"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/modules/ppcp-applepay/composer.lock b/modules/ppcp-applepay/composer.lock
new file mode 100644
index 000000000..66d9792d7
--- /dev/null
+++ b/modules/ppcp-applepay/composer.lock
@@ -0,0 +1,156 @@
+{
+ "_readme": [
+ "This file locks the dependencies of your project to a known state",
+ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
+ "This file is @generated automatically"
+ ],
+ "content-hash": "30c5bd428bece98b555ddc0b2da044f3",
+ "packages": [
+ {
+ "name": "container-interop/service-provider",
+ "version": "v0.4.0",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/container-interop/service-provider.git",
+ "reference": "4969b9e49460690b7430b3f1a87cab07be61418a"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/container-interop/service-provider/zipball/4969b9e49460690b7430b3f1a87cab07be61418a",
+ "reference": "4969b9e49460690b7430b3f1a87cab07be61418a",
+ "shasum": ""
+ },
+ "require": {
+ "psr/container": "^1.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Interop\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "description": "Promoting container interoperability through standard service providers",
+ "homepage": "https://github.com/container-interop/service-provider",
+ "support": {
+ "issues": "https://github.com/container-interop/service-provider/issues",
+ "source": "https://github.com/container-interop/service-provider/tree/master"
+ },
+ "time": "2017-09-20T14:13:36+00:00"
+ },
+ {
+ "name": "dhii/module-interface",
+ "version": "v0.3.0-alpha2",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/Dhii/module-interface.git",
+ "reference": "0e39f167d7ed8990c82f5d2e6084159d1a502a5b"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/Dhii/module-interface/zipball/0e39f167d7ed8990c82f5d2e6084159d1a502a5b",
+ "reference": "0e39f167d7ed8990c82f5d2e6084159d1a502a5b",
+ "shasum": ""
+ },
+ "require": {
+ "container-interop/service-provider": "^0.4",
+ "php": "^7.1 | ^8.0",
+ "psr/container": "^1.0"
+ },
+ "require-dev": {
+ "phpunit/phpunit": "^7.0 | ^8.0 | ^9.0",
+ "slevomat/coding-standard": "^6.0",
+ "vimeo/psalm": "^3.11.7 | ^4.0"
+ },
+ "type": "library",
+ "extra": {
+ "branch-alias": {
+ "dev-develop": "0.3.x-dev"
+ }
+ },
+ "autoload": {
+ "psr-4": {
+ "Dhii\\Modular\\Module\\": "src"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "Dhii Team",
+ "email": "development@dhii.co"
+ }
+ ],
+ "description": "Interfaces for modules",
+ "support": {
+ "issues": "https://github.com/Dhii/module-interface/issues",
+ "source": "https://github.com/Dhii/module-interface/tree/v0.3.0-alpha2"
+ },
+ "time": "2021-08-23T08:23:01+00:00"
+ },
+ {
+ "name": "psr/container",
+ "version": "1.1.1",
+ "source": {
+ "type": "git",
+ "url": "https://github.com/php-fig/container.git",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf"
+ },
+ "dist": {
+ "type": "zip",
+ "url": "https://api.github.com/repos/php-fig/container/zipball/8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "reference": "8622567409010282b7aeebe4bb841fe98b58dcaf",
+ "shasum": ""
+ },
+ "require": {
+ "php": ">=7.2.0"
+ },
+ "type": "library",
+ "autoload": {
+ "psr-4": {
+ "Psr\\Container\\": "src/"
+ }
+ },
+ "notification-url": "https://packagist.org/downloads/",
+ "license": [
+ "MIT"
+ ],
+ "authors": [
+ {
+ "name": "PHP-FIG",
+ "homepage": "https://www.php-fig.org/"
+ }
+ ],
+ "description": "Common Container Interface (PHP FIG PSR-11)",
+ "homepage": "https://github.com/php-fig/container",
+ "keywords": [
+ "PSR-11",
+ "container",
+ "container-interface",
+ "container-interop",
+ "psr"
+ ],
+ "support": {
+ "issues": "https://github.com/php-fig/container/issues",
+ "source": "https://github.com/php-fig/container/tree/1.1.1"
+ },
+ "time": "2021-03-05T17:36:06+00:00"
+ }
+ ],
+ "packages-dev": [],
+ "aliases": [],
+ "minimum-stability": "dev",
+ "stability-flags": [],
+ "prefer-stable": true,
+ "prefer-lowest": false,
+ "platform": {
+ "php": "^7.2 | ^8.0"
+ },
+ "platform-dev": [],
+ "plugin-api-version": "2.3.0"
+}
diff --git a/modules/ppcp-applepay/extensions.php b/modules/ppcp-applepay/extensions.php
new file mode 100644
index 000000000..39aa3ed81
--- /dev/null
+++ b/modules/ppcp-applepay/extensions.php
@@ -0,0 +1,185 @@
+ function ( ContainerInterface $container, array $fields ): array {
+
+ // Eligibility check.
+ if ( ! $container->has( 'applepay.eligible' ) || ! $container->get( 'applepay.eligible' ) ) {
+ return $fields;
+ }
+
+ $is_available = $container->get( 'applepay.available' );
+ $is_referral = $container->get( 'applepay.is_referral' );
+
+ $insert_after = function ( array $array, string $key, array $new ): array {
+ $keys = array_keys( $array );
+ $index = array_search( $key, $keys, true );
+ $pos = false === $index ? count( $array ) : $index + 1;
+
+ return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
+ };
+ $display_manager = $container->get( 'wcgateway.display-manager' );
+ assert( $display_manager instanceof DisplayManager );
+
+ // Connection tab fields.
+ $fields = $insert_after(
+ $fields,
+ 'ppcp_dcc_status',
+ array(
+ 'applepay_status' => array(
+ 'title' => __( 'Apple Pay Payments', 'woocommerce-paypal-payments' ),
+ 'type' => 'ppcp-text',
+ 'text' => $container->get( 'applepay.settings.connection.status-text' ),
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
+ ),
+ )
+ );
+
+ if ( ! $is_available && $is_referral ) {
+ $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' );
+ $connection_link = '';
+ return $insert_after(
+ $fields,
+ 'allow_card_button_gateway',
+ array(
+ 'applepay_button_enabled' => array(
+ 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'class' => array( 'ppcp-grayed-out-text' ),
+ 'input_class' => array( 'ppcp-disabled-checkbox' ),
+ 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' )
+ . ''
+ . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Your PayPal account %1$srequires additional permissions%2$s to enable Apple Pay.', 'woocommerce-paypal-payments' ),
+ $connection_link,
+ ' '
+ )
+ . '
',
+ 'default' => 'yes',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_is_true( false )
+ ->action_enable( 'applepay_button_enabled' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ )
+ );
+ }
+
+ return $insert_after(
+ $fields,
+ 'allow_card_button_gateway',
+ array(
+ 'applepay_button_enabled' => array(
+ 'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'label' => __( 'Enable Apple Pay button', 'woocommerce-paypal-payments' )
+ . ''
+ . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Buyers can use %1$sApple Pay%2$s to make payments on the web using the Safari web browser or an iOS device.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ )
+ . '
',
+ 'default' => 'yes',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_element( 'applepay_button_enabled', '1' )
+ ->action_visible( 'applepay_button_color' )
+ ->action_visible( 'applepay_button_type' )
+ ->action_visible( 'applepay_button_language' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ 'applepay_button_type' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Label', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'This controls the label of the Apple Pay button.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'pay',
+ 'options' => PropertiesDictionary::button_types(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ 'applepay_button_color' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'The Apple Pay Button may appear as a black button with white lettering, white button with black lettering, or a white button with black lettering and a black outline.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'label' => '',
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'class' => array(),
+ 'default' => 'black',
+ 'options' => PropertiesDictionary::button_colors(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ 'applepay_button_language' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Language', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'The language and region used for the displayed Apple Pay button. The default value is the current language and region setting in a browser.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'en',
+ 'options' => PropertiesDictionary::button_languages(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ )
+ );
+ },
+);
diff --git a/modules/ppcp-applepay/module.php b/modules/ppcp-applepay/module.php
new file mode 100644
index 000000000..b8a05dcc9
--- /dev/null
+++ b/modules/ppcp-applepay/module.php
@@ -0,0 +1,16 @@
+ 0.5%",
+ "Safari >= 8",
+ "Chrome >= 41",
+ "Firefox >= 43",
+ "Edge >= 14"
+ ],
+ "dependencies": {
+ "@paypal/paypal-js": "^6.0.0",
+ "core-js": "^3.25.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.19",
+ "@babel/preset-env": "^7.19",
+ "@babel/preset-react": "^7.18.6",
+ "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
+ "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-applepay/resources/css/styles.scss b/modules/ppcp-applepay/resources/css/styles.scss
new file mode 100644
index 000000000..d3f834119
--- /dev/null
+++ b/modules/ppcp-applepay/resources/css/styles.scss
@@ -0,0 +1,56 @@
+#applepay-container {
+ --apple-pay-button-height: 45px;
+ --apple-pay-button-min-height: 40px;
+ --apple-pay-button-width: 100%;
+ --apple-pay-button-max-width: 750px;
+ --apple-pay-button-border-radius: 4px;
+ --apple-pay-button-overflow: hidden;
+ --apple-pay-button-margin:7px 0;
+ &.ppcp-button-pill {
+ --apple-pay-button-border-radius: 50px;
+ }
+
+ &.ppcp-button-minicart {
+ --apple-pay-button-display: block;
+ --apple-pay-button-height: 40px;
+ }
+}
+
+.woocommerce-checkout {
+ #applepay-container {
+ margin-top: 0.5em;
+ --apple-pay-button-border-radius: 4px;
+ --apple-pay-button-height: 45px;
+ &.ppcp-button-pill {
+ --apple-pay-button-border-radius: 50px;
+ }
+ }
+}
+
+.ppcp-has-applepay-block {
+
+ .wp-block-woocommerce-checkout {
+ #applepay-container {
+ --apple-pay-button-margin: 0;
+ --apple-pay-button-height: 40px;
+ &.ppcp-button-pill {
+ --apple-pay-button-border-radius: 50px;
+ }
+ }
+ }
+
+ .wp-block-woocommerce-cart {
+ #applepay-container {
+ --apple-pay-button-margin: 0;
+ --apple-pay-button-height: 40px;
+ }
+ /* Workaround for blocks grid */
+ .wc-block-components-express-payment__event-buttons {
+ --apple-pay-button-display: block;
+ li[id*="express-payment-method-ppcp-"] {
+ --apple-pay-button-padding-bottom: 0;
+ }
+ }
+ }
+
+}
diff --git a/modules/ppcp-applepay/resources/js/ApplepayButton.js b/modules/ppcp-applepay/resources/js/ApplepayButton.js
new file mode 100644
index 000000000..19c03c5d2
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/ApplepayButton.js
@@ -0,0 +1,588 @@
+import ContextHandlerFactory from "./Context/ContextHandlerFactory";
+import {createAppleErrors} from "./Helper/applePayError";
+import {setVisible} from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
+import {setEnabled} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
+import FormValidator from "../../../ppcp-button/resources/js/modules/Helper/FormValidator";
+import ErrorHandler from '../../../ppcp-button/resources/js/modules/ErrorHandler';
+
+class ApplepayButton {
+
+ constructor(context, externalHandler, buttonConfig, ppcpConfig) {
+ this.isInitialized = false;
+
+ this.context = context;
+ this.externalHandler = externalHandler;
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+ this.paymentsClient = null;
+ this.form_saved = false;
+
+ this.contextHandler = ContextHandlerFactory.create(
+ this.context,
+ this.buttonConfig,
+ this.ppcpConfig
+ );
+
+ this.updated_contact_info = []
+ this.selectedShippingMethod = []
+ this.nonce = document.getElementById('woocommerce-process-checkout-nonce')?.value
+
+ this.log = function() {
+ if ( this.buttonConfig.is_debug ) {
+ console.log('[ApplePayButton]', ...arguments);
+ }
+ }
+
+ this.refreshContextData();
+ }
+
+ init(config) {
+ if (this.isInitialized) {
+ return;
+ }
+
+ this.log('Init', this.context);
+ this.initEventHandlers();
+ this.isInitialized = true;
+ this.applePayConfig = config;
+ const isEligible = this.applePayConfig.isEligible;
+ if (isEligible) {
+ this.fetchTransactionInfo().then(() => {
+ const isSubscriptionProduct = this.ppcpConfig.data_client_id.has_subscriptions === true;
+ if (isSubscriptionProduct) {
+ return;
+ }
+ this.addButton();
+ const id_minicart = "#apple-" + this.buttonConfig.button.mini_cart_wrapper;
+ const id = "#apple-" + 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();
+ });
+ }
+
+ // Listen for changes on any input within the WooCommerce checkout form
+ jQuery('form.checkout').on('change', 'input, select, textarea', () => {
+ this.fetchTransactionInfo();
+ });
+ });
+ }
+ }
+
+ reinit() {
+ if (!this.applePayConfig) {
+ return;
+ }
+
+ this.isInitialized = false;
+ this.init(this.applePayConfig);
+ }
+
+ async fetchTransactionInfo() {
+ this.transactionInfo = await this.contextHandler.transactionInfo();
+ }
+ /**
+ * 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;
+ }
+
+ if (['cart-block', 'checkout-block'].indexOf(this.context) !== -1) {
+ config.ppcpButtonWrapper = '#express-payment-method-ppcp-gateway';
+ }
+
+ return config;
+ }
+ initEventHandlers() {
+ const { wrapper, ppcpButtonWrapper } = this.contextConfig();
+ const wrapper_id = '#' + wrapper;
+
+ const syncButtonVisibility = () => {
+ const $ppcpButtonWrapper = jQuery(ppcpButtonWrapper);
+ setVisible(wrapper_id, $ppcpButtonWrapper.is(':visible'));
+ setEnabled(wrapper_id, !$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();
+ }
+
+ applePaySession(paymentRequest) {
+ this.log('applePaySession', paymentRequest);
+ const session = new ApplePaySession(4, paymentRequest)
+ session.begin()
+
+ if (this.buttonConfig.product.needShipping) {
+ session.onshippingmethodselected = this.onshippingmethodselected(session)
+ session.onshippingcontactselected = this.onshippingcontactselected(session)
+ }
+ session.onvalidatemerchant = this.onvalidatemerchant(session);
+ session.onpaymentauthorized = this.onpaymentauthorized(session);
+ return session;
+ }
+
+
+
+
+ /**
+ * Add a Apple Pay purchase button
+ */
+ addButton() {
+ const wrapper =
+ (this.context === 'mini-cart')
+ ? this.buttonConfig.button.mini_cart_wrapper
+ : this.buttonConfig.button.wrapper;
+ const shape =
+ (this.context === 'mini-cart')
+ ? this.ppcpConfig.button.mini_cart_style.shape
+ : this.ppcpConfig.button.style.shape;
+ const appleContainer = this.context === 'mini-cart' ? document.getElementById("applepay-container-minicart") : document.getElementById("applepay-container");
+ const type = this.buttonConfig.button.type;
+ const language = this.buttonConfig.button.lang;
+ const color = this.buttonConfig.button.color;
+ const id = "apple-" + wrapper;
+
+ if (appleContainer) {
+ appleContainer.innerHTML = ``;
+ }
+
+ jQuery('#' + wrapper).addClass('ppcp-button-' + shape);
+ jQuery(wrapper).append(appleContainer);
+ }
+
+ //------------------------
+ // Button click
+ //------------------------
+
+ /**
+ * Show Apple Pay payment sheet when Apple Pay payment button is clicked
+ */
+ async onButtonClick() {
+ this.log('onButtonClick', this.context);
+
+ const paymentDataRequest = this.paymentDataRequest();
+ // trigger woocommerce validation if we are in the checkout page
+ if (this.context === 'checkout') {
+ const checkoutFormSelector = 'form.woocommerce-checkout';
+ const errorHandler = new ErrorHandler(
+ PayPalCommerceGateway.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+ try {
+ const formData = new FormData(document.querySelector(checkoutFormSelector));
+ this.form_saved = Object.fromEntries(formData.entries());
+ // This line should be reviewed, the paypal.Applepay().confirmOrder fails if we add it.
+ //this.update_request_data_with_form(paymentDataRequest);
+ } catch (error) {
+ console.error(error);
+ }
+ const session = this.applePaySession(paymentDataRequest)
+ const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
+ new FormValidator(
+ PayPalCommerceGateway.ajax.validate_checkout.endpoint,
+ PayPalCommerceGateway.ajax.validate_checkout.nonce,
+ ) : null;
+ if (formValidator) {
+ try {
+ const errors = await formValidator.validate(document.querySelector(checkoutFormSelector));
+ if (errors.length > 0) {
+ errorHandler.messages(errors);
+ jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
+ session.abort();
+ return;
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+ return;
+ }
+ this.applePaySession(paymentDataRequest)
+ }
+
+ update_request_data_with_form(paymentDataRequest) {
+ paymentDataRequest.billingContact = this.fill_billing_contact(this.form_saved);
+ paymentDataRequest.applicationData = this.fill_application_data(this.form_saved);
+ if (!this.buttonConfig.product.needShipping) {
+ return;
+ }
+ paymentDataRequest.shippingContact = this.fill_shipping_contact(this.form_saved);
+ }
+
+ paymentDataRequest() {
+ const applepayConfig = this.applePayConfig
+ const buttonConfig = this.buttonConfig
+ let baseRequest = {
+ countryCode: applepayConfig.countryCode,
+ merchantCapabilities: applepayConfig.merchantCapabilities,
+ supportedNetworks: applepayConfig.supportedNetworks,
+ requiredShippingContactFields: ["postalAddress", "email", "phone"],
+ requiredBillingContactFields: ["postalAddress", "email", "phone"],
+ }
+ const paymentDataRequest = Object.assign({}, baseRequest);
+ paymentDataRequest.currencyCode = buttonConfig.shop.currencyCode;
+ paymentDataRequest.total = {
+ label: buttonConfig.shop.totalLabel,
+ type: "final",
+ amount: this.transactionInfo.totalPrice,
+ }
+
+ return paymentDataRequest
+ }
+
+ refreshContextData() {
+ switch (this.context) {
+ case 'product':
+ // 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);
+ break;
+ }
+ }
+
+ //------------------------
+ // Payment process
+ //------------------------
+
+ onvalidatemerchant(session) {
+ this.log('onvalidatemerchant', this.buttonConfig.ajax_url);
+ return (applePayValidateMerchantEvent) => {
+ this.log('onvalidatemerchant call');
+
+ paypal.Applepay().validateMerchant({
+ 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,
+ }
+ })
+ })
+ .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.log('onvalidatemerchant session abort');
+ session.abort();
+ });
+ };
+ }
+ onshippingmethodselected(session) {
+ this.log('onshippingmethodselected', this.buttonConfig.ajax_url);
+ const ajax_url = this.buttonConfig.ajax_url
+ return (event) => {
+ this.log('onshippingmethodselected call');
+
+ const data = this.getShippingMethodData(event);
+ jQuery.ajax({
+ url: ajax_url,
+ method: 'POST',
+ data: data,
+ success: (applePayShippingMethodUpdate, textStatus, jqXHR) => {
+ this.log('onshippingmethodselected ok');
+ let response = applePayShippingMethodUpdate.data
+ if (applePayShippingMethodUpdate.success === false) {
+ response.errors = createAppleErrors(response.errors)
+ }
+ this.selectedShippingMethod = event.shippingMethod
+ //order the response shipping methods, so that the selected shipping method is the first one
+ let orderedShippingMethods = response.newShippingMethods.sort((a, b) => {
+ if (a.label === this.selectedShippingMethod.label) {
+ return -1
+ }
+ return 1
+ })
+ //update the response.newShippingMethods with the ordered shipping methods
+ response.newShippingMethods = orderedShippingMethods
+ if (applePayShippingMethodUpdate.success === false) {
+ response.errors = createAppleErrors(response.errors)
+ }
+ session.completeShippingMethodSelection(response)
+ },
+ error: (jqXHR, textStatus, errorThrown) => {
+ this.log('onshippingmethodselected error', textStatus);
+ console.warn(textStatus, errorThrown)
+ session.abort()
+ },
+ })
+ };
+ }
+ onshippingcontactselected(session) {
+ this.log('onshippingcontactselected', this.buttonConfig.ajax_url);
+ const ajax_url = this.buttonConfig.ajax_url
+ return (event) => {
+ this.log('onshippingcontactselected call');
+
+ const data = this.getShippingContactData(event);
+ jQuery.ajax({
+ url: ajax_url,
+ method: 'POST',
+ data: data,
+ success: (applePayShippingContactUpdate, textStatus, jqXHR) => {
+ this.log('onshippingcontactselected ok');
+ let response = applePayShippingContactUpdate.data
+ this.updated_contact_info = event.shippingContact
+ if (applePayShippingContactUpdate.success === false) {
+ response.errors = createAppleErrors(response.errors)
+ }
+ if (response.newShippingMethods) {
+ this.selectedShippingMethod = response.newShippingMethods[0]
+ }
+ session.completeShippingContactSelection(response)
+ },
+ error: (jqXHR, textStatus, errorThrown) => {
+ this.log('onshippingcontactselected error', textStatus);
+ console.warn(textStatus, errorThrown)
+ session.abort()
+ },
+ })
+ };
+ }
+ getShippingContactData(event) {
+ const product_id = this.buttonConfig.product.id;
+
+ this.refreshContextData();
+
+ switch (this.context) {
+ case 'product':
+ return {
+ action: 'ppcp_update_shipping_contact',
+ product_id: product_id,
+ products: JSON.stringify(this.products),
+ caller_page: 'productDetail',
+ product_quantity: this.productQuantity,
+ simplified_contact: event.shippingContact,
+ need_shipping: this.buttonConfig.product.needShipping,
+ 'woocommerce-process-checkout-nonce': this.nonce,
+ };
+ case 'cart':
+ case 'checkout':
+ case 'cart-block':
+ case 'checkout-block':
+ case 'mini-cart':
+ return {
+ action: 'ppcp_update_shipping_contact',
+ simplified_contact: event.shippingContact,
+ caller_page: 'cart',
+ need_shipping: this.buttonConfig.product.needShipping,
+ 'woocommerce-process-checkout-nonce': this.nonce,
+ };
+ }
+ }
+ getShippingMethodData(event) {
+ const product_id = this.buttonConfig.product.id;
+
+ this.refreshContextData();
+
+ switch (this.context) {
+ case 'product': return {
+ action: 'ppcp_update_shipping_method',
+ shipping_method: event.shippingMethod,
+ product_id: product_id,
+ products: JSON.stringify(this.products),
+ caller_page: 'productDetail',
+ product_quantity: this.productQuantity,
+ simplified_contact: this.updated_contact_info,
+ 'woocommerce-process-checkout-nonce': this.nonce,
+ }
+ case 'cart':
+ case 'checkout':
+ case 'cart-block':
+ case 'checkout-block':
+ case 'mini-cart':
+ return {
+ action: 'ppcp_update_shipping_method',
+ shipping_method: event.shippingMethod,
+ caller_page: 'cart',
+ simplified_contact: this.updated_contact_info,
+ 'woocommerce-process-checkout-nonce': this.nonce,
+ }
+ }
+ }
+
+ onpaymentauthorized(session) {
+ this.log('onpaymentauthorized');
+ return async (event) => {
+ this.log('onpaymentauthorized call');
+
+ function form() {
+ return document.querySelector('form.cart');
+ }
+ const processInWooAndCapture = async (data) => {
+ return new Promise((resolve, reject) => {
+ try {
+ const billingContact = data.billing_contact
+ const shippingContact = data.shipping_contact
+ let request_data = {
+ action: 'ppcp_create_order',
+ 'caller_page': this.context,
+ 'product_id': this.buttonConfig.product.id ?? null,
+ 'products': JSON.stringify(this.products),
+ 'product_quantity': this.productQuantity ?? null,
+ 'shipping_contact': shippingContact,
+ 'billing_contact': billingContact,
+ 'token': event.payment.token,
+ 'shipping_method': this.selectedShippingMethod,
+ 'woocommerce-process-checkout-nonce': this.nonce,
+ 'funding_source': 'applepay',
+ '_wp_http_referer': '/?wc-ajax=update_order_review',
+ 'paypal_order_id': data.paypal_order_id,
+ };
+
+ this.log('onpaymentauthorized request', this.buttonConfig.ajax_url, data);
+
+ jQuery.ajax({
+ url: this.buttonConfig.ajax_url,
+ method: 'POST',
+ data: request_data,
+ complete: (jqXHR, textStatus) => {
+ this.log('onpaymentauthorized complete');
+ },
+ success: (authorizationResult, textStatus, jqXHR) => {
+ this.log('onpaymentauthorized ok');
+ resolve(authorizationResult)
+ },
+ error: (jqXHR, textStatus, errorThrown) => {
+ this.log('onpaymentauthorized error', textStatus);
+ reject(new Error(errorThrown));
+ },
+ })
+ } catch (error) {
+ this.log('onpaymentauthorized catch', error);
+ console.log(error) // handle error
+ }
+ });
+ }
+
+ let id = await this.contextHandler.createOrder();
+
+ this.log('onpaymentauthorized paypal order ID', id, event.payment.token, event.payment.billingContact);
+
+ try {
+ const confirmOrderResponse = await paypal.Applepay().confirmOrder({
+ orderId: id,
+ token: event.payment.token,
+ billingContact: event.payment.billingContact,
+ });
+
+ this.log('onpaymentauthorized confirmOrderResponse', confirmOrderResponse);
+
+ if (confirmOrderResponse && confirmOrderResponse.approveApplePayPayment) {
+ if (confirmOrderResponse.approveApplePayPayment.status === "APPROVED") {
+ try {
+ let data = {
+ billing_contact: event.payment.billingContact,
+ shipping_contact: event.payment.shippingContact,
+ paypal_order_id: id,
+ };
+ let authorizationResult = await processInWooAndCapture(data);
+ if (authorizationResult.result === "success") {
+ session.completePayment(ApplePaySession.STATUS_SUCCESS)
+ window.location.href = authorizationResult.redirect
+ } else {
+ session.completePayment(ApplePaySession.STATUS_FAILURE)
+ }
+ } catch (error) {
+ session.completePayment(ApplePaySession.STATUS_FAILURE);
+ session.abort()
+ console.error(error);
+ }
+ } else {
+ console.error('Error status is not APPROVED');
+ session.completePayment(ApplePaySession.STATUS_FAILURE);
+ }
+ } else {
+ console.error('Invalid confirmOrderResponse');
+ session.completePayment(ApplePaySession.STATUS_FAILURE);
+ }
+ } catch (error) {
+ console.error('Error confirming order with applepay token', error);
+ session.completePayment(ApplePaySession.STATUS_FAILURE);
+ session.abort()
+ }
+ };
+ }
+
+ fill_billing_contact(form_saved) {
+ return {
+ givenName: form_saved.billing_first_name ?? '',
+ familyName: form_saved.billing_last_name ?? '',
+ emailAddress: form_saved.billing_email ?? '',
+ phoneNumber: form_saved.billing_phone ?? '',
+ addressLines: [form_saved.billing_address_1, form_saved.billing_address_2],
+ locality: form_saved.billing_city ?? '',
+ postalCode: form_saved.billing_postcode ?? '',
+ countryCode: form_saved.billing_country ?? '',
+ administrativeArea: form_saved.billing_state ?? '',
+ }
+ }
+ fill_shipping_contact(form_saved) {
+ if (form_saved.shipping_first_name === "") {
+ return this.fill_billing_contact(form_saved)
+ }
+ return {
+ givenName: (form_saved?.shipping_first_name && form_saved.shipping_first_name !== "") ? form_saved.shipping_first_name : form_saved?.billing_first_name,
+ familyName: (form_saved?.shipping_last_name && form_saved.shipping_last_name !== "") ? form_saved.shipping_last_name : form_saved?.billing_last_name,
+ emailAddress: (form_saved?.shipping_email && form_saved.shipping_email !== "") ? form_saved.shipping_email : form_saved?.billing_email,
+ phoneNumber: (form_saved?.shipping_phone && form_saved.shipping_phone !== "") ? form_saved.shipping_phone : form_saved?.billing_phone,
+ addressLines: [form_saved.shipping_address_1 ?? '', form_saved.shipping_address_2 ?? ''],
+ locality: (form_saved?.shipping_city && form_saved.shipping_city !== "") ? form_saved.shipping_city : form_saved?.billing_city,
+ postalCode: (form_saved?.shipping_postcode && form_saved.shipping_postcode !== "") ? form_saved.shipping_postcode : form_saved?.billing_postcode,
+ countryCode: (form_saved?.shipping_country && form_saved.shipping_country !== "") ? form_saved.shipping_country : form_saved?.billing_country,
+ administrativeArea: (form_saved?.shipping_state && form_saved.shipping_state !== "") ? form_saved.shipping_state : form_saved?.billing_state,
+ }
+ }
+
+ fill_application_data(form_saved) {
+ const jsonString = JSON.stringify(form_saved);
+ let utf8Str = encodeURIComponent(jsonString).replace(/%([0-9A-F]{2})/g, (match, p1) => {
+ return String.fromCharCode('0x' + p1);
+ });
+
+ return btoa(utf8Str);
+ }
+}
+
+export default ApplepayButton;
diff --git a/modules/ppcp-applepay/resources/js/ApplepayManager.js b/modules/ppcp-applepay/resources/js/ApplepayManager.js
new file mode 100644
index 000000000..fa9622143
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/ApplepayManager.js
@@ -0,0 +1,55 @@
+import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
+import ApplepayButton from "./ApplepayButton";
+
+class ApplepayManager {
+
+ constructor(buttonConfig, ppcpConfig) {
+
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+ this.ApplePayConfig = null;
+ this.buttons = [];
+
+ buttonModuleWatcher.watchContextBootstrap((bootstrap) => {
+ const button = new ApplepayButton(
+ bootstrap.context,
+ bootstrap.handler,
+ buttonConfig,
+ ppcpConfig,
+ );
+
+ this.buttons.push(button);
+
+ if (this.ApplePayConfig) {
+ button.init(this.ApplePayConfig);
+ }
+ });
+ }
+
+ init() {
+ (async () => {
+ await this.config();
+ for (const button of this.buttons) {
+ button.init(this.ApplePayConfig);
+ }
+ })();
+ }
+
+ reinit() {
+ for (const button of this.buttons) {
+ button.reinit();
+ }
+ }
+
+ /**
+ * Gets ApplePay configuration of the PayPal merchant.
+ * @returns {Promise}
+ */
+ async config() {
+ this.ApplePayConfig = await paypal.Applepay().config();
+ return this.ApplePayConfig;
+ }
+
+}
+
+export default ApplepayManager;
diff --git a/modules/ppcp-applepay/resources/js/Context/BaseHandler.js b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js
new file mode 100644
index 000000000..dea7de698
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/BaseHandler.js
@@ -0,0 +1,73 @@
+import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
+import CartActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
+import onApprove
+ from "../../../../ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue";
+
+class BaseHandler {
+
+ constructor(buttonConfig, ppcpConfig) {
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+ }
+
+ transactionInfo() {
+ return new Promise((resolve, reject) => {
+
+ fetch(
+ this.ppcpConfig.ajax.cart_script_params.endpoint,
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (! result.success) {
+ return;
+ }
+
+ // handle script reload
+ const data = result.data;
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+
+ });
+ });
+ }
+
+ createOrder() {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ const actionHandler = new CartActionHandler(
+ this.ppcpConfig,
+ errorHandler,
+ );
+
+ return actionHandler.configuration().createOrder(null, null);
+ }
+
+ approveOrderForContinue(data, actions) {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ let onApproveHandler = onApprove({
+ config: this.ppcpConfig
+ }, errorHandler);
+
+ return onApproveHandler(data, actions);
+ }
+
+}
+
+export default BaseHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js b/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js
new file mode 100644
index 000000000..5f62080ee
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/CartBlockHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class CartBlockHandler extends BaseHandler {
+
+}
+
+export default CartBlockHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/CartHandler.js b/modules/ppcp-applepay/resources/js/Context/CartHandler.js
new file mode 100644
index 000000000..295bec718
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/CartHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class CartHandler extends BaseHandler {
+
+}
+
+export default CartHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js
new file mode 100644
index 000000000..8bbe9dff6
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/CheckoutBlockHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class CheckoutBlockHandler extends BaseHandler{
+
+}
+
+export default CheckoutBlockHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js
new file mode 100644
index 000000000..9d134fc55
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/CheckoutHandler.js
@@ -0,0 +1,28 @@
+import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
+import CheckoutActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
+import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
+import BaseHandler from "./BaseHandler";
+
+class CheckoutHandler extends BaseHandler {
+
+ createOrder() {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ const spinner = new Spinner();
+
+ const actionHandler = new CheckoutActionHandler(
+ this.ppcpConfig,
+ errorHandler,
+ spinner
+ );
+
+ return actionHandler.configuration().createOrder(null, null);
+ }
+
+}
+
+export default CheckoutHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js
new file mode 100644
index 000000000..72ee4f5fa
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/ContextHandlerFactory.js
@@ -0,0 +1,29 @@
+import SingleProductHandler from "./SingleProductHandler";
+import CartHandler from "./CartHandler";
+import CheckoutHandler from "./CheckoutHandler";
+import CartBlockHandler from "./CartBlockHandler";
+import CheckoutBlockHandler from "./CheckoutBlockHandler";
+import MiniCartHandler from "./MiniCartHandler";
+
+class ContextHandlerFactory {
+
+ static create(context, buttonConfig, ppcpConfig) {
+ switch (context) {
+ case 'product':
+ return new SingleProductHandler(buttonConfig, ppcpConfig);
+ case 'cart':
+ return new CartHandler(buttonConfig, ppcpConfig);
+ case 'checkout':
+ case 'pay-now': // same as checkout
+ return new CheckoutHandler(buttonConfig, ppcpConfig);
+ case 'mini-cart':
+ return new MiniCartHandler(buttonConfig, ppcpConfig);
+ case 'cart-block':
+ return new CartBlockHandler(buttonConfig, ppcpConfig);
+ case 'checkout-block':
+ return new CheckoutBlockHandler(buttonConfig, ppcpConfig);
+ }
+ }
+}
+
+export default ContextHandlerFactory;
diff --git a/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js b/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js
new file mode 100644
index 000000000..1884203b7
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/MiniCartHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class MiniCartHandler extends BaseHandler {
+
+}
+
+export default MiniCartHandler;
diff --git a/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js
new file mode 100644
index 000000000..f9adb7774
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Context/SingleProductHandler.js
@@ -0,0 +1,78 @@
+import SingleProductActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler";
+import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
+import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
+import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart";
+import BaseHandler from "./BaseHandler";
+
+class SingleProductHandler extends BaseHandler {
+
+ transactionInfo() {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ function form() {
+ return document.querySelector('form.cart');
+ }
+
+ const actionHandler = new SingleProductActionHandler(
+ null,
+ null,
+ form(),
+ errorHandler,
+ );
+
+ const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
+
+ const products = hasSubscriptions
+ ? actionHandler.getSubscriptionProducts()
+ : actionHandler.getProducts();
+
+ return new Promise((resolve, reject) => {
+ (new SimulateCart(
+ this.ppcpConfig.ajax.simulate_cart.endpoint,
+ this.ppcpConfig.ajax.simulate_cart.nonce,
+ )).simulate((data) => {
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+
+ }, products);
+ });
+ }
+
+ createOrder() {
+ return this.actionHandler().configuration().createOrder();
+ }
+
+ products() {
+ return this.actionHandler().getProducts();
+ }
+
+ actionHandler() {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ return new SingleProductActionHandler(
+ this.ppcpConfig,
+ new UpdateCart(
+ this.ppcpConfig.ajax.change_cart.endpoint,
+ this.ppcpConfig.ajax.change_cart.nonce,
+ ),
+ document.querySelector('form.cart'),
+ errorHandler,
+ );
+ }
+
+}
+
+export default SingleProductHandler;
diff --git a/modules/ppcp-applepay/resources/js/Helper/applePayError.js b/modules/ppcp-applepay/resources/js/Helper/applePayError.js
new file mode 100644
index 000000000..8775eea4e
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Helper/applePayError.js
@@ -0,0 +1,10 @@
+export function createAppleErrors(errors) {
+ const errorList = []
+ for (const error of errors) {
+ const {contactField = null, code = null, message = null} = error
+ const appleError = contactField ? new ApplePayError(code, contactField, message) : new ApplePayError(code)
+ errorList.push(appleError)
+ }
+
+ return errorList
+}
diff --git a/modules/ppcp-applepay/resources/js/Helper/utils.js b/modules/ppcp-applepay/resources/js/Helper/utils.js
new file mode 100644
index 000000000..b5cfee8f7
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/Helper/utils.js
@@ -0,0 +1,8 @@
+export const buttonID = 'applepay-container';
+export const endpoints = {
+ validation: '_apple_pay_validation',
+ createOrderCart: '_apple_pay_create_order_cart',
+ createOrderProduct: '_apple_pay_create_order_product',
+ updateShippingMethod: '_apple_pay_update_shipping_contact',
+ updateShippingContact: '_apple_pay_update_billing_contact',
+}
diff --git a/modules/ppcp-applepay/resources/js/boot-block.js b/modules/ppcp-applepay/resources/js/boot-block.js
new file mode 100644
index 000000000..2f97b7fbc
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/boot-block.js
@@ -0,0 +1,70 @@
+import {useEffect, useState} from '@wordpress/element';
+import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
+import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
+import ApplepayManager from "./ApplepayManager";
+import {loadCustomScript} from "@paypal/paypal-js";
+
+const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
+const ppcpConfig = ppcpData.scriptData;
+
+const buttonData = wc.wcSettings.getSetting('ppcp-applepay_data');
+const buttonConfig = buttonData.scriptData;
+
+if (typeof window.PayPalCommerceGateway === 'undefined') {
+ window.PayPalCommerceGateway = ppcpConfig;
+}
+
+const ApplePayComponent = () => {
+ const [bootstrapped, setBootstrapped] = useState(false);
+ const [paypalLoaded, setPaypalLoaded] = useState(false);
+ const [applePayLoaded, setApplePayLoaded] = useState(false);
+
+ const bootstrap = function () {
+ const manager = new ApplepayManager(buttonConfig, ppcpConfig);
+ manager.init();
+ };
+ useEffect(() => {
+ const bodyClass = 'ppcp-has-applepay-block';
+ if (!document.body.classList.contains(bodyClass)) {
+ document.body.classList.add(bodyClass);
+ }
+ }, []);
+
+ useEffect(() => {
+ // Load ApplePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ setApplePayLoaded(true);
+ });
+
+ // Load PayPal
+ loadPaypalScript(ppcpConfig, () => {
+ setPaypalLoaded(true);
+ });
+ }, []);
+
+ useEffect(() => {
+ if (!bootstrapped && paypalLoaded && applePayLoaded) {
+ setBootstrapped(true);
+ bootstrap();
+ }
+ }, [paypalLoaded, applePayLoaded]);
+
+ return (
+
+ );
+}
+
+const features = ['products'];
+let registerMethod = registerExpressPaymentMethod;
+
+registerMethod({
+ name: buttonData.id,
+ label:
,
+ content: ,
+ edit: ,
+ ariaLabel: buttonData.title,
+ canMakePayment: () => buttonData.enabled,
+ supports: {
+ features: features,
+ },
+});
diff --git a/modules/ppcp-applepay/resources/js/boot.js b/modules/ppcp-applepay/resources/js/boot.js
new file mode 100644
index 000000000..b28df3ce9
--- /dev/null
+++ b/modules/ppcp-applepay/resources/js/boot.js
@@ -0,0 +1,70 @@
+import {loadCustomScript} from "@paypal/paypal-js";
+import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
+import ApplepayManager from "./ApplepayManager";
+
+(function ({
+ buttonConfig,
+ ppcpConfig,
+ jQuery
+ }) {
+
+ let manager;
+
+ const bootstrap = function () {
+ manager = new ApplepayManager(buttonConfig, ppcpConfig);
+ manager.init();
+ };
+
+ jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
+ if (manager) {
+ manager.reinit();
+ }
+ });
+
+ document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ if (
+ (typeof (buttonConfig) === 'undefined') ||
+ (typeof (ppcpConfig) === 'undefined')
+ ) {
+ return;
+ }
+ const isMiniCart = ppcpConfig.mini_cart_buttons_enabled;
+ const isButton = jQuery('#' + buttonConfig.button.wrapper).length > 0;
+ // If button wrapper is not present then there is no need to load the scripts.
+ // minicart loads later?
+ if (!isMiniCart && !isButton) {
+ return;
+ }
+
+ let bootstrapped = false;
+ let paypalLoaded = false;
+ let applePayLoaded = false;
+
+ const tryToBoot = () => {
+ if (!bootstrapped && paypalLoaded && applePayLoaded) {
+ bootstrapped = true;
+ bootstrap();
+ }
+ }
+
+ // Load ApplePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ applePayLoaded = true;
+ tryToBoot();
+ });
+
+ // Load PayPal
+ loadPaypalScript(ppcpConfig, () => {
+ paypalLoaded = true;
+ tryToBoot();
+ });
+ },
+ );
+
+})({
+ buttonConfig: window.wc_ppcp_applepay,
+ ppcpConfig: window.PayPalCommerceGateway,
+ jQuery: window.jQuery
+});
diff --git a/modules/ppcp-applepay/services.php b/modules/ppcp-applepay/services.php
new file mode 100644
index 000000000..a49ab7fad
--- /dev/null
+++ b/modules/ppcp-applepay/services.php
@@ -0,0 +1,228 @@
+ static function ( ContainerInterface $container ): bool {
+ $apm_applies = $container->get( 'applepay.helpers.apm-applies' );
+ assert( $apm_applies instanceof ApmApplies );
+
+ return $apm_applies->for_country_currency();
+ },
+ 'applepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
+ return new ApmApplies(
+ $container->get( 'applepay.supported-country-currency-matrix' ),
+ $container->get( 'api.shop.currency' ),
+ $container->get( 'api.shop.country' )
+ );
+ },
+ '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 {
+ $status = $container->get( 'applepay.apple-product-status' );
+ assert( $status instanceof AppleProductStatus );
+
+ return ! $status->has_request_failure();
+ },
+
+ 'applepay.availability_notice' => static function ( ContainerInterface $container ): AvailabilityNotice {
+ $settings = $container->get( 'wcgateway.settings' );
+
+ return new AvailabilityNotice(
+ $container->get( 'applepay.apple-product-status' ),
+ $container->get( 'wcgateway.is-wc-gateways-list-page' ),
+ $container->get( 'wcgateway.is-ppcp-settings-page' ),
+ $container->get( 'applepay.available' ) || ( ! $container->get( 'applepay.is_referral' ) ),
+ $container->get( 'applepay.server_supported' ),
+ $settings->has( 'applepay_validated' ) ? $settings->get( 'applepay_validated' ) === true : false,
+ $container->get( 'applepay.button' )
+ );
+ },
+
+ 'applepay.apple-product-status' => static function( ContainerInterface $container ): AppleProductStatus {
+ return new AppleProductStatus(
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'api.endpoint.partners' ),
+ $container->get( 'onboarding.state' ),
+ $container->get( 'api.helper.failure-registry' )
+ );
+ },
+ '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 );
+ /**
+ * If merchant isn't onboarded via /v1/customer/partner-referrals this returns false as the API call fails.
+ */
+ return apply_filters( 'woocommerce_paypal_payments_applepay_product_status', $status->is_active() );
+ }
+ return true;
+ },
+ 'applepay.server_supported' => static function ( ContainerInterface $container ): bool {
+ return ! empty( $_SERVER['HTTPS'] ) && $_SERVER['HTTPS'] !== 'off';
+ },
+ 'applepay.url' => static function ( ContainerInterface $container ): string {
+ $path = realpath( __FILE__ );
+ if ( false === $path ) {
+ return '';
+ }
+ return plugins_url(
+ '/modules/ppcp-applepay/',
+ dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
+ );
+ },
+ '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 {
+ return new DataToAppleButtonScripts( $container->get( 'applepay.sdk_script_url' ), $container->get( 'wcgateway.settings' ) );
+ },
+ 'applepay.button' => static function ( ContainerInterface $container ): ApplePayButton {
+ return new ApplePayButton(
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'woocommerce.logger.woocommerce' ),
+ $container->get( 'wcgateway.order-processor' ),
+ $container->get( 'applepay.url' ),
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'applepay.data_to_scripts' ),
+ $container->get( 'wcgateway.settings.status' ),
+ $container->get( 'button.helper.cart-products' )
+ );
+ },
+ 'applepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
+ return new BlocksPaymentMethod(
+ 'ppcp-applepay',
+ $container->get( 'applepay.url' ),
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'applepay.button' ),
+ $container->get( 'blocks.method' )
+ );
+ },
+ /**
+ * The matrix which countries and currency combinations can be used for ApplePay.
+ */
+ 'applepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
+ /**
+ * Returns which countries and currency combinations can be used for ApplePay.
+ */
+ return apply_filters(
+ 'woocommerce_paypal_payments_applepay_supported_country_currency_matrix',
+ array(
+ 'GB' => array(
+ 'AUD',
+ 'CAD',
+ 'CHF',
+ 'CZK',
+ 'DKK',
+ 'EUR',
+ 'GBP',
+ 'HKD',
+ 'HUF',
+ 'JPY',
+ 'NOK',
+ 'NZD',
+ 'PLN',
+ 'SEK',
+ 'SGD',
+ 'USD',
+ ),
+ 'US' => array(
+ 'AUD',
+ 'CAD',
+ 'EUR',
+ 'GBP',
+ 'JPY',
+ 'USD',
+ ),
+ 'CA' => array(
+ 'AUD',
+ 'CAD',
+ 'CHF',
+ 'CZK',
+ 'DKK',
+ 'EUR',
+ 'GBP',
+ 'HKD',
+ 'HUF',
+ 'JPY',
+ 'NOK',
+ 'NZD',
+ 'PLN',
+ 'SEK',
+ 'SGD',
+ 'USD',
+ ),
+ )
+ );
+ },
+
+ '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 {
+ return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=APPLE_PAY';
+ },
+
+ 'applepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() < State::STATE_ONBOARDED ) {
+ return '';
+ }
+
+ $product_status = $container->get( 'applepay.apple-product-status' );
+ assert( $product_status instanceof AppleProductStatus );
+
+ $environment = $container->get( 'onboarding.environment' );
+ assert( $environment instanceof Environment );
+
+ $enabled = $product_status->is_active();
+
+ $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
+ $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
+
+ $button_text = $enabled
+ ? esc_html__( 'Settings', 'woocommerce-paypal-payments' )
+ : esc_html__( 'Enable Apple Pay', 'woocommerce-paypal-payments' );
+
+ $enable_url = $environment->current_environment_is( Environment::PRODUCTION )
+ ? $container->get( 'applepay.enable-url-live' )
+ : $container->get( 'applepay.enable-url-sandbox' );
+
+ $button_url = $enabled
+ ? admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway#field-alternative_payment_methods' )
+ : $enable_url;
+
+ return sprintf(
+ '%1$s %2$s
%5$s
',
+ $enabled ? $enabled_status_text : $disabled_status_text,
+ $enabled ? ' ' : ' ',
+ $enabled ? '_self' : '_blank',
+ esc_url( $button_url ),
+ esc_html( $button_text )
+ );
+ },
+
+);
diff --git a/modules/ppcp-applepay/src/ApplepayModule.php b/modules/ppcp-applepay/src/ApplepayModule.php
new file mode 100644
index 000000000..b81329811
--- /dev/null
+++ b/modules/ppcp-applepay/src/ApplepayModule.php
@@ -0,0 +1,261 @@
+get( 'applepay.apple-product-status' );
+ assert( $apm_status instanceof AppleProductStatus );
+ $apm_status->clear( $settings );
+ }
+ );
+
+ add_action(
+ 'init',
+ static function () use ( $c, $module ) {
+
+ // Check if the module is applicable, correct country, currency, ... etc.
+ if ( ! $c->get( 'applepay.eligible' ) ) {
+ return;
+ }
+
+ // Load the button handler.
+ $apple_payment_method = $c->get( 'applepay.button' );
+ // add onboarding and referrals hooks.
+ assert( $apple_payment_method instanceof ApplepayButton );
+ $apple_payment_method->initialize();
+
+ // Show notice if there are product availability issues.
+ $availability_notice = $c->get( 'applepay.availability_notice' );
+ assert( $availability_notice instanceof AvailabilityNotice );
+ $availability_notice->execute();
+
+ // Return if server not supported.
+ if ( ! $c->get( 'applepay.server_supported' ) ) {
+ return;
+ }
+
+ // Check if this merchant can activate / use the buttons.
+ // We allow non referral merchants as they can potentially still use ApplePay, we just have no way of checking the capability.
+ if ( ( ! $c->get( 'applepay.available' ) ) && $c->get( 'applepay.is_referral' ) ) {
+ return;
+ }
+
+ $module->load_assets( $c, $apple_payment_method );
+ $module->handle_validation_file( $c );
+ $module->render_buttons( $c, $apple_payment_method );
+
+ $apple_payment_method->bootstrap_ajax_request();
+ }
+ );
+
+ add_filter(
+ 'nonce_user_logged_out',
+ /**
+ * Prevents nonce from being changed for non logged in users.
+ *
+ * @param int $uid The uid.
+ * @param string|int $action The action.
+ * @return int
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $uid, $action ) {
+ if ( $action === PropertiesDictionary::NONCE_ACTION ) {
+ return 0;
+ }
+ return $uid;
+ },
+ 100,
+ 2
+ );
+ }
+
+ /**
+ * Returns the key for the module.
+ *
+ * @return string|void
+ */
+ public function getKey() {
+ }
+
+ /**
+ * Loads the validation string.
+ *
+ * @param boolean $is_sandbox The environment for this merchant.
+ */
+ protected function load_domain_association_file( bool $is_sandbox ): void {
+ if ( ! isset( $_SERVER['REQUEST_URI'] ) ) {
+ return;
+ }
+ $request_uri = (string) filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ), FILTER_SANITIZE_URL );
+ if ( strpos( $request_uri, '.well-known/apple-developer-merchantid-domain-association' ) !== false ) {
+ $validation_string = $this->validation_string( $is_sandbox );
+ nocache_headers();
+ header( 'Content-Type: text/plain', true, 200 );
+ echo $validation_string;// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ exit;
+ }
+ }
+
+ /**
+ * Registers and enqueues the assets.
+ *
+ * @param ContainerInterface $c The container.
+ * @param ApplePayButton $button The button.
+ * @return void
+ */
+ public function load_assets( ContainerInterface $c, ApplePayButton $button ): void {
+ if ( ! $button->is_enabled() ) {
+ return;
+ }
+
+ add_action(
+ 'wp_enqueue_scripts',
+ function () use ( $c, $button ) {
+ $smart_button = $c->get( 'button.smart-button' );
+ assert( $smart_button instanceof SmartButtonInterface );
+ if ( $smart_button->should_load_ppcp_script() ) {
+ $button->enqueue();
+ }
+
+ if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) {
+ /**
+ * Should add this to the ButtonInterface.
+ *
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $button->enqueue_styles();
+ }
+ }
+ );
+ add_action(
+ 'woocommerce_blocks_payment_method_type_registration',
+ function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
+ $payment_method_registry->register( $c->get( 'applepay.blocks-payment-method' ) );
+ }
+ );
+ }
+
+ /**
+ * Renders the Apple Pay buttons in the enabled places.
+ *
+ * @param ContainerInterface $c The container.
+ * @param ApplePayButton $button The button.
+ * @return void
+ */
+ public function render_buttons( ContainerInterface $c, ApplePayButton $button ): void {
+ if ( ! $button->is_enabled() ) {
+ return;
+ }
+
+ add_action(
+ 'wp',
+ static function () use ( $c ) {
+ if ( is_admin() ) {
+ return;
+ }
+ $button = $c->get( 'applepay.button' );
+
+ /**
+ * The Button.
+ *
+ * @var ButtonInterface $button
+ */
+ $button->render();
+ }
+ );
+ }
+
+ /**
+ * Handles the validation file.
+ *
+ * @param ContainerInterface $c The container.
+ * @return void
+ */
+ public function handle_validation_file( ContainerInterface $c ): void {
+ $env = $c->get( 'onboarding.environment' );
+ assert( $env instanceof Environment );
+ $is_sandobx = $env->current_environment_is( Environment::SANDBOX );
+ $this->load_domain_association_file( $is_sandobx );
+ }
+
+ /**
+ * Returns the validation string, depending on the environment.
+ *
+ * @param bool $is_sandbox The environment for this merchant.
+ * @return string
+ */
+ public function validation_string( bool $is_sandbox ) {
+ $sandbox_string = $this->sandbox_validation_string();
+ $live_string = $this->live_validation_string();
+ return $is_sandbox ? $sandbox_string : $live_string;
+ }
+
+ /**
+ * Returns the sandbox validation string.
+ *
+ * @return string
+ */
+ private function sandbox_validation_string(): string {
+ return apply_filters(
+ 'woocommerce_paypal_payments_applepay_sandbox_validation_string',
+ '7B227073704964223A2241443631324543383841333039314132314539434132433035304439454130353741414535444341304542413237424243333838463239344231353534434233222C2276657273696F6E223A312C22637265617465644F6E223A313632343438393037393630362C227369676E6174757265223A2233303830303630393261383634383836663730643031303730326130383033303830303230313031333130663330306430363039363038363438303136353033303430323031303530303330383030363039326138363438383666373064303130373031303030306130383033303832303365343330383230333862613030333032303130323032303835396438613162636161663465336364333030613036303832613836343863653364303430333032333037613331326533303263303630333535303430333063323534313730373036633635323034313730373036633639363336313734363936663665323034393665373436353637373236313734363936663665323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303165313730643332333133303334333233303331333933333337333033303561313730643332333633303334333133393331333933333336333533393561333036323331323833303236303630333535303430333063316636353633363332643733366437303264363237323666366236353732326437333639363736653566353534333334326435333431346534343432346635383331313433303132303630333535303430623063306236393466353332303533373937333734363536643733333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303539333031333036303732613836343863653364303230313036303832613836343863653364303330313037303334323030303438323330666461626333396366373565323032633530643939623435313265363337653261393031646436636233653062316364346235323637393866386366346562646538316132356138633231653463333364646365386532613936633266366166613139333033343563346538376134343236636539353162313239356133383230323131333038323032306433303063303630333535316431333031303166663034303233303030333031663036303335353164323330343138333031363830313432336632343963343466393365346566323765366334663632383663336661326262666432653462333034353036303832623036303130353035303730313031303433393330333733303335303630383262303630313035303530373330303138363239363837343734373033613266326636663633373337303265363137303730366336353265363336663664326636663633373337303330333432643631373037303663363536313639363336313333333033323330383230313164303630333535316432303034383230313134333038323031313033303832303130633036303932613836343838366637363336343035303133303831666533303831633330363038326230363031303530353037303230323330383162363063383162333532363536633639363136653633363532303666366532303734363836393733323036333635373237343639363636393633363137343635323036323739323036313665373932303730363137323734373932303631373337333735366436353733323036313633363336353730373436313665363336353230366636363230373436383635323037343638363536653230363137303730366336393633363136323663363532303733373436313665363436313732363432303734363537323664373332303631366536343230363336663665363436393734363936663665373332303666363632303735373336353263323036333635373237343639363636393633363137343635323037303666366336393633373932303631366536343230363336353732373436393636363936333631373436393666366532303730373236313633373436393633363532303733373436313734363536643635366537343733326533303336303630383262303630313035303530373032303131363261363837343734373033613266326637373737373732653631373037303663363532653633366636643266363336353732373436393636363936333631373436353631373537343638366637323639373437393266333033343036303335353164316630343264333032623330323961303237613032353836323336383734373437303361326632663633373236633265363137303730366336353265363336663664326636313730373036633635363136393633363133333265363337323663333031643036303335353164306530343136303431343032323433303062396165656564343633313937613461363561323939653432373138323163343533303065303630333535316430663031303166663034303430333032303738303330306630363039326138363438383666373633363430363164303430323035303033303061303630383261383634386365336430343033303230333437303033303434303232303734613162333234646234323439343330646433323734633530373463343830386439613166343830653361383563356331333632353636333235666263613330323230363933363930353361626635306235613532663966363030346463353861616436633530613764363038363833373930653061373361643031653461643938313330383230326565333038323032373561303033303230313032303230383439366432666266336139386461393733303061303630383261383634386365336430343033303233303637333131623330313930363033353530343033306331323431373037303663363532303532366636663734323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303165313730643331333433303335333033363332333333343336333333303561313730643332333933303335333033363332333333343336333333303561333037613331326533303263303630333535303430333063323534313730373036633635323034313730373036633639363336313734363936663665323034393665373436353637373236313734363936663665323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303539333031333036303732613836343863653364303230313036303832613836343863653364303330313037303334323030303466303137313138343139643736343835643531613565323538313037373665383830613265666465376261653464653038646663346239336531333335366435363635623335616532326430393737363064323234653762626130386664373631376365383863623736626236363730626563386538323938346666353434356133383166373330383166343330343630363038326230363031303530353037303130313034336133303338333033363036303832623036303130353035303733303031383632613638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635373236663666373436333631363733333330316430363033353531643065303431363034313432336632343963343466393365346566323765366334663632383663336661326262666432653462333030663036303335353164313330313031666630343035333030333031303166663330316630363033353531643233303431383330313638303134626262306465613135383333383839616134386139396465626562646562616664616362323461623330333730363033353531643166303433303330326533303263613032616130323838363236363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353732366636663734363336313637333332653633373236633330306530363033353531643066303130316666303430343033303230313036333031303036306132613836343838366637363336343036303230653034303230353030333030613036303832613836343863653364303430333032303336373030333036343032333033616366373238333531313639396231383666623335633335366361363262666634313765646439306637353464613238656265663139633831356534326237383966383938663739623539396639386435343130643866396465396332666530323330333232646435343432316230613330353737366335646633333833623930363766643137376332633231366439363466633637323639383231323666353466383761376431623939636239623039383932313631303639393066303939323164303030303331383230313863333038323031383830323031303133303831383633303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333032303835396438613162636161663465336364333030643036303936303836343830313635303330343032303130353030613038313935333031383036303932613836343838366637306430313039303333313062303630393261383634383836663730643031303730313330316330363039326138363438383666373064303130393035333130663137306433323331333033363332333333323332333533373335333935613330326130363039326138363438383666373064303130393334333131643330316233303064303630393630383634383031363530333034303230313035303061313061303630383261383634386365336430343033303233303266303630393261383634383836663730643031303930343331323230343230343239323932333766306638303764346538373932333134643438393635623165626262633038636265386333333432643365643261333939623963336538353330306130363038326138363438636533643034303330323034343733303435303232313030643430333138326637626530396663386265393738316463646461613434623332663362353634386566353666323664323933363738343237393933616530383032323037646663306563316361306461343531653464663031386236376538326231366330313065313930373431363762666632363839356230336563336430396134303030303030303030303030227D'
+ );
+ }
+
+ /**
+ * Returns the live validation string.
+ *
+ * @return string
+ */
+ private function live_validation_string(): string {
+ return apply_filters(
+ 'woocommerce_paypal_payments_applepay_live_validation_string',
+ '7B227073704964223A2246354246304143324336314131413238313043373531453439333444414433384346393037313041303935303844314133453241383436314141363232414145222C2276657273696F6E223A312C22637265617465644F6E223A313633343737323736393531342C227369676E6174757265223A223330383030363039326138363438383666373064303130373032613038303330383030323031303133313066333030643036303936303836343830313635303330343032303130353030333038303036303932613836343838366637306430313037303130303030613038303330383230336533333038323033383861303033303230313032303230383463333034313439353139643534333633303061303630383261383634386365336430343033303233303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330316531373064333133393330333533313338333033313333333233353337356131373064333233343330333533313336333033313333333233353337356133303566333132353330323330363033353530343033306331633635363336333264373336643730326436323732366636623635373232643733363936373665356635353433333432643530353234663434333131343330313230363033353530343062306330623639346635333230353337393733373436353664373333313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333330353933303133303630373261383634386365336430323031303630383261383634386365336430333031303730333432303030346332313537376564656264366337623232313866363864643730393061313231386463376230626436663263323833643834363039356439346166346135343131623833343230656438313166333430376538333333316631633534633366376562333232306436626164356434656666343932383938393365376330663133613338323032313133303832303230643330306330363033353531643133303130316666303430323330303033303166303630333535316432333034313833303136383031343233663234396334346639336534656632376536633466363238366333666132626266643265346233303435303630383262303630313035303530373031303130343339333033373330333530363038326230363031303530353037333030313836323936383734373437303361326632663666363337333730326536313730373036633635326536333666366432663666363337333730333033343264363137303730366336353631363936333631333333303332333038323031316430363033353531643230303438323031313433303832303131303330383230313063303630393261383634383836663736333634303530313330383166653330383163333036303832623036303130353035303730323032333038316236306338316233353236353663363936313665363336353230366636653230373436383639373332303633363537323734363936363639363336313734363532303632373932303631366537393230373036313732373437393230363137333733373536643635373332303631363336333635373037343631366536333635323036663636323037343638363532303734363836353665323036313730373036633639363336313632366336353230373337343631366536343631373236343230373436353732366437333230363136653634323036333666366536343639373436393666366537333230366636363230373537333635326332303633363537323734363936363639363336313734363532303730366636633639363337393230363136653634323036333635373237343639363636393633363137343639366636653230373037323631363337343639363336353230373337343631373436353664363536653734373332653330333630363038326230363031303530353037303230313136326136383734373437303361326632663737373737373265363137303730366336353265363336663664326636333635373237343639363636393633363137343635363137353734363836663732363937343739326633303334303630333535316431663034326433303262333032396130323761303235383632333638373437343730336132663266363337323663326536313730373036633635326536333666366432663631373037303663363536313639363336313333326536333732366333303164303630333535316430653034313630343134393435376462366664353734383138363839383937363266376535373835303765373962353832343330306530363033353531643066303130316666303430343033303230373830333030663036303932613836343838366637363336343036316430343032303530303330306130363038326138363438636533643034303330323033343930303330343630323231303062653039353731666537316531653733356235356535616661636234633732666562343435663330313835323232633732353130303262363165626436663535303232313030643138623335306135646436646436656231373436303335623131656232636538376366613365366166366362643833383038393064633832636464616136333330383230326565333038323032373561303033303230313032303230383439366432666266336139386461393733303061303630383261383634386365336430343033303233303637333131623330313930363033353530343033306331323431373037303663363532303532366636663734323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303165313730643331333433303335333033363332333333343336333333303561313730643332333933303335333033363332333333343336333333303561333037613331326533303263303630333535303430333063323534313730373036633635323034313730373036633639363336313734363936663665323034393665373436353637373236313734363936663665323034333431323032643230343733333331323633303234303630333535303430623063316434313730373036633635323034333635373237343639363636393633363137343639366636653230343137353734363836663732363937343739333131333330313130363033353530343061306330613431373037303663363532303439366536333265333130623330303930363033353530343036313330323535353333303539333031333036303732613836343863653364303230313036303832613836343863653364303330313037303334323030303466303137313138343139643736343835643531613565323538313037373665383830613265666465376261653464653038646663346239336531333335366435363635623335616532326430393737363064323234653762626130386664373631376365383863623736626236363730626563386538323938346666353434356133383166373330383166343330343630363038326230363031303530353037303130313034336133303338333033363036303832623036303130353035303733303031383632613638373437343730336132663266366636333733373032653631373037303663363532653633366636643266366636333733373033303334326436313730373036633635373236663666373436333631363733333330316430363033353531643065303431363034313432336632343963343466393365346566323765366334663632383663336661326262666432653462333030663036303335353164313330313031666630343035333030333031303166663330316630363033353531643233303431383330313638303134626262306465613135383333383839616134386139396465626562646562616664616362323461623330333730363033353531643166303433303330326533303263613032616130323838363236363837343734373033613266326636333732366332653631373037303663363532653633366636643266363137303730366336353732366636663734363336313637333332653633373236633330306530363033353531643066303130316666303430343033303230313036333031303036306132613836343838366637363336343036303230653034303230353030333030613036303832613836343863653364303430333032303336373030333036343032333033616366373238333531313639396231383666623335633335366361363262666634313765646439306637353464613238656265663139633831356534326237383966383938663739623539396639386435343130643866396465396332666530323330333232646435343432316230613330353737366335646633333833623930363766643137376332633231366439363466633637323639383231323666353466383761376431623939636239623039383932313631303639393066303939323164303030303331383230313863333038323031383830323031303133303831383633303761333132653330326330363033353530343033306332353431373037303663363532303431373037303663363936333631373436393666366532303439366537343635363737323631373436393666366532303433343132303264323034373333333132363330323430363033353530343062306331643431373037303663363532303433363537323734363936363639363336313734363936663665323034313735373436383666373236393734373933313133333031313036303335353034306130633061343137303730366336353230343936653633326533313062333030393036303335353034303631333032353535333032303834633330343134393531396435343336333030643036303936303836343830313635303330343032303130353030613038313935333031383036303932613836343838366637306430313039303333313062303630393261383634383836663730643031303730313330316330363039326138363438383666373064303130393035333130663137306433323331333133303332333033323333333333323334333935613330326130363039326138363438383666373064303130393334333131643330316233303064303630393630383634383031363530333034303230313035303061313061303630383261383634386365336430343033303233303266303630393261383634383836663730643031303930343331323230343230623935666665303261316539316665656565396330623239616361656661643465333031396331666237626238313665366631623762343233346666306533353330306130363038326138363438636533643034303330323034343733303435303232303665356233363937366364383733653632623339326330353136633134326362356639303938663330323535656435343938633436393039356133636462346430323231303038396261626335356162626635653037393163633139373562306535383630633937336532336661313266643338343533303930353938343061326363363337303030303030303030303030227D'
+ );
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/ApplePayButton.php b/modules/ppcp-applepay/src/Assets/ApplePayButton.php
new file mode 100644
index 000000000..fe7313a85
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/ApplePayButton.php
@@ -0,0 +1,1083 @@
+settings = $settings;
+ $this->response_templates = new ResponsesToApple();
+ $this->logger = $logger;
+ $this->id = 'applepay';
+ $this->method_title = __( 'Apple Pay', 'woocommerce-paypal-payments' );
+ $this->order_processor = $order_processor;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->script_data = $data;
+ $this->settings_status = $settings_status;
+ $this->cart_products = $cart_products;
+ }
+
+ /**
+ * Initializes the class hooks.
+ */
+ public function initialize(): void {
+ add_filter( 'ppcp_onboarding_options', array( $this, 'add_apple_onboarding_option' ), 10, 1 );
+ add_filter(
+ 'ppcp_partner_referrals_option',
+ function ( array $option ): array {
+ if ( $option['valid'] ) {
+ return $option;
+ }
+ if ( $option['field'] === 'ppcp-onboarding-apple' ) {
+ $option['valid'] = true;
+ $option['value'] = ( $option['value'] ? '1' : '' );
+ }
+ return $option;
+ }
+ );
+ add_filter(
+ 'ppcp_partner_referrals_data',
+ function ( array $data ): array {
+ try {
+ $onboard_with_apple = $this->settings->get( 'ppcp-onboarding-apple' );
+ if ( $onboard_with_apple !== '1' ) {
+ return $data;
+ }
+ } catch ( NotFoundException $exception ) {
+ return $data;
+ }
+
+ if ( in_array( 'PPCP', $data['products'], true ) ) {
+ $data['products'][] = 'PAYMENT_METHODS';
+ } elseif ( in_array( 'EXPRESS_CHECKOUT', $data['products'], true ) ) {
+ $data['products'][0] = 'PAYMENT_METHODS';
+ }
+ $data['capabilities'][] = 'APPLE_PAY';
+
+ return $data;
+ }
+ );
+ }
+
+ /**
+ * Adds the ApplePay onboarding option.
+ *
+ * @param string $options The options.
+ *
+ * @return string
+ */
+ public function add_apple_onboarding_option( $options ): string {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_apple_pay_onboarding_option', false ) ) {
+ return $options;
+ }
+
+ $checked = '';
+ try {
+ $onboard_with_apple = $this->settings->get( 'ppcp-onboarding-apple' );
+ if ( $onboard_with_apple === '1' ) {
+ $checked = 'checked';
+ }
+ } catch ( NotFoundException $exception ) {
+ $checked = '';
+ }
+
+ return $options . ' ' .
+ __( 'Onboard with ApplePay', 'woocommerce-paypal-payments' ) . '
+ ';
+
+ }
+
+ /**
+ * Adds all the Ajax actions to perform the whole workflow
+ */
+ public function bootstrap_ajax_request(): void {
+ add_action(
+ 'wp_ajax_' . PropertiesDictionary::VALIDATE,
+ array( $this, 'validate' )
+ );
+ add_action(
+ 'wp_ajax_nopriv_' . PropertiesDictionary::VALIDATE,
+ array( $this, 'validate' )
+ );
+ add_action(
+ 'wp_ajax_' . PropertiesDictionary::CREATE_ORDER,
+ array( $this, 'create_wc_order' )
+ );
+ add_action(
+ 'wp_ajax_nopriv_' . PropertiesDictionary::CREATE_ORDER,
+ array( $this, 'create_wc_order' )
+ );
+ add_action(
+ 'wp_ajax_' . PropertiesDictionary::UPDATE_SHIPPING_CONTACT,
+ array( $this, 'update_shipping_contact' )
+ );
+ add_action(
+ 'wp_ajax_nopriv_' . PropertiesDictionary::UPDATE_SHIPPING_CONTACT,
+ array( $this, 'update_shipping_contact' )
+ );
+ add_action(
+ 'wp_ajax_' . PropertiesDictionary::UPDATE_SHIPPING_METHOD,
+ array( $this, 'update_shipping_method' )
+ );
+ add_action(
+ 'wp_ajax_nopriv_' . PropertiesDictionary::UPDATE_SHIPPING_METHOD,
+ array( $this, 'update_shipping_method' )
+ );
+ }
+
+ /**
+ * Method to validate the merchant in the db flag
+ * On fail triggers and option that shows an admin notice showing the error
+ * On success removes such flag
+ */
+ public function validate(): void {
+ $applepay_request_data_object = $this->applepay_data_object_http();
+ if ( ! $this->is_nonce_valid() ) {
+ return;
+ }
+ $applepay_request_data_object->validation_data();
+ $settings = $this->settings;
+ $settings->set( 'applepay_validated', $applepay_request_data_object->validated_flag() );
+ $settings->persist();
+ wp_send_json_success();
+ }
+ /**
+ * Method to validate and update the shipping contact of the user
+ * It updates the amount paying information if needed
+ * On error returns an array of errors to be handled by the script
+ * On success returns the new contact data
+ */
+ public function update_shipping_contact(): void {
+ $applepay_request_data_object = $this->applepay_data_object_http();
+ if ( ! $this->is_nonce_valid() ) {
+ return;
+ }
+ $applepay_request_data_object->update_contact_data();
+ if ( $applepay_request_data_object->has_errors() ) {
+ $this->response_templates->response_with_data_errors( $applepay_request_data_object->errors() );
+ return;
+ }
+
+ if ( ! class_exists( 'WC_Countries' ) ) {
+ return;
+ }
+
+ $countries = $this->create_wc_countries();
+ $allowed_selling_countries = $countries->get_allowed_countries();
+ $allowed_shipping_countries = $countries->get_shipping_countries();
+ $user_country = $applepay_request_data_object->simplified_contact()['country'];
+ $is_allowed_selling_country = array_key_exists(
+ $user_country,
+ $allowed_selling_countries
+ );
+
+ $is_allowed_shipping_country = array_key_exists(
+ $user_country,
+ $allowed_shipping_countries
+ );
+ $product_need_shipping = $applepay_request_data_object->need_shipping();
+
+ if ( ! $is_allowed_selling_country ) {
+ $this->response_templates->response_with_data_errors(
+ array( array( 'errorCode' => 'addressUnserviceable' ) )
+ );
+ return;
+ }
+ if ( $product_need_shipping && ! $is_allowed_shipping_country ) {
+ $this->response_templates->response_with_data_errors(
+ array( array( 'errorCode' => 'addressUnserviceable' ) )
+ );
+ return;
+ }
+ try {
+ $payment_details = $this->which_calculate_totals( $applepay_request_data_object );
+ if ( ! is_array( $payment_details ) ) {
+ $this->response_templates->response_with_data_errors(
+ array(
+ array(
+ 'errorCode' => 'addressUnserviceable',
+ 'message' => __( 'Error processing cart', 'woocommerce-paypal-payments' ),
+ ),
+ )
+ );
+ return;
+ }
+ $response = $this->response_templates->apple_formatted_response( $payment_details );
+ $this->response_templates->response_success( $response );
+ } catch ( \Exception $e ) {
+ $this->response_templates->response_with_data_errors(
+ array(
+ array(
+ 'errorCode' => 'addressUnserviceable',
+ 'message' => $e->getMessage(),
+ ),
+ )
+ );
+ }
+ }
+
+ /**
+ * Method to validate and update the shipping method selected by the user
+ * It updates the amount paying information if needed
+ * On error returns an array of errors to be handled by the script
+ * On success returns the new contact data
+ */
+ public function update_shipping_method(): void {
+ $applepay_request_data_object = $this->applepay_data_object_http();
+ if ( ! $this->is_nonce_valid() ) {
+ return;
+ }
+ $applepay_request_data_object->update_method_data();
+ if ( $applepay_request_data_object->has_errors() ) {
+ $this->response_templates->response_with_data_errors( $applepay_request_data_object->errors() );
+ }
+ try {
+ $payment_details = $this->which_calculate_totals( $applepay_request_data_object );
+ if ( ! is_array( $payment_details ) ) {
+ $this->response_templates->response_with_data_errors(
+ array(
+ array(
+ 'errorCode' => 'addressUnserviceable',
+ 'message' => __( 'Error processing cart', 'woocommerce-paypal-payments' ),
+ ),
+ )
+ );
+ return;
+ }
+ $response = $this->response_templates->apple_formatted_response( $payment_details );
+ $this->response_templates->response_success( $response );
+ } catch ( \Exception $e ) {
+ $this->response_templates->response_with_data_errors(
+ array(
+ array(
+ 'errorCode' => 'addressUnserviceable',
+ 'message' => $e->getMessage(),
+ ),
+ )
+ );
+ }
+ }
+
+ /**
+ * Method to create a WC order from the data received from the ApplePay JS
+ * 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.
+ */
+ public function create_wc_order(): void {
+ $applepay_request_data_object = $this->applepay_data_object_http();
+ //phpcs:disable WordPress.Security.NonceVerification
+ $context = wc_clean( wp_unslash( $_POST['caller_page'] ?? '' ) );
+ if ( ! is_string( $context ) ) {
+ $this->response_templates->response_with_data_errors(
+ array(
+ array(
+ 'errorCode' => 'unableToProcess',
+ 'message' => 'Unable to process the order',
+ ),
+ )
+ );
+ return;
+ }
+ $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(
+ array(
+ 'errorCode' => 'unableToProcess',
+ 'message' => 'Unable to process the order',
+ ),
+ )
+ );
+ return;
+ }
+ 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;
+ }
+ );
+ }
+ $this->add_addresses_to_order( $applepay_request_data_object );
+ WC()->checkout()->process_checkout();
+ }
+
+
+ /**
+ * Checks if the nonce in the data object is valid
+ *
+ * @return bool|int
+ */
+ 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(
+ $nonce,
+ 'woocommerce-process_checkout'
+ ) === 1;
+ }
+
+ /**
+ * Data Object to collect and validate all needed data collected
+ * through HTTP
+ */
+ protected function applepay_data_object_http(): ApplePayDataObjectHttp {
+ return new ApplePayDataObjectHttp( $this->logger );
+ }
+
+ /**
+ * Returns a WC_Countries instance to check shipping
+ *
+ * @return \WC_Countries
+ */
+ protected function create_wc_countries(): \WC_Countries {
+ return new \WC_Countries();
+ }
+
+ /**
+ * Selector between product detail and cart page calculations
+ *
+ * @param ApplePayDataObjectHttp $applepay_request_data_object The data object.
+ *
+ * @return array|bool
+ * @throws Exception If cannot be added to cart.
+ */
+ protected function which_calculate_totals(
+ $applepay_request_data_object
+ ) {
+ $address = empty( $applepay_request_data_object->shipping_address() ) ? $applepay_request_data_object->simplified_contact() : $applepay_request_data_object->shipping_address();
+ if ( $applepay_request_data_object->caller_page() === 'productDetail' ) {
+ $cart_item_key = $this->prepare_cart( $applepay_request_data_object );
+ $cart = WC()->cart;
+
+ $totals = $this->calculate_totals_single_product(
+ $cart,
+ $address,
+ $applepay_request_data_object->shipping_method()
+ );
+ if ( is_string( $cart_item_key ) ) {
+ $this->clear_current_cart( $cart, $cart_item_key );
+ $this->reload_cart( $cart );
+ }
+ return $totals;
+ }
+ if ( $applepay_request_data_object->caller_page() === 'cart' ) {
+ return $this->calculate_totals_cart_page(
+ $address,
+ $applepay_request_data_object->shipping_method()
+ );
+ }
+ return false;
+ }
+
+ /**
+ * Calculates totals for the product with the given information
+ * Saves the previous cart to reload it after calculations
+ * If no shippingMethodId provided will return the first available shipping
+ * method
+ *
+ * @param WC_Cart $cart The cart.
+ * @param array $customer_address customer address to use.
+ * @param array|null $shipping_method shipping method to use.
+ */
+ protected function calculate_totals_single_product(
+ $cart,
+ $customer_address,
+ $shipping_method = null
+ ): array {
+ $results = array();
+ try {
+ // I just care about apple address details.
+ $shipping_method_id = '';
+ $shipping_methods_array = array();
+ $selected_shipping_method = array();
+ $this->customer_address( $customer_address );
+ if ( $shipping_method ) {
+ $shipping_method_id = $shipping_method['identifier'];
+ WC()->session->set(
+ 'chosen_shipping_methods',
+ array( $shipping_method_id )
+ );
+ }
+ if ( $cart->needs_shipping() ) {
+ list(
+ $shipping_methods_array, $selected_shipping_method
+ ) = $this->cart_shipping_methods(
+ $cart,
+ $customer_address,
+ $shipping_method,
+ $shipping_method_id
+ );
+ }
+ $cart->calculate_shipping();
+ $cart->calculate_fees();
+ $cart->calculate_totals();
+
+ $results = $this->cart_calculation_results(
+ $cart,
+ $selected_shipping_method,
+ $shipping_methods_array
+ );
+ } catch ( Exception $exception ) {
+ return array();
+ }
+ return $results;
+ }
+
+ /**
+ * Sets the customer address with ApplePay details to perform correct
+ * calculations
+ * If no parameter passed then it resets the customer to shop details
+ *
+ * @param array $address customer address.
+ */
+ protected function customer_address( array $address = array() ): void {
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'];
+ WC()->customer->set_shipping_country(
+ $address['country'] ?? $shop_country_code
+ );
+ WC()->customer->set_billing_country(
+ $address['country'] ?? $shop_country_code
+ );
+ WC()->customer->set_shipping_postcode(
+ $address['postcode'] ?? $shop_country_code
+ );
+ WC()->customer->set_shipping_city(
+ $address['city'] ?? $shop_country_code
+ );
+ }
+
+ /**
+ * Add shipping methods to cart to perform correct calculations
+ *
+ * @param WC_Cart $cart WC Cart instance.
+ * @param array $customer_address Customer address.
+ * @param array|null $shipping_method Shipping method.
+ * @param string $shipping_method_id Shipping method id.
+ */
+ protected function cart_shipping_methods(
+ $cart,
+ $customer_address,
+ $shipping_method = null,
+ $shipping_method_id = ''
+ ): array {
+
+ $shipping_methods_array = array();
+ $shipping_methods = WC()->shipping->calculate_shipping(
+ $this->getShippingPackages(
+ $customer_address,
+ $cart->get_total( 'edit' )
+ )
+ );
+ $done = false;
+ foreach ( $shipping_methods[0]['rates'] as $rate ) {
+ $shipping_methods_array[] = array(
+ 'label' => $rate->get_label(),
+ 'detail' => '',
+ 'amount' => $rate->get_cost(),
+ 'identifier' => $rate->get_id(),
+ );
+ if ( ! $done ) {
+ $done = true;
+ $shipping_method_id = $shipping_method ? $shipping_method_id
+ : $rate->get_id();
+ WC()->session->set(
+ 'chosen_shipping_methods',
+ array( $shipping_method_id )
+ );
+ }
+ }
+
+ $selected_shipping_method = $shipping_methods_array[0];
+ if ( $shipping_method ) {
+ $selected_shipping_method = $shipping_method;
+ }
+
+ return array( $shipping_methods_array, $selected_shipping_method );
+ }
+
+ /**
+ * Sets shipping packages for correct calculations
+ *
+ * @param array $customer_address ApplePay address details.
+ * @param float $total Total amount of the cart.
+ *
+ * @return mixed|void|null
+ */
+ protected function getShippingPackages( $customer_address, $total ) {
+ // Packages array for storing 'carts'.
+ $packages = array();
+ $packages[0]['contents'] = WC()->cart->cart_contents;
+ $packages[0]['contents_cost'] = $total;
+ $packages[0]['applied_coupons'] = WC()->session->applied_coupon;
+ $packages[0]['destination']['country'] = $customer_address['country'] ?? '';
+ $packages[0]['destination']['state'] = '';
+ $packages[0]['destination']['postcode'] = $customer_address['postcode'] ?? '';
+ $packages[0]['destination']['city'] = $customer_address['city'] ?? '';
+ $packages[0]['destination']['address'] = '';
+ $packages[0]['destination']['address_2'] = '';
+
+ return apply_filters( 'woocommerce_cart_shipping_packages', $packages );
+ }
+
+ /**
+ * Returns the formatted results of the cart calculations
+ *
+ * @param WC_Cart $cart WC Cart object.
+ * @param array $selected_shipping_method Selected shipping method.
+ * @param array $shipping_methods_array Shipping methods array.
+ */
+ protected function cart_calculation_results(
+ $cart,
+ $selected_shipping_method,
+ $shipping_methods_array
+ ): array {
+ $total = (float) $cart->get_total( 'edit' );
+ $total = round( $total, 2 );
+ return array(
+ 'subtotal' => $cart->get_subtotal(),
+ 'shipping' => array(
+ 'amount' => $cart->needs_shipping()
+ ? $cart->get_shipping_total() : null,
+ 'label' => $cart->needs_shipping()
+ ? $selected_shipping_method['label'] : null,
+ ),
+
+ 'shippingMethods' => $cart->needs_shipping()
+ ? $shipping_methods_array : null,
+ 'taxes' => $cart->get_total_tax(),
+ 'total' => $total,
+ );
+ }
+
+ /**
+ * Calculates totals for the cart page with the given information
+ * If no shippingMethodId provided will return the first available shipping
+ * method
+ *
+ * @param array $customer_address The customer address.
+ * @param array|null $shipping_method The shipping method.
+ */
+ protected function calculate_totals_cart_page(
+ array $customer_address,
+ $shipping_method = null
+ ): array {
+
+ $results = array();
+ if ( WC()->cart->is_empty() ) {
+ return array();
+ }
+ try {
+ $shipping_methods_array = array();
+ $selected_shipping_method = array();
+ // I just care about apple address details.
+ $this->customer_address( $customer_address );
+ $cart = WC()->cart;
+ if ( $shipping_method ) {
+ WC()->session->set(
+ 'chosen_shipping_methods',
+ array( $shipping_method['identifier'] )
+ );
+ }
+
+ if ( $cart->needs_shipping() ) {
+ $shipping_method_id = $shipping_method['identifier'] ?? '';
+ list(
+ $shipping_methods_array, $selected_shipping_method
+ ) = $this->cart_shipping_methods(
+ $cart,
+ $customer_address,
+ $shipping_method,
+ $shipping_method_id
+ );
+ }
+ $cart->calculate_shipping();
+ $cart->calculate_fees();
+ $cart->calculate_totals();
+
+ $results = $this->cart_calculation_results(
+ $cart,
+ $selected_shipping_method,
+ $shipping_methods_array
+ );
+
+ $this->customer_address();
+ } catch ( Exception $e ) {
+ return array();
+ }
+
+ return $results;
+ }
+
+ /**
+ * Add address billing and shipping data to order
+ *
+ * @param ApplePayDataObjectHttp $applepay_request_data_object ApplePayDataObjectHttp.
+ */
+ protected function add_addresses_to_order(
+ ApplePayDataObjectHttp $applepay_request_data_object
+ ): void {
+ add_action(
+ 'woocommerce_checkout_create_order',
+ static function ( WC_Order $order, array $data ) use ( $applepay_request_data_object ) {
+ if ( ! empty( $applepay_request_data_object->shipping_method() ) ) {
+ $billing_address = $applepay_request_data_object->billing_address();
+ $shipping_address = $applepay_request_data_object->shipping_address();
+ // apple puts email in shipping_address while we get it from WC's billing_address.
+ $billing_address['email'] = $shipping_address['email'];
+ $billing_address['phone'] = $shipping_address['phone'];
+
+ $order->set_address( $billing_address, 'billing' );
+ $order->set_address( $shipping_address, 'shipping' );
+ }
+ },
+ 10,
+ 2
+ );
+ }
+ /**
+ * Empty the cart to use for calculations
+ * while saving its contents in a field
+ */
+ protected function save_old_cart(): void {
+ $cart = WC()->cart;
+ if ( $cart->is_empty() ) {
+ return;
+ }
+ $this->old_cart_contents = $cart->get_cart_contents();
+ foreach ( $this->old_cart_contents as $cart_item_key => $value ) {
+ $cart->remove_cart_item( $cart_item_key );
+ }
+ $this->reload_cart = true;
+ }
+
+ /**
+ * Reloads the previous cart contents
+ *
+ * @param WC_Cart $cart The cart to reload.
+ */
+ protected function reload_cart( WC_Cart $cart ): void {
+ if ( ! $this->reload_cart ) {
+ return;
+ }
+ foreach ( $this->old_cart_contents as $cart_item_key => $value ) {
+ $cart->restore_cart_item( $cart_item_key );
+ }
+ }
+
+ /**
+ * Clear the current cart
+ *
+ * @param WC_Cart|null $cart The cart object.
+ * @param string $cart_item_key The cart item key.
+ * @return void
+ */
+ public function clear_current_cart( ?WC_Cart $cart, string $cart_item_key ): void {
+ if ( ! $cart ) {
+ return;
+ }
+ $cart->remove_cart_item( $cart_item_key );
+ $this->customer_address();
+ }
+
+ /**
+ * Removes the old cart, saves it, and creates a new one
+ *
+ * @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.
+ */
+ public function prepare_cart( ApplePayDataObjectHttp $applepay_request_data_object ): string {
+ $this->save_old_cart();
+ $this->cart_products->set_cart( WC()->cart );
+
+ $product = $this->cart_products->product_from_data(
+ array(
+ 'id' => (int) $applepay_request_data_object->product_id(),
+ 'quantity' => (int) $applepay_request_data_object->product_quantity(),
+ 'variations' => $applepay_request_data_object->product_variations(),
+ 'extra' => $applepay_request_data_object->product_extra(),
+ 'booking' => $applepay_request_data_object->product_booking(),
+ )
+ );
+
+ $this->cart_products->add_products( array( $product ) );
+ return $this->cart_products->cart_item_keys()[0];
+ }
+
+ /**
+ * Update the posted data to match the Apple Pay request data
+ *
+ * @param ApplePayDataObjectHttp $applepay_request_data_object The Apple Pay request data.
+ */
+ protected function update_posted_data( $applepay_request_data_object ): void {
+ add_filter(
+ 'woocommerce_checkout_posted_data',
+ function ( array $data ) use ( $applepay_request_data_object ): array {
+
+ $data['payment_method'] = 'ppcp-gateway';
+ $data['shipping_method'] = $applepay_request_data_object->shipping_method();
+ $data['billing_first_name'] = $applepay_request_data_object->billing_address()['first_name'] ?? '';
+ $data['billing_last_name'] = $applepay_request_data_object->billing_address()['last_name'] ?? '';
+ $data['billing_company'] = $applepay_request_data_object->billing_address()['company'] ?? '';
+ $data['billing_country'] = $applepay_request_data_object->billing_address()['country'] ?? '';
+ $data['billing_address_1'] = $applepay_request_data_object->billing_address()['address_1'] ?? '';
+ $data['billing_address_2'] = $applepay_request_data_object->billing_address()['address_2'] ?? '';
+ $data['billing_city'] = $applepay_request_data_object->billing_address()['city'] ?? '';
+ $data['billing_state'] = $applepay_request_data_object->billing_address()['state'] ?? '';
+ $data['billing_postcode'] = $applepay_request_data_object->billing_address()['postcode'] ?? '';
+
+ if ( ! empty( $applepay_request_data_object->shipping_method() ) ) {
+ $data['billing_email'] = $applepay_request_data_object->shipping_address()['email'] ?? '';
+ $data['billing_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? '';
+ $data['shipping_first_name'] = $applepay_request_data_object->shipping_address()['first_name'] ?? '';
+ $data['shipping_last_name'] = $applepay_request_data_object->shipping_address()['last_name'] ?? '';
+ $data['shipping_company'] = $applepay_request_data_object->shipping_address()['company'] ?? '';
+ $data['shipping_country'] = $applepay_request_data_object->shipping_address()['country'] ?? '';
+ $data['shipping_address_1'] = $applepay_request_data_object->shipping_address()['address_1'] ?? '';
+ $data['shipping_address_2'] = $applepay_request_data_object->shipping_address()['address_2'] ?? '';
+ $data['shipping_city'] = $applepay_request_data_object->shipping_address()['city'] ?? '';
+ $data['shipping_state'] = $applepay_request_data_object->shipping_address()['state'] ?? '';
+ $data['shipping_postcode'] = $applepay_request_data_object->shipping_address()['postcode'] ?? '';
+ $data['shipping_email'] = $applepay_request_data_object->shipping_address()['email'] ?? '';
+ $data['shipping_phone'] = $applepay_request_data_object->shipping_address()['phone'] ?? '';
+ }
+
+ return $data;
+ }
+ );
+ }
+
+ /**
+ * Renders the Apple Pay button on the page
+ *
+ * @return bool
+ *
+ * @psalm-suppress RedundantCondition
+ */
+ public function render(): bool {
+ if ( ! $this->is_enabled() ) {
+ return true;
+ }
+
+ $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_payorder = true;
+ $button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
+
+ add_filter(
+ 'woocommerce_paypal_payments_sdk_components_hook',
+ function( array $components ) {
+ $components[] = 'applepay';
+ return $components;
+ }
+ );
+ if ( $button_enabled_product ) {
+ $default_hookname = 'woocommerce_paypal_payments_single_product_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_applepay_render_hook_product', $default_hookname );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hookname;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->applepay_button();
+ }
+ );
+ }
+ if ( $button_enabled_cart ) {
+ $default_hook_name = 'woocommerce_paypal_payments_cart_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_cart_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->applepay_button();
+ }
+ );
+ }
+
+ if ( $button_enabled_checkout ) {
+ $default_hook_name = 'woocommerce_paypal_payments_checkout_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_checkout_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->applepay_button();
+ },
+ 21
+ );
+ }
+ if ( $button_enabled_payorder ) {
+ $default_hook_name = 'woocommerce_paypal_payments_payorder_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_payorder_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->applepay_button();
+ },
+ 21
+ );
+ }
+
+ if ( $button_enabled_minicart ) {
+ $default_hook_name = 'woocommerce_paypal_payments_minicart_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_minicart_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ echo ' ';
+ },
+ 21
+ );
+ }
+
+ return true;
+ }
+ /**
+ * ApplePay button markup
+ */
+ protected function applepay_button(): void {
+ ?>
+
+
+
+ module_url ) . '/assets/js/boot.js',
+ array(),
+ $this->version,
+ true
+ );
+ wp_enqueue_script( 'wc-ppcp-applepay' );
+
+ $this->enqueue_styles();
+
+ wp_localize_script(
+ 'wc-ppcp-applepay',
+ 'wc_ppcp_applepay',
+ $this->script_data()
+ );
+ add_action(
+ 'wp_enqueue_scripts',
+ function () {
+ wp_enqueue_script( 'wc-ppcp-applepay' );
+ }
+ );
+ }
+
+ /**
+ * Enqueues styles.
+ */
+ public function enqueue_styles(): void {
+ if ( ! $this->is_enabled() ) {
+ return;
+ }
+
+ wp_register_style(
+ 'wc-ppcp-applepay',
+ untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
+ array(),
+ $this->version
+ );
+ wp_enqueue_style( 'wc-ppcp-applepay' );
+ }
+
+ /**
+ * Returns the script data.
+ *
+ * @return array
+ */
+ public function script_data(): array {
+ return $this->script_data->apple_pay_script_data();
+ }
+
+ /**
+ * Returns true if the module is enabled.
+ *
+ * @return bool
+ */
+ public function is_enabled(): bool {
+ try {
+ return $this->settings->has( 'applepay_button_enabled' ) && $this->settings->get( 'applepay_button_enabled' );
+ } catch ( Exception $e ) {
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php
new file mode 100644
index 000000000..219e3c6d9
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/ApplePayDataObjectHttp.php
@@ -0,0 +1,740 @@
+logger = $logger;
+ }
+
+ /**
+ * Resets the errors array
+ */
+ protected function reset_errors(): void {
+ $this->errors = array();
+ }
+
+ /**
+ * Returns if the object has any errors
+ *
+ * @return bool
+ */
+ public function has_errors(): bool {
+ return ! empty( $this->errors );
+ }
+ /**
+ * Returns errors
+ *
+ * @return array
+ */
+ public function errors(): array {
+ return $this->errors;
+ }
+
+ /**
+ * Assigns the validation flag
+ *
+ * @return void
+ */
+ public function validation_data(): void {
+ $data = filter_input( INPUT_POST, 'validation', FILTER_VALIDATE_BOOL );
+ if ( ! $data ) {
+ return;
+ }
+ $this->validation_flag = $data;
+ }
+
+ /**
+ * Set the object with the data relevant to ApplePay on update shipping contact
+ * Required data depends on callerPage
+ */
+ public function update_contact_data(): void {
+ $nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
+ if ( ! $nonce ) {
+ return;
+ }
+ $is_nonce_valid = wp_verify_nonce(
+ $nonce,
+ 'woocommerce-process_checkout'
+ );
+ if ( ! $is_nonce_valid ) {
+ return;
+ }
+ $data = $this->get_filtered_request_data();
+ if ( ! $data ) {
+ return;
+ }
+
+ $data = $this->preprocess_request_data( $data );
+
+ $result = $this->update_required_data(
+ $data,
+ PropertiesDictionary::UPDATE_CONTACT_SINGLE_PROD_REQUIRED_FIELDS,
+ PropertiesDictionary::UPDATE_CONTACT_CART_REQUIRED_FIELDS
+ );
+ if ( ! $result ) {
+ return;
+ }
+ $this->update_simplified_contact( $data[ PropertiesDictionary::SIMPLIFIED_CONTACT ] );
+ }
+
+ /**
+ * Set the object with the data relevant to ApplePay on update shipping method
+ * Required data depends on callerPage
+ */
+ public function update_method_data(): void {
+ $nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
+ if ( ! $nonce ) {
+ return;
+ }
+ $is_nonce_valid = wp_verify_nonce(
+ $nonce,
+ 'woocommerce-process_checkout'
+ );
+ if ( ! $is_nonce_valid ) {
+ return;
+ }
+
+ $data = $this->get_filtered_request_data();
+ if ( ! $data ) {
+ return;
+ }
+
+ $data = $this->preprocess_request_data( $data );
+
+ $result = $this->update_required_data(
+ $data,
+ PropertiesDictionary::UPDATE_METHOD_SINGLE_PROD_REQUIRED_FIELDS,
+ PropertiesDictionary::UPDATE_METHOD_CART_REQUIRED_FIELDS
+ );
+ if ( ! $result ) {
+ return;
+ }
+ $this->update_simplified_contact( $data[ PropertiesDictionary::SIMPLIFIED_CONTACT ] );
+ $this->update_shipping_method( $data );
+ }
+
+ /**
+ * Set the object with the data relevant to ApplePay on authorized order
+ * Required data depends on callerPage
+ *
+ * @param string $caller_page The caller page.
+ */
+ public function order_data( string $caller_page ): void {
+
+ if ( ! $this->is_nonce_valid() ) {
+ return;
+ }
+ //phpcs:disable WordPress.Security.NonceVerification
+ $data = filter_var_array( $_POST, FILTER_SANITIZE_SPECIAL_CHARS );
+ if ( ! $data ) {
+ return;
+ }
+
+ $data = $this->append_products_to_data( $data, $_POST );
+ $data = $this->preprocess_request_data( $data );
+
+ $data[ PropertiesDictionary::CALLER_PAGE ] = $caller_page;
+ $result = $this->update_required_data(
+ $data,
+ PropertiesDictionary::CREATE_ORDER_SINGLE_PROD_REQUIRED_FIELDS,
+ PropertiesDictionary::CREATE_ORDER_CART_REQUIRED_FIELDS
+ );
+
+ if ( ! $result ) {
+ return;
+ }
+ if (
+ ! array_key_exists( 'emailAddress', $data[ PropertiesDictionary::SHIPPING_CONTACT ] )
+ || ! $data[ PropertiesDictionary::SHIPPING_CONTACT ]['emailAddress']
+ ) {
+ $this->errors[] = array(
+ 'errorCode' => PropertiesDictionary::SHIPPING_CONTACT_INVALID,
+ 'contactField' => 'emailAddress',
+ );
+
+ return;
+ }
+
+ $filtered_shipping_contact = $data[ PropertiesDictionary::SHIPPING_CONTACT ];
+ $this->shipping_contact = $this->complete_address(
+ $filtered_shipping_contact,
+ PropertiesDictionary::SHIPPING_CONTACT_INVALID
+ );
+ $filtered_billing_contact = $data[ PropertiesDictionary::BILLING_CONTACT ];
+ $this->billing_contact = $this->complete_address(
+ $filtered_billing_contact,
+ PropertiesDictionary::BILLING_CONTACT_INVALID
+ );
+ $this->update_shipping_method( $data );
+ }
+
+ /**
+ * Pre-processes request data to transform it to a standard format.
+ *
+ * @param array $data The data.
+ * @return array
+ */
+ protected function preprocess_request_data( array $data ): array {
+ // Fill product variables if a products object is received.
+ if ( is_array( $data[ PropertiesDictionary::PRODUCTS ] ?? null ) ) {
+ $product = $data[ PropertiesDictionary::PRODUCTS ][0];
+
+ $data[ PropertiesDictionary::PRODUCT_ID ] = $product['id'] ?? 0;
+ $data[ PropertiesDictionary::PRODUCT_QUANTITY ] = $product['quantity'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_VARIATIONS ] = $product['variations'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_EXTRA ] = $product['extra'] ?? array();
+ $data[ PropertiesDictionary::PRODUCT_BOOKING ] = $product['booking'] ?? array();
+ }
+ unset( $data[ PropertiesDictionary::PRODUCTS ] );
+ return $data;
+ }
+
+ /**
+ * Checks if the array contains all required fields and if those
+ * are not empty.
+ * If not it adds an unkown error to the object's error list, as this errors
+ * are not supported by ApplePay.
+ *
+ * @param array $data The data.
+ * @param array $required The required fields.
+ *
+ * @return bool
+ */
+ protected function has_required_fields_values_or_error( array $data, array $required ) {
+ foreach ( $required as $required_field ) {
+ if ( ! array_key_exists( $required_field, $data ) ) {
+ $this->logger->debug(
+ sprintf( 'ApplePay Data Error: Missing index %s', $required_field )
+ );
+
+ $this->errors[] = array( 'errorCode' => 'unknown' );
+ continue;
+ }
+ if ( $data[ $required_field ] === null || $data[ $required_field ] === '' ) {
+ $this->logger->debug(
+ sprintf( 'ApplePay Data Error: Missing value for %s', $required_field )
+ );
+ $this->errors[] = array( 'errorCode' => 'unknown' );
+ continue;
+ }
+ }
+ return ! $this->has_errors();
+ }
+
+ /**
+ * Sets the value to the appropriate field in the object.
+ *
+ * @param array $data The data.
+ */
+ protected function assign_data_object_values( array $data ): void {
+ foreach ( $data as $key => $value ) {
+ // Null values may give origin to type errors. If necessary replace this condition with a specialized field filter.
+ if ( null === $value ) {
+ continue;
+ }
+ if ( $key === 'woocommerce-process-checkout-nonce' ) {
+ $key = 'nonce';
+ }
+ $this->$key = $value;
+ }
+
+ }
+
+ /**
+ * Returns the address details used in pre-authorization steps.
+ *
+ * @param array $contact_info The contact info.
+ *
+ * @return string[]
+ */
+ protected function simplified_address( array $contact_info ) {
+ $required = array(
+ 'locality' => 'locality',
+ 'postalCode' => 'postalCode',
+ 'countryCode' => 'countryCode',
+ );
+ if (
+ ! $this->address_has_required_fields_values(
+ $contact_info,
+ $required,
+ PropertiesDictionary::SHIPPING_CONTACT_INVALID
+ )
+ ) {
+ return array();
+ }
+ return array(
+ 'city' => $contact_info['locality'],
+ 'postcode' => $contact_info['postalCode'],
+ 'country' => strtoupper( $contact_info['countryCode'] ),
+ );
+ }
+
+ /**
+ * Checks if the address array contains all required fields and if those
+ * are not empty.
+ * If not it adds a contacField error to the object's error list.
+ *
+ * @param array $post The address to check.
+ * @param array $required The required fields for the given address.
+ * @param string $error_code Either shipping or billing kind.
+ *
+ * @return bool
+ */
+ protected function address_has_required_fields_values(
+ array $post,
+ array $required,
+ string $error_code
+ ) {
+
+ foreach ( $required as $required_field => $error_value ) {
+ if ( ! array_key_exists( $required_field, $post ) ) {
+ $this->logger->debug(
+ sprintf( 'ApplePay Data Error: Missing index %s', $required_field )
+ );
+
+ $this->errors[] = array( 'errorCode' => 'unknown' );
+ continue;
+ }
+ if ( ! $post[ $required_field ] ) {
+ $this->logger->debug(
+ sprintf( 'ApplePay Data Error: Missing value for %s', $required_field )
+ );
+ $this->errors[]
+ = array(
+ 'errorCode' => $error_code,
+ 'contactField' => $error_value,
+ );
+ continue;
+ }
+ }
+ return ! $this->has_errors();
+ }
+
+ /**
+ * Returns the address details for after authorization steps.
+ *
+ * @param array $data The data.
+ * @param string $error_code differentiates between billing and shipping information.
+ *
+ * @return string[]
+ */
+ protected function complete_address( array $data, string $error_code ): array {
+ $required = array(
+ 'givenName' => 'name',
+ 'familyName' => 'name',
+ 'addressLines' => 'addressLines',
+ 'locality' => 'locality',
+ 'postalCode' => 'postalCode',
+ 'countryCode' => 'countryCode',
+ );
+ if (
+ ! $this->address_has_required_fields_values(
+ $data,
+ $required,
+ $error_code
+ )
+ ) {
+ return array();
+ }
+
+ return array(
+ 'first_name' => $data['givenName'],
+ 'last_name' => $data['familyName'],
+ 'email' => $data['emailAddress'] ?? '',
+ 'phone' => $data['phoneNumber'] ?? '',
+ 'address_1' => $data['addressLines'][0] ?? '',
+ 'address_2' => $data['addressLines'][1] ?? '',
+ 'city' => $data['locality'],
+ 'state' => $data['administrativeArea'],
+ 'postcode' => $data['postalCode'],
+ 'country' => strtoupper( $data['countryCode'] ),
+ );
+ }
+
+ /**
+ * Updates the object with the required data.
+ *
+ * @param array $data The data.
+ * @param array $required_product_fields The required product fields.
+ * @param array $required_cart_fields The required cart fields.
+ * @return bool
+ */
+ protected function update_required_data( array $data, array $required_product_fields, array $required_cart_fields ) {
+ $this->reset_errors();
+ $required_fields = $required_product_fields;
+ if (
+ isset( $data[ PropertiesDictionary::CALLER_PAGE ] )
+ && $data[ PropertiesDictionary::CALLER_PAGE ] !== 'product'
+ ) {
+ $required_fields = $required_cart_fields;
+ }
+ $has_required_fields_values = $this->has_required_fields_values_or_error(
+ $data,
+ $required_fields
+ );
+ if ( ! $has_required_fields_values ) {
+ return false;
+ }
+ $this->assign_data_object_values( $data );
+ return true;
+ }
+
+ /**
+ * Updates the data object with the contact values from the request.
+ *
+ * @param array $data The data.
+ * @return void
+ */
+ protected function update_simplified_contact( array $data ) : void {
+ $simplified_contact_info = array_map( 'sanitize_text_field', $data );
+ $this->simplified_contact = $this->simplified_address(
+ $simplified_contact_info
+ );
+ }
+
+ /**
+ * Updates the data object with the shipping values from the request.
+ *
+ * @param array $data The data.
+ * @return void
+ */
+ protected function update_shipping_method( array $data ): void {
+ if (
+ array_key_exists(
+ PropertiesDictionary::SHIPPING_METHOD,
+ $data
+ )
+ ) {
+ $this->shipping_method = filter_var_array(
+ $data[ PropertiesDictionary::SHIPPING_METHOD ],
+ FILTER_SANITIZE_SPECIAL_CHARS
+ );
+ }
+ }
+
+ /**
+ * Returns the billing address.
+ *
+ * @return string[]
+ */
+ public function billing_address(): array {
+ return $this->billing_contact;
+ }
+
+ /**
+ * Returns the shipping address.
+ *
+ * @return string[]
+ */
+ public function shipping_address(): array {
+ return $this->shipping_contact;
+ }
+
+ /**
+ * Returns the shipping method.
+ *
+ * @return array
+ */
+ public function shipping_method(): array {
+ return $this->shipping_method ?? array();
+ }
+
+ /**
+ * Returns if the shipping is needed.
+ *
+ * @return bool
+ */
+ public function need_shipping(): bool {
+ return $this->need_shipping ?? false;
+ }
+
+ /**
+ * Returns the product id.
+ *
+ * @return string
+ */
+ public function product_id(): string {
+ return $this->product_id;
+ }
+
+ /**
+ * Returns the product id.
+ *
+ * @return string
+ */
+ public function caller_page(): string {
+ return $this->caller_page;
+ }
+
+ /**
+ * Returns the product quantity.
+ *
+ * @return string
+ */
+ public function product_quantity(): string {
+ return $this->product_quantity;
+ }
+
+ /**
+ * Returns the product variations.
+ *
+ * @return array
+ */
+ public function product_variations(): array {
+ return $this->product_variations;
+ }
+
+ /**
+ * Returns the product extra.
+ *
+ * @return array
+ */
+ public function product_extra(): array {
+ return $this->product_extra;
+ }
+
+ /**
+ * Returns the product booking.
+ *
+ * @return array
+ */
+ public function product_booking(): array {
+ return $this->product_booking;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public function nonce(): string {
+ return $this->nonce;
+ }
+
+ /**
+ * Returns the simplified contact.
+ *
+ * @return mixed
+ */
+ public function simplified_contact() {
+ return $this->simplified_contact;
+ }
+
+ /**
+ * Returns the validated flag.
+ *
+ * @return bool
+ */
+ public function validated_flag() {
+ return $this->validation_flag;
+ }
+
+ /**
+ * Returns the filtered request data.
+ *
+ * @return array|false|null
+ */
+ public function get_filtered_request_data() {
+ $data = filter_input_array(
+ INPUT_POST,
+ array(
+ PropertiesDictionary::CALLER_PAGE => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'woocommerce-process-checkout-nonce' => FILTER_SANITIZE_SPECIAL_CHARS,
+ PropertiesDictionary::NEED_SHIPPING => FILTER_VALIDATE_BOOLEAN,
+ PropertiesDictionary::SIMPLIFIED_CONTACT => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::SHIPPING_CONTACT => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::BILLING_CONTACT => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::SHIPPING_METHOD => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::PRODUCT_ID => FILTER_SANITIZE_NUMBER_INT,
+ PropertiesDictionary::PRODUCT_QUANTITY => FILTER_SANITIZE_NUMBER_INT,
+ PropertiesDictionary::PRODUCT_VARIATIONS => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::PRODUCT_EXTRA => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ PropertiesDictionary::PRODUCT_BOOKING => array(
+ 'filter' => FILTER_SANITIZE_SPECIAL_CHARS,
+ 'flags' => FILTER_REQUIRE_ARRAY,
+ ),
+ )
+ );
+
+ if ( ! $data ) {
+ return false;
+ }
+
+ return $this->append_products_to_data( $data, $_POST );
+ }
+
+ /**
+ * Appends product to a data array.
+ *
+ * @param array $data The data.
+ * @param array $request_data The request data.
+ * @return array
+ */
+ public function append_products_to_data( array $data, array $request_data ): array {
+ $products = json_decode( wp_unslash( $request_data[ PropertiesDictionary::PRODUCTS ] ?? '' ), true );
+
+ if ( $products ) {
+ $data[ PropertiesDictionary::PRODUCTS ] = $products;
+ }
+
+ return $data;
+ }
+
+ /**
+ * Returns if the nonce is valid.
+ *
+ * @return bool
+ */
+ public function is_nonce_valid():bool {
+ $nonce = filter_input( INPUT_POST, 'woocommerce-process-checkout-nonce', FILTER_SANITIZE_SPECIAL_CHARS );
+ if ( ! $nonce ) {
+ return false;
+ }
+ return (bool) wp_verify_nonce(
+ $nonce,
+ 'woocommerce-process_checkout'
+ );
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/AppleProductStatus.php b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php
new file mode 100644
index 000000000..2c00ba59e
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/AppleProductStatus.php
@@ -0,0 +1,192 @@
+settings = $settings;
+ $this->partners_endpoint = $partners_endpoint;
+ $this->onboarding_state = $onboarding_state;
+ $this->api_failure_registry = $api_failure_registry;
+ }
+
+ /**
+ * Whether the active/subscribed products support Applepay.
+ *
+ * @return bool
+ */
+ public function is_active() : bool {
+
+ // If not onboarded then makes no sense to check status.
+ if ( ! $this->is_onboarded() ) {
+ return false;
+ }
+
+ // If status was already checked on this request return the same result.
+ if ( null !== $this->current_status ) {
+ return $this->current_status;
+ }
+
+ // Check if status was checked on previous requests.
+ if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
+ $this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
+ return $this->current_status;
+ }
+
+ // Check API failure registry to prevent multiple failed API requests.
+ if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) {
+ $this->has_request_failure = true;
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ // Request seller status via PayPal API.
+ try {
+ $seller_status = $this->partners_endpoint->seller_status();
+ } catch ( Throwable $error ) {
+ $this->has_request_failure = true;
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ // Check the seller status for the intended capability.
+ foreach ( $seller_status->products() as $product ) {
+ if ( $product->name() !== 'PAYMENT_METHODS' ) {
+ continue;
+ }
+
+ if ( in_array( self::CAPABILITY_NAME, $product->capabilities(), true ) ) {
+ // Capability found, persist status and return true.
+ $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
+ $this->settings->persist();
+
+ $this->current_status = true;
+ return $this->current_status;
+ }
+ }
+
+ // Capability not found, persist status and return false.
+ $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
+ $this->settings->persist();
+
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ /**
+ * Returns if the seller is onboarded.
+ *
+ * @return bool
+ */
+ public function is_onboarded(): bool {
+ return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED;
+ }
+
+ /**
+ * Returns if there was a request failure.
+ *
+ * @return bool
+ */
+ public function has_request_failure(): bool {
+ return $this->has_request_failure;
+ }
+
+ /**
+ * Clears the persisted result to force a recheck.
+ *
+ * @param Settings|null $settings The settings object.
+ * We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
+ * @return void
+ */
+ public function clear( Settings $settings = null ): void {
+ if ( null === $settings ) {
+ $settings = $this->settings;
+ }
+
+ $this->current_status = null;
+
+ if ( $settings->has( self::SETTINGS_KEY ) ) {
+ $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
+ $settings->persist();
+ }
+ }
+
+}
diff --git a/modules/ppcp-applepay/src/Assets/BlocksPaymentMethod.php b/modules/ppcp-applepay/src/Assets/BlocksPaymentMethod.php
new file mode 100644
index 000000000..432deaa21
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/BlocksPaymentMethod.php
@@ -0,0 +1,114 @@
+name = $name;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->button = $button;
+ $this->paypal_payment_method = $paypal_payment_method;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function initialize() { }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function is_active() {
+ return $this->paypal_payment_method->is_active();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_script_handles() {
+ $handle = $this->name . '-block';
+
+ wp_register_script(
+ $handle,
+ trailingslashit( $this->module_url ) . 'assets/js/boot-block.js',
+ array(),
+ $this->version,
+ true
+ );
+
+ return array( $handle );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_data() {
+ $paypal_data = $this->paypal_payment_method->get_payment_method_data();
+
+ return array(
+ 'id' => $this->name,
+ 'title' => $paypal_data['title'], // TODO : see if we should use another.
+ 'description' => $paypal_data['description'], // TODO : see if we should use another.
+ 'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
+ 'scriptData' => $this->button->script_data(),
+ );
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php
new file mode 100644
index 000000000..0b15fbe78
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/DataToAppleButtonScripts.php
@@ -0,0 +1,200 @@
+sdk_url = $sdk_url;
+ $this->settings = $settings;
+ }
+
+ /**
+ * Sets the appropriate data to send to ApplePay script
+ * Data differs between product page and cart page
+ *
+ * @param bool $is_block Whether the button is in a block or not.
+ * @return array
+ * @throws NotFoundException When the setting is not found.
+ */
+ public function apple_pay_script_data( bool $is_block = false ): array {
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'];
+ $currency_code = get_woocommerce_currency();
+ $total_label = get_bloginfo( 'name' );
+ if ( is_product() ) {
+ return $this->data_for_product_page(
+ $shop_country_code,
+ $currency_code,
+ $total_label
+ );
+ }
+
+ return $this->data_for_cart_page(
+ $shop_country_code,
+ $currency_code,
+ $total_label
+ );
+ }
+
+ /**
+ * Check if the product needs shipping
+ *
+ * @param \WC_Product $product The product.
+ *
+ * @return bool
+ */
+ protected function check_if_need_shipping( $product ) {
+ if (
+ ! wc_shipping_enabled()
+ || 0 === wc_get_shipping_method_count(
+ true
+ )
+ ) {
+ return false;
+ }
+ $needs_shipping = false;
+
+ if ( $product->needs_shipping() ) {
+ $needs_shipping = true;
+ }
+
+ return $needs_shipping;
+ }
+
+ /**
+ * 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
+ ) {
+ $product = wc_get_product( get_the_id() );
+ if ( ! $product ) {
+ return array();
+ }
+ $is_variation = false;
+ 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' ) : '';
+
+ return array(
+ 'sdk_url' => $this->sdk_url,
+ 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
+ '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' ),
+ );
+ }
+
+ /**
+ * 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
+ ) {
+ $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 );
+
+ return array(
+ 'sdk_url' => $this->sdk_url,
+ 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
+ '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' ),
+ );
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php
new file mode 100644
index 000000000..25227e11b
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/PropertiesDictionary.php
@@ -0,0 +1,188 @@
+ __( 'White', 'woocommerce-paypal-payments' ),
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white-outline' => __( 'White with outline', 'woocommerce-paypal-payments' ),
+ );
+ }
+
+ /**
+ * Returns the possible list of button types.
+ *
+ * @return array
+ */
+ public static function button_types(): array {
+ return array(
+ 'book' => __( 'Book', 'woocommerce-paypal-payments' ),
+ 'buy' => __( 'Buy', 'woocommerce-paypal-payments' ),
+ 'check-out' => __( 'Checkout', 'woocommerce-paypal-payments' ),
+ 'donate' => __( 'Donate', 'woocommerce-paypal-payments' ),
+ 'order' => __( 'Order', 'woocommerce-paypal-payments' ),
+ 'pay' => __( 'Pay', 'woocommerce-paypal-payments' ),
+ 'plain' => __( 'Plain', 'woocommerce-paypal-payments' ),
+ 'subscribe' => __( 'Book', 'woocommerce-paypal-payments' ),
+ 'add-money' => __( 'Add money', 'woocommerce-paypal-payments' ),
+ 'continue' => __( 'Continue', 'woocommerce-paypal-payments' ),
+ 'contribute' => __( 'Contribute', 'woocommerce-paypal-payments' ),
+ 'reload' => __( 'Reload', 'woocommerce-paypal-payments' ),
+ 'rent' => __( 'Rent', 'woocommerce-paypal-payments' ),
+ 'setup' => __( 'Setup', 'woocommerce-paypal-payments' ),
+ 'support' => __( 'Support', 'woocommerce-paypal-payments' ),
+ 'tip' => __( 'Tip', 'woocommerce-paypal-payments' ),
+ 'top-up' => __( 'Top up', 'woocommerce-paypal-payments' ),
+ );
+ }
+
+ /**
+ * Returns the possible list of button languages.
+ *
+ * @return array
+ */
+ public static function button_languages(): array {
+ return array(
+ '' => __( 'Browser language', 'woocommerce-paypal-payments' ),
+ 'ar-AB' => __( 'Arabic', 'woocommerce-paypal-payments' ),
+ 'ca-ES' => __( 'Catalan', 'woocommerce-paypal-payments' ),
+ 'cs-CZ' => __( 'Czech', 'woocommerce-paypal-payments' ),
+ 'da-DK' => __( 'Danish', 'woocommerce-paypal-payments' ),
+ 'de-DE' => __( 'German', 'woocommerce-paypal-payments' ),
+ 'el-GR' => __( 'Greek', 'woocommerce-paypal-payments' ),
+ 'en-AU' => __( 'English (Australia)', 'woocommerce-paypal-payments' ),
+ 'en-GB' => __( 'English (United Kingdom)', 'woocommerce-paypal-payments' ),
+ 'en-US' => __( 'English (United States)', 'woocommerce-paypal-payments' ),
+ 'es-ES' => __( 'Spanish (Spain)', 'woocommerce-paypal-payments' ),
+ 'es-MX' => __( 'Spanish (Mexico)', 'woocommerce-paypal-payments' ),
+ 'fi-FI' => __( 'Finnish', 'woocommerce-paypal-payments' ),
+ 'fr-CA' => __( 'French (Canada)', 'woocommerce-paypal-payments' ),
+ 'fr-FR' => __( 'French (France)', 'woocommerce-paypal-payments' ),
+ 'he-IL' => __( 'Hebrew', 'woocommerce-paypal-payments' ),
+ 'hi-IN' => __( 'Hindi', 'woocommerce-paypal-payments' ),
+ 'hr-HR' => __( 'Croatian', 'woocommerce-paypal-payments' ),
+ 'hu-HU' => __( 'Hungarian', 'woocommerce-paypal-payments' ),
+ 'id-ID' => __( 'Indonesian', 'woocommerce-paypal-payments' ),
+ 'it-IT' => __( 'Italian', 'woocommerce-paypal-payments' ),
+ 'ja-JP' => __( 'Japanese', 'woocommerce-paypal-payments' ),
+ 'ko-KR' => __( 'Korean', 'woocommerce-paypal-payments' ),
+ 'ms-MY' => __( 'Malay', 'woocommerce-paypal-payments' ),
+ 'nb-NO' => __( 'Norwegian', 'woocommerce-paypal-payments' ),
+ 'nl-NL' => __( 'Dutch', 'woocommerce-paypal-payments' ),
+ 'pl-PL' => __( 'Polish', 'woocommerce-paypal-payments' ),
+ 'pt-BR' => __( 'Portuguese (Brazil)', 'woocommerce-paypal-payments' ),
+ 'pt-PT' => __( 'Portuguese (Portugal)', 'woocommerce-paypal-payments' ),
+ 'ro-RO' => __( 'Romanian', 'woocommerce-paypal-payments' ),
+ 'ru-RU' => __( 'Russian', 'woocommerce-paypal-payments' ),
+ 'sk-SK' => __( 'Slovak', 'woocommerce-paypal-payments' ),
+ 'sv-SE' => __( 'Swedish', 'woocommerce-paypal-payments' ),
+ 'th-TH' => __( 'Thai', 'woocommerce-paypal-payments' ),
+ 'tr-TR' => __( 'Turkish', 'woocommerce-paypal-payments' ),
+ 'uk-UA' => __( 'Ukrainian', 'woocommerce-paypal-payments' ),
+ 'vi-VN' => __( 'Vietnamese', 'woocommerce-paypal-payments' ),
+ 'zh-CN' => __( 'Chinese (Simplified)', 'woocommerce-paypal-payments' ),
+ 'zh-HK' => __( 'Chinese (Hong Kong)', 'woocommerce-paypal-payments' ),
+ 'zh-TW' => __( 'Chinese (Traditional)', 'woocommerce-paypal-payments' ),
+ );
+ }
+}
diff --git a/modules/ppcp-applepay/src/Assets/ResponsesToApple.php b/modules/ppcp-applepay/src/Assets/ResponsesToApple.php
new file mode 100644
index 000000000..d2294a8a5
--- /dev/null
+++ b/modules/ppcp-applepay/src/Assets/ResponsesToApple.php
@@ -0,0 +1,184 @@
+ success, 1 => error.
+ * @param string $order_id The order ID.
+ * @param array $error_list [['errorCode'=>required, 'contactField'=>'']].
+ * @param string $return_url The URL to redirect to.
+ *
+ * @return array
+ */
+ public function authorization_result_response(
+ $status,
+ $order_id = '',
+ $error_list = array(),
+ $return_url = ''
+ ) {
+
+ $response = array();
+ if ( $status === 'STATUS_SUCCESS' ) {
+ $response['returnUrl'] = $return_url;
+ $response['responseToApple'] = array( 'status' => 0 );
+ } else {
+ $response = array(
+ 'status' => 1,
+ 'errors' => $this->apple_pay_error( $error_list ),
+ );
+ }
+
+ return $response;
+ }
+
+ /**
+ * Returns an error response to be handled by the script
+ *
+ * @param array $error_list [['errorCode'=>required, 'contactField'=>'']].
+ * @return void
+ */
+ public function response_with_data_errors( $error_list ) {
+ $response = array();
+ $response['errors'] = $this->apple_pay_error( $error_list );
+ $response['newTotal'] = $this->apple_new_total_response(
+ 0,
+ 'pending'
+ );
+ wp_send_json_error( $response );
+ }
+
+ /**
+ * Creates a response formatted for ApplePay
+ *
+ * @param array $payment_details Payment details.
+ * @return array
+ */
+ public function apple_formatted_response( array $payment_details ) {
+ $response = array();
+ if ( $payment_details['shippingMethods'] ) {
+ $response['newShippingMethods'] = $payment_details['shippingMethods'];
+ }
+
+ $response['newLineItems'] = $this->apple_new_line_items_response(
+ $payment_details
+ );
+
+ $response['newTotal'] = $this->apple_new_total_response(
+ $payment_details['total']
+ );
+ return $response;
+ }
+
+ /**
+ * Returns a success response to be handled by the script
+ *
+ * @param array $response Response to send.
+ */
+ public function response_success( array $response ): void {
+ wp_send_json_success( $response );
+ }
+
+ /**
+ * Creates an array of errors formatted
+ *
+ * @param array $error_list List of errors.
+ * @param array $errors (optional).
+ *
+ * @return array
+ */
+ protected function apple_pay_error( $error_list, $errors = array() ) {
+ foreach ( $error_list as $error ) {
+ $errors[] = array(
+ 'code' => $error['errorCode'],
+ 'contactField' => $error['contactField'] ?? null,
+ 'message' => array_key_exists( 'contactField', $error )
+ ? sprintf( 'Missing %s', $error['contactField'] ) : '',
+ );
+ }
+
+ return $errors;
+ }
+
+ /**
+ * Creates NewTotals line
+ *
+ * @param float $total Total amount.
+ * @param string $type final or pending.
+ *
+ * @return array
+ */
+ protected function apple_new_total_response( $total, string $type = 'final' ): array {
+ return $this->apple_item_format(
+ get_bloginfo( 'name' ),
+ $total,
+ $type
+ );
+ }
+
+ /**
+ * Creates item line
+ *
+ * @param string $subtotal_label Subtotal label.
+ * @param float $subtotal Subtotal amount.
+ * @param string $type final or pending.
+ *
+ * @return array
+ */
+ protected function apple_item_format( $subtotal_label, $subtotal, $type ): array {
+ return array(
+ 'label' => $subtotal_label,
+ 'amount' => $subtotal,
+ 'type' => $type,
+ );
+ }
+
+ /**
+ * Creates NewLineItems line
+ *
+ * @param array $payment_details Payment details.
+ * @return array[]
+ */
+ protected function apple_new_line_items_response( array $payment_details ): array {
+ $type = 'final';
+ $response = array();
+ $response[] = $this->apple_item_format(
+ 'Subtotal',
+ round( floatval( $payment_details['subtotal'] ), 2 ),
+ $type
+ );
+
+ if ( $payment_details['shipping']['amount'] ) {
+ $response[] = $this->apple_item_format(
+ $payment_details['shipping']['label'] ?: '',
+ round( floatval( $payment_details['shipping']['amount'] ), 2 ),
+ $type
+ );
+ }
+ $isset_fee_amount = isset( $payment_details['fee'] ) && isset( $payment_details['fee']['amount'] );
+ if ( $isset_fee_amount ) {
+ $response[] = $this->apple_item_format(
+ $payment_details['fee']['label'] ?: '',
+ round( floatval( $payment_details['fee']['amount'] ), 2 ),
+ $type
+ );
+ }
+ $response[] = $this->apple_item_format( 'Estimated Tax', round( floatval( $payment_details['taxes'] ), 2 ), $type );
+ return $response;
+ }
+}
diff --git a/modules/ppcp-applepay/src/Helper/ApmApplies.php b/modules/ppcp-applepay/src/Helper/ApmApplies.php
new file mode 100644
index 000000000..dec9b1a08
--- /dev/null
+++ b/modules/ppcp-applepay/src/Helper/ApmApplies.php
@@ -0,0 +1,67 @@
+allowed_country_currency_matrix = $allowed_country_currency_matrix;
+ $this->currency = $currency;
+ $this->country = $country;
+ }
+
+ /**
+ * Returns whether ApplePay can be used in the current country and the current currency 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 );
+ }
+
+}
diff --git a/modules/ppcp-applepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-applepay/src/Helper/AvailabilityNotice.php
new file mode 100644
index 000000000..e38f9a2c2
--- /dev/null
+++ b/modules/ppcp-applepay/src/Helper/AvailabilityNotice.php
@@ -0,0 +1,291 @@
+product_status = $product_status;
+ $this->is_wc_gateways_list_page = $is_wc_gateways_list_page;
+ $this->is_ppcp_settings_page = $is_ppcp_settings_page;
+ $this->is_available = $is_available;
+ $this->is_server_supported = $is_server_supported;
+ $this->is_merchant_validated = $is_merchant_validated;
+ $this->button = $button;
+ }
+
+ /**
+ * Adds availability notice if applicable.
+ *
+ * @return void
+ */
+ public function execute(): void {
+ if ( ! $this->should_display() ) {
+ return;
+ }
+
+ // We need to check is active before checking failure requests, otherwise failure status won't be set.
+ $is_active = $this->product_status->is_active();
+
+ if ( $this->product_status->has_request_failure() ) {
+ $this->add_seller_status_failure_notice();
+ } elseif ( ! $is_active ) {
+ $this->add_not_available_notice();
+ }
+
+ if ( ! $this->is_available ) {
+ return;
+ }
+
+ if ( ! $this->is_server_supported ) {
+ $this->add_server_not_supported_notice();
+ }
+
+ $button_enabled = $this->button->is_enabled();
+
+ // We do this check on $_POST because this is called before settings are saved.
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ if ( isset( $_POST['ppcp'] ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $post_data = wc_clean( (array) wp_unslash( $_POST['ppcp'] ) );
+ $button_enabled = wc_string_to_bool( $post_data['applepay_button_enabled'] ?? false );
+ }
+
+ if ( ! $button_enabled ) {
+ return;
+ }
+
+ if ( ! $this->is_merchant_validated ) {
+ $this->add_merchant_not_validated_notice();
+ }
+
+ }
+
+ /**
+ * Whether the message should display.
+ *
+ * @return bool
+ */
+ protected function should_display(): bool {
+ if ( ! $this->product_status->is_onboarded() ) {
+ return false;
+ }
+ if ( ! $this->is_wc_gateways_list_page && ! $this->is_ppcp_settings_page ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds seller status failure notice.
+ *
+ * @return void
+ */
+ private function add_seller_status_failure_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds seller status notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __(
+ 'Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our %1$sonboarding process%2$s to resolve this.
Don\'t worry if you cannot use the %1$sonboarding process%2$s; most functionalities available to your account should work.
',
+ 'woocommerce-paypal-payments'
+ ),
+ ' ',
+ ' '
+ );
+
+ // Name the key so it can be overridden in other modules.
+ $notices['error_product_status'] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+ /**
+ * Adds not available notice.
+ *
+ * @return void
+ */
+ private function add_not_available_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds ApplePay not available notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ __(
+ 'Apple Pay is not available on your PayPal seller account.',
+ 'woocommerce-paypal-payments'
+ )
+ );
+
+ $notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+ /**
+ * Adds ApplePay server not supported notice.
+ *
+ * @return void
+ */
+ private function add_server_not_supported_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds ApplePay server not supported notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ __(
+ 'Apple Pay is not supported on this server. Please contact your hosting provider to enable it.',
+ 'woocommerce-paypal-payments'
+ )
+ );
+
+ $notices[] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+ /**
+ * Adds ApplePay merchant not validated notice.
+ *
+ * @return void
+ */
+ private function add_merchant_not_validated_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds ApplePay merchant not validated notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag for the well-known file, %3$s and %4$s are the opening and closing of HTML tag for the help document.
+ __(
+ 'Apple Pay Validation Error. Please ensure the presentment of the correct %1$sdomain association file%2$s for Apple to validate your domain. %3$sLearn more%4$s about the Apple Pay requirements',
+ 'woocommerce-paypal-payments'
+ ),
+ ' ',
+ ' ',
+ '',
+ ' '
+ );
+
+ $notices[] = new Message( $message, 'error', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+}
diff --git a/modules/ppcp-applepay/webpack.config.js b/modules/ppcp-applepay/webpack.config.js
new file mode 100644
index 000000000..63834c8a6
--- /dev/null
+++ b/modules/ppcp-applepay/webpack.config.js
@@ -0,0 +1,40 @@
+const path = require('path');
+const isProduction = process.env.NODE_ENV === 'production';
+
+const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
+
+module.exports = {
+ devtool: isProduction ? 'source-map' : 'eval-source-map',
+ mode: isProduction ? 'production' : 'development',
+ target: 'web',
+ plugins: [ new DependencyExtractionWebpackPlugin() ],
+ entry: {
+ 'boot': path.resolve('./resources/js/boot.js'),
+ 'boot-block': path.resolve('./resources/js/boot-block.js'),
+ "styles": path.resolve('./resources/css/styles.scss')
+ },
+ 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-applepay/yarn.lock b/modules/ppcp-applepay/yarn.lock
new file mode 100644
index 000000000..d17c79d6d
--- /dev/null
+++ b/modules/ppcp-applepay/yarn.lock
@@ -0,0 +1,2269 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
+ integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
+ dependencies:
+ "@babel/highlight" "^7.22.5"
+
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544"
+ integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==
+
+"@babel/core@^7.19":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.6.tgz#aafafbe86e9a1679d876b99dc46382964ef72494"
+ integrity sha512-HPIyDa6n+HKw5dEuway3vVAhBboYCtREBMp+IWeseZy6TFtzn6MHkCH2KKYUOC/vKKwgSMHQW4htBOrmuRPXfw==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.22.5"
+ "@babel/generator" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helpers" "^7.22.6"
+ "@babel/parser" "^7.22.6"
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.6"
+ "@babel/types" "^7.22.5"
+ "@nicolo-ribaudo/semver-v6" "^6.3.3"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+
+"@babel/generator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.5.tgz#1e7bf768688acfb05cf30b2369ef855e82d984f7"
+ integrity sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==
+ dependencies:
+ "@babel/types" "^7.22.5"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
+ integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.5.tgz#a3f4758efdd0190d8927fcffd261755937c71878"
+ integrity sha512-m1EP3lVOPptR+2DwD125gziZNcmoNSHGmJROKoy87loWUQyJaVXDgpmruWqDARZSmtYQ+Dl25okU8+qhVzuykw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz#e30d61abe9480aa5a83232eb31c111be922d2e52"
+ integrity sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==
+ dependencies:
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-validator-option" "^7.22.5"
+ "@nicolo-ribaudo/semver-v6" "^6.3.3"
+ browserslist "^4.21.9"
+ lru-cache "^5.1.1"
+
+"@babel/helper-create-class-features-plugin@^7.22.5":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.6.tgz#58564873c889a6fea05a538e23f9f6d201f10950"
+ integrity sha512-iwdzgtSiBxF6ni6mzVnZCF3xt5qE6cEA0J7nFt8QOAWZ0zjCFceEgpn3vtb2V7WFR6QzP2jmIFOHMTRo7eNJjQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@nicolo-ribaudo/semver-v6" "^6.3.3"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.6.tgz#87afd63012688ad792de430ceb3b6dc28e4e7a40"
+ integrity sha512-nBookhLKxAWo/TUCmhnaEJyLz2dekjQvv5SRpE9epWQBcpedWLKt8aZdsuT9XV5ovzR3fENLjRXVT0GsSlGGhA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@nicolo-ribaudo/semver-v6" "^6.3.3"
+ regexpu-core "^5.3.1"
+
+"@babel/helper-define-polyfill-provider@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8"
+ integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+ semver "^6.1.2"
+
+"@babel/helper-environment-visitor@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
+ integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
+
+"@babel/helper-function-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
+ integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
+ dependencies:
+ "@babel/template" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-member-expression-to-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2"
+ integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-module-imports@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c"
+ integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-module-transforms@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef"
+ integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-simple-access" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-optimise-call-expression@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
+ integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@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.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
+"@babel/helper-remap-async-to-generator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.5.tgz#14a38141a7bf2165ad38da61d61cf27b43015da2"
+ integrity sha512-cU0Sq1Rf4Z55fgz7haOakIyM7+x/uCFwXpLPaeRzfoUtAEAuUZjZvFPjL/rk5rW693dIgn2hng1W7xbT7lWT4g==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-wrap-function" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-replace-supers@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.5.tgz#71bc5fb348856dea9fdc4eafd7e2e49f585145dc"
+ integrity sha512-aLdNM5I3kdI/V9xGNyKSF3X/gTyMUBohTZ+/3QdQKAA9vxIiy12E+8E2HoOP1/DjeqU+g6as35QHJNMDDYpuCg==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-simple-access@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
+ integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847"
+ integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
+"@babel/helper-validator-identifier@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
+ integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
+
+"@babel/helper-validator-option@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac"
+ integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==
+
+"@babel/helper-wrap-function@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.5.tgz#44d205af19ed8d872b4eefb0d2fa65f45eb34f06"
+ integrity sha512-bYqLIBSEshYcYQyfks8ewYA8S30yaGSeRslcvKMvoUk6HHPySbxHq9YRi6ghhzEU+yhQv9bP/jXnygkStOcqZw==
+ dependencies:
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helpers@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd"
+ integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==
+ dependencies:
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.6"
+ "@babel/types" "^7.22.5"
+
+"@babel/highlight@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
+ integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.5"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.22.5", "@babel/parser@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.6.tgz#201f8b47be20c76c7c5743b9c16129760bf9a975"
+ integrity sha512-EIQu22vNkceq3LbjAq7knDf/UmtI2qbcNI8GRBlijez6TpQLvSodJPYfydQmNA5buwkxxxa/PVI44jjYZ+/cLw==
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5":
+ version "7.22.5"
+ 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.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e"
+ integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca"
+ integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-transform-optional-chaining" "^7.22.5"
+
+"@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-proposal-unicode-property-regex@^7.4.4":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
+ integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@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.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98"
+ integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-import-attributes@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb"
+ integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@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-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918"
+ integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@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.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958"
+ integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-async-generator-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.5.tgz#7336356d23380eda9a56314974f053a020dab0c3"
+ integrity sha512-gGOEvFzm3fWoyD5uZq7vVTD57pPJ3PczPUD/xCFGjzBpUosnklmXyKnGQbbbGs1NPNPskFex0j93yKbHt0cHyg==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.5"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
+"@babel/plugin-transform-async-to-generator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775"
+ integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.5"
+
+"@babel/plugin-transform-block-scoped-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024"
+ integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-block-scoping@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.5.tgz#8bfc793b3a4b2742c0983fadc1480d843ecea31b"
+ integrity sha512-EcACl1i5fSQ6bt+YGuU/XGCeZKStLmyVGytWkpyhCLeQVA0eu6Wtiw92V+I1T/hnezUv7j74dA/Ro69gWcU+hg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77"
+ integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-static-block@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba"
+ integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
+"@babel/plugin-transform-classes@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363"
+ integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869"
+ integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/template" "^7.22.5"
+
+"@babel/plugin-transform-destructuring@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.5.tgz#d3aca7438f6c26c78cdd0b0ba920a336001b27cc"
+ integrity sha512-GfqcFuGW8vnEqTUBM7UtPd5A4q797LTvvwKxXTgRsFjoqaJiEg9deBG6kWeQYkVEL569NpnmpC0Pkr/8BLKGnQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dotall-regex@^7.22.5", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165"
+ integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-duplicate-keys@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285"
+ integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dynamic-import@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e"
+ integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
+"@babel/plugin-transform-exponentiation-operator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a"
+ integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-export-namespace-from@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b"
+ integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-transform-for-of@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f"
+ integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-function-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143"
+ integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-json-strings@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0"
+ integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
+"@babel/plugin-transform-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920"
+ integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-logical-assignment-operators@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c"
+ integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def"
+ integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-amd@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526"
+ integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-commonjs@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa"
+ integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-simple-access" "^7.22.5"
+
+"@babel/plugin-transform-modules-systemjs@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496"
+ integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+
+"@babel/plugin-transform-modules-umd@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98"
+ integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f"
+ integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-new-target@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d"
+ integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381"
+ integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-transform-numeric-separator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58"
+ integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-transform-object-rest-spread@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1"
+ integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==
+ dependencies:
+ "@babel/compat-data" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.22.5"
+
+"@babel/plugin-transform-object-super@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c"
+ integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+
+"@babel/plugin-transform-optional-catch-binding@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333"
+ integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-transform-optional-chaining@^7.22.5", "@babel/plugin-transform-optional-chaining@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.6.tgz#4bacfe37001fe1901117672875e931d439811564"
+ integrity sha512-Vd5HiWml0mDVtcLHIoEU5sw6HOUW/Zk0acLs/SAeuLzkGNOPc9DB4nkUajemhCmTIz3eiaKREZn2hQQqF79YTg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-transform-parameters@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18"
+ integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-methods@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722"
+ integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-property-in-object@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32"
+ integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-transform-property-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766"
+ integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-display-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b"
+ integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx-development@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87"
+ integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416"
+ integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-jsx" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/plugin-transform-react-pure-annotations@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0"
+ integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-regenerator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.5.tgz#cd8a68b228a5f75fa01420e8cc2fc400f0fc32aa"
+ integrity sha512-rR7KePOE7gfEtNTh9Qw+iO3Q/e4DEsoQ+hdvM6QUDH7JRJ5qxq5AA52ZzBWbI5i9lfNuvySgOGP8ZN7LAmaiPw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ regenerator-transform "^0.15.1"
+
+"@babel/plugin-transform-reserved-words@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb"
+ integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-shorthand-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624"
+ integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-spread@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b"
+ integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+
+"@babel/plugin-transform-sticky-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa"
+ integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-template-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff"
+ integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-typeof-symbol@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34"
+ integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-escapes@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.5.tgz#ce0c248522b1cb22c7c992d88301a5ead70e806c"
+ integrity sha512-biEmVg1IYB/raUO5wT1tgfacCef15Fbzhkx493D3urBI++6hpJ+RFG4SrWMn0NEZLfvilqKf3QDrRVZHo08FYg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-property-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81"
+ integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183"
+ integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91"
+ integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/preset-env@^7.19":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.6.tgz#41bade05f2f8782d693b52d707ede7cef9da3b38"
+ integrity sha512-IHr0AXHGk8oh8HYSs45Mxuv6iySUBwDTIzJSnXN7PURqHdxJVQlCoXmKJgyvSS9bcNf9NVRVE35z+LkCvGmi6w==
+ dependencies:
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.5"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5"
+ "@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.22.5"
+ "@babel/plugin-syntax-import-attributes" "^7.22.5"
+ "@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.22.5"
+ "@babel/plugin-transform-async-generator-functions" "^7.22.5"
+ "@babel/plugin-transform-async-to-generator" "^7.22.5"
+ "@babel/plugin-transform-block-scoped-functions" "^7.22.5"
+ "@babel/plugin-transform-block-scoping" "^7.22.5"
+ "@babel/plugin-transform-class-properties" "^7.22.5"
+ "@babel/plugin-transform-class-static-block" "^7.22.5"
+ "@babel/plugin-transform-classes" "^7.22.6"
+ "@babel/plugin-transform-computed-properties" "^7.22.5"
+ "@babel/plugin-transform-destructuring" "^7.22.5"
+ "@babel/plugin-transform-dotall-regex" "^7.22.5"
+ "@babel/plugin-transform-duplicate-keys" "^7.22.5"
+ "@babel/plugin-transform-dynamic-import" "^7.22.5"
+ "@babel/plugin-transform-exponentiation-operator" "^7.22.5"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.5"
+ "@babel/plugin-transform-for-of" "^7.22.5"
+ "@babel/plugin-transform-function-name" "^7.22.5"
+ "@babel/plugin-transform-json-strings" "^7.22.5"
+ "@babel/plugin-transform-literals" "^7.22.5"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.22.5"
+ "@babel/plugin-transform-member-expression-literals" "^7.22.5"
+ "@babel/plugin-transform-modules-amd" "^7.22.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.22.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.22.5"
+ "@babel/plugin-transform-modules-umd" "^7.22.5"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5"
+ "@babel/plugin-transform-new-target" "^7.22.5"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5"
+ "@babel/plugin-transform-numeric-separator" "^7.22.5"
+ "@babel/plugin-transform-object-rest-spread" "^7.22.5"
+ "@babel/plugin-transform-object-super" "^7.22.5"
+ "@babel/plugin-transform-optional-catch-binding" "^7.22.5"
+ "@babel/plugin-transform-optional-chaining" "^7.22.6"
+ "@babel/plugin-transform-parameters" "^7.22.5"
+ "@babel/plugin-transform-private-methods" "^7.22.5"
+ "@babel/plugin-transform-private-property-in-object" "^7.22.5"
+ "@babel/plugin-transform-property-literals" "^7.22.5"
+ "@babel/plugin-transform-regenerator" "^7.22.5"
+ "@babel/plugin-transform-reserved-words" "^7.22.5"
+ "@babel/plugin-transform-shorthand-properties" "^7.22.5"
+ "@babel/plugin-transform-spread" "^7.22.5"
+ "@babel/plugin-transform-sticky-regex" "^7.22.5"
+ "@babel/plugin-transform-template-literals" "^7.22.5"
+ "@babel/plugin-transform-typeof-symbol" "^7.22.5"
+ "@babel/plugin-transform-unicode-escapes" "^7.22.5"
+ "@babel/plugin-transform-unicode-property-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5"
+ "@babel/preset-modules" "^0.1.5"
+ "@babel/types" "^7.22.5"
+ "@nicolo-ribaudo/semver-v6" "^6.3.3"
+ babel-plugin-polyfill-corejs2 "^0.4.3"
+ babel-plugin-polyfill-corejs3 "^0.8.1"
+ babel-plugin-polyfill-regenerator "^0.5.0"
+ core-js-compat "^3.31.0"
+
+"@babel/preset-modules@^0.1.5":
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
+ integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@babel/preset-react@^7.18.6":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6"
+ integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.5"
+ "@babel/plugin-transform-react-display-name" "^7.22.5"
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+ "@babel/plugin-transform-react-jsx-development" "^7.22.5"
+ "@babel/plugin-transform-react-pure-annotations" "^7.22.5"
+
+"@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.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.6.tgz#57d64b9ae3cff1d67eb067ae117dac087f5bd438"
+ integrity sha512-wDb5pWm4WDdF6LFUde3Jl8WzPA+3ZbxYqkC6xAXuD3irdEHN1k0NfTRrJD8ZD378SJ61miMLCqIOXYhd8x+AJQ==
+ dependencies:
+ regenerator-runtime "^0.13.11"
+
+"@babel/template@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
+ integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
+ dependencies:
+ "@babel/code-frame" "^7.22.5"
+ "@babel/parser" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.6.tgz#8f2f83a5c588251584914debeee38f35f661a300"
+ integrity sha512-53CijMvKlLIDlOTrdWiHileRddlIiwUIyCKqYa7lYnnPldXCG5dUSN38uT0cA6i7rHWNKJLH0VU/Kxdr1GzB3w==
+ dependencies:
+ "@babel/code-frame" "^7.22.5"
+ "@babel/generator" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.22.6"
+ "@babel/types" "^7.22.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.22.5", "@babel/types@^7.4.4":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
+ integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+ 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.0", "@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+ integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@nicolo-ribaudo/semver-v6@^6.3.3":
+ version "6.3.3"
+ resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
+ integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==
+
+"@paypal/paypal-js@^6.0.0":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.1.tgz#5d68d5863a5176383fee9424bc944231668fcffd"
+ integrity sha512-bvYetmkg2GEC6onsUJQx1E9hdAJWff2bS3IPeiZ9Sh9U7h26/fIgMKm240cq/908sbSoDjHys75XXd8at9OpQA==
+ dependencies:
+ promise-polyfill "^8.3.0"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.40.2"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.2.tgz#2833bc112d809677864a4b0e7d1de4f04d7dac2d"
+ integrity sha512-PRVjQ4Eh9z9pmmtaq8nTjZjQwKFk7YIHIud3lRoKRBgUQjgjRmoGxxGEPXQkF+lH7QkHJRNr5F4aBgYCW0lqpQ==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+ integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
+"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
+ version "7.0.12"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
+
+"@types/node@*":
+ version "20.3.3"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.3.3.tgz#329842940042d2b280897150e023e604d11657d6"
+ integrity sha512-wheIYdr4NYML61AjC8MKj/2jrR/kDQri/CIpVoZwldwhnIrD/j9jIU5bJ8yBKuB2VhpFV7Ab6G2XkBjv9r9Zzw==
+
+"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
+ integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
+ 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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
+ integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
+
+"@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
+ integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+
+"@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.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
+ integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/helper-wasm-section" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-opt" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+ "@webassemblyjs/wast-printer" "1.11.6"
+
+"@webassemblyjs/wasm-gen@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
+ integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
+ dependencies:
+ "@webassemblyjs/ast" "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/wasm-opt@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
+ integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+
+"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
+ integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
+ integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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==
+
+"@woocommerce/dependency-extraction-webpack-plugin@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz#230d674a67585bc32e31bc28485bec99b41dbd1f"
+ integrity sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A==
+ dependencies:
+ "@wordpress/dependency-extraction-webpack-plugin" "^3.3.0"
+
+"@wordpress/dependency-extraction-webpack-plugin@^3.3.0":
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-3.7.0.tgz#e52ef31f66b8c4add3d773a87e11007375127b04"
+ integrity sha512-SHyp88D1ICSaRVMfs/kKEicjKXWf1y2wecUeZIiMtkfAi8Bnk3JsnUo11LH7drJIXfjmDoer2B2rrBMZmRm8VA==
+ dependencies:
+ json2php "^0.0.4"
+ webpack-sources "^3.2.2"
+
+"@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-assertions@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+ integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn@^8.7.1, acorn@^8.8.2:
+ version "8.9.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59"
+ integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==
+
+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.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd"
+ integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==
+ dependencies:
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ semver "^6.1.1"
+
+babel-plugin-polyfill-corejs3@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a"
+ integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ core-js-compat "^3.30.1"
+
+babel-plugin-polyfill-regenerator@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380"
+ integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+
+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.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browserslist@^4.14.5, browserslist@^4.21.5, browserslist@^4.21.9:
+ version "4.21.9"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635"
+ integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==
+ dependencies:
+ caniuse-lite "^1.0.30001503"
+ electron-to-chromium "^1.4.431"
+ node-releases "^2.0.12"
+ update-browserslist-db "^1.0.11"
+
+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.30001503:
+ version "1.0.30001512"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001512.tgz#7450843fb581c39f290305a83523c7a9ef0d4cb4"
+ integrity sha512-2S9nK0G/mE+jasCUsMPlARhRCts1ebcp2Ji8Y8PWi4NDE1iRdLCnEPHkEfeBrGC45L4isBx5ur3IQ6yTE2mRZw==
+
+chalk@^2.0.0:
+ 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.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ 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.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+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@^1.7.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+core-js-compat@^3.30.1, core-js-compat@^3.31.0:
+ version "3.31.0"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.31.0.tgz#4030847c0766cc0e803dcdfb30055d7ef2064bf1"
+ integrity sha512-hM7YCu1cU6Opx7MXNu0NuumM0ezNeAeRKadixyiQELWY3vT3De9S4J5ZBMraWV2vZnrE1Cirl0GtFtDtMUXzPw==
+ dependencies:
+ browserslist "^4.21.5"
+
+core-js@^3.25.0:
+ version "3.31.0"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.31.0.tgz#4471dd33e366c79d8c0977ed2d940821719db344"
+ integrity sha512-NIp2TQSGfR6ba5aalZD+ZQ1fSxGhDo/s1w0nx3RYzf2pnJxt7YynxFlFScP6eV7+GZsKO95NSjGxyJsU3DZgeQ==
+
+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:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+electron-to-chromium@^1.4.431:
+ version "1.4.449"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.449.tgz#79ffe4514c81c35d4eb13030a63ff3383a8cc655"
+ integrity sha512-TxLRpRUj/107ATefeP8VIUWNOv90xJxZZbCW/eIbSZQiuiFANCx2b7u+GbVc9X4gU+xnbvypNMYVM/WArE1DNQ==
+
+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.15.0:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
+ integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+envinfo@^7.7.3:
+ version "7.10.0"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13"
+ integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==
+
+es-module-lexer@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f"
+ integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+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.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ 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"
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+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.4, graceful-fs@^4.2.9:
+ 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==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+immutable@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
+ integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
+
+import-local@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+ 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.11.0:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+ integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
+ dependencies:
+ has "^1.0.3"
+
+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==
+
+json2php@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/json2php/-/json2php-0.0.4.tgz#6bd85a1dda6a5dd7e91022bb24403cc1b7c2ee34"
+ integrity sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g==
+
+json5@^2.1.2, json5@^2.2.2:
+ 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.12:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039"
+ integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==
+
+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:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+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"
+
+promise-polyfill@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
+ integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
+
+punycode@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+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.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
+ 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.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
+regenerator-transform@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
+ integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
+ 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.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+ integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+ dependencies:
+ is-core-module "^2.11.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.63.6"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.6.tgz#481610e612902e0c31c46b46cf2dad66943283ea"
+ integrity sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==
+ 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.1.1, semver@^6.1.2:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+serialize-javascript@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
+ 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.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+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.7:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.8"
+
+terser@^5.16.8:
+ version "5.18.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.18.2.tgz#ff3072a0faf21ffd38f99acc9a0ddf7b5f07b948"
+ integrity sha512-Ah19JS86ypbJzTzvUCX7KOsEIhDaRONungA4aYBjEP3JZRf4ocuDzTg4QWZnPn9DEMiMYGJPiSOy7aykoCc70w==
+ 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"
+
+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.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
+ integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+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.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ 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.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
+ dependencies:
+ clone-deep "^4.0.1"
+ wildcard "^2.0.0"
+
+webpack-sources@^3.2.2, 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.88.1"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.1.tgz#21eba01e81bd5edff1968aea726e2fbfd557d3f8"
+ integrity sha512-FROX3TxQnC/ox4N+3xQoWZzvGXSuscxR32rbzjpXgEzWudJFEJBpdlkkob2ylrv5yzzufD1zph1OoFsLtm6stQ==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.9.0"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.15.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.9"
+ 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.7"
+ watchpack "^2.4.0"
+ 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-blocks/.babelrc b/modules/ppcp-blocks/.babelrc
new file mode 100644
index 000000000..822778e6c
--- /dev/null
+++ b/modules/ppcp-blocks/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "usage",
+ "corejs": "3.25.0"
+ }
+ ],
+ [
+ "@babel/preset-react"
+ ]
+ ]
+}
diff --git a/modules/ppcp-blocks/.gitignore b/modules/ppcp-blocks/.gitignore
new file mode 100644
index 000000000..0bd2b9f58
--- /dev/null
+++ b/modules/ppcp-blocks/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+assets/js
+assets/css
diff --git a/modules/ppcp-blocks/composer.json b/modules/ppcp-blocks/composer.json
new file mode 100644
index 000000000..f71b084a2
--- /dev/null
+++ b/modules/ppcp-blocks/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "woocommerce/ppcp-blocks",
+ "type": "dhii-mod",
+ "description": "Blocks module for PPCP",
+ "license": "GPL-2.0",
+ "require": {
+ "php": "^7.2 | ^8.0",
+ "dhii/module-interface": "^0.3.0-alpha1"
+ },
+ "autoload": {
+ "psr-4": {
+ "WooCommerce\\PayPalCommerce\\Blocks\\": "src"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/modules/ppcp-blocks/extensions.php b/modules/ppcp-blocks/extensions.php
new file mode 100644
index 000000000..31f90576d
--- /dev/null
+++ b/modules/ppcp-blocks/extensions.php
@@ -0,0 +1,74 @@
+ function ( ContainerInterface $container, array $locations ): array {
+ return array_merge(
+ $locations,
+ array(
+ 'checkout-block-express' => _x( 'Block Express Checkout', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
+ 'cart-block' => _x( 'Block Cart', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
+ )
+ );
+ },
+ 'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array {
+ unset( $locations['checkout-block-express'] );
+ unset( $locations['cart-block'] );
+ return $locations;
+ },
+
+ 'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
+ $insert_after = function( array $array, string $key, array $new ): array {
+ $keys = array_keys( $array );
+ $index = array_search( $key, $keys, true );
+ $pos = false === $index ? count( $array ) : $index + 1;
+
+ return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
+ };
+
+ return $insert_after(
+ $fields,
+ 'smart_button_locations',
+ array(
+ 'blocks_final_review_enabled' => array(
+ 'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'label' => __(
+ 'Require customers to confirm express payments from the Block Cart and Block Express Checkout on the checkout page.
+If this setting is not enabled, payment confirmation on the checkout will be skipped .
+Skipping the final confirmation on the checkout page may impact the buyer experience during the PayPal payment process.
',
+ 'woocommerce-paypal-payments'
+ ),
+ 'default' => true,
+ 'screens' => array( State::STATE_START, State::STATE_ONBOARDED ),
+ 'requirements' => array(),
+ 'gateway' => 'paypal',
+ ),
+ )
+ );
+ },
+
+ 'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array {
+ if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) {
+ $contexts[] = 'checkout-block';
+ $contexts[] = 'cart-block';
+ }
+
+ return $contexts;
+ },
+
+ 'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool {
+ return ! $container->get( 'blocks.settings.final_review_enabled' );
+ },
+);
diff --git a/modules/ppcp-blocks/module.php b/modules/ppcp-blocks/module.php
new file mode 100644
index 000000000..cfb4f43f9
--- /dev/null
+++ b/modules/ppcp-blocks/module.php
@@ -0,0 +1,16 @@
+ 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/preset-react": "^7.18.6",
+ "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
+ "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-blocks/resources/js/Helper/Address.js b/modules/ppcp-blocks/resources/js/Helper/Address.js
new file mode 100644
index 000000000..6a999f141
--- /dev/null
+++ b/modules/ppcp-blocks/resources/js/Helper/Address.js
@@ -0,0 +1,152 @@
+/**
+ * @param {String} fullName
+ * @returns {Array}
+ */
+export const splitFullName = (fullName) => {
+ fullName = fullName.trim()
+ if (!fullName.includes(' ')) {
+ return [fullName, ''];
+ }
+ const parts = fullName.split(' ');
+ const firstName = parts[0];
+ parts.shift();
+ const lastName = parts.join(' ');
+ return [firstName, lastName];
+}
+
+/**
+ * @param {Object} address
+ * @returns {Object}
+ */
+export const paypalAddressToWc = (address) => {
+ let map = {
+ country_code: 'country',
+ address_line_1: 'address_1',
+ address_line_2: 'address_2',
+ admin_area_1: 'state',
+ admin_area_2: 'city',
+ postal_code: 'postcode',
+ };
+ if (address.city) { // address not from API, such as onShippingChange
+ map = {
+ country_code: 'country',
+ state: 'state',
+ city: 'city',
+ postal_code: 'postcode',
+ };
+ }
+ const result = {};
+ Object.entries(map).forEach(([paypalKey, wcKey]) => {
+ if (address[paypalKey]) {
+ result[wcKey] = address[paypalKey];
+ }
+ });
+
+ const defaultAddress = {
+ first_name: '',
+ last_name: '',
+ company: '',
+ address_1: '',
+ address_2: '',
+ city: '',
+ state: '',
+ postcode: '',
+ country: '',
+ phone: '',
+ };
+
+ return {...defaultAddress, ...result};
+}
+
+/**
+ * @param {Object} shipping
+ * @returns {Object}
+ */
+export const paypalShippingToWc = (shipping) => {
+ const [firstName, lastName] = splitFullName(shipping.name.full_name);
+ return {
+ ...paypalAddressToWc(shipping.address),
+ first_name: firstName,
+ last_name: lastName,
+ }
+}
+
+/**
+ * @param {Object} payer
+ * @returns {Object}
+ */
+export const paypalPayerToWc = (payer) => {
+ const firstName = payer.name.given_name;
+ const lastName = payer.name.surname;
+ const address = payer.address ? paypalAddressToWc(payer.address) : {};
+ return {
+ ...address,
+ first_name: firstName,
+ last_name: lastName,
+ email: payer.email_address,
+ }
+}
+
+/**
+ * @param {Object} order
+ * @returns {Object}
+ */
+export const paypalOrderToWcShippingAddress = (order) => {
+ const shipping = order.purchase_units[0].shipping;
+ if (!shipping) {
+ return {};
+ }
+
+ const res = paypalShippingToWc(shipping);
+
+ // use the name from billing if the same, to avoid possible mistakes when splitting full_name
+ const billingAddress = paypalPayerToWc(order.payer);
+ if (`${res.first_name} ${res.last_name}` === `${billingAddress.first_name} ${billingAddress.last_name}`) {
+ res.first_name = billingAddress.first_name;
+ res.last_name = billingAddress.last_name;
+ }
+
+ return res;
+}
+
+/**
+ *
+ * @param order
+ * @returns {{shippingAddress: Object, billingAddress: Object}}
+ */
+export const paypalOrderToWcAddresses = (order) => {
+ const shippingAddress = paypalOrderToWcShippingAddress(order);
+ let billingAddress = paypalPayerToWc(order.payer);
+ // no billing address, such as if billing address retrieval is not allowed in the merchant account
+ if (!billingAddress.address_line_1) {
+ billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)};
+ }
+
+ return {billingAddress, shippingAddress};
+}
+
+/**
+ * Merges two WC addresses.
+ * The objects can contain either the WC form fields or billingAddress, shippingAddress objects.
+ *
+ * @param {Object} address1
+ * @param {Object} address2
+ * @returns {any}
+ */
+export const mergeWcAddress = (address1, address2) => {
+ if ('billingAddress' in address1) {
+ return {
+ billingAddress: mergeWcAddress(address1.billingAddress, address2.billingAddress),
+ shippingAddress: mergeWcAddress(address1.shippingAddress, address2.shippingAddress),
+ }
+ }
+
+ let address2WithoutEmpty = {...address2};
+ Object.keys(address2).forEach(key => {
+ if (address2[key] === '') {
+ delete address2WithoutEmpty[key];
+ }
+ });
+
+ return {...address1, ...address2WithoutEmpty};
+}
diff --git a/modules/ppcp-blocks/resources/js/checkout-block.js b/modules/ppcp-blocks/resources/js/checkout-block.js
new file mode 100644
index 000000000..582fdede3
--- /dev/null
+++ b/modules/ppcp-blocks/resources/js/checkout-block.js
@@ -0,0 +1,335 @@
+import {useEffect, useState} from '@wordpress/element';
+import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
+import {mergeWcAddress, paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address";
+import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
+import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
+
+const config = wc.wcSettings.getSetting('ppcp-gateway_data');
+
+window.ppcpFundingSource = config.fundingSource;
+
+const PayPalComponent = ({
+ onClick,
+ onClose,
+ onSubmit,
+ onError,
+ eventRegistration,
+ emitResponse,
+ activePaymentMethod,
+ shippingData,
+ isEditing,
+}) => {
+ const {onPaymentSetup, onCheckoutFail, onCheckoutValidation} = eventRegistration;
+ const {responseTypes} = emitResponse;
+
+ const [paypalOrder, setPaypalOrder] = useState(null);
+
+ useEffect(() => {
+ // fill the form if in continuation (for product or mini-cart buttons)
+ if (!config.scriptData.continuation || !config.scriptData.continuation.order || window.ppcpContinuationFilled) {
+ return;
+ }
+ const paypalAddresses = paypalOrderToWcAddresses(config.scriptData.continuation.order);
+ const wcAddresses = wp.data.select('wc/store/cart').getCustomerData();
+ const addresses = mergeWcAddress(wcAddresses, paypalAddresses);
+ wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress);
+ if (shippingData.needsShipping) {
+ wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress);
+ }
+ // this useEffect should run only once, but adding this in case of some kind of full re-rendering
+ window.ppcpContinuationFilled = true;
+ }, [])
+
+ const [loaded, setLoaded] = useState(false);
+ useEffect(() => {
+ if (!loaded) {
+ loadPaypalScript(config.scriptData, () => {
+ setLoaded(true);
+
+ buttonModuleWatcher.registerContextBootstrap(config.scriptData.context, {
+ createOrder: () => {
+ return createOrder();
+ },
+ onApprove: (data, actions) => {
+ return handleApprove(data, actions);
+ },
+ });
+ });
+ }
+ }, [loaded]);
+
+ const createOrder = async () => {
+ try {
+ const res = await fetch(config.scriptData.ajax.create_order.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: config.scriptData.ajax.create_order.nonce,
+ bn_code: '',
+ context: config.scriptData.context,
+ payment_method: 'ppcp-gateway',
+ createaccount: false
+ }),
+ });
+
+ const json = await res.json();
+
+ if (!json.success) {
+ if (json.data?.details?.length > 0) {
+ throw new Error(json.data.details.map(d => `${d.issue} ${d.description}`).join(' '));
+ } else if (json.data?.message) {
+ throw new Error(json.data.message);
+ }
+
+ throw new Error(config.scriptData.labels.error.generic);
+ }
+ return json.data.id;
+ } catch (err) {
+ console.error(err);
+
+ onError(err.message);
+
+ onClose();
+
+ throw err;
+ }
+ };
+
+ const getCheckoutRedirectUrl = () => {
+ const checkoutUrl = new URL(config.scriptData.redirect);
+ // sometimes some browsers may load some kind of cached version of the page,
+ // so adding a parameter to avoid that
+ checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString());
+ return checkoutUrl.toString();
+ }
+
+ const handleApprove = async (data, actions) => {
+ try {
+ const order = await actions.order.get();
+
+ if (order) {
+ const addresses = paypalOrderToWcAddresses(order);
+
+ let promises = [
+ // save address on server
+ wp.data.dispatch('wc/store/cart').updateCustomerData({
+ billing_address: addresses.billingAddress,
+ shipping_address: addresses.shippingAddress,
+ }),
+ ];
+ if (!config.finalReviewEnabled) {
+ // set address in UI
+ promises.push(wp.data.dispatch('wc/store/cart').setBillingAddress(addresses.billingAddress));
+ if (shippingData.needsShipping) {
+ promises.push(wp.data.dispatch('wc/store/cart').setShippingAddress(addresses.shippingAddress))
+ }
+ }
+ await Promise.all(promises);
+ }
+
+ setPaypalOrder(order);
+
+ const res = await fetch(config.scriptData.ajax.approve_order.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: config.scriptData.ajax.approve_order.nonce,
+ order_id: data.orderID,
+ funding_source: window.ppcpFundingSource ?? 'paypal',
+ })
+ });
+
+ const json = await res.json();
+
+ if (!json.success) {
+ if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') {
+ return actions.restart();
+ }
+ if (json.data?.message) {
+ throw new Error(json.data.message);
+ }
+
+ throw new Error(config.scriptData.labels.error.generic)
+ }
+
+ if (config.finalReviewEnabled) {
+ location.href = getCheckoutRedirectUrl();
+ } else {
+ onSubmit();
+ }
+ } catch (err) {
+ console.error(err);
+
+ onError(err.message);
+
+ onClose();
+
+ throw err;
+ }
+ };
+
+ useEffect(() => {
+ const unsubscribe = onCheckoutValidation(() => {
+ if (config.scriptData.continuation) {
+ return true;
+ }
+ if (wp.data.select('wc/store/validation').hasValidationErrors()) {
+ location.href = getCheckoutRedirectUrl();
+ return { type: responseTypes.ERROR };
+ }
+
+ return true;
+ });
+ return unsubscribe;
+ }, [onCheckoutValidation] );
+
+ const handleClick = (data, actions) => {
+ if (isEditing) {
+ return actions.reject();
+ }
+
+ window.ppcpFundingSource = data.fundingSource;
+
+ onClick();
+ };
+
+ let handleShippingChange = null;
+ if (shippingData.needsShipping && !config.finalReviewEnabled) {
+ handleShippingChange = async (data, actions) => {
+ try {
+ const shippingOptionId = data.selected_shipping_option?.id;
+ if (shippingOptionId) {
+ await shippingData.setSelectedRates(shippingOptionId);
+ }
+
+ const address = paypalAddressToWc(data.shipping_address);
+
+ await wp.data.dispatch('wc/store/cart').updateCustomerData({
+ shipping_address: address,
+ });
+
+ await shippingData.setShippingAddress(address);
+
+ const res = await fetch(config.ajax.update_shipping.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: config.ajax.update_shipping.nonce,
+ order_id: data.orderID,
+ })
+ });
+
+ const json = await res.json();
+
+ if (!json.success) {
+ throw new Error(json.data.message);
+ }
+ } catch (e) {
+ console.error(e);
+
+ actions.reject();
+ }
+ };
+ }
+
+ useEffect(() => {
+ if (activePaymentMethod !== config.id) {
+ return;
+ }
+
+ const unsubscribeProcessing = onPaymentSetup(() => {
+ if (config.scriptData.continuation) {
+ return {
+ type: responseTypes.SUCCESS,
+ meta: {
+ paymentMethodData: {
+ 'paypal_order_id': config.scriptData.continuation.order_id,
+ 'funding_source': window.ppcpFundingSource ?? 'paypal',
+ }
+ },
+ };
+ }
+
+ const addresses = paypalOrderToWcAddresses(paypalOrder);
+
+ return {
+ type: responseTypes.SUCCESS,
+ meta: {
+ paymentMethodData: {
+ 'paypal_order_id': paypalOrder.id,
+ 'funding_source': window.ppcpFundingSource ?? 'paypal',
+ },
+ ...addresses,
+ },
+ };
+ });
+ return () => {
+ unsubscribeProcessing();
+ };
+ }, [onPaymentSetup, paypalOrder, activePaymentMethod]);
+
+ useEffect(() => {
+ if (activePaymentMethod !== config.id) {
+ return;
+ }
+ const unsubscribe = onCheckoutFail(({ processingResponse }) => {
+ console.error(processingResponse)
+ if (onClose) {
+ onClose();
+ }
+ if (config.scriptData.continuation) {
+ return true;
+ }
+ if (!config.finalReviewEnabled) {
+ location.href = getCheckoutRedirectUrl();
+ }
+ return true;
+ });
+ return unsubscribe;
+ }, [onCheckoutFail, onClose, activePaymentMethod]);
+
+ if (config.scriptData.continuation) {
+ return (
+
+
+
+ )
+ }
+
+ if (!loaded) {
+ return null;
+ }
+
+ const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });
+
+ return (
+
+ );
+}
+
+const features = ['products'];
+let registerMethod = registerExpressPaymentMethod;
+if (config.scriptData.continuation) {
+ features.push('ppcp_continuation');
+ registerMethod = registerPaymentMethod;
+}
+
+registerMethod({
+ name: config.id,
+ label:
,
+ content: ,
+ edit: ,
+ ariaLabel: config.title,
+ canMakePayment: () => config.enabled,
+ supports: {
+ features: features,
+ },
+});
diff --git a/modules/ppcp-blocks/services.php b/modules/ppcp-blocks/services.php
new file mode 100644
index 000000000..5b5a5159b
--- /dev/null
+++ b/modules/ppcp-blocks/services.php
@@ -0,0 +1,57 @@
+ static function ( ContainerInterface $container ): string {
+ /**
+ * The path cannot be false.
+ *
+ * @psalm-suppress PossiblyFalseArgument
+ */
+ return plugins_url(
+ '/modules/ppcp-blocks/',
+ dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
+ );
+ },
+ 'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod {
+ return new PayPalPaymentMethod(
+ $container->get( 'blocks.url' ),
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'button.smart-button' ),
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'wcgateway.settings.status' ),
+ $container->get( 'wcgateway.paypal-gateway' ),
+ $container->get( 'blocks.settings.final_review_enabled' ),
+ $container->get( 'session.cancellation.view' ),
+ $container->get( 'session.handler' )
+ );
+ },
+ 'blocks.settings.final_review_enabled' => static function ( ContainerInterface $container ): bool {
+ $settings = $container->get( 'wcgateway.settings' );
+ assert( $settings instanceof ContainerInterface );
+
+ return $settings->has( 'blocks_final_review_enabled' ) ?
+ (bool) $settings->get( 'blocks_final_review_enabled' ) :
+ true;
+ },
+
+ 'blocks.endpoint.update-shipping' => static function ( ContainerInterface $container ): UpdateShippingEndpoint {
+ return new UpdateShippingEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'api.endpoint.order' ),
+ $container->get( 'api.factory.purchase-unit' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+);
diff --git a/modules/ppcp-blocks/src/BlocksModule.php b/modules/ppcp-blocks/src/BlocksModule.php
new file mode 100644
index 000000000..1766806b7
--- /dev/null
+++ b/modules/ppcp-blocks/src/BlocksModule.php
@@ -0,0 +1,100 @@
+%1$s
',
+ wp_kses_post(
+ __(
+ 'PayPal checkout block initialization failed, possibly old WooCommerce version or disabled WooCommerce Blocks plugin.',
+ 'woocommerce-paypal-payments'
+ )
+ )
+ );
+ }
+ );
+
+ return;
+ }
+
+ add_action(
+ 'woocommerce_blocks_payment_method_type_registration',
+ function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
+ $payment_method_registry->register( $c->get( 'blocks.method' ) );
+ }
+ );
+
+ woocommerce_store_api_register_payment_requirements(
+ array(
+ 'data_callback' => function() use ( $c ): array {
+ $smart_button = $c->get( 'button.smart-button' );
+ assert( $smart_button instanceof SmartButtonInterface );
+
+ if ( isset( $smart_button->script_data()['continuation'] ) ) {
+ return array( 'ppcp_continuation' );
+ }
+
+ return array();
+ },
+ )
+ );
+
+ add_action(
+ 'wc_ajax_' . UpdateShippingEndpoint::ENDPOINT,
+ static function () use ( $c ) {
+ $endpoint = $c->get( 'blocks.endpoint.update-shipping' );
+ assert( $endpoint instanceof UpdateShippingEndpoint );
+
+ $endpoint->handle_request();
+ }
+ );
+ }
+
+ /**
+ * Returns the key for the module.
+ *
+ * @return string|void
+ */
+ public function getKey() {
+ }
+}
diff --git a/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php
new file mode 100644
index 000000000..f9322700d
--- /dev/null
+++ b/modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php
@@ -0,0 +1,133 @@
+request_data = $request_data;
+ $this->order_endpoint = $order_endpoint;
+ $this->purchase_unit_factory = $purchase_unit_factory;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ */
+ public function handle_request(): bool {
+ try {
+ $data = $this->request_data->read_request( $this->nonce() );
+
+ $order_id = $data['order_id'];
+
+ $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.
+ // Also may consider patching only amount and options instead of the whole PU, though not sure if it makes any difference.
+ $patches = new PatchCollection(
+ new Patch(
+ 'replace',
+ "/purchase_units/@reference_id=='{$pu->reference_id()}'",
+ $pu_data
+ )
+ );
+
+ $this->order_endpoint->patch( $order_id, $patches );
+
+ wp_send_json_success();
+ return true;
+ } catch ( Exception $error ) {
+ wp_send_json_error(
+ array(
+ 'message' => $error->getMessage(),
+ )
+ );
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-blocks/src/PayPalPaymentMethod.php b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
new file mode 100644
index 000000000..3c407f2a2
--- /dev/null
+++ b/modules/ppcp-blocks/src/PayPalPaymentMethod.php
@@ -0,0 +1,193 @@
+name = PayPalGateway::ID;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->smart_button = $smart_button;
+ $this->plugin_settings = $plugin_settings;
+ $this->settings_status = $settings_status;
+ $this->gateway = $gateway;
+ $this->final_review_enabled = $final_review_enabled;
+ $this->cancellation_view = $cancellation_view;
+ $this->session_handler = $session_handler;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function initialize() { }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function is_active() {
+ // Do not load when definitely not needed,
+ // but we still need to check the locations later and handle in JS
+ // because has_block cannot be called here (too early).
+ return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' )
+ && ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) ||
+ $this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_script_handles() {
+ wp_register_script(
+ 'ppcp-checkout-block',
+ trailingslashit( $this->module_url ) . 'assets/js/checkout-block.js',
+ array(),
+ $this->version,
+ true
+ );
+
+ return array( 'ppcp-checkout-block' );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_data() {
+ $script_data = $this->smart_button->script_data();
+
+ if ( isset( $script_data['continuation'] ) ) {
+ $url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() );
+
+ $script_data['continuation']['cancel'] = array(
+ 'html' => $this->cancellation_view->render_session_cancellation( $url, $this->session_handler->funding_source() ),
+ );
+
+ $order = $this->session_handler->order();
+ if ( $order ) {
+ $script_data['continuation']['order'] = $order->to_array();
+ }
+ }
+
+ return array(
+ 'id' => $this->gateway->id,
+ 'title' => $this->gateway->title,
+ 'description' => $this->gateway->description,
+ 'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ),
+ 'fundingSource' => $this->session_handler->funding_source(),
+ 'finalReviewEnabled' => $this->final_review_enabled,
+ 'ajax' => array(
+ 'update_shipping' => array(
+ 'endpoint' => WC_AJAX::get_endpoint( UpdateShippingEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( UpdateShippingEndpoint::nonce() ),
+ ),
+ ),
+ 'scriptData' => $script_data,
+ );
+ }
+}
diff --git a/modules/ppcp-blocks/webpack.config.js b/modules/ppcp-blocks/webpack.config.js
new file mode 100644
index 000000000..4695769ed
--- /dev/null
+++ b/modules/ppcp-blocks/webpack.config.js
@@ -0,0 +1,38 @@
+const path = require('path');
+const isProduction = process.env.NODE_ENV === 'production';
+
+const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
+
+module.exports = {
+ devtool: isProduction ? 'source-map' : 'eval-source-map',
+ mode: isProduction ? 'production' : 'development',
+ target: 'web',
+ plugins: [ new DependencyExtractionWebpackPlugin() ],
+ entry: {
+ 'checkout-block': path.resolve('./resources/js/checkout-block.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-vaulting/yarn.lock b/modules/ppcp-blocks/yarn.lock
similarity index 71%
rename from modules/ppcp-vaulting/yarn.lock
rename to modules/ppcp-blocks/yarn.lock
index c25ba6dd3..209adc4a4 100644
--- a/modules/ppcp-vaulting/yarn.lock
+++ b/modules/ppcp-blocks/yarn.lock
@@ -17,48 +17,41 @@
dependencies:
"@babel/highlight" "^7.18.6"
-"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.18.8", "@babel/compat-data@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.19.0.tgz#2a592fd89bacb1fcde68de31bee4f2f2dacb0e86"
- integrity sha512-y5rqgTTPTmaF5e2nVhOxw+Ur9HDJLsWb6U/KpgUzRZEdPfE6VOubXBKLdbcUTijzRptednSBDQbYZBOSqJxpJw==
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.20.0", "@babel/compat-data@^7.20.1":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.20.5.tgz#86f172690b093373a933223b4745deeb6049e733"
+ integrity sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==
"@babel/core@^7.19":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.19.0.tgz#d2f5f4f2033c00de8096be3c9f45772563e150c3"
- integrity sha512-reM4+U7B9ss148rh2n1Qs9ASS+w94irYXga7c2jaQv9RVzpS7Mv1a9rnYYwuDa45G+DkORt9g6An2k/V4d9LbQ==
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.5.tgz#45e2114dc6cd4ab167f81daf7820e8fa1250d113"
+ integrity sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==
dependencies:
"@ampproject/remapping" "^2.1.0"
"@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.19.0"
- "@babel/helper-compilation-targets" "^7.19.0"
- "@babel/helper-module-transforms" "^7.19.0"
- "@babel/helpers" "^7.19.0"
- "@babel/parser" "^7.19.0"
+ "@babel/generator" "^7.20.5"
+ "@babel/helper-compilation-targets" "^7.20.0"
+ "@babel/helper-module-transforms" "^7.20.2"
+ "@babel/helpers" "^7.20.5"
+ "@babel/parser" "^7.20.5"
"@babel/template" "^7.18.10"
- "@babel/traverse" "^7.19.0"
- "@babel/types" "^7.19.0"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
convert-source-map "^1.7.0"
debug "^4.1.0"
gensync "^1.0.0-beta.2"
json5 "^2.2.1"
semver "^6.3.0"
-"@babel/generator@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.19.0.tgz#785596c06425e59334df2ccee63ab166b738419a"
- integrity sha512-S1ahxf1gZ2dpoiFgA+ohK9DIpz50bJ0CWs7Zlzb54Z4sG8qmdIrGrVqmy1sAtTVRb+9CU6U8VqT9L0Zj7hxHVg==
+"@babel/generator@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.20.5.tgz#cb25abee3178adf58d6814b68517c62bdbfdda95"
+ integrity sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==
dependencies:
- "@babel/types" "^7.19.0"
+ "@babel/types" "^7.20.5"
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
-"@babel/helper-annotate-as-pure@^7.14.5":
- version "7.15.4"
- resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.15.4.tgz#3d0e43b00c5e49fdb6c57e421601a7a658d5f835"
- integrity sha512-QwrtdNvUNsPCj2lfNQacsGSQvGX8ee1ttrBrcozUP2Sv/jylewBP/8QFe6ZkBsC8T/GYWonNAWJV4aRR9AL2DA==
- dependencies:
- "@babel/types" "^7.15.4"
-
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -74,49 +67,41 @@
"@babel/helper-explode-assignable-expression" "^7.18.6"
"@babel/types" "^7.18.9"
-"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.19.0.tgz#537ec8339d53e806ed422f1e06c8f17d55b96bb0"
- integrity sha512-Ai5bNWXIvwDvWM7njqsG3feMlL9hCVQsPYXodsZyLwshYkZVJt59Gftau4VrE8S9IT9asd2uSP1hG6wCNw+sXA==
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz#6bf5374d424e1b3922822f1d9bdaa43b1a139d0a"
+ integrity sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==
dependencies:
- "@babel/compat-data" "^7.19.0"
+ "@babel/compat-data" "^7.20.0"
"@babel/helper-validator-option" "^7.18.6"
- browserslist "^4.20.2"
+ browserslist "^4.21.3"
semver "^6.3.0"
-"@babel/helper-create-class-features-plugin@^7.18.6":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.19.0.tgz#bfd6904620df4e46470bae4850d66be1054c404b"
- integrity sha512-NRz8DwF4jT3UfrmUoZjd0Uph9HQnP30t7Ash+weACcyNkiYTywpIjDBgReJMKgr+n86sn2nPVVmJ28Dm053Kqw==
+"@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.20.5.tgz#327154eedfb12e977baa4ecc72e5806720a85a06"
+ integrity sha512-3RCdA/EmEaikrhayahwToF0fpweU/8o2p8vhc1c/1kftHOdTKuC65kik/TLc+qfbS8JKw4qqJbne4ovICDhmww==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-member-expression-to-functions" "^7.18.9"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-replace-supers" "^7.19.1"
"@babel/helper-split-export-declaration" "^7.18.6"
-"@babel/helper-create-regexp-features-plugin@^7.14.5":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.5.tgz#c7d5ac5e9cf621c26057722fb7a8a4c5889358c4"
- integrity sha512-TLawwqpOErY2HhWbGJ2nZT5wSkR192QpN+nBg1THfBfftrlvOh+WbhrxXCH4q4xJ9Gl16BGPR/48JA+Ryiho/A==
- dependencies:
- "@babel/helper-annotate-as-pure" "^7.14.5"
- regexpu-core "^4.7.1"
-
-"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.19.0.tgz#7976aca61c0984202baca73d84e2337a5424a41b"
- integrity sha512-htnV+mHX32DF81amCDrwIDr8nrp1PTm+3wfBN9/v8QJOLEioOCOG7qNyq0nHeFiWbT3Eb7gsPwEmV64UCQ1jzw==
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.20.5.tgz#5ea79b59962a09ec2acf20a963a01ab4d076ccca"
+ integrity sha512-m68B1lkg3XDGX5yCvGO0kPx3v9WIYLnzjKfPcQiwntEQa5ZeRkPmo2X/ISJc8qxWGfwUr+kvZAeEzAwLec2r2w==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- regexpu-core "^5.1.0"
+ regexpu-core "^5.2.1"
-"@babel/helper-define-polyfill-provider@^0.3.2":
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.2.tgz#bd10d0aca18e8ce012755395b05a79f45eca5073"
- integrity sha512-r9QJJ+uDWrd+94BSPcP6/de67ygLtvVy6cK4luE6MOuDsZIdoaPBnfSpbO/+LTifjPckbKXRuI9BB/Z2/y3iTg==
+"@babel/helper-define-polyfill-provider@^0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.3.3.tgz#8612e55be5d51f0cd1f36b4a5a83924e89884b7a"
+ integrity sha512-z5aQKU4IzbqCC1XH0nAqfsFLMVSo22SBKUc0BxGrLkolTdPTructy0ToNnlO2zA4j9Q/7pjMZf0DSY+DSTYzww==
dependencies:
"@babel/helper-compilation-targets" "^7.17.7"
"@babel/helper-plugin-utils" "^7.16.7"
@@ -166,19 +151,19 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.19.0.tgz#309b230f04e22c58c6a2c0c0c7e50b216d350c30"
- integrity sha512-3HBZ377Fe14RbLIA+ac3sY4PTgpxHVkFrESaWhoI5PuyXPBBX8+C34qblV9G89ZtycGJCmCI/Ut+VUDK4bltNQ==
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.19.6", "@babel/helper-module-transforms@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz#ac53da669501edd37e658602a21ba14c08748712"
+ integrity sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-module-imports" "^7.18.6"
- "@babel/helper-simple-access" "^7.18.6"
+ "@babel/helper-simple-access" "^7.20.2"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/helper-validator-identifier" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
"@babel/template" "^7.18.10"
- "@babel/traverse" "^7.19.0"
- "@babel/types" "^7.19.0"
+ "@babel/traverse" "^7.20.1"
+ "@babel/types" "^7.20.2"
"@babel/helper-optimise-call-expression@^7.18.6":
version "7.18.6"
@@ -187,15 +172,10 @@
dependencies:
"@babel/types" "^7.18.6"
-"@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.8.0", "@babel/helper-plugin-utils@^7.8.3":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.14.5.tgz#5ac822ce97eec46741ab70a517971e443a70c5a9"
- integrity sha512-/37qQCE3K0vvZKwoK4XU/irIJQdIfCJuhU5eKnNxpFDsOkgFaUAwbv+RYw6eYgsC0E4hS7r5KqGULUogqui0fQ==
-
-"@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.19.0.tgz#4796bb14961521f0f8715990bee2fb6e51ce21bf"
- integrity sha512-40Ryx7I8mT+0gaNxm8JGTZFUITNqdLAgdg0hXzeVZxVD6nFsdhQvip6v8dqkRHzsz1VFpFAaOCHNn0vKBL7Czw==
+"@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.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz#d1b9000752b18d0877cff85a5c376ce5c3121629"
+ integrity sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==
"@babel/helper-remap-async-to-generator@^7.18.6", "@babel/helper-remap-async-to-generator@^7.18.9":
version "7.18.9"
@@ -207,30 +187,30 @@
"@babel/helper-wrap-function" "^7.18.9"
"@babel/types" "^7.18.9"
-"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.9.tgz#1092e002feca980fbbb0bd4d51b74a65c6a500e6"
- integrity sha512-dNsWibVI4lNT6HiuOIBr1oyxo40HvIVmbwPUm3XZ7wMh4k2WxrxTqZwSqw/eEmXDS9np0ey5M2bz9tBmO9c+YQ==
+"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.19.1.tgz#e1592a9b4b368aa6bdb8784a711e0bcbf0612b78"
+ integrity sha512-T7ahH7wV0Hfs46SFh5Jz3s0B6+o8g3c+7TMxu7xKfmHikg7EAZ3I2Qk9LFhjxXq8sL7UkP5JflezNwoZa8WvWw==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-member-expression-to-functions" "^7.18.9"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/traverse" "^7.18.9"
- "@babel/types" "^7.18.9"
+ "@babel/traverse" "^7.19.1"
+ "@babel/types" "^7.19.0"
-"@babel/helper-simple-access@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.18.6.tgz#d6d8f51f4ac2978068df934b569f08f29788c7ea"
- integrity sha512-iNpIgTgyAvDQpDj76POqg+YEt8fPxx3yaNBg3S30dxNKm2SWfYhD0TGrK/Eu9wHpUW63VQU894TsTg+GLbUa1g==
+"@babel/helper-simple-access@^7.19.4", "@babel/helper-simple-access@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz#0ab452687fe0c2cfb1e2b9e0015de07fc2d62dd9"
+ integrity sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==
dependencies:
- "@babel/types" "^7.18.6"
+ "@babel/types" "^7.20.2"
"@babel/helper-skip-transparent-expression-wrappers@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.18.9.tgz#778d87b3a758d90b471e7b9918f34a9a02eb5818"
- integrity sha512-imytd2gHi3cJPsybLRbmFrF7u5BIEuI2cNheyKi3/iOBC63kNn3q8Crn2xVuESli0aM4KYsyEqKyS7lFL8YVtw==
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684"
+ integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==
dependencies:
- "@babel/types" "^7.18.9"
+ "@babel/types" "^7.20.0"
"@babel/helper-split-export-declaration@^7.18.6":
version "7.18.6"
@@ -239,20 +219,15 @@
dependencies:
"@babel/types" "^7.18.6"
-"@babel/helper-string-parser@^7.18.10":
- version "7.18.10"
- resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.18.10.tgz#181f22d28ebe1b3857fa575f5c290b1aaf659b56"
- integrity sha512-XtIfWmeNY3i4t7t4D2t02q50HvqHybPqW2ki1kosnvWCwuCMeo81Jf0gwr85jy/neUdg5XDdeFE/80DXiO+njw==
+"@babel/helper-string-parser@^7.19.4":
+ version "7.19.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz#38d3acb654b4701a9b77fb0615a96f775c3a9e63"
+ integrity sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==
-"@babel/helper-validator-identifier@^7.14.9":
- version "7.15.7"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.15.7.tgz#220df993bfe904a4a6b02ab4f3385a5ebf6e2389"
- integrity sha512-K4JvCtQqad9OY2+yTU8w+E82ywk/fe+ELNlt1G8z3bVGlZfn/hOcQQsUhGhW/N+tb3fxK800wLtKOE/aM0m72w==
-
-"@babel/helper-validator-identifier@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
- integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
+"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
+ integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
@@ -260,23 +235,23 @@
integrity sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==
"@babel/helper-wrap-function@^7.18.9":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.19.0.tgz#89f18335cff1152373222f76a4b37799636ae8b1"
- integrity sha512-txX8aN8CZyYGTwcLhlk87KRqncAzhh5TpQamZUa0/u3an36NtDpUP6bQgBCBcLeBs09R/OwQu3OjK0k/HwfNDg==
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3"
+ integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==
dependencies:
"@babel/helper-function-name" "^7.19.0"
"@babel/template" "^7.18.10"
- "@babel/traverse" "^7.19.0"
- "@babel/types" "^7.19.0"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
-"@babel/helpers@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.19.0.tgz#f30534657faf246ae96551d88dd31e9d1fa1fc18"
- integrity sha512-DRBCKGwIEdqY3+rPJgG/dKfQy9+08rHIAJx8q2p+HSWP87s2HCrQmaAMMyMll2kIXKCW0cO1RdQskx15Xakftg==
+"@babel/helpers@^7.20.5":
+ version "7.20.6"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.20.6.tgz#e64778046b70e04779dfbdf924e7ebb45992c763"
+ integrity sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==
dependencies:
"@babel/template" "^7.18.10"
- "@babel/traverse" "^7.19.0"
- "@babel/types" "^7.19.0"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
"@babel/highlight@^7.18.6":
version "7.18.6"
@@ -287,10 +262,10 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
-"@babel/parser@^7.18.10", "@babel/parser@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.19.0.tgz#497fcafb1d5b61376959c1c338745ef0577aa02c"
- integrity sha512-74bEXKX2h+8rrfQUfsBfuZZHzsEs6Eql4pqy/T4Nn6Y9wNPggQOqD6z6pn5Bl8ZfysKouFZT/UXEH94ummEeQw==
+"@babel/parser@^7.18.10", "@babel/parser@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.5.tgz#7f3c7335fe417665d929f34ae5dceae4c04015e8"
+ integrity sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
@@ -308,10 +283,10 @@
"@babel/helper-skip-transparent-expression-wrappers" "^7.18.9"
"@babel/plugin-proposal-optional-chaining" "^7.18.9"
-"@babel/plugin-proposal-async-generator-functions@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.19.0.tgz#cf5740194f170467df20581712400487efc79ff1"
- integrity sha512-nhEByMUTx3uZueJ/QkJuSlCfN4FGg+xy+vRsfGQGzSauq5ks2Deid2+05Q3KhfaUjvec1IGhw/Zm3cFm8JigTQ==
+"@babel/plugin-proposal-async-generator-functions@^7.20.1":
+ version "7.20.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.20.1.tgz#352f02baa5d69f4e7529bdac39aaa02d41146af9"
+ integrity sha512-Gh5rchzSwE4kC+o/6T8waD0WHEQIsDmjltY8WnWRXHUdH8axZhuH86Ov9M72YhJfDrZseQwuuWaaIT/TmePp3g==
dependencies:
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-plugin-utils" "^7.19.0"
@@ -383,16 +358,16 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-syntax-numeric-separator" "^7.10.4"
-"@babel/plugin-proposal-object-rest-spread@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.18.9.tgz#f9434f6beb2c8cae9dfcf97d2a5941bbbf9ad4e7"
- integrity sha512-kDDHQ5rflIeY5xl69CEqGEZ0KY369ehsCIEbTGb4siHG5BE9sga/T0r0OUwyZNLMmZE79E1kbsqAjwFCW4ds6Q==
+"@babel/plugin-proposal-object-rest-spread@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.20.2.tgz#a556f59d555f06961df1e572bb5eca864c84022d"
+ integrity sha512-Ks6uej9WFK+fvIMesSqbAto5dD8Dz4VuuFvGJFKgIGSkJuRGcrwGECPA1fDgQK3/DbExBJpEkTeYeB8geIFCSQ==
dependencies:
- "@babel/compat-data" "^7.18.8"
- "@babel/helper-compilation-targets" "^7.18.9"
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/compat-data" "^7.20.1"
+ "@babel/helper-compilation-targets" "^7.20.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-object-rest-spread" "^7.8.3"
- "@babel/plugin-transform-parameters" "^7.18.8"
+ "@babel/plugin-transform-parameters" "^7.20.1"
"@babel/plugin-proposal-optional-catch-binding@^7.18.6":
version "7.18.6"
@@ -420,16 +395,16 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/plugin-proposal-private-property-in-object@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.18.6.tgz#a64137b232f0aca3733a67eb1a144c192389c503"
- integrity sha512-9Rysx7FOctvT5ouj5JODjAFAkgGoudQuLPamZb0v1TGLpapdNaftzifU8NTWQm0IRjqoYypdrSmyWgkocDQ8Dw==
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.20.5.tgz#309c7668f2263f1c711aa399b5a9a6291eef6135"
+ integrity sha512-Vq7b9dUA12ByzB4EjQTPo25sFhY+08pQDBSZRtUAkj7lb7jahaHR5igera16QZ+3my1nYR4dKsNdYj5IjPHilQ==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-create-class-features-plugin" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.20.5"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-syntax-private-property-in-object" "^7.14.5"
-"@babel/plugin-proposal-unicode-property-regex@^7.18.6":
+"@babel/plugin-proposal-unicode-property-regex@^7.18.6", "@babel/plugin-proposal-unicode-property-regex@^7.4.4":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
@@ -437,14 +412,6 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.14.5.tgz#0f95ee0e757a5d647f378daa0eca7e93faa8bbe8"
- integrity sha512-6axIeOU5LnY471KenAB9vI8I5j7NQ2d652hIYwVyRfgaZT5UpiqFKCuVXCDMSrU+3VFafnu2c5m3lrWIlr6A5Q==
- dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.14.5"
- "@babel/helper-plugin-utils" "^7.14.5"
-
"@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"
@@ -480,12 +447,12 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.3"
-"@babel/plugin-syntax-import-assertions@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.18.6.tgz#cd6190500a4fa2fe31990a963ffab4b63e4505e4"
- integrity sha512-/DU3RXad9+bZwrgWJQKbr39gYbJpLJHezqEzRzi/BHRlJ9zsQb4CK2CA/5apllXNomwA1qHwzvHl+AdEmC5krQ==
+"@babel/plugin-syntax-import-assertions@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4"
+ integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.19.0"
"@babel/plugin-syntax-json-strings@^7.8.3":
version "7.8.3"
@@ -494,6 +461,13 @@
dependencies:
"@babel/helper-plugin-utils" "^7.8.0"
+"@babel/plugin-syntax-jsx@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.18.6.tgz#a8feef63b010150abd97f1649ec296e849943ca0"
+ integrity sha512-6mmljtAedFGTWu2p/8WIORGwy+61PLgOMPOdazc7YoJ9ZCWUyFy3A6CpPkRKLKD1ToAesxX8KGEViAiLo9N+7Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
"@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"
@@ -573,25 +547,25 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-block-scoping@^7.18.9":
- version "7.18.9"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.18.9.tgz#f9b7e018ac3f373c81452d6ada8bd5a18928926d"
- integrity sha512-5sDIJRV1KtQVEbt/EIBwGy4T01uYIo4KRB3VUqzkhrAIOGx7AoctL9+Ux88btY0zXdDyPJ9mW+bg+v+XEkGmtw==
+"@babel/plugin-transform-block-scoping@^7.20.2":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.20.5.tgz#401215f9dc13dc5262940e2e527c9536b3d7f237"
+ integrity sha512-WvpEIW9Cbj9ApF3yJCjIEEf1EiNJLtXagOrL5LNWEZOo3jv8pmPoYTSNJQvqej8OavVlgOoOPw6/htGZro6IkA==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
-"@babel/plugin-transform-classes@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.19.0.tgz#0e61ec257fba409c41372175e7c1e606dc79bb20"
- integrity sha512-YfeEE9kCjqTS9IitkgfJuxjcEtLUHMqa8yUJ6zdz8vR7hKuo6mOy2C05P0F1tdMmDCeuyidKnlrw/iTppHcr2A==
+"@babel/plugin-transform-classes@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.20.2.tgz#c0033cf1916ccf78202d04be4281d161f6709bb2"
+ integrity sha512-9rbPp0lCVVoagvtEyQKSo5L8oo0nQS/iif+lwlAz29MccX2642vWDlSZK+2T2buxbopotId2ld7zZAzRfz9j1g==
dependencies:
"@babel/helper-annotate-as-pure" "^7.18.6"
- "@babel/helper-compilation-targets" "^7.19.0"
+ "@babel/helper-compilation-targets" "^7.20.0"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-optimise-call-expression" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.19.0"
- "@babel/helper-replace-supers" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-replace-supers" "^7.19.1"
"@babel/helper-split-export-declaration" "^7.18.6"
globals "^11.1.0"
@@ -602,14 +576,14 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.9"
-"@babel/plugin-transform-destructuring@^7.18.13":
- version "7.18.13"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.18.13.tgz#9e03bc4a94475d62b7f4114938e6c5c33372cbf5"
- integrity sha512-TodpQ29XekIsex2A+YJPj5ax2plkGa8YYY6mFjCohk/IG9IY42Rtuj1FuDeemfg2ipxIFLzPeA83SIBnlhSIow==
+"@babel/plugin-transform-destructuring@^7.20.2":
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.20.2.tgz#c23741cfa44ddd35f5e53896e88c75331b8b2792"
+ integrity sha512-mENM+ZHrvEgxLTBXUiQ621rRXZes3KWUv6NdQlrnr1TkWVw+hUjQBZuP2X32qKlrlG2BzgR95gkuCRSkJl8vIw==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.20.2"
-"@babel/plugin-transform-dotall-regex@^7.18.6":
+"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8"
integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==
@@ -617,14 +591,6 @@
"@babel/helper-create-regexp-features-plugin" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-dotall-regex@^7.4.4":
- version "7.14.5"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.14.5.tgz#2f6bf76e46bdf8043b4e7e16cf24532629ba0c7a"
- integrity sha512-loGlnBdj02MDsFaHhAIJzh7euK89lBrGIdM9EAtHFo6xKygCUGuuWe07o1oZVk287amtW1n0808sQM99aZt3gw==
- dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.14.5"
- "@babel/helper-plugin-utils" "^7.14.5"
-
"@babel/plugin-transform-duplicate-keys@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e"
@@ -670,35 +636,32 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-modules-amd@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.18.6.tgz#8c91f8c5115d2202f277549848874027d7172d21"
- integrity sha512-Pra5aXsmTsOnjM3IajS8rTaLCy++nGM4v3YR4esk5PCsyg9z8NA5oQLwxzMUtDBd8F+UmVza3VxoAaWCbzH1rg==
+"@babel/plugin-transform-modules-amd@^7.19.6":
+ version "7.19.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.19.6.tgz#aca391801ae55d19c4d8d2ebfeaa33df5f2a2cbd"
+ integrity sha512-uG3od2mXvAtIFQIh0xrpLH6r5fpSQN04gIVovl+ODLdUMANokxQLZnPBHcjmv3GxRjnqwLuHvppjjcelqUFZvg==
dependencies:
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-module-transforms" "^7.19.6"
+ "@babel/helper-plugin-utils" "^7.19.0"
-"@babel/plugin-transform-modules-commonjs@^7.18.6":
- version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883"
- integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q==
+"@babel/plugin-transform-modules-commonjs@^7.19.6":
+ version "7.19.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz#25b32feef24df8038fc1ec56038917eacb0b730c"
+ integrity sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==
dependencies:
- "@babel/helper-module-transforms" "^7.18.6"
- "@babel/helper-plugin-utils" "^7.18.6"
- "@babel/helper-simple-access" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-module-transforms" "^7.19.6"
+ "@babel/helper-plugin-utils" "^7.19.0"
+ "@babel/helper-simple-access" "^7.19.4"
-"@babel/plugin-transform-modules-systemjs@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.0.tgz#5f20b471284430f02d9c5059d9b9a16d4b085a1f"
- integrity sha512-x9aiR0WXAWmOWsqcsnrzGR+ieaTMVyGyffPVA7F8cXAGt/UxefYv6uSHZLkAFChN5M5Iy1+wjE+xJuPt22H39A==
+"@babel/plugin-transform-modules-systemjs@^7.19.6":
+ version "7.19.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.19.6.tgz#59e2a84064b5736a4471b1aa7b13d4431d327e0d"
+ integrity sha512-fqGLBepcc3kErfR9R3DnVpURmckXP7gj7bAlrTQyBxrigFqszZCkFkcoxzCp2v32XmwXLvbw+8Yq9/b+QqksjQ==
dependencies:
"@babel/helper-hoist-variables" "^7.18.6"
- "@babel/helper-module-transforms" "^7.19.0"
+ "@babel/helper-module-transforms" "^7.19.6"
"@babel/helper-plugin-utils" "^7.19.0"
- "@babel/helper-validator-identifier" "^7.18.6"
- babel-plugin-dynamic-import-node "^2.3.3"
+ "@babel/helper-validator-identifier" "^7.19.1"
"@babel/plugin-transform-modules-umd@^7.18.6":
version "7.18.6"
@@ -708,13 +671,13 @@
"@babel/helper-module-transforms" "^7.18.6"
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-named-capturing-groups-regex@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.19.0.tgz#58c52422e4f91a381727faed7d513c89d7f41ada"
- integrity sha512-HDSuqOQzkU//kfGdiHBt71/hkDTApw4U/cMVgKgX7PqfB3LOaK+2GtCEsBu1dL9CkswDm0Gwehht1dCr421ULQ==
+"@babel/plugin-transform-named-capturing-groups-regex@^7.19.1":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.20.5.tgz#626298dd62ea51d452c3be58b285d23195ba69a8"
+ integrity sha512-mOW4tTzi5iTLnw+78iEq3gr8Aoq4WNRGpmSlrogqaiCBoR1HFhpU4JkpQFOHfeYx3ReVIFWOQJS4aZBRvuZ6mA==
dependencies:
- "@babel/helper-create-regexp-features-plugin" "^7.19.0"
- "@babel/helper-plugin-utils" "^7.19.0"
+ "@babel/helper-create-regexp-features-plugin" "^7.20.5"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-new-target@^7.18.6":
version "7.18.6"
@@ -731,12 +694,12 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/helper-replace-supers" "^7.18.6"
-"@babel/plugin-transform-parameters@^7.18.8":
- version "7.18.8"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.18.8.tgz#ee9f1a0ce6d78af58d0956a9378ea3427cccb48a"
- integrity sha512-ivfbE3X2Ss+Fj8nnXvKJS6sjRG4gzwPMsP+taZC+ZzEGjAYlvENixmt1sZ5Ca6tWls+BlKSGKPJ6OOXvXCbkFg==
+"@babel/plugin-transform-parameters@^7.20.1":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.20.5.tgz#f8f9186c681d10c3de7620c916156d893c8a019e"
+ integrity sha512-h7plkOmcndIUWXZFLgpbrh2+fXAi47zcUX7IrOQuZdLD0I0KvjJ6cvo3BEcAOsDOcZhVKGJqv07mkSqK0y2isQ==
dependencies:
- "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/plugin-transform-property-literals@^7.18.6":
version "7.18.6"
@@ -745,13 +708,46 @@
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
-"@babel/plugin-transform-regenerator@^7.18.6":
+"@babel/plugin-transform-react-display-name@^7.18.6":
version "7.18.6"
- resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.18.6.tgz#585c66cb84d4b4bf72519a34cfce761b8676ca73"
- integrity sha512-poqRI2+qiSdeldcz4wTSTXBRryoq3Gc70ye7m7UD5Ww0nE29IXqMl6r7Nd15WBgRd74vloEMlShtH6CKxVzfmQ==
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.18.6.tgz#8b1125f919ef36ebdfff061d664e266c666b9415"
+ integrity sha512-TV4sQ+T013n61uMoygyMRm+xf04Bd5oqFpv2jAEQwSZ8NwQA7zeRPg1LMVg2PWi3zWBz+CLKD+v5bcpZ/BS0aA==
dependencies:
"@babel/helper-plugin-utils" "^7.18.6"
- regenerator-transform "^0.15.0"
+
+"@babel/plugin-transform-react-jsx-development@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.18.6.tgz#dbe5c972811e49c7405b630e4d0d2e1380c0ddc5"
+ integrity sha512-SA6HEjwYFKF7WDjWcMcMGUimmw/nhNRDWxr+KaLSCrkD/LMDBvWRmHAYgE1HDeF8KUuI8OAu+RT6EOtKxSW2qA==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.18.6"
+
+"@babel/plugin-transform-react-jsx@^7.18.6":
+ version "7.19.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.19.0.tgz#b3cbb7c3a00b92ec8ae1027910e331ba5c500eb9"
+ integrity sha512-UVEvX3tXie3Szm3emi1+G63jyw1w5IcMY0FSKM+CRnKRI5Mr1YbCNgsSTwoTwKphQEG9P+QqmuRFneJPZuHNhg==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.19.0"
+ "@babel/plugin-syntax-jsx" "^7.18.6"
+ "@babel/types" "^7.19.0"
+
+"@babel/plugin-transform-react-pure-annotations@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.18.6.tgz#561af267f19f3e5d59291f9950fd7b9663d0d844"
+ integrity sha512-I8VfEPg9r2TRDdvnHgPepTKvuRomzA8+u+nhY7qSI1fR2hRNebasZEETLyM5mAUr0Ku56OkXJ0I7NHJnO6cJiQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-regenerator@^7.18.6":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.20.5.tgz#57cda588c7ffb7f4f8483cc83bdcea02a907f04d"
+ integrity sha512-kW/oO7HPBtntbsahzQ0qSE3tFvkFwnbozz3NWFhLGqH75vLEg+sCGngLlhVkePlCs3Jv0dBBHDzCHxNiFAQKCQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ regenerator-transform "^0.15.1"
"@babel/plugin-transform-reserved-words@^7.18.6":
version "7.18.6"
@@ -812,17 +808,17 @@
"@babel/helper-plugin-utils" "^7.18.6"
"@babel/preset-env@^7.19":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.19.0.tgz#fd18caf499a67d6411b9ded68dc70d01ed1e5da7"
- integrity sha512-1YUju1TAFuzjIQqNM9WsF4U6VbD/8t3wEAlw3LFYuuEr+ywqLRcSXxFKz4DCEj+sN94l/XTDiUXYRrsvMpz9WQ==
+ version "7.20.2"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.20.2.tgz#9b1642aa47bb9f43a86f9630011780dab7f86506"
+ integrity sha512-1G0efQEWR1EHkKvKHqbG+IN/QdgwfByUpM5V5QroDzGV2t3S/WXNQd693cHiHTlCFMpr9B6FkPFXDA2lQcKoDg==
dependencies:
- "@babel/compat-data" "^7.19.0"
- "@babel/helper-compilation-targets" "^7.19.0"
- "@babel/helper-plugin-utils" "^7.19.0"
+ "@babel/compat-data" "^7.20.1"
+ "@babel/helper-compilation-targets" "^7.20.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
"@babel/helper-validator-option" "^7.18.6"
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.18.9"
- "@babel/plugin-proposal-async-generator-functions" "^7.19.0"
+ "@babel/plugin-proposal-async-generator-functions" "^7.20.1"
"@babel/plugin-proposal-class-properties" "^7.18.6"
"@babel/plugin-proposal-class-static-block" "^7.18.6"
"@babel/plugin-proposal-dynamic-import" "^7.18.6"
@@ -831,7 +827,7 @@
"@babel/plugin-proposal-logical-assignment-operators" "^7.18.9"
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.18.6"
"@babel/plugin-proposal-numeric-separator" "^7.18.6"
- "@babel/plugin-proposal-object-rest-spread" "^7.18.9"
+ "@babel/plugin-proposal-object-rest-spread" "^7.20.2"
"@babel/plugin-proposal-optional-catch-binding" "^7.18.6"
"@babel/plugin-proposal-optional-chaining" "^7.18.9"
"@babel/plugin-proposal-private-methods" "^7.18.6"
@@ -842,7 +838,7 @@
"@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.18.6"
+ "@babel/plugin-syntax-import-assertions" "^7.20.0"
"@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"
@@ -855,10 +851,10 @@
"@babel/plugin-transform-arrow-functions" "^7.18.6"
"@babel/plugin-transform-async-to-generator" "^7.18.6"
"@babel/plugin-transform-block-scoped-functions" "^7.18.6"
- "@babel/plugin-transform-block-scoping" "^7.18.9"
- "@babel/plugin-transform-classes" "^7.19.0"
+ "@babel/plugin-transform-block-scoping" "^7.20.2"
+ "@babel/plugin-transform-classes" "^7.20.2"
"@babel/plugin-transform-computed-properties" "^7.18.9"
- "@babel/plugin-transform-destructuring" "^7.18.13"
+ "@babel/plugin-transform-destructuring" "^7.20.2"
"@babel/plugin-transform-dotall-regex" "^7.18.6"
"@babel/plugin-transform-duplicate-keys" "^7.18.9"
"@babel/plugin-transform-exponentiation-operator" "^7.18.6"
@@ -866,14 +862,14 @@
"@babel/plugin-transform-function-name" "^7.18.9"
"@babel/plugin-transform-literals" "^7.18.9"
"@babel/plugin-transform-member-expression-literals" "^7.18.6"
- "@babel/plugin-transform-modules-amd" "^7.18.6"
- "@babel/plugin-transform-modules-commonjs" "^7.18.6"
- "@babel/plugin-transform-modules-systemjs" "^7.19.0"
+ "@babel/plugin-transform-modules-amd" "^7.19.6"
+ "@babel/plugin-transform-modules-commonjs" "^7.19.6"
+ "@babel/plugin-transform-modules-systemjs" "^7.19.6"
"@babel/plugin-transform-modules-umd" "^7.18.6"
- "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.0"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.19.1"
"@babel/plugin-transform-new-target" "^7.18.6"
"@babel/plugin-transform-object-super" "^7.18.6"
- "@babel/plugin-transform-parameters" "^7.18.8"
+ "@babel/plugin-transform-parameters" "^7.20.1"
"@babel/plugin-transform-property-literals" "^7.18.6"
"@babel/plugin-transform-regenerator" "^7.18.6"
"@babel/plugin-transform-reserved-words" "^7.18.6"
@@ -885,11 +881,11 @@
"@babel/plugin-transform-unicode-escapes" "^7.18.10"
"@babel/plugin-transform-unicode-regex" "^7.18.6"
"@babel/preset-modules" "^0.1.5"
- "@babel/types" "^7.19.0"
- babel-plugin-polyfill-corejs2 "^0.3.2"
- babel-plugin-polyfill-corejs3 "^0.5.3"
- babel-plugin-polyfill-regenerator "^0.4.0"
- core-js-compat "^3.22.1"
+ "@babel/types" "^7.20.2"
+ babel-plugin-polyfill-corejs2 "^0.3.3"
+ babel-plugin-polyfill-corejs3 "^0.6.0"
+ babel-plugin-polyfill-regenerator "^0.4.1"
+ core-js-compat "^3.25.1"
semver "^6.3.0"
"@babel/preset-modules@^0.1.5":
@@ -903,12 +899,24 @@
"@babel/types" "^7.4.4"
esutils "^2.0.2"
-"@babel/runtime@^7.8.4":
- version "7.15.4"
- resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
- integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
+"@babel/preset-react@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.18.6.tgz#979f76d6277048dc19094c217b507f3ad517dd2d"
+ integrity sha512-zXr6atUmyYdiWRVLOZahakYmOBHtWc2WGCkP8PYTgZi0iJXDY2CN180TdrIW4OGOAdLc7TifzDIvtx6izaRIzg==
dependencies:
- regenerator-runtime "^0.13.4"
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-validator-option" "^7.18.6"
+ "@babel/plugin-transform-react-display-name" "^7.18.6"
+ "@babel/plugin-transform-react-jsx" "^7.18.6"
+ "@babel/plugin-transform-react-jsx-development" "^7.18.6"
+ "@babel/plugin-transform-react-pure-annotations" "^7.18.6"
+
+"@babel/runtime@^7.8.4":
+ version "7.20.6"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3"
+ integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==
+ dependencies:
+ regenerator-runtime "^0.13.11"
"@babel/template@^7.18.10":
version "7.18.10"
@@ -919,43 +927,35 @@
"@babel/parser" "^7.18.10"
"@babel/types" "^7.18.10"
-"@babel/traverse@^7.18.9", "@babel/traverse@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.19.0.tgz#eb9c561c7360005c592cc645abafe0c3c4548eed"
- integrity sha512-4pKpFRDh+utd2mbRC8JLnlsMUii3PMHjpL6a0SZ4NMZy7YFP9aXORxEhdMVOc9CpWtDF09IkciQLEhK7Ml7gRA==
+"@babel/traverse@^7.19.1", "@babel/traverse@^7.20.1", "@babel/traverse@^7.20.5":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.20.5.tgz#78eb244bea8270fdda1ef9af22a5d5e5b7e57133"
+ integrity sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==
dependencies:
"@babel/code-frame" "^7.18.6"
- "@babel/generator" "^7.19.0"
+ "@babel/generator" "^7.20.5"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.19.0"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
- "@babel/parser" "^7.19.0"
- "@babel/types" "^7.19.0"
+ "@babel/parser" "^7.20.5"
+ "@babel/types" "^7.20.5"
debug "^4.1.0"
globals "^11.1.0"
-"@babel/types@^7.15.4", "@babel/types@^7.4.4":
- version "7.15.6"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.15.6.tgz#99abdc48218b2881c058dd0a7ab05b99c9be758f"
- integrity sha512-BPU+7QhqNjmWyDO0/vitH/CuhpV8ZmK1wpKva8nuyNF5MJfuRNWMc+hc14+u9xT93kvykMdncrJT19h74uB1Ig==
+"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0", "@babel/types@^7.20.0", "@babel/types@^7.20.2", "@babel/types@^7.20.5", "@babel/types@^7.4.4":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.5.tgz#e206ae370b5393d94dfd1d04cd687cace53efa84"
+ integrity sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==
dependencies:
- "@babel/helper-validator-identifier" "^7.14.9"
- to-fast-properties "^2.0.0"
-
-"@babel/types@^7.18.10", "@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.19.0":
- version "7.19.0"
- resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.19.0.tgz#75f21d73d73dc0351f3368d28db73465f4814600"
- integrity sha512-YuGopBq3ke25BVSiS6fgF49Ul9gH1x70Bcr6bqRLjWCkcX8Hre1/5+z+IiWOIerRMSSEfGZVB9z9kyq7wVs9YA==
- dependencies:
- "@babel/helper-string-parser" "^7.18.10"
- "@babel/helper-validator-identifier" "^7.18.6"
+ "@babel/helper-string-parser" "^7.19.4"
+ "@babel/helper-validator-identifier" "^7.19.1"
to-fast-properties "^2.0.0"
"@discoveryjs/json-ext@^0.5.0":
- version "0.5.5"
- resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.5.tgz#9283c9ce5b289a3c4f61c12757469e59377f81f3"
- integrity sha512-6nFkfkmSeV/rqSaS4oWHgmpnYw194f6hmWF5is6b0J1naJZoiD0NTc9AiUwPHvWsowkjuHErCZT1wa0jg+BLIA==
+ 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.1.0":
version "0.1.1"
@@ -974,7 +974,7 @@
"@jridgewell/sourcemap-codec" "^1.4.10"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/resolve-uri@^3.0.3":
+"@jridgewell/resolve-uri@3.1.0":
version "3.1.0"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
@@ -992,18 +992,18 @@
"@jridgewell/gen-mapping" "^0.3.0"
"@jridgewell/trace-mapping" "^0.3.9"
-"@jridgewell/sourcemap-codec@^1.4.10":
+"@jridgewell/sourcemap-codec@1.4.14", "@jridgewell/sourcemap-codec@^1.4.10":
version "1.4.14"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
-"@jridgewell/trace-mapping@^0.3.9":
- version "0.3.14"
- resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
- integrity sha512-bJWEfQ9lPTvm3SneWwRFVLzrh6nhjwqw7TUFFBEMzwvg7t7PCDenf2lDwqo4NQXzdpgBXyFgDWnQA+2vkruksQ==
+"@jridgewell/trace-mapping@^0.3.14", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.17"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz#793041277af9073b0951a7fe0f0d8c4c98c36985"
+ integrity sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==
dependencies:
- "@jridgewell/resolve-uri" "^3.0.3"
- "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
"@types/eslint-scope@^3.7.3":
version "3.7.4"
@@ -1014,17 +1014,17 @@
"@types/estree" "*"
"@types/eslint@*":
- version "7.28.0"
- resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-7.28.0.tgz#7e41f2481d301c68e14f483fe10b017753ce8d5a"
- integrity sha512-07XlgzX0YJUn4iG1ocY4IX9DzKSmMGUs6ESKlxWhZRaa0fatIWaHWUVapcuGa8r5HFnTqzj+4OCjd5f7EZ/i/A==
+ version "8.4.10"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.10.tgz#19731b9685c19ed1552da7052b6f668ed7eb64bb"
+ integrity sha512-Sl/HOqN8NKPmhWo2VBEPm0nvHnu2LL3v9vKo8MEq0EtbJ4eVzGPl41VNPvn5E1i5poMk4/XD8UriLHpJvEP/Nw==
dependencies:
"@types/estree" "*"
"@types/json-schema" "*"
"@types/estree@*":
- version "0.0.50"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
- integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.0.tgz#5fb2e536c1ae9bf35366eed879e827fa59ca41c2"
+ integrity sha512-WulqXMDUTYAXCjZnk6JtIHPigp55cVtDgDrO2gHRwhyJto21+1zbVCtOYB2L1F9w4qCQ0rOGWBnBe0FNTiEJIQ==
"@types/estree@^0.0.51":
version "0.0.51"
@@ -1032,14 +1032,14 @@
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
- version "7.0.9"
- resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
- integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
+ version "7.0.11"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3"
+ integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==
"@types/node@*":
- version "16.10.1"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-16.10.1.tgz#f3647623199ca920960006b3dccf633ea905f243"
- integrity sha512-4/Z9DMPKFexZj/Gn3LylFgamNKHm4K3QDi0gz9B26Uk0c8izYf97B5fxfpspMNkWlFupblKM/nV8+NA9Ffvr+w==
+ version "18.11.11"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.11.tgz#1d455ac0211549a8409d3cdb371cd55cc971e8dc"
+ integrity sha512-KJ021B1nlQUBLopzZmPBVuGU9un7WJd/W4ya7Ih02B4Uwky5Nja0yGYav2EfYIk0RR2Q9oVhf60S2XR1BCWJ2g==
"@webassemblyjs/ast@1.11.1":
version "1.11.1"
@@ -1179,6 +1179,21 @@
resolved "https://registry.yarnpkg.com/@webpack-cli/serve/-/serve-1.7.0.tgz#e1993689ac42d2b16e9194376cfb6753f6254db1"
integrity sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==
+"@woocommerce/dependency-extraction-webpack-plugin@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz#230d674a67585bc32e31bc28485bec99b41dbd1f"
+ integrity sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A==
+ dependencies:
+ "@wordpress/dependency-extraction-webpack-plugin" "^3.3.0"
+
+"@wordpress/dependency-extraction-webpack-plugin@^3.3.0":
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-3.7.0.tgz#e52ef31f66b8c4add3d773a87e11007375127b04"
+ integrity sha512-SHyp88D1ICSaRVMfs/kKEicjKXWf1y2wecUeZIiMtkfAi8Bnk3JsnUo11LH7drJIXfjmDoer2B2rrBMZmRm8VA==
+ dependencies:
+ json2php "^0.0.4"
+ webpack-sources "^3.2.2"
+
"@xtuc/ieee754@^1.2.0":
version "1.2.0"
resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790"
@@ -1190,19 +1205,14 @@
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
acorn-import-assertions@^1.7.6:
- version "1.7.6"
- resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
- integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
+ integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
-acorn@^8.5.0:
- version "8.7.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
- integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
-
-acorn@^8.7.1:
- version "8.8.0"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
- integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
+acorn@^8.5.0, acorn@^8.7.1:
+ version "8.8.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
+ integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
ajv-keywords@^3.5.2:
version "3.5.2"
@@ -1227,53 +1237,46 @@ ansi-styles@^3.2.1:
color-convert "^1.9.0"
anymatch@~3.1.2:
- version "3.1.2"
- resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716"
- integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==
+ 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.2.5"
- resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.2.5.tgz#d45f585e654d5a5d90f5350a779d7647c5ed512e"
- integrity sha512-OSiFfH89LrEMiWd4pLNqGz4CwJDtbs2ZVc+iGu2HrkRfPxId9F2anQj38IxWpmRfsUY0aBZYi1EFcd3mhtRMLQ==
+ 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-dynamic-import-node@^2.3.3:
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz#84fda19c976ec5c6defef57f9427b3def66e17a3"
- integrity sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==
- dependencies:
- object.assign "^4.1.0"
-
-babel-plugin-polyfill-corejs2@^0.3.2:
- version "0.3.2"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.2.tgz#e4c31d4c89b56f3cf85b92558954c66b54bd972d"
- integrity sha512-LPnodUl3lS0/4wN3Rb+m+UK8s7lj2jcLRrjho4gLw+OJs+I4bvGXshINesY5xx/apM+biTnQ9reDI8yj+0M5+Q==
+babel-plugin-polyfill-corejs2@^0.3.3:
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.3.3.tgz#5d1bd3836d0a19e1b84bbf2d9640ccb6f951c122"
+ integrity sha512-8hOdmFYFSZhqg2C/JgLUQ+t52o5nirNwaWM2B9LWteozwIvM14VSwdsCAUET10qT+kmySAlseadmfeeSWFCy+Q==
dependencies:
"@babel/compat-data" "^7.17.7"
- "@babel/helper-define-polyfill-provider" "^0.3.2"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
semver "^6.1.1"
-babel-plugin-polyfill-corejs3@^0.5.3:
- version "0.5.3"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.5.3.tgz#d7e09c9a899079d71a8b670c6181af56ec19c5c7"
- integrity sha512-zKsXDh0XjnrUEW0mxIHLfjBfnXSMr5Q/goMe/fxpQnLm07mcOZiIZHBNWCMx60HmdvjxfXcalac0tfFg0wqxyw==
+babel-plugin-polyfill-corejs3@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.6.0.tgz#56ad88237137eade485a71b52f72dbed57c6230a"
+ integrity sha512-+eHqR6OPcBhJOGgsIar7xoAB1GcSwVUA3XjAd7HJNzOXT4wv6/H7KIdA/Nc60cvUlDbKApmqNvD1B1bzOt4nyA==
dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.2"
- core-js-compat "^3.21.0"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
+ core-js-compat "^3.25.1"
-babel-plugin-polyfill-regenerator@^0.4.0:
- version "0.4.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.0.tgz#8f51809b6d5883e07e71548d75966ff7635527fe"
- integrity sha512-RW1cnryiADFeHmfLS+WW/G431p1PsW5qdRdz0SDRi7TKcUgc7Oh/uXkT7MZ/+tGsT1BkczEAmD5XjUyJ5SWDTw==
+babel-plugin-polyfill-regenerator@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.4.1.tgz#390f91c38d90473592ed43351e801a9d3e0fd747"
+ integrity sha512-NtQGmyQDXjQqQ+IzRkBVwEOz9lQ4zxAQZgoAYEtU9dJjnl1Oc98qnN7jcp+bE7O7aYzVpavXE3/VKXNzUbh7aw==
dependencies:
- "@babel/helper-define-polyfill-provider" "^0.3.2"
+ "@babel/helper-define-polyfill-provider" "^0.3.3"
big.js@^5.2.2:
version "5.2.2"
@@ -1292,49 +1295,25 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.14.5:
- version "4.17.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
- integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
+browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.4:
+ version "4.21.4"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.4.tgz#e7496bbc67b9e39dd0f98565feccdcb0d4ff6987"
+ integrity sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==
dependencies:
- caniuse-lite "^1.0.30001259"
- electron-to-chromium "^1.3.846"
- escalade "^3.1.1"
- nanocolors "^0.1.5"
- node-releases "^1.1.76"
-
-browserslist@^4.20.2, browserslist@^4.21.3:
- version "4.21.3"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
- integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
- dependencies:
- caniuse-lite "^1.0.30001370"
- electron-to-chromium "^1.4.202"
+ caniuse-lite "^1.0.30001400"
+ electron-to-chromium "^1.4.251"
node-releases "^2.0.6"
- update-browserslist-db "^1.0.5"
+ update-browserslist-db "^1.0.9"
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==
-call-bind@^1.0.0:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
- integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
- dependencies:
- function-bind "^1.1.1"
- get-intrinsic "^1.0.2"
-
-caniuse-lite@^1.0.30001259:
- version "1.0.30001261"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
- integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
-
-caniuse-lite@^1.0.30001370:
- version "1.0.30001393"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
- integrity sha512-N/od11RX+Gsk+1qY/jbPa0R6zJupEa0lxeBG598EbrtblxVCTJsQwbRBm6+V+rxpc5lHKdsXb9RY83cZIPLseA==
+caniuse-lite@^1.0.30001400:
+ version "1.0.30001436"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz#22d7cbdbbbb60cdc4ca1030ccd6dea9f5de4848b"
+ integrity sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==
chalk@^2.0.0:
version "2.4.2"
@@ -1346,9 +1325,9 @@ chalk@^2.0.0:
supports-color "^5.3.0"
"chokidar@>=3.0.0 <4.0.0":
- version "3.5.2"
- resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75"
- integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ==
+ version "3.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
dependencies:
anymatch "~3.1.2"
braces "~3.0.2"
@@ -1384,7 +1363,7 @@ color-convert@^1.9.0:
color-name@1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
- integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=
+ integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
colorette@^2.0.14:
version "2.0.19"
@@ -1404,26 +1383,24 @@ commander@^7.0.0:
commondir@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b"
- integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=
+ integrity sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==
convert-source-map@^1.7.0:
- version "1.8.0"
- resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
- integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
- dependencies:
- safe-buffer "~5.1.1"
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
-core-js-compat@^3.21.0, core-js-compat@^3.22.1:
- version "3.25.1"
- resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.25.1.tgz#6f13a90de52f89bbe6267e5620a412c7f7ff7e42"
- integrity sha512-pOHS7O0i8Qt4zlPW/eIFjwp+NrTPx+wTL0ctgI2fHn31sZOq89rDsmtc/A2vAX7r6shl+bmVI+678He46jgBlw==
+core-js-compat@^3.25.1:
+ version "3.26.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.26.1.tgz#0e710b09ebf689d719545ac36e49041850f943df"
+ integrity sha512-622/KzTudvXCDLRw70iHW4KKs1aGpcRcowGWyYJr2DEBfRrd6hNJybxSWJFuZYD4ma86xhrwDDHxmDaIq4EA8A==
dependencies:
- browserslist "^4.21.3"
+ browserslist "^4.21.4"
core-js@^3.25.0:
- version "3.25.1"
- resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.25.1.tgz#5818e09de0db8956e16bf10e2a7141e931b7c69c"
- integrity sha512-sr0FY4lnO1hkQ4gLDr24K0DGnweGO1QwSj5BpfQjpSJPdqWalja4cTps29Y/PJVG/P7FYlPDkH3hO+Tr0CvDgQ==
+ version "3.26.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.26.1.tgz#7a9816dabd9ee846c1c0fe0e8fcad68f3709134e"
+ integrity sha512-21491RRQVzUn0GGM9Z1Jrpr6PNPxPi+Za8OM9q4tksTSnlbXXGKK1nXNg/QvwFYettXvSX6zWKCtHHfjN4puyA==
cross-env@^7.0.3:
version "7.0.3"
@@ -1442,28 +1419,16 @@ cross-spawn@^7.0.1, cross-spawn@^7.0.3:
which "^2.0.1"
debug@^4.1.0, debug@^4.1.1:
- version "4.3.2"
- resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b"
- integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
-define-properties@^1.1.3:
- version "1.1.3"
- resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
- integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==
- dependencies:
- object-keys "^1.0.12"
-
-electron-to-chromium@^1.3.846:
- version "1.3.853"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
- integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
-
-electron-to-chromium@^1.4.202:
- version "1.4.247"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
- integrity sha512-FLs6R4FQE+1JHM0hh3sfdxnYjKvJpHZyhQDjc2qFq/xFvmmRt/TATNToZhrcGUFzpF2XjeiuozrA8lI0PZmYYw==
+electron-to-chromium@^1.4.251:
+ version "1.4.284"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz#61046d1e4cab3a25238f6bf7413795270f125592"
+ integrity sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==
emojis-list@^3.0.0:
version "3.0.0"
@@ -1471,9 +1436,9 @@ emojis-list@^3.0.0:
integrity sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==
enhanced-resolve@^5.10.0:
- version "5.10.0"
- resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.10.0.tgz#0dc579c3bb2a1032e357ac45b8f3a6f3ad4fb1e6"
- integrity sha512-T0yTFjdpldGY8PmuXXR0PyQ1ufZpEGiHVrp7zHKB7jdR4qlmZHhONVM5AQOAWXuF/w3dnHbEQVrNptJgt7F+cQ==
+ version "5.12.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz#300e1c90228f5b570c4d35babf263f6da7155634"
+ integrity sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==
dependencies:
graceful-fs "^4.2.4"
tapable "^2.2.0"
@@ -1484,9 +1449,9 @@ envinfo@^7.7.3:
integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
es-module-lexer@^0.9.0:
- version "0.9.1"
- resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.1.tgz#f203bf394a630a552d381acf01a17ef08843b140"
- integrity sha512-17Ed9misDnpyNBJh63g1OhW3qUFecDgGOivI85JeZY/LGhDum8e+cltukbkSK8pcJnXXEkya56sp4vSS1nzoUw==
+ version "0.9.3"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-0.9.3.tgz#6f13db00cc38417137daf74366f535c8eb438f19"
+ integrity sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==
escalade@^3.1.1:
version "3.1.1"
@@ -1496,7 +1461,7 @@ escalade@^3.1.1:
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 sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+ integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
eslint-scope@5.1.1:
version "5.1.1"
@@ -1519,9 +1484,9 @@ estraverse@^4.1.1:
integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
estraverse@^5.2.0:
- version "5.2.0"
- resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.2.0.tgz#307df42547e6cc7324d3cf03c155d5cdb8c53880"
- integrity sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==
+ 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"
@@ -1544,9 +1509,9 @@ fast-json-stable-stringify@^2.0.0:
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fastest-levenshtein@^1.0.12:
- version "1.0.12"
- resolved "https://registry.yarnpkg.com/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz#9990f7d3a88cc5a9ffd1f1745745251700d497e2"
- integrity sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==
+ 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"
@@ -1595,15 +1560,6 @@ gensync@^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==
-get-intrinsic@^1.0.2:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.1.1.tgz#15f59f376f855c446963948f0d24cd3637b4abc6"
- integrity sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==
- dependencies:
- function-bind "^1.1.1"
- has "^1.0.3"
- has-symbols "^1.0.1"
-
glob-parent@~5.1.2:
version "5.1.2"
resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
@@ -1621,12 +1577,7 @@ globals@^11.1.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.4:
- version "4.2.8"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
- integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
-
-graceful-fs@^4.2.9:
+graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@@ -1634,18 +1585,13 @@ graceful-fs@^4.2.9:
has-flag@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
- integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
+ 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==
-has-symbols@^1.0.1:
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423"
- integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==
-
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@@ -1653,10 +1599,15 @@ has@^1.0.3:
dependencies:
function-bind "^1.1.1"
+immutable@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.1.0.tgz#f795787f0db780183307b9eb2091fcac1f6fafef"
+ integrity sha512-oNkuqVTA8jqG1Q6c+UglTOD1xhC1BtjKI7XkCXRkZHrN5m18/XsnUp8Q89GkQO/z+0WjonSvl0FLhDYftp46nQ==
+
import-local@^3.0.2:
- version "3.0.2"
- resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
- integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
dependencies:
pkg-dir "^4.2.0"
resolve-cwd "^3.0.0"
@@ -1673,22 +1624,22 @@ is-binary-path@~2.1.0:
dependencies:
binary-extensions "^2.0.0"
-is-core-module@^2.2.0:
- version "2.7.0"
- resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.7.0.tgz#3c0ef7d31b4acfc574f80c58409d568a836848e3"
- integrity sha512-ByY+tjCciCr+9nLryBYcSD50EOGWt95c7tIsKTG1J2ixKKXPvF7Ej3AVd+UfDydAJom3biBGDBALaO79ktwgEQ==
+is-core-module@^2.9.0:
+ version "2.11.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
+ integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
dependencies:
has "^1.0.3"
is-extglob@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
- integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=
+ integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
is-glob@^4.0.1, is-glob@~4.0.1:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.2.tgz#859fc2e731e58c902f99fcabccb75a7dd07d29d8"
- integrity sha512-ZZTOjRcDjuAAAv2cTBQP/lL59ZTArx77+7UzHdWW/XB1mrfp7DEaVpKmZ0XIzx+M7AxfhKcqV+nMetUQmFifwg==
+ 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"
@@ -1707,17 +1658,17 @@ is-plain-object@^2.0.4:
isexe@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+ 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 sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+ integrity sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==
-jest-worker@^27.0.6:
- version "27.2.3"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.2.3.tgz#396e83d04ca575230a9bcb255c2b66aec07cb931"
- integrity sha512-ZwOvv4GCIPviL+Ie4pVguz4N5w/6IGbTaHBYOl3ZcsZZktaL7d8JOU0rmovoED7AJZKA8fvmLbBg8yg80u/tGA==
+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"
@@ -1736,7 +1687,7 @@ jsesc@^2.5.1:
jsesc@~0.5.0:
version "0.5.0"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
- integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
+ integrity sha512-uZz5UnB7u4T9LvwmFqXii7pZSouaRPorGs5who1Ip7VO0wxanFvBL7GkM6dTHlgX+jhBApRetaWpnDabOeTcnA==
json-parse-even-better-errors@^2.3.1:
version "2.3.1"
@@ -1748,17 +1699,15 @@ json-schema-traverse@^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:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
- integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
- dependencies:
- minimist "^1.2.5"
+json2php@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/json2php/-/json2php-0.0.4.tgz#6bd85a1dda6a5dd7e91022bb24403cc1b7c2ee34"
+ integrity sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g==
-json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+json5@^2.1.2, json5@^2.2.1:
+ 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"
@@ -1766,19 +1715,19 @@ kind-of@^6.0.2:
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
klona@^2.0.4:
- version "2.0.4"
- resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.4.tgz#7bb1e3affb0cb8624547ef7e8f6708ea2e39dfc0"
- integrity sha512-ZRbnvdg/NxqzC7L9Uyqzf4psi1OM4Cuc+sJAkQPjO6XkQIJTNbfK2Rsmbw8fx1p2mkZdp2FZYo2+LwXYY/uwIA==
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/klona/-/klona-2.0.5.tgz#d166574d90076395d9963aa7a928fabb8d76afbc"
+ integrity sha512-pJiBpiXMbt7dkzXe8Ghj/u4FfXOOa98fPW+bihOJ4SjnoijweJrNThJfd3ifXpXhREjpoF2mZVH1GfS9LV3kHQ==
loader-runner@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-4.2.0.tgz#d7022380d66d14c5fb1d496b89864ebcfd478384"
- integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
+ 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.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
- integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
+ 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"
@@ -1794,7 +1743,7 @@ locate-path@^5.0.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 sha1-gteb/zCmfEAF/9XiUVMArZyk168=
+ integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==
make-dir@^3.0.2, make-dir@^3.1.0:
version "3.1.0"
@@ -1808,43 +1757,28 @@ merge-stream@^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.49.0:
- version "1.49.0"
- resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.49.0.tgz#f3dfde60c99e9cf3bc9701d687778f537001cbed"
- integrity sha512-CIc8j9URtOVApSFCQIF+VBkX1RwXp/oMMOrqdyXSBXq5RWNEsRfyj1kiRnQgmNXmHxPoFIxOroKA3zcU9P+nAA==
+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.32"
- resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.32.tgz#1d00e89e7de7fe02008db61001d9e02852670fd5"
- integrity sha512-hJGaVS4G4c9TSMYh2n6SQAGrC4RnfU+daP8G7cSCmaqNjiOoUY0VHCMS42pxnQmVF1GWwFhbHWn3RIxCqTmZ9A==
+ 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.49.0"
-
-minimist@^1.2.5:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
- integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
+ 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==
-nanocolors@^0.1.5:
- version "0.1.12"
- resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
- integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
-
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@^1.1.76:
- version "1.1.76"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
- integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
-
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -1855,21 +1789,6 @@ normalize-path@^3.0.0, normalize-path@~3.0.0:
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
-object-keys@^1.0.12, object-keys@^1.1.1:
- version "1.1.1"
- resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
- integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-
-object.assign@^4.1.0:
- version "4.1.2"
- resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940"
- integrity sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==
- dependencies:
- call-bind "^1.0.0"
- define-properties "^1.1.3"
- has-symbols "^1.0.1"
- object-keys "^1.1.1"
-
p-limit@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
@@ -1877,13 +1796,6 @@ p-limit@^2.2.0:
dependencies:
p-try "^2.0.0"
-p-limit@^3.1.0:
- version "3.1.0"
- resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
- integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
- dependencies:
- yocto-queue "^0.1.0"
-
p-locate@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
@@ -1906,7 +1818,7 @@ path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
-path-parse@^1.0.6:
+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==
@@ -1917,9 +1829,9 @@ picocolors@^1.0.0:
integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
picomatch@^2.0.4, picomatch@^2.2.1:
- version "2.3.0"
- resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972"
- integrity sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==
+ 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"
@@ -1954,17 +1866,10 @@ rechoir@^0.7.0:
dependencies:
resolve "^1.9.0"
-regenerate-unicode-properties@^10.0.1:
- version "10.0.1"
- resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.0.1.tgz#7f442732aa7934a3740c779bb9b3340dccc1fb56"
- integrity sha512-vn5DU6yg6h8hP/2OkQo3K7uVILvY4iu0oI4t3HFa81UPkhGJwkRwM10JEc3upjdhHjs/k8GJY1sRBhk5sr69Bw==
- dependencies:
- regenerate "^1.4.2"
-
-regenerate-unicode-properties@^9.0.0:
- version "9.0.0"
- resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-9.0.0.tgz#54d09c7115e1f53dc2314a974b32c1c344efe326"
- integrity sha512-3E12UeNSPfjrgwjkR81m5J7Aw/T55Tu7nUyZVQYCKEOs+2dkxEY+DpPtZzO4YruuiPb7NkYLVcyJC4+zCbk5pA==
+regenerate-unicode-properties@^10.1.0:
+ version "10.1.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
dependencies:
regenerate "^1.4.2"
@@ -1973,63 +1878,39 @@ regenerate@^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.13.4:
- version "0.13.9"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52"
- integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA==
+regenerator-runtime@^0.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
-regenerator-transform@^0.15.0:
- version "0.15.0"
- resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537"
- integrity sha512-LsrGtPmbYg19bcPHwdtmXwbW+TqNvtY4riE3P83foeHRroMbH6/2ddFBfab3t7kbzc7v7p4wbkIecHImqt0QNg==
+regenerator-transform@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
+ integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
dependencies:
"@babel/runtime" "^7.8.4"
-regexpu-core@^4.7.1:
- version "4.8.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.8.0.tgz#e5605ba361b67b1718478501327502f4479a98f0"
- integrity sha512-1F6bYsoYiz6is+oz70NWur2Vlh9KWtswuRuzJOfeYUrfPX2o8n74AnUVaOGDbUqVGO9fNHu48/pjJO4sNVwsOg==
+regexpu-core@^5.2.1:
+ version "5.2.2"
+ resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.2.2.tgz#3e4e5d12103b64748711c3aad69934d7718e75fc"
+ integrity sha512-T0+1Zp2wjF/juXMrMxHxidqGYn8U4R+zleSJhX9tQ1PUsS8a9UtYfbsF9LdiVgNX3kiX8RNaKM42nfSgvFJjmw==
dependencies:
regenerate "^1.4.2"
- regenerate-unicode-properties "^9.0.0"
- regjsgen "^0.5.2"
- regjsparser "^0.7.0"
+ regenerate-unicode-properties "^10.1.0"
+ regjsgen "^0.7.1"
+ regjsparser "^0.9.1"
unicode-match-property-ecmascript "^2.0.0"
- unicode-match-property-value-ecmascript "^2.0.0"
+ unicode-match-property-value-ecmascript "^2.1.0"
-regexpu-core@^5.1.0:
- version "5.1.0"
- resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d"
- integrity sha512-bb6hk+xWd2PEOkj5It46A16zFMs2mv86Iwpdu94la4S3sJ7C973h2dHpYKwIBGaWSO7cIRJ+UX0IeMaWcO4qwA==
- dependencies:
- regenerate "^1.4.2"
- regenerate-unicode-properties "^10.0.1"
- regjsgen "^0.6.0"
- regjsparser "^0.8.2"
- unicode-match-property-ecmascript "^2.0.0"
- unicode-match-property-value-ecmascript "^2.0.0"
+regjsgen@^0.7.1:
+ version "0.7.1"
+ resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.7.1.tgz#ee5ef30e18d3f09b7c369b76e7c2373ed25546f6"
+ integrity sha512-RAt+8H2ZEzHeYWxZ3H2z6tF18zyyOnlcdaafLrm21Bguj7uZy6ULibiAFdXEtKQY4Sy7wDTwDiOazasMLc4KPA==
-regjsgen@^0.5.2:
- version "0.5.2"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
- integrity sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==
-
-regjsgen@^0.6.0:
- version "0.6.0"
- resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.6.0.tgz#83414c5354afd7d6627b16af5f10f41c4e71808d"
- integrity sha512-ozE883Uigtqj3bx7OhL1KNbCzGyW2NQZPl6Hs09WTvCuZD5sTI4JY58bkbQWa/Y9hxIsvJ3M8Nbf7j54IqeZbA==
-
-regjsparser@^0.7.0:
- version "0.7.0"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.7.0.tgz#a6b667b54c885e18b52554cb4960ef71187e9968"
- integrity sha512-A4pcaORqmNMDVwUjWoTzuhwMGpP+NykpfqAsEgI1FSH/EzC7lrN5TMd+kN8YCovX+jMpu8eaqXgXPCa0g8FQNQ==
- dependencies:
- jsesc "~0.5.0"
-
-regjsparser@^0.8.2:
- version "0.8.4"
- resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.8.4.tgz#8a14285ffcc5de78c5b95d62bbf413b6bc132d5f"
- integrity sha512-J3LABycON/VNEu3abOviqGHuB/LOtOQj8SKmfP9anY5GfAVw/SPjwzSjxGjbZXIxbGfqTHtJw58C2Li/WkStmA==
+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"
@@ -2046,37 +1927,35 @@ resolve-from@^5.0.0:
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
resolve@^1.14.2, resolve@^1.9.0:
- version "1.20.0"
- resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975"
- integrity sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==
+ version "1.22.1"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
+ integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==
dependencies:
- is-core-module "^2.2.0"
- path-parse "^1.0.6"
+ is-core-module "^2.9.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==
-safe-buffer@~5.1.1:
- version "5.1.2"
- resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
- integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
-
sass-loader@^12.1.0:
- version "12.1.0"
- resolved "https://registry.yarnpkg.com/sass-loader/-/sass-loader-12.1.0.tgz#b73324622231009da6fba61ab76013256380d201"
- integrity sha512-FVJZ9kxVRYNZTIe2xhw93n3xJNYZADr+q69/s98l9nTCrWASo+DR2Ot0s5xTKQDDEosUkatsGeHxcH4QBp5bSg==
+ 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.42.1"
- resolved "https://registry.yarnpkg.com/sass/-/sass-1.42.1.tgz#5ab17bebc1cb1881ad2e0c9a932c66ad64e441e2"
- integrity sha512-/zvGoN8B7dspKc5mC6HlaygyCBRvnyzzgD5khiaCfglWztY99cYoiTUksVx11NlnemrcfH5CEaCpsUKoW0cQqg==
+ version "1.56.1"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.56.1.tgz#94d3910cd468fd075fa87f5bb17437a0b617d8a7"
+ integrity sha512-VpEyKpyBPCxE7qGDtOcdJ6fFbcpOM+Emu7uZLxVrkX8KVU/Dp5UF7WLvzqRuUhB6mqqQt1xffLoG+AndxTZrCQ==
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"
@@ -2127,6 +2006,11 @@ shebang-regex@^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.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
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"
@@ -2135,7 +2019,7 @@ source-map-support@~0.5.20:
buffer-from "^1.0.0"
source-map "^0.6.0"
-source-map@^0.6.0, source-map@^0.6.1:
+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==
@@ -2154,27 +2038,31 @@ supports-color@^8.0.0:
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.1.3:
- version "5.2.4"
- resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.2.4.tgz#ad1be7639b1cbe3ea49fab995cbe7224b31747a1"
- integrity sha512-E2CkNMN+1cho04YpdANyRrn8CyN4yMy+WdFKZIySFZrGXZxJwJP6PMNGGc/Mcr6qygQHUUqRxnAPmi0M9f00XA==
+ version "5.3.6"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz#5590aec31aa3c6f771ce1b1acca60639eab3195c"
+ integrity sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==
dependencies:
- jest-worker "^27.0.6"
- p-limit "^3.1.0"
+ "@jridgewell/trace-mapping" "^0.3.14"
+ jest-worker "^27.4.5"
schema-utils "^3.1.1"
serialize-javascript "^6.0.0"
- source-map "^0.6.1"
- terser "^5.7.2"
+ terser "^5.14.1"
-terser@^5.7.2:
- version "5.14.2"
- resolved "https://registry.yarnpkg.com/terser/-/terser-5.14.2.tgz#9ac9f22b06994d736174f4091aa368db896f1c10"
- integrity sha512-oL0rGeM/WFQCUd0y2QrWxYnq7tfSuKBiqTjRPWrRgB46WD/kiwHwF8T23z78H6Q6kGCuuHcPB+KULHRdxvVGQA==
+terser@^5.14.1:
+ version "5.16.1"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.16.1.tgz#5af3bc3d0f24241c7fb2024199d5c461a1075880"
+ integrity sha512-xvQfyfA1ayT0qdK47zskQgRZeWLoOQ8JQ6mIgRGVNwZKdQMU+5FkCBjmv4QjcrTzyZquRw2FVtlJSRUmMKQslw==
dependencies:
"@jridgewell/source-map" "^0.3.2"
acorn "^8.5.0"
@@ -2184,7 +2072,7 @@ terser@^5.7.2:
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 sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
+ integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
to-regex-range@^5.0.1:
version "5.0.1"
@@ -2206,20 +2094,20 @@ unicode-match-property-ecmascript@^2.0.0:
unicode-canonical-property-names-ecmascript "^2.0.0"
unicode-property-aliases-ecmascript "^2.0.0"
-unicode-match-property-value-ecmascript@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-2.0.0.tgz#1a01aa57247c14c568b89775a54938788189a714"
- integrity sha512-7Yhkc0Ye+t4PNYzOGKedDhXbYIBe1XEQYQxOPyhcXNMJ0WCABqqj6ckydd6pWRZTHV4GuCPKdBAUiMc60tsKVw==
+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.0.0"
- resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.0.0.tgz#0a36cb9a585c4f6abd51ad1deddb285c165297c8"
- integrity sha512-5Zfuy9q/DFr4tfO7ZPeVXb1aPoeQSdeFMLpYuFebehDAhbuevLs5yxSZmIFN1tP5F9Wl4IpJrYojg85/zgyZHQ==
+ 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.0.5:
- version "1.0.7"
- resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.7.tgz#16279639cff1d0f800b14792de43d97df2d11b7d"
- integrity sha512-iN/XYesmZ2RmmWAiI4Z5rq0YqSiv0brj9Ce9CfhNE4xIW2h+MFxcgkxIzZ+ShkFPUkjU3gQ+3oypadD3RAMtrg==
+update-browserslist-db@^1.0.9:
+ version "1.0.10"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz#0f54b876545726f17d00cd9a2561e6dade943ff3"
+ integrity sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==
dependencies:
escalade "^3.1.1"
picocolors "^1.0.0"
@@ -2265,15 +2153,15 @@ webpack-merge@^5.7.3:
clone-deep "^4.0.1"
wildcard "^2.0.0"
-webpack-sources@^3.2.3:
+webpack-sources@^3.2.2, 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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
@@ -2311,8 +2199,3 @@ wildcard@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec"
integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==
-
-yocto-queue@^0.1.0:
- version "0.1.0"
- resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
- integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/modules/ppcp-button/package.json b/modules/ppcp-button/package.json
index 5fb4286e1..77828e436 100644
--- a/modules/ppcp-button/package.json
+++ b/modules/ppcp-button/package.json
@@ -11,9 +11,10 @@
"Edge >= 14"
],
"dependencies": {
+ "@paypal/paypal-js": "^6.0.0",
"core-js": "^3.25.0",
- "formdata-polyfill": "^4.0.10",
- "deepmerge": "^4.2.2"
+ "deepmerge": "^4.2.2",
+ "formdata-polyfill": "^4.0.10"
},
"devDependencies": {
"@babel/core": "^7.19",
@@ -23,7 +24,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-button/resources/css/gateway.scss b/modules/ppcp-button/resources/css/gateway.scss
index 0a97127ab..42b216e92 100644
--- a/modules/ppcp-button/resources/css/gateway.scss
+++ b/modules/ppcp-button/resources/css/gateway.scss
@@ -1,3 +1,17 @@
#place_order.ppcp-hidden {
display: none !important;
}
+
+.ppcp-disabled {
+ cursor: not-allowed;
+ -webkit-filter: grayscale(100%);
+ filter: grayscale(100%);
+
+ * {
+ pointer-events: none;
+ }
+}
+
+.ppc-button-wrapper #ppcp-messages:first-child {
+ padding-top: 10px;
+}
diff --git a/modules/ppcp-button/resources/css/hosted-fields.scss b/modules/ppcp-button/resources/css/hosted-fields.scss
index 2305d313b..578a70a09 100644
--- a/modules/ppcp-button/resources/css/hosted-fields.scss
+++ b/modules/ppcp-button/resources/css/hosted-fields.scss
@@ -15,3 +15,7 @@
.ppcp-dcc-order-button {
float: right;
}
+
+iframe[id^="hosted-fields-tokenization-frame_"] {
+ display: none;
+}
diff --git a/modules/ppcp-button/resources/js/button.js b/modules/ppcp-button/resources/js/button.js
index a118211a2..7d3812fc5 100644
--- a/modules/ppcp-button/resources/js/button.js
+++ b/modules/ppcp-button/resources/js/button.js
@@ -6,7 +6,6 @@ import PayNowBootstrap from "./modules/ContextBootstrap/PayNowBootstrap";
import Renderer from './modules/Renderer/Renderer';
import ErrorHandler from './modules/ErrorHandler';
import CreditCardRenderer from "./modules/Renderer/CreditCardRenderer";
-import dataClientIdAttributeHandler from "./modules/DataClientIdAttributeHandler";
import MessageRenderer from "./modules/Renderer/MessageRenderer";
import Spinner from "./modules/Helper/Spinner";
import {
@@ -14,9 +13,14 @@ import {
ORDER_BUTTON_SELECTOR,
PaymentMethods
} from "./modules/Helper/CheckoutMethodState";
-import {hide, setVisible, setVisibleByClass} from "./modules/Helper/Hiding";
+import {setVisibleByClass} from "./modules/Helper/Hiding";
import {isChangePaymentPage} from "./modules/Helper/Subscriptions";
import FreeTrialHandler from "./modules/ActionHandler/FreeTrialHandler";
+import FormSaver from './modules/Helper/FormSaver';
+import FormValidator from "./modules/Helper/FormValidator";
+import {loadPaypalScript} from "./modules/Helper/ScriptLoading";
+import buttonModuleWatcher from "./modules/ButtonModuleWatcher";
+import MessagesBootstrap from "./modules/ContextBootstrap/MessagesBootstap";
// TODO: could be a good idea to have a separate spinner for each gateway,
// but I think we care mainly about the script loading, so one spinner should be enough.
@@ -24,11 +28,29 @@ const buttonsSpinner = new Spinner(document.querySelector('.ppc-button-wrapper')
const cardsSpinner = new Spinner('#ppcp-hosted-fields');
const bootstrap = () => {
- const errorHandler = new ErrorHandler(PayPalCommerceGateway.labels.error.generic);
+ const checkoutFormSelector = 'form.woocommerce-checkout';
+
+ const context = PayPalCommerceGateway.context;
+
+ const errorHandler = new ErrorHandler(
+ PayPalCommerceGateway.labels.error.generic,
+ document.querySelector(checkoutFormSelector) ?? document.querySelector('.woocommerce-notices-wrapper')
+ );
const spinner = new Spinner();
const creditCardRenderer = new CreditCardRenderer(PayPalCommerceGateway, errorHandler, spinner);
- const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, spinner, errorHandler);
+ const formSaver = new FormSaver(
+ PayPalCommerceGateway.ajax.save_checkout_form.endpoint,
+ PayPalCommerceGateway.ajax.save_checkout_form.nonce,
+ );
+
+ const formValidator = PayPalCommerceGateway.early_checkout_validation_enabled ?
+ new FormValidator(
+ PayPalCommerceGateway.ajax.validate_checkout.endpoint,
+ PayPalCommerceGateway.ajax.validate_checkout.nonce,
+ ) : null;
+
+ const freeTrialHandler = new FreeTrialHandler(PayPalCommerceGateway, checkoutFormSelector, formSaver, formValidator, spinner, errorHandler);
jQuery('form.woocommerce-checkout input').on('keydown', e => {
if (e.key === 'Enter' && [
@@ -40,7 +62,12 @@ const bootstrap = () => {
}
});
- const onSmartButtonClick = (data, actions) => {
+ const hasMessages = () => {
+ return PayPalCommerceGateway.messages.is_hidden === false
+ && document.querySelector(PayPalCommerceGateway.messages.wrapper);
+ }
+
+ const onSmartButtonClick = async (data, actions) => {
window.ppcpFundingSource = data.fundingSource;
const requiredFields = jQuery('form.woocommerce-checkout .validate-required:visible :input');
requiredFields.each((i, input) => {
@@ -88,7 +115,7 @@ const bootstrap = () => {
}
}
- const form = document.querySelector('form.woocommerce-checkout');
+ const form = document.querySelector(checkoutFormSelector);
if (form) {
jQuery('#ppcp-funding-source-form-input').remove();
form.insertAdjacentHTML(
@@ -102,34 +129,48 @@ const bootstrap = () => {
freeTrialHandler.handle();
return actions.reject();
}
+
+ if (context === 'checkout') {
+ try {
+ await formSaver.save(form);
+ } catch (error) {
+ console.error(error);
+ }
+ }
};
+
const onSmartButtonsInit = () => {
buttonsSpinner.unblock();
};
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit);
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages);
- const context = PayPalCommerceGateway.context;
- if (context === 'mini-cart' || context === 'product') {
- if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
- const miniCartBootstrap = new MiniCartBootstap(
- PayPalCommerceGateway,
- renderer,
- errorHandler,
- );
- miniCartBootstrap.init();
- }
+ if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
+ const miniCartBootstrap = new MiniCartBootstap(
+ PayPalCommerceGateway,
+ renderer,
+ errorHandler,
+ );
+
+ miniCartBootstrap.init();
+ buttonModuleWatcher.registerContextBootstrap('mini-cart', miniCartBootstrap);
}
- if (context === 'product' && PayPalCommerceGateway.single_product_buttons_enabled === '1') {
+ if (
+ context === 'product'
+ && (
+ PayPalCommerceGateway.single_product_buttons_enabled === '1'
+ || hasMessages()
+ )
+ ) {
const singleProductBootstrap = new SingleProductBootstap(
PayPalCommerceGateway,
renderer,
- messageRenderer,
errorHandler,
);
singleProductBootstrap.init();
+ buttonModuleWatcher.registerContextBootstrap('product', singleProductBootstrap);
}
if (context === 'cart') {
@@ -140,35 +181,39 @@ const bootstrap = () => {
);
cartBootstrap.init();
+ buttonModuleWatcher.registerContextBootstrap('cart', cartBootstrap);
}
if (context === 'checkout') {
const checkoutBootstap = new CheckoutBootstap(
PayPalCommerceGateway,
renderer,
- messageRenderer,
spinner,
errorHandler,
);
checkoutBootstap.init();
+ buttonModuleWatcher.registerContextBootstrap('checkout', checkoutBootstap);
}
if (context === 'pay-now' ) {
const payNowBootstrap = new PayNowBootstrap(
PayPalCommerceGateway,
renderer,
- messageRenderer,
spinner,
errorHandler,
);
payNowBootstrap.init();
+ buttonModuleWatcher.registerContextBootstrap('pay-now', payNowBootstrap);
}
- if (context !== 'checkout') {
- messageRenderer.render();
- }
+ const messagesBootstrap = new MessagesBootstrap(
+ PayPalCommerceGateway,
+ messageRenderer,
+ );
+ messagesBootstrap.init();
};
+
document.addEventListener(
'DOMContentLoaded',
() => {
@@ -240,24 +285,10 @@ document.addEventListener(
hideOrderButtonIfPpcpGateway();
});
- const script = document.createElement('script');
- script.addEventListener('load', (event) => {
+ loadPaypalScript(PayPalCommerceGateway, () => {
bootstrapped = true;
bootstrap();
});
- script.setAttribute('src', PayPalCommerceGateway.button.url);
- Object.entries(PayPalCommerceGateway.script_attributes).forEach(
- (keyValue) => {
- script.setAttribute(keyValue[0], keyValue[1]);
- }
- );
-
- if (PayPalCommerceGateway.data_client_id.set_attribute) {
- dataClientIdAttributeHandler(script, PayPalCommerceGateway.data_client_id);
- return;
- }
-
- document.body.appendChild(script);
},
);
diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js
index dce4259c6..4b7b20e9e 100644
--- a/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CartActionHandler.js
@@ -9,6 +9,39 @@ class CartActionHandler {
this.errorHandler = errorHandler;
}
+ subscriptionsConfiguration() {
+ return {
+ createSubscription: (data, actions) => {
+ return actions.subscription.create({
+ 'plan_id': this.config.subscription_plan_id
+ });
+ },
+ onApprove: (data, actions) => {
+ fetch(this.config.ajax.approve_subscription.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.ajax.approve_subscription.nonce,
+ order_id: data.orderID,
+ subscription_id: data.subscriptionID
+ })
+ }).then((res)=>{
+ return res.json();
+ }).then((data) => {
+ if (!data.success) {
+ console.log(data)
+ throw Error(data.data.message);
+ }
+
+ location.href = this.config.redirect;
+ });
+ },
+ onError: (err) => {
+ console.error(err);
+ }
+ }
+ }
+
configuration() {
const createOrder = (data, actions) => {
const payer = payerData();
@@ -16,6 +49,9 @@ class CartActionHandler {
this.config.bn_codes[this.config.context] : '';
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
index e743f2083..b6882ff3f 100644
--- a/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler.js
@@ -11,6 +11,34 @@ class CheckoutActionHandler {
this.spinner = spinner;
}
+ subscriptionsConfiguration() {
+ return {
+ createSubscription: (data, actions) => {
+ return actions.subscription.create({
+ 'plan_id': this.config.subscription_plan_id
+ });
+ },
+ onApprove: (data, actions) => {
+ fetch(this.config.ajax.approve_subscription.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.ajax.approve_subscription.nonce,
+ order_id: data.orderID,
+ subscription_id: data.subscriptionID
+ })
+ }).then((res)=>{
+ return res.json();
+ }).then((data) => {
+ document.querySelector('#place_order').click();
+ });
+ },
+ onError: (err) => {
+ console.error(err);
+ }
+ }
+ }
+
configuration() {
const spinner = this.spinner;
const createOrder = (data, actions) => {
@@ -22,8 +50,6 @@ class CheckoutActionHandler {
const formSelector = this.config.context === 'checkout' ? 'form.checkout' : 'form#order_review';
const formData = new FormData(document.querySelector(formSelector));
- // will not handle fields with multiple values (checkboxes, ), but we do not care about this here
- const formJsonObj = Object.fromEntries(formData.entries());
const createaccount = jQuery('#createaccount').is(":checked") ? true : false;
@@ -32,6 +58,9 @@ class CheckoutActionHandler {
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
@@ -41,7 +70,8 @@ class CheckoutActionHandler {
order_id:this.config.order_id,
payment_method: paymentMethod,
funding_source: fundingSource,
- form: formJsonObj,
+ // send as urlencoded string to handle complex fields via PHP functions the same as normal form submit
+ form_encoded: new URLSearchParams(formData).toString(),
createaccount: createaccount
})
}).then(function (res) {
@@ -59,13 +89,21 @@ class CheckoutActionHandler {
);
} else {
errorHandler.clear();
+
+ if (data.data.refresh) {
+ jQuery( document.body ).trigger( 'update_checkout' );
+ }
+
if (data.data.errors?.length > 0) {
errorHandler.messages(data.data.errors);
} else if (data.data.details?.length > 0) {
- errorHandler.message(data.data.details.map(d => `${d.issue} ${d.description}`).join(' '), true);
+ errorHandler.message(data.data.details.map(d => `${d.issue} ${d.description}`).join(' '));
} else {
- errorHandler.message(data.data.message, true);
+ errorHandler.message(data.data.message);
}
+
+ // fire WC event for other plugins
+ jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
}
throw {type: 'create-order-error', data: data.data};
@@ -73,7 +111,7 @@ class CheckoutActionHandler {
const input = document.createElement('input');
input.setAttribute('type', 'hidden');
input.setAttribute('name', 'ppcp-resume-order');
- input.setAttribute('value', data.data.purchase_units[0].custom_id);
+ input.setAttribute('value', data.data.custom_id);
document.querySelector(formSelector).appendChild(input);
return data.data.id;
});
diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js
index bee968e96..5486576ff 100644
--- a/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ActionHandler/FreeTrialHandler.js
@@ -1,44 +1,80 @@
-import {PaymentMethods} from "../Helper/CheckoutMethodState";
-import errorHandler from "../ErrorHandler";
-
class FreeTrialHandler {
+ /**
+ * @param config
+ * @param formSelector
+ * @param {FormSaver} formSaver
+ * @param {FormValidator|null} formValidator
+ * @param {Spinner} spinner
+ * @param {ErrorHandler} errorHandler
+ */
constructor(
config,
+ formSelector,
+ formSaver,
+ formValidator,
spinner,
errorHandler
) {
this.config = config;
+ this.formSelector = formSelector;
+ this.formSaver = formSaver;
+ this.formValidator = formValidator;
this.spinner = spinner;
this.errorHandler = errorHandler;
}
- handle()
+ async handle()
{
this.spinner.block();
- fetch(this.config.ajax.vault_paypal.endpoint, {
- method: 'POST',
- credentials: 'same-origin',
- body: JSON.stringify({
- nonce: this.config.ajax.vault_paypal.nonce,
- return_url: location.href
- }),
- }).then(res => {
- return res.json();
- }).then(data => {
+ try {
+ await this.formSaver.save(document.querySelector(this.formSelector));
+ } catch (error) {
+ console.error(error);
+ }
+
+ try {
+ if (this.formValidator) {
+ try {
+ const errors = await this.formValidator.validate(document.querySelector(this.formSelector));
+ if (errors.length > 0) {
+ this.spinner.unblock();
+ this.errorHandler.messages(errors);
+
+ // fire WC event for other plugins
+ jQuery( document.body ).trigger( 'checkout_error' , [ this.errorHandler.currentHtml() ] );
+
+ return;
+ }
+ } catch (error) {
+ console.error(error);
+ }
+ }
+
+ const res = await fetch(this.config.ajax.vault_paypal.endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.ajax.vault_paypal.nonce,
+ return_url: location.href,
+ }),
+ });
+
+ const data = await res.json();
+
if (!data.success) {
- this.spinner.unblock();
- console.error(data);
- this.errorHandler.message(data.data.message);
throw Error(data.data.message);
}
location.href = data.data.approve_link;
- }).catch(error => {
+ } catch (error) {
this.spinner.unblock();
console.error(error);
- this.errorHandler.genericError();
- });
+ this.errorHandler.message(data.data.message);
+ }
}
}
export default FreeTrialHandler;
diff --git a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js
index b94864604..42f746b5f 100644
--- a/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler.js
@@ -1,85 +1,162 @@
-import ButtonsToggleListener from '../Helper/ButtonsToggleListener';
import Product from '../Entity/Product';
+import BookingProduct from "../Entity/BookingProduct";
import onApprove from '../OnApproveHandler/onApproveForContinue';
import {payerData} from "../Helper/PayerData";
import {PaymentMethods} from "../Helper/CheckoutMethodState";
+import CartHelper from "../Helper/CartHelper";
+import FormHelper from "../Helper/FormHelper";
class SingleProductActionHandler {
constructor(
config,
updateCart,
- showButtonCallback,
- hideButtonCallback,
formElement,
errorHandler
) {
this.config = config;
this.updateCart = updateCart;
- this.showButtonCallback = showButtonCallback;
- this.hideButtonCallback = hideButtonCallback;
this.formElement = formElement;
this.errorHandler = errorHandler;
+ this.cartHelper = null;
+ }
+
+ subscriptionsConfiguration(subscription_plan) {
+ return {
+ createSubscription: (data, actions) => {
+ return actions.subscription.create({
+ 'plan_id': subscription_plan
+ });
+ },
+ onApprove: (data, actions) => {
+ fetch(this.config.ajax.approve_subscription.endpoint, {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.ajax.approve_subscription.nonce,
+ order_id: data.orderID,
+ subscription_id: data.subscriptionID
+ })
+ }).then((res)=>{
+ return res.json();
+ }).then(() => {
+ const products = this.getSubscriptionProducts();
+
+ fetch(this.config.ajax.change_cart.endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.ajax.change_cart.nonce,
+ products,
+ })
+ }).then((result) => {
+ return result.json();
+ }).then((result) => {
+ if (!result.success) {
+ console.log(result)
+ throw Error(result.data.message);
+ }
+
+ location.href = this.config.redirect;
+ })
+ });
+ },
+ onError: (err) => {
+ console.error(err);
+ }
+ }
+ }
+
+ getSubscriptionProducts()
+ {
+ const id = document.querySelector('[name="add-to-cart"]').value;
+ return [new Product(id, 1, this.variations(), this.extraFields())];
}
configuration()
{
-
- if ( this.hasVariations() ) {
- const observer = new ButtonsToggleListener(
- this.formElement.querySelector('.single_add_to_cart_button'),
- this.showButtonCallback,
- this.hideButtonCallback
- );
- observer.init();
- }
-
return {
createOrder: this.createOrder(),
onApprove: onApprove(this, this.errorHandler),
onError: (error) => {
+ this.refreshMiniCart();
+
+ if (this.isBookingProduct() && error.message) {
+ this.errorHandler.clear();
+ this.errorHandler.message(error.message);
+ return;
+ }
this.errorHandler.genericError();
+ },
+ onCancel: () => {
+ // Could be used for every product type,
+ // but only clean the cart for Booking products for now.
+ if (this.isBookingProduct()) {
+ this.cleanCart();
+ } else {
+ this.refreshMiniCart();
+ }
}
}
}
+ getProducts()
+ {
+ if ( this.isBookingProduct() ) {
+ const id = document.querySelector('[name="add-to-cart"]').value;
+ return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"), this.extraFields())];
+ } else if ( this.isGroupedProduct() ) {
+ const products = [];
+ this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
+ if (! element.value) {
+ return;
+ }
+ const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
+ if (elementName.length !== 2) {
+ return;
+ }
+ const id = parseInt(elementName[1]);
+ const quantity = parseInt(element.value);
+ products.push(new Product(id, quantity, null, this.extraFields()));
+ })
+ return products;
+ } else {
+ const id = document.querySelector('[name="add-to-cart"]').value;
+ const qty = document.querySelector('[name="quantity"]').value;
+ const variations = this.variations();
+ return [new Product(id, qty, variations, this.extraFields())];
+ }
+ }
+
+ extraFields() {
+ return FormHelper.getFilteredFields(
+ this.formElement,
+ ['add-to-cart', 'quantity', 'product_id', 'variation_id'],
+ ['attribute_', 'wc_bookings_field']
+ );
+ }
+
createOrder()
{
- var getProducts = null;
- if (! this.isGroupedProduct() ) {
- getProducts = () => {
- const id = document.querySelector('[name="add-to-cart"]').value;
- const qty = document.querySelector('[name="quantity"]').value;
- const variations = this.variations();
- return [new Product(id, qty, variations)];
- }
- } else {
- getProducts = () => {
- const products = [];
- this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
- if (! element.value) {
- return;
- }
- const elementName = element.getAttribute('name').match(/quantity\[([\d]*)\]/);
- if (elementName.length !== 2) {
- return;
- }
- const id = parseInt(elementName[1]);
- const quantity = parseInt(element.value);
- products.push(new Product(id, quantity, null));
- })
- return products;
- }
- }
- const createOrder = (data, actions) => {
+ this.cartHelper = null;
+
+ return (data, actions) => {
this.errorHandler.clear();
const onResolve = (purchase_units) => {
+ this.cartHelper = (new CartHelper()).addFromPurchaseUnits(purchase_units);
+
const payer = payerData();
const bnCode = typeof this.config.bn_codes[this.config.context] !== 'undefined' ?
this.config.bn_codes[this.config.context] : '';
return fetch(this.config.ajax.create_order.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.config.ajax.create_order.nonce,
@@ -101,19 +178,16 @@ class SingleProductActionHandler {
});
};
- const promise = this.updateCart.update(onResolve, getProducts());
- return promise;
+ return this.updateCart.update(onResolve, this.getProducts());
};
- return createOrder;
}
variations()
{
-
if (! this.hasVariations()) {
return null;
}
- const attributes = [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
+ return [...this.formElement.querySelectorAll("[name^='attribute_']")].map(
(element) => {
return {
value:element.value,
@@ -121,7 +195,6 @@ class SingleProductActionHandler {
}
}
);
- return attributes;
}
hasVariations()
@@ -133,5 +206,24 @@ class SingleProductActionHandler {
{
return this.formElement.classList.contains('grouped_form');
}
+
+ isBookingProduct()
+ {
+ // detection for "woocommerce-bookings" plugin
+ return !!this.formElement.querySelector('.wc-booking-product-id');
+ }
+
+ cleanCart() {
+ this.cartHelper.removeFromCart().then(() => {
+ this.refreshMiniCart();
+ }).catch(error => {
+ this.refreshMiniCart();
+ });
+ }
+
+ refreshMiniCart() {
+ jQuery(document.body).trigger('wc_fragment_refresh');
+ }
+
}
export default SingleProductActionHandler;
diff --git a/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js
new file mode 100644
index 000000000..c6165674a
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/ButtonModuleWatcher.js
@@ -0,0 +1,32 @@
+
+class ButtonModuleWatcher {
+
+ constructor() {
+ this.contextBootstrapRegistry = {};
+ this.contextBootstrapWatchers = [];
+ }
+
+ watchContextBootstrap(callable) {
+ console.log('ButtonModuleWatcher.js: watchContextBootstrap', this.contextBootstrapRegistry)
+ this.contextBootstrapWatchers.push(callable);
+ Object.values(this.contextBootstrapRegistry).forEach(callable);
+ }
+
+ registerContextBootstrap(context, handler) {
+ this.contextBootstrapRegistry[context] = {
+ context: context,
+ handler: handler
+ }
+
+ // Call registered watchers
+ for (const callable of this.contextBootstrapWatchers) {
+ callable(this.contextBootstrapRegistry[context]);
+ }
+ }
+
+}
+
+window.ppcpResources = window.ppcpResources || {};
+const buttonModuleWatcher = window.ppcpResources['ButtonModuleWatcher'] = window.ppcpResources['ButtonModuleWatcher'] || new ButtonModuleWatcher();
+
+export default buttonModuleWatcher;
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js
index ebc00bfdc..c40e03ce1 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CartBootstap.js
@@ -1,39 +1,110 @@
import CartActionHandler from '../ActionHandler/CartActionHandler';
+import BootstrapHelper from "../Helper/BootstrapHelper";
class CartBootstrap {
constructor(gateway, renderer, errorHandler) {
this.gateway = gateway;
this.renderer = renderer;
this.errorHandler = errorHandler;
+
+ this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
+ this.handleButtonStatus();
+ }, true);
}
init() {
+ if (this.shouldRender()) {
+ this.render();
+ this.handleButtonStatus();
+ }
+
+ jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
+ if (this.shouldRender()) {
+ this.render();
+ this.handleButtonStatus();
+ }
+
+ fetch(
+ this.gateway.ajax.cart_script_params.endpoint,
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (! result.success) {
+ return;
+ }
+
+ // handle script reload
+ const newParams = result.data.url_params;
+ const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams);
+
+ if (reloadRequired) {
+ this.gateway.url_params = newParams;
+ jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
+ }
+
+ // handle button status
+ const newData = {};
+ if (result.data.button) {
+ newData.button = result.data.button;
+ }
+ if (result.data.messages) {
+ newData.messages = result.data.messages;
+ }
+ if (newData) {
+ BootstrapHelper.updateScriptData(this, newData);
+ this.handleButtonStatus();
+ }
+
+ jQuery(document.body).trigger('ppcp_cart_total_updated', [result.data.amount]);
+ });
+ });
+ }
+
+ handleButtonStatus() {
+ BootstrapHelper.handleButtonStatus(this);
+ }
+
+ shouldRender() {
+ return document.querySelector(this.gateway.button.wrapper) !== null;
+ }
+
+ shouldEnable() {
+ return BootstrapHelper.shouldEnable(this);
+ }
+
+ render() {
if (!this.shouldRender()) {
return;
}
- this.render();
-
- jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
- this.render();
- });
- }
-
- shouldRender() {
- return document.querySelector(this.gateway.button.wrapper) !==
- null || document.querySelector(this.gateway.hosted_fields.wrapper) !==
- null;
- }
-
- render() {
const actionHandler = new CartActionHandler(
PayPalCommerceGateway,
this.errorHandler,
);
+ if(
+ PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
+ ) {
+ this.renderer.render(actionHandler.subscriptionsConfiguration());
+
+ if(!PayPalCommerceGateway.subscription_product_allowed) {
+ this.gateway.button.is_disabled = true;
+ this.handleButtonStatus();
+ }
+
+ return;
+ }
+
this.renderer.render(
actionHandler.configuration()
);
+
+ jQuery(document.body).trigger('ppcp_cart_rendered');
}
}
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
index af04b6811..e7fae33b0 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/CheckoutBootstap.js
@@ -5,20 +5,25 @@ import {
isSavedCardSelected, ORDER_BUTTON_SELECTOR,
PaymentMethods
} from "../Helper/CheckoutMethodState";
+import BootstrapHelper from "../Helper/BootstrapHelper";
class CheckoutBootstap {
- constructor(gateway, renderer, messages, spinner, errorHandler) {
+ constructor(gateway, renderer, spinner, errorHandler) {
this.gateway = gateway;
this.renderer = renderer;
- this.messages = messages;
this.spinner = spinner;
this.errorHandler = errorHandler;
this.standardOrderButtonSelector = ORDER_BUTTON_SELECTOR;
+
+ this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
+ this.handleButtonStatus();
+ }, true);
}
init() {
this.render();
+ this.handleButtonStatus();
// Unselect saved card.
// WC saves form values, so with our current UI it would be a bit weird
@@ -28,6 +33,25 @@ class CheckoutBootstap {
jQuery(document.body).on('updated_checkout', () => {
this.render()
+ this.handleButtonStatus();
+
+ if (this.shouldShowMessages() && document.querySelector(this.gateway.messages.wrapper)) { // currently we need amount only for Pay Later
+ fetch(
+ this.gateway.ajax.cart_script_params.endpoint,
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (! result.success) {
+ return;
+ }
+
+ jQuery(document.body).trigger('ppcp_checkout_total_updated', [result.data.amount]);
+ });
+ }
});
jQuery(document.body).on('updated_checkout payment_method_selected', () => {
@@ -40,9 +64,19 @@ class CheckoutBootstap {
})
});
+ jQuery(document).on('ppcp_should_show_messages', (e, data) => {
+ if (!this.shouldShowMessages()) {
+ data.result = false;
+ }
+ });
+
this.updateUi();
}
+ handleButtonStatus() {
+ BootstrapHelper.handleButtonStatus(this);
+ }
+
shouldRender() {
if (document.querySelector(this.gateway.button.cancel_wrapper)) {
return false;
@@ -51,6 +85,10 @@ class CheckoutBootstap {
return document.querySelector(this.gateway.button.wrapper) !== null || document.querySelector(this.gateway.hosted_fields.wrapper) !== null;
}
+ shouldEnable() {
+ return BootstrapHelper.shouldEnable(this);
+ }
+
render() {
if (!this.shouldRender()) {
return;
@@ -64,9 +102,21 @@ class CheckoutBootstap {
this.spinner
);
- this.renderer.render(
- actionHandler.configuration()
- );
+ if(
+ PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
+ ) {
+ this.renderer.render(actionHandler.subscriptionsConfiguration(), {}, actionHandler.configuration());
+
+ if(!PayPalCommerceGateway.subscription_product_allowed) {
+ this.gateway.button.is_disabled = true;
+ this.handleButtonStatus();
+ }
+
+ return;
+ }
+
+ this.renderer.render(actionHandler.configuration(), {}, actionHandler.configuration());
}
updateUi() {
@@ -89,16 +139,11 @@ class CheckoutBootstap {
setVisibleByClass(this.standardOrderButtonSelector, (isPaypal && isFreeTrial && hasVaultedPaypal) || isNotOurGateway || isSavedCard, 'ppcp-hidden');
setVisible('.ppcp-vaulted-paypal-details', isPaypal);
setVisible(this.gateway.button.wrapper, isPaypal && !(isFreeTrial && hasVaultedPaypal));
- setVisible(this.gateway.messages.wrapper, isPaypal && !isFreeTrial);
setVisible(this.gateway.hosted_fields.wrapper, isCard && !isSavedCard);
for (const [gatewayId, wrapper] of Object.entries(paypalButtonWrappers)) {
setVisible(wrapper, gatewayId === currentPaymentMethod);
}
- if (isPaypal && !isFreeTrial) {
- this.messages.render();
- }
-
if (isCard) {
if (isSavedCard) {
this.disableCreditCardFields();
@@ -106,6 +151,20 @@ class CheckoutBootstap {
this.enableCreditCardFields();
}
}
+
+ jQuery(document.body).trigger('ppcp_checkout_rendered');
+ }
+
+ shouldShowMessages() {
+ // hide when another method selected only if messages are near buttons
+ const messagesWrapper = document.querySelector(this.gateway.messages.wrapper);
+ if (getCurrentPaymentMethod() !== PaymentMethods.PAYPAL &&
+ messagesWrapper && jQuery(messagesWrapper).closest('.ppc-button-wrapper').length
+ ) {
+ return false;
+ }
+
+ return !PayPalCommerceGateway.is_free_trial_cart;
}
disableCreditCardFields() {
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js
new file mode 100644
index 000000000..fe1c49438
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MessagesBootstap.js
@@ -0,0 +1,55 @@
+import {setVisible} from "../Helper/Hiding";
+
+class MessagesBootstrap {
+ constructor(gateway, messageRenderer) {
+ this.gateway = gateway;
+ this.renderer = messageRenderer;
+ this.lastAmount = this.gateway.messages.amount;
+ }
+
+ init() {
+ jQuery(document.body).on('ppcp_cart_rendered ppcp_checkout_rendered', () => {
+ this.render();
+ });
+ jQuery(document.body).on('ppcp_script_data_changed', (e, data) => {
+ this.gateway = data;
+
+ this.render();
+ });
+ jQuery(document.body).on('ppcp_cart_total_updated ppcp_checkout_total_updated ppcp_product_total_updated', (e, amount) => {
+ if (this.lastAmount !== amount) {
+ this.lastAmount = amount;
+
+ this.render();
+ }
+ });
+
+ this.render();
+ }
+
+ shouldShow() {
+ if (this.gateway.messages.is_hidden === true) {
+ return false;
+ }
+
+ const eventData = {result: true}
+ jQuery(document.body).trigger('ppcp_should_show_messages', [eventData]);
+ return eventData.result;
+ }
+
+ shouldRender() {
+ return this.shouldShow() && this.renderer.shouldRender();
+ }
+
+ render() {
+ setVisible(this.gateway.messages.wrapper, this.shouldShow());
+
+ if (!this.shouldRender()) {
+ return;
+ }
+
+ this.renderer.renderWithAmount(this.lastAmount);
+ }
+}
+
+export default MessagesBootstrap;
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js
index 443c9afe4..4b4e3efd6 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/MiniCartBootstap.js
@@ -1,4 +1,5 @@
import CartActionHandler from '../ActionHandler/CartActionHandler';
+import BootstrapHelper from "../Helper/BootstrapHelper";
class MiniCartBootstap {
constructor(gateway, renderer, errorHandler) {
@@ -15,9 +16,22 @@ class MiniCartBootstap {
this.errorHandler,
);
this.render();
+ this.handleButtonStatus();
jQuery(document.body).on('wc_fragments_loaded wc_fragments_refreshed', () => {
this.render();
+ this.handleButtonStatus();
+ });
+
+ this.renderer.onButtonsInit(this.gateway.button.mini_cart_wrapper, () => {
+ this.handleButtonStatus();
+ }, true);
+ }
+
+ handleButtonStatus() {
+ BootstrapHelper.handleButtonStatus(this, {
+ wrapper: this.gateway.button.mini_cart_wrapper,
+ skipMessages: true
});
}
@@ -26,6 +40,12 @@ class MiniCartBootstap {
|| document.querySelector(this.gateway.hosted_fields.mini_cart_wrapper) !== null;
}
+ shouldEnable() {
+ return BootstrapHelper.shouldEnable(this, {
+ isDisabled: !!this.gateway.button.is_mini_cart_disabled
+ });
+ }
+
render() {
if (!this.shouldRender()) {
return;
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js
index 4d69532eb..678b60e63 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/PayNowBootstrap.js
@@ -2,8 +2,8 @@ import CheckoutBootstap from './CheckoutBootstap'
import {isChangePaymentPage} from "../Helper/Subscriptions";
class PayNowBootstrap extends CheckoutBootstap {
- constructor(gateway, renderer, messages, spinner, errorHandler) {
- super(gateway, renderer, messages, spinner, errorHandler)
+ constructor(gateway, renderer, spinner, errorHandler) {
+ super(gateway, renderer, spinner, errorHandler)
}
updateUi() {
diff --git a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
index f336c0a81..c9978306e 100644
--- a/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
+++ b/modules/ppcp-button/resources/js/modules/ContextBootstrap/SingleProductBootstap.js
@@ -1,67 +1,132 @@
import UpdateCart from "../Helper/UpdateCart";
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
+import {hide, show} from "../Helper/Hiding";
+import BootstrapHelper from "../Helper/BootstrapHelper";
+import {loadPaypalJsScript} from "../Helper/ScriptLoading";
+import {getPlanIdFromVariation} from "../Helper/Subscriptions"
+import SimulateCart from "../Helper/SimulateCart";
+import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils";
+import merge from "deepmerge";
class SingleProductBootstap {
- constructor(gateway, renderer, messages, errorHandler) {
+ constructor(gateway, renderer, errorHandler) {
this.gateway = gateway;
this.renderer = renderer;
- this.messages = messages;
this.errorHandler = errorHandler;
this.mutationObserver = new MutationObserver(this.handleChange.bind(this));
+ 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.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
+ this.handleChange();
+ }, true);
+
+ this.subscriptionButtonsLoaded = false
}
+ form() {
+ return document.querySelector(this.formSelector);
+ }
handleChange() {
+ this.subscriptionButtonsLoaded = false
+
if (!this.shouldRender()) {
- this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
- this.renderer.hideButtons(this.gateway.button.wrapper);
- this.messages.hideMessages();
+ this.renderer.disableSmartButtons(this.gateway.button.wrapper);
+ hide(this.gateway.button.wrapper, this.formSelector);
return;
}
this.render();
+
+ this.renderer.enableSmartButtons(this.gateway.button.wrapper);
+ show(this.gateway.button.wrapper);
+
+ this.handleButtonStatus();
+ }
+
+ handleButtonStatus(simulateCart = true) {
+ BootstrapHelper.handleButtonStatus(this, {
+ formSelector: this.formSelector
+ });
+
+ if (simulateCart) {
+ this.simulateCartThrottled();
+ }
}
init() {
+ const form = this.form();
- const form = document.querySelector('form.cart');
if (!form) {
return;
}
- form.addEventListener('change', this.handleChange.bind(this));
- this.mutationObserver.observe(form, {childList: true, subtree: true});
+ jQuery(document).on('change', this.formSelector, () => {
+ this.handleChange();
+ });
+ this.mutationObserver.observe(form, { childList: true, subtree: true });
+
+ const addToCartButton = form.querySelector('.single_add_to_cart_button');
+
+ if (addToCartButton) {
+ (new MutationObserver(this.handleButtonStatus.bind(this)))
+ .observe(addToCartButton, { attributes : true });
+ }
+
+ jQuery(document).on('ppcp_should_show_messages', (e, data) => {
+ if (!this.shouldRender()) {
+ data.result = false;
+ }
+ });
if (!this.shouldRender()) {
- this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
- this.messages.hideMessages();
return;
}
this.render();
-
+ this.handleChange();
}
shouldRender() {
-
- return document.querySelector('form.cart') !== null && !this.priceAmountIsZero();
-
+ return this.form() !== null
+ && !this.isWcsattSubscriptionMode();
}
- priceAmount() {
+ shouldEnable() {
+ const form = this.form();
+ const addToCartButton = form ? form.querySelector('.single_add_to_cart_button') : null;
+
+ return BootstrapHelper.shouldEnable(this)
+ && !this.priceAmountIsZero()
+ && ((null === addToCartButton) || !addToCartButton.classList.contains('disabled'));
+ }
+
+ priceAmount(returnOnUndefined = 0) {
const priceText = [
() => document.querySelector('form.cart ins .woocommerce-Price-amount')?.innerText,
() => document.querySelector('form.cart .woocommerce-Price-amount')?.innerText,
() => {
const priceEl = document.querySelector('.product .woocommerce-Price-amount');
// variable products show price like 10.00 - 20.00 here
- if (priceEl && priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount').length === 1) {
- return priceEl.innerText;
+ // but the second price also can be the suffix with the price incl/excl tax
+ if (priceEl) {
+ const allPriceElements = Array.from(priceEl.parentElement.querySelectorAll('.woocommerce-Price-amount'))
+ .filter(el => !el.parentElement.classList.contains('woocommerce-price-suffix'));
+ if (allPriceElements.length === 1) {
+ return priceEl.innerText;
+ }
}
return null;
},
].map(f => f()).find(val => val);
+ if (typeof priceText === 'undefined') {
+ return returnOnUndefined;
+ }
+
if (!priceText) {
return 0;
}
@@ -70,10 +135,41 @@ class SingleProductBootstap {
}
priceAmountIsZero() {
- const price = this.priceAmount();
+ const price = this.priceAmount(-1);
+
+ // if we can't find the price in the DOM we want to return true so the button is visible.
+ if (price === -1) {
+ return false;
+ }
+
return !price || price === 0;
}
+ isWcsattSubscriptionMode() {
+ // Check "All products for subscriptions" plugin.
+ return document.querySelector('.wcsatt-options-product:not(.wcsatt-options-product--hidden) .subscription-option input[type="radio"]:checked') !== null
+ || document.querySelector('.wcsatt-options-prompt-label-subscription input[type="radio"]:checked') !== null; // grouped
+ }
+
+ variations() {
+ if (!this.hasVariations()) {
+ return null;
+ }
+
+ return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
+ (element) => {
+ return {
+ value: element.value,
+ name: element.name
+ }
+ }
+ );
+ }
+
+ hasVariations() {
+ return document.querySelector('form.cart')?.classList.contains('variations_form');
+ }
+
render() {
const actionHandler = new SingleProductActionHandler(
this.gateway,
@@ -81,24 +177,112 @@ class SingleProductBootstap {
this.gateway.ajax.change_cart.endpoint,
this.gateway.ajax.change_cart.nonce,
),
- () => {
- this.renderer.showButtons(this.gateway.button.wrapper);
- this.renderer.showButtons(this.gateway.hosted_fields.wrapper);
- this.messages.renderWithAmount(this.priceAmount())
- },
- () => {
- this.renderer.hideButtons(this.gateway.button.wrapper);
- this.renderer.hideButtons(this.gateway.hosted_fields.wrapper);
- this.messages.hideMessages();
- },
- document.querySelector('form.cart'),
+ this.form(),
this.errorHandler,
);
+ if(
+ PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
+ ) {
+ const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway');
+ buttonWrapper.innerHTML = '';
+
+ const subscription_plan = this.variations() !== null
+ ? getPlanIdFromVariation(this.variations())
+ : PayPalCommerceGateway.subscription_plan_id
+ if(!subscription_plan) {
+ return;
+ }
+
+ if(this.subscriptionButtonsLoaded) return
+ loadPaypalJsScript(
+ {
+ clientId: PayPalCommerceGateway.client_id,
+ currency: PayPalCommerceGateway.currency,
+ intent: 'subscription',
+ vault: true
+ },
+ actionHandler.subscriptionsConfiguration(subscription_plan),
+ this.gateway.button.wrapper
+ );
+
+ this.subscriptionButtonsLoaded = true
+ return;
+ }
+
this.renderer.render(
actionHandler.configuration()
);
}
+
+ simulateCart() {
+ if (!this.gateway.simulate_cart.enabled) {
+ return;
+ }
+
+ const actionHandler = new SingleProductActionHandler(
+ null,
+ null,
+ this.form(),
+ this.errorHandler,
+ );
+
+ const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
+
+ const products = hasSubscriptions
+ ? actionHandler.getSubscriptionProducts()
+ : actionHandler.getProducts();
+
+ (new SimulateCart(
+ this.gateway.ajax.simulate_cart.endpoint,
+ this.gateway.ajax.simulate_cart.nonce,
+ )).simulate((data) => {
+
+ jQuery(document.body).trigger('ppcp_product_total_updated', [data.total]);
+
+ let newData = {};
+ if (typeof data.button.is_disabled === 'boolean') {
+ newData = merge(newData, {button: {is_disabled: data.button.is_disabled}});
+ }
+ if (typeof data.messages.is_hidden === 'boolean') {
+ newData = merge(newData, {messages: {is_hidden: data.messages.is_hidden}});
+ }
+ if (newData) {
+ BootstrapHelper.updateScriptData(this, newData);
+ }
+
+ if ( this.gateway.single_product_buttons_enabled !== '1' ) {
+ return;
+ }
+
+ let enableFunding = this.gateway.url_params['enable-funding'];
+ let disableFunding = this.gateway.url_params['disable-funding'];
+
+ for (const [fundingSource, funding] of Object.entries(data.funding)) {
+ if (funding.enabled === true) {
+ enableFunding = strAddWord(enableFunding, fundingSource);
+ disableFunding = strRemoveWord(disableFunding, fundingSource);
+ } else if (funding.enabled === false) {
+ enableFunding = strRemoveWord(enableFunding, fundingSource);
+ disableFunding = strAddWord(disableFunding, fundingSource);
+ }
+ }
+
+ if (
+ (enableFunding !== this.gateway.url_params['enable-funding']) ||
+ (disableFunding !== this.gateway.url_params['disable-funding'])
+ ) {
+ this.gateway.url_params['enable-funding'] = enableFunding;
+ this.gateway.url_params['disable-funding'] = disableFunding;
+ jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
+ }
+
+ this.handleButtonStatus(false);
+
+ }, products);
+ }
}
export default SingleProductBootstap;
diff --git a/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js b/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js
index 16ddca85c..bb143eb08 100644
--- a/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js
+++ b/modules/ppcp-button/resources/js/modules/DataClientIdAttributeHandler.js
@@ -1,3 +1,5 @@
+import {loadScript} from "@paypal/paypal-js";
+
const storageKey = 'ppcp-data-client-id';
const validateToken = (token, user) => {
@@ -24,9 +26,12 @@ const storeToken = (token) => {
sessionStorage.setItem(storageKey, JSON.stringify(token));
}
-const dataClientIdAttributeHandler = (script, config) => {
+const dataClientIdAttributeHandler = (scriptOptions, config, callback) => {
fetch(config.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: config.nonce
@@ -39,8 +44,14 @@ const dataClientIdAttributeHandler = (script, config) => {
return;
}
storeToken(data);
- script.setAttribute('data-client-token', data.token);
- document.body.appendChild(script);
+
+ scriptOptions['data-client-token'] = data.token;
+
+ loadScript(scriptOptions).then((paypal) => {
+ if (typeof callback === 'function') {
+ callback(paypal);
+ }
+ });
});
}
diff --git a/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js b/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js
new file mode 100644
index 000000000..4245bcb6c
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Entity/BookingProduct.js
@@ -0,0 +1,18 @@
+import Product from "./Product";
+
+class BookingProduct extends Product {
+
+ constructor(id, quantity, booking, extra) {
+ super(id, quantity, null, extra);
+ this.booking = booking;
+ }
+
+ data() {
+ return {
+ ...super.data(),
+ booking: this.booking
+ }
+ }
+}
+
+export default BookingProduct;
diff --git a/modules/ppcp-button/resources/js/modules/Entity/Product.js b/modules/ppcp-button/resources/js/modules/Entity/Product.js
index 9f5648490..b99f74b61 100644
--- a/modules/ppcp-button/resources/js/modules/Entity/Product.js
+++ b/modules/ppcp-button/resources/js/modules/Entity/Product.js
@@ -1,18 +1,19 @@
class Product {
- constructor(id, quantity, variations) {
+ constructor(id, quantity, variations, extra) {
this.id = id;
this.quantity = quantity;
this.variations = variations;
+ this.extra = extra;
}
-
data() {
return {
id:this.id,
- quantity:this.quantity,
- variations:this.variations
+ quantity: this.quantity,
+ variations: this.variations,
+ extra: this.extra,
}
}
}
-export default Product;
\ No newline at end of file
+export default Product;
diff --git a/modules/ppcp-button/resources/js/modules/ErrorHandler.js b/modules/ppcp-button/resources/js/modules/ErrorHandler.js
index c402aa751..6048360b2 100644
--- a/modules/ppcp-button/resources/js/modules/ErrorHandler.js
+++ b/modules/ppcp-button/resources/js/modules/ErrorHandler.js
@@ -1,74 +1,68 @@
class ErrorHandler {
- constructor(genericErrorText)
+ /**
+ * @param {String} genericErrorText
+ * @param {Element} wrapper
+ */
+ constructor(genericErrorText, wrapper)
{
this.genericErrorText = genericErrorText;
- this.wrapper = document.querySelector('.woocommerce-notices-wrapper');
- this.messagesList = document.querySelector('ul.woocommerce-error');
+ this.wrapper = wrapper;
}
genericError() {
- if (this.wrapper.classList.contains('ppcp-persist')) {
- return;
- }
this.clear();
this.message(this.genericErrorText)
}
appendPreparedErrorMessageElement(errorMessageElement)
{
- if (this.messagesList === null) {
- this._prepareMessagesList();
- }
-
- this.messagesList.replaceWith(errorMessageElement);
+ this._getMessageContainer().replaceWith(errorMessageElement);
}
/**
* @param {String} text
- * @param {Boolean} persist
*/
- message(text, persist = false)
+ message(text)
{
- this._addMessage(text, persist);
+ this._addMessage(text);
this._scrollToMessages();
}
/**
* @param {Array} texts
- * @param {Boolean} persist
*/
- messages(texts, persist = false)
+ messages(texts)
{
- texts.forEach(t => this._addMessage(t, persist));
+ texts.forEach(t => this._addMessage(t));
this._scrollToMessages();
}
+ /**
+ * @returns {String}
+ */
+ currentHtml()
+ {
+ const messageContainer = this._getMessageContainer();
+ return messageContainer.outerHTML;
+ }
+
/**
* @private
* @param {String} text
- * @param {Boolean} persist
*/
- _addMessage(text, persist = false)
+ _addMessage(text)
{
if(! typeof String || text.length === 0) {
throw new Error('A new message text must be a non-empty string.');
}
- if (this.messagesList === null){
- this._prepareMessagesList();
- }
+ const messageContainer = this._getMessageContainer();
- if (persist) {
- this.wrapper.classList.add('ppcp-persist');
- } else {
- this.wrapper.classList.remove('ppcp-persist');
- }
-
- let messageNode = this._prepareMessagesListItem(text);
- this.messagesList.appendChild(messageNode);
+ let messageNode = this._prepareMessageElement(text);
+ messageContainer.appendChild(messageNode);
}
/**
@@ -76,26 +70,28 @@ class ErrorHandler {
*/
_scrollToMessages()
{
- jQuery.scroll_to_notices(jQuery('.woocommerce-notices-wrapper'));
+ jQuery.scroll_to_notices(jQuery('.woocommerce-error'));
}
/**
* @private
*/
- _prepareMessagesList()
+ _getMessageContainer()
{
- if (this.messagesList === null) {
- this.messagesList = document.createElement('ul');
- this.messagesList.setAttribute('class', 'woocommerce-error');
- this.messagesList.setAttribute('role', 'alert');
- this.wrapper.appendChild(this.messagesList);
+ let messageContainer = document.querySelector('ul.woocommerce-error');
+ if (messageContainer === null) {
+ messageContainer = document.createElement('ul');
+ messageContainer.setAttribute('class', 'woocommerce-error');
+ messageContainer.setAttribute('role', 'alert');
+ jQuery(this.wrapper).prepend(messageContainer);
}
+ return messageContainer;
}
/**
* @private
*/
- _prepareMessagesListItem(message)
+ _prepareMessageElement(message)
{
const li = document.createElement('li');
li.innerHTML = message;
@@ -105,11 +101,7 @@ class ErrorHandler {
clear()
{
- if (this.messagesList === null) {
- return;
- }
-
- this.messagesList.innerHTML = '';
+ jQuery( '.woocommerce-error, .woocommerce-message' ).remove();
}
}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js
new file mode 100644
index 000000000..41eb62e25
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/BootstrapHelper.js
@@ -0,0 +1,51 @@
+import {disable, enable, isDisabled} from "./ButtonDisabler";
+import merge from "deepmerge";
+
+/**
+ * Common Bootstrap methods to avoid code repetition.
+ */
+export default class BootstrapHelper {
+
+ static handleButtonStatus(bs, options) {
+ options = options || {};
+ options.wrapper = options.wrapper || bs.gateway.button.wrapper;
+
+ const wasDisabled = isDisabled(options.wrapper);
+ const shouldEnable = bs.shouldEnable();
+
+ // Handle enable / disable
+ if (shouldEnable && wasDisabled) {
+ bs.renderer.enableSmartButtons(options.wrapper);
+ enable(options.wrapper);
+ } else if (!shouldEnable && !wasDisabled) {
+ bs.renderer.disableSmartButtons(options.wrapper);
+ disable(options.wrapper, options.formSelector || null);
+ }
+
+ if (wasDisabled !== !shouldEnable) {
+ jQuery(options.wrapper).trigger('ppcp_buttons_enabled_changed', [shouldEnable]);
+ }
+ }
+
+ static shouldEnable(bs, options) {
+ options = options || {};
+ if (typeof options.isDisabled === 'undefined') {
+ options.isDisabled = bs.gateway.button.is_disabled;
+ }
+
+ return bs.shouldRender()
+ && options.isDisabled !== true;
+ }
+
+ static updateScriptData(bs, newData) {
+ const newObj = merge(bs.gateway, newData);
+
+ const isChanged = JSON.stringify(bs.gateway) !== JSON.stringify(newObj);
+
+ bs.gateway = newObj;
+
+ if (isChanged) {
+ jQuery(document.body).trigger('ppcp_script_data_changed', [newObj]);
+ }
+ }
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js
new file mode 100644
index 000000000..c4042d079
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/ButtonDisabler.js
@@ -0,0 +1,83 @@
+/**
+ * @param selectorOrElement
+ * @returns {Element}
+ */
+const getElement = (selectorOrElement) => {
+ if (typeof selectorOrElement === 'string') {
+ return document.querySelector(selectorOrElement);
+ }
+ return selectorOrElement;
+}
+
+const triggerEnabled = (selectorOrElement, element) => {
+ jQuery(document).trigger('ppcp-enabled', {
+ 'handler': 'ButtonsDisabler.setEnabled',
+ 'action': 'enable',
+ 'selector': selectorOrElement,
+ 'element': element
+ });
+}
+
+const triggerDisabled = (selectorOrElement, element) => {
+ jQuery(document).trigger('ppcp-disabled', {
+ 'handler': 'ButtonsDisabler.setEnabled',
+ 'action': 'disable',
+ 'selector': selectorOrElement,
+ 'element': element
+ });
+}
+
+export const setEnabled = (selectorOrElement, enable, form = null) => {
+ const element = getElement(selectorOrElement);
+
+ if (!element) {
+ return;
+ }
+
+ if (enable) {
+ jQuery(element)
+ .removeClass('ppcp-disabled')
+ .off('mouseup')
+ .find('> *')
+ .css('pointer-events', '');
+
+ triggerEnabled(selectorOrElement, element);
+
+ } else {
+ jQuery(element)
+ .addClass('ppcp-disabled')
+ .on('mouseup', function(event) {
+ event.stopImmediatePropagation();
+
+ if (form) {
+ // Trigger form submit to show the error message
+ let $form = jQuery(form);
+ if ($form.find('.single_add_to_cart_button').hasClass('disabled')) {
+ $form.find(':submit').trigger('click');
+ }
+ }
+ })
+ .find('> *')
+ .css('pointer-events', 'none');
+
+ triggerDisabled(selectorOrElement, element);
+ }
+};
+
+export const isDisabled = (selectorOrElement) => {
+ const element = getElement(selectorOrElement);
+
+ if (!element) {
+ return false;
+ }
+
+ return jQuery(element).hasClass('ppcp-disabled');
+};
+
+export const disable = (selectorOrElement, form = null) => {
+ setEnabled(selectorOrElement, false, form);
+};
+
+export const enable = (selectorOrElement) => {
+ setEnabled(selectorOrElement, true);
+};
diff --git a/modules/ppcp-button/resources/js/modules/Helper/ButtonsToggleListener.js b/modules/ppcp-button/resources/js/modules/Helper/ButtonsToggleListener.js
deleted file mode 100644
index bed5afcda..000000000
--- a/modules/ppcp-button/resources/js/modules/Helper/ButtonsToggleListener.js
+++ /dev/null
@@ -1,36 +0,0 @@
-/**
- * When you can't add something to the cart, the PayPal buttons should not show.
- * Therefore we listen for changes on the add to cart button and show/hide the buttons accordingly.
- */
-
-class ButtonsToggleListener {
- constructor(element, showCallback, hideCallback)
- {
- this.element = element;
- this.showCallback = showCallback;
- this.hideCallback = hideCallback;
- this.observer = null;
- }
-
- init()
- {
- const config = { attributes : true };
- const callback = () => {
- if (this.element.classList.contains('disabled')) {
- this.hideCallback();
- return;
- }
- this.showCallback();
- }
- this.observer = new MutationObserver(callback);
- this.observer.observe(this.element, config);
- callback();
- }
-
- disconnect()
- {
- this.observer.disconnect();
- }
-}
-
-export default ButtonsToggleListener;
\ No newline at end of file
diff --git a/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js b/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js
new file mode 100644
index 000000000..1bc308413
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/CartHelper.js
@@ -0,0 +1,74 @@
+class CartHelper {
+
+ constructor(cartItemKeys = [])
+ {
+ this.cartItemKeys = cartItemKeys;
+ }
+
+ getEndpoint() {
+ let ajaxUrl = "/?wc-ajax=%%endpoint%%";
+
+ if ((typeof wc_cart_fragments_params !== 'undefined') && wc_cart_fragments_params.wc_ajax_url) {
+ ajaxUrl = wc_cart_fragments_params.wc_ajax_url;
+ }
+
+ return ajaxUrl.toString().replace('%%endpoint%%', 'remove_from_cart');
+ }
+
+ addFromPurchaseUnits(purchaseUnits) {
+ for (const purchaseUnit of purchaseUnits || []) {
+ for (const item of purchaseUnit.items || []) {
+ if (!item.cart_item_key) {
+ continue;
+ }
+ this.cartItemKeys.push(item.cart_item_key);
+ }
+ }
+
+ return this;
+ }
+
+ removeFromCart()
+ {
+ return new Promise((resolve, reject) => {
+ if (!this.cartItemKeys || !this.cartItemKeys.length) {
+ resolve();
+ return;
+ }
+
+ const numRequests = this.cartItemKeys.length;
+ let numResponses = 0;
+
+ const tryToResolve = () => {
+ numResponses++;
+ if (numResponses >= numRequests) {
+ resolve();
+ }
+ }
+
+ for (const cartItemKey of this.cartItemKeys) {
+ const params = new URLSearchParams();
+ params.append('cart_item_key', cartItemKey);
+
+ if (!cartItemKey) {
+ tryToResolve();
+ continue;
+ }
+
+ fetch(this.getEndpoint(), {
+ method: 'POST',
+ credentials: 'same-origin',
+ body: params
+ }).then(function (res) {
+ return res.json();
+ }).then(() => {
+ tryToResolve();
+ }).catch(() => {
+ tryToResolve();
+ });
+ }
+ });
+ }
+}
+
+export default CartHelper;
diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js b/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js
new file mode 100644
index 000000000..7f504e1c4
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/FormHelper.js
@@ -0,0 +1,50 @@
+
+/**
+ * Common Form utility methods
+ */
+export default class FormHelper {
+
+ static getPrefixedFields(formElement, prefix) {
+ const formData = new FormData(formElement);
+ let fields = {};
+
+ for (const [name, value] of formData.entries()) {
+ if (!prefix || name.startsWith(prefix)) {
+ fields[name] = value;
+ }
+ }
+
+ return fields;
+ }
+
+ static getFilteredFields(formElement, exactFilters, prefixFilters) {
+ const formData = new FormData(formElement);
+ let fields = {};
+ let counters = {};
+
+ for (let [name, value] of formData.entries()) {
+
+ // Handle array format
+ if (name.indexOf('[]') !== -1) {
+ const k = name;
+ counters[k] = counters[k] || 0;
+ name = name.replace('[]', `[${counters[k]}]`);
+ counters[k]++;
+ }
+
+ if (!name) {
+ continue;
+ }
+ if (exactFilters && (exactFilters.indexOf(name) !== -1)) {
+ continue;
+ }
+ if (prefixFilters && prefixFilters.some(prefixFilter => name.startsWith(prefixFilter))) {
+ continue;
+ }
+
+ fields[name] = value;
+ }
+
+ return fields;
+ }
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js b/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js
new file mode 100644
index 000000000..0250b6b29
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/FormSaver.js
@@ -0,0 +1,28 @@
+export default class FormSaver {
+ constructor(url, nonce) {
+ this.url = url;
+ this.nonce = nonce;
+ }
+
+ async save(form) {
+ const formData = new FormData(form);
+
+ const res = await fetch(this.url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.nonce,
+ form_encoded: new URLSearchParams(formData).toString(),
+ }),
+ });
+
+ const data = await res.json();
+
+ if (!data.success) {
+ throw Error(data.data.message);
+ }
+ }
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js b/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js
new file mode 100644
index 000000000..0c03ca4d4
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/FormValidator.js
@@ -0,0 +1,37 @@
+export default class FormValidator {
+ constructor(url, nonce) {
+ this.url = url;
+ this.nonce = nonce;
+ }
+
+ async validate(form) {
+ const formData = new FormData(form);
+
+ const res = await fetch(this.url, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.nonce,
+ form_encoded: new URLSearchParams(formData).toString(),
+ }),
+ });
+
+ const data = await res.json();
+
+ if (!data.success) {
+ if (data.data.refresh) {
+ jQuery( document.body ).trigger( 'update_checkout' );
+ }
+
+ if (data.data.errors) {
+ return data.data.errors;
+ }
+ throw Error(data.data.message);
+ }
+
+ return [];
+ }
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/Hiding.js b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js
index 96836de0c..9ffd5a031 100644
--- a/modules/ppcp-button/resources/js/modules/Helper/Hiding.js
+++ b/modules/ppcp-button/resources/js/modules/Helper/Hiding.js
@@ -9,6 +9,24 @@ const getElement = (selectorOrElement) => {
return selectorOrElement;
}
+const triggerHidden = (handler, selectorOrElement, element) => {
+ jQuery(document).trigger('ppcp-hidden', {
+ 'handler': handler,
+ 'action': 'hide',
+ 'selector': selectorOrElement,
+ 'element': element
+ });
+}
+
+const triggerShown = (handler, selectorOrElement, element) => {
+ jQuery(document).trigger('ppcp-shown', {
+ 'handler': handler,
+ 'action': 'show',
+ 'selector': selectorOrElement,
+ 'element': element
+ });
+}
+
export const isVisible = (element) => {
return !!(element.offsetWidth || element.offsetHeight || element.getClientRects().length);
}
@@ -27,14 +45,18 @@ export const setVisible = (selectorOrElement, show, important = false) => {
}
element.style.setProperty('display', 'none', important ? 'important' : '');
+ triggerHidden('Hiding.setVisible', selectorOrElement, element);
+
} else {
if (currentValue === 'none') {
element.style.removeProperty('display');
+ triggerShown('Hiding.setVisible', selectorOrElement, element);
}
// still not visible (if something else added display: none in CSS)
if (!isVisible(element)) {
element.style.setProperty('display', 'block');
+ triggerShown('Hiding.setVisible', selectorOrElement, element);
}
}
};
@@ -47,8 +69,10 @@ export const setVisibleByClass = (selectorOrElement, show, hiddenClass) => {
if (show) {
element.classList.remove(hiddenClass);
+ triggerShown('Hiding.setVisibleByClass', selectorOrElement, element);
} else {
element.classList.add(hiddenClass);
+ triggerHidden('Hiding.setVisibleByClass', selectorOrElement, element);
}
};
diff --git a/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js
new file mode 100644
index 000000000..fea81923b
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/ScriptLoading.js
@@ -0,0 +1,59 @@
+import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler";
+import {loadScript} from "@paypal/paypal-js";
+import widgetBuilder from "../Renderer/WidgetBuilder";
+import merge from "deepmerge";
+import {keysToCamelCase} from "./Utils";
+
+// This component may be used by multiple modules. This assures that options are shared between all instances.
+let options = window.ppcpWidgetBuilder = window.ppcpWidgetBuilder || {
+ isLoading: false,
+ onLoadedCallbacks: []
+};
+
+export const loadPaypalScript = (config, onLoaded) => {
+ // If PayPal is already loaded call the onLoaded callback and return.
+ if (typeof paypal !== 'undefined') {
+ onLoaded();
+ return;
+ }
+
+ // Add the onLoaded callback to the onLoadedCallbacks stack.
+ options.onLoadedCallbacks.push(onLoaded);
+
+ // Return if it's still loading.
+ if (options.isLoading) {
+ return;
+ }
+ options.isLoading = true;
+
+ // Callback to be called once the PayPal script is loaded.
+ const callback = (paypal) => {
+ widgetBuilder.setPaypal(paypal);
+
+ for (const onLoadedCallback of options.onLoadedCallbacks) {
+ onLoadedCallback();
+ }
+
+ options.isLoading = false;
+ options.onLoadedCallbacks = [];
+ }
+
+ // Build the PayPal script options.
+ let scriptOptions = keysToCamelCase(config.url_params);
+ scriptOptions = merge(scriptOptions, config.script_attributes);
+
+ // Load PayPal script for special case with data-client-token
+ if (config.data_client_id.set_attribute) {
+ dataClientIdAttributeHandler(scriptOptions, config.data_client_id, callback);
+ return;
+ }
+
+ // Load PayPal script
+ loadScript(scriptOptions).then(callback);
+}
+
+export const loadPaypalJsScript = (options, buttons, container) => {
+ loadScript(options).then((paypal) => {
+ paypal.Buttons(buttons).render(container);
+ });
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js b/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js
new file mode 100644
index 000000000..106dfe989
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/SimulateCart.js
@@ -0,0 +1,48 @@
+class SimulateCart {
+
+ constructor(endpoint, nonce)
+ {
+ this.endpoint = endpoint;
+ this.nonce = nonce;
+ }
+
+ /**
+ *
+ * @param onResolve
+ * @param {Product[]} products
+ * @returns {Promise}
+ */
+ simulate(onResolve, products)
+ {
+ return new Promise((resolve, reject) => {
+ fetch(
+ this.endpoint,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.nonce,
+ products,
+ })
+ }
+ ).then(
+ (result) => {
+ return result.json();
+ }
+ ).then((result) => {
+ if (! result.success) {
+ reject(result.data);
+ return;
+ }
+
+ const resolved = onResolve(result.data);
+ resolve(resolved);
+ })
+ });
+ }
+}
+
+export default SimulateCart;
diff --git a/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js
index a205ac459..4366fd9d2 100644
--- a/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js
+++ b/modules/ppcp-button/resources/js/modules/Helper/Subscriptions.js
@@ -2,3 +2,19 @@ export const isChangePaymentPage = () => {
const urlParams = new URLSearchParams(window.location.search)
return urlParams.has('change_payment_method');
}
+
+export const getPlanIdFromVariation = (variation) => {
+ let subscription_plan = '';
+ PayPalCommerceGateway.variable_paypal_subscription_variations.forEach((element) => {
+ let obj = {};
+ variation.forEach(({name, value}) => {
+ Object.assign(obj, {[name.replace('attribute_', '')]: value});
+ })
+
+ if(JSON.stringify(obj) === JSON.stringify(element.attributes) && element.subscription_plan !== '') {
+ subscription_plan = element.subscription_plan;
+ }
+ });
+
+ return subscription_plan;
+}
diff --git a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js
index 43a95ee83..4c5b08355 100644
--- a/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js
+++ b/modules/ppcp-button/resources/js/modules/Helper/UpdateCart.js
@@ -20,6 +20,9 @@ class UpdateCart {
this.endpoint,
{
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: this.nonce,
diff --git a/modules/ppcp-button/resources/js/modules/Helper/Utils.js b/modules/ppcp-button/resources/js/modules/Helper/Utils.js
new file mode 100644
index 000000000..c1fbb8aec
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Helper/Utils.js
@@ -0,0 +1,69 @@
+export const toCamelCase = (str) => {
+ return str.replace(/([-_]\w)/g, function(match) {
+ return match[1].toUpperCase();
+ });
+}
+
+export const keysToCamelCase = (obj) => {
+ let output = {};
+ for (const key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ output[toCamelCase(key)] = obj[key];
+ }
+ }
+ return output;
+}
+
+export const strAddWord = (str, word, separator = ',') => {
+ let arr = str.split(separator);
+ if (!arr.includes(word)) {
+ arr.push(word);
+ }
+ return arr.join(separator);
+};
+
+export const strRemoveWord = (str, word, separator = ',') => {
+ let arr = str.split(separator);
+ let index = arr.indexOf(word);
+ if (index !== -1) {
+ arr.splice(index, 1);
+ }
+ return arr.join(separator);
+};
+
+export const throttle = (func, limit) => {
+ let inThrottle, lastArgs, lastContext;
+
+ function execute() {
+ inThrottle = true;
+ func.apply(this, arguments);
+ setTimeout(() => {
+ inThrottle = false;
+ if (lastArgs) {
+ const nextArgs = lastArgs;
+ const nextContext = lastContext;
+ lastArgs = lastContext = null;
+ execute.apply(nextContext, nextArgs);
+ }
+ }, limit);
+ }
+
+ return function() {
+ if (!inThrottle) {
+ execute.apply(this, arguments);
+ } else {
+ lastArgs = arguments;
+ lastContext = this;
+ }
+ };
+}
+
+const Utils = {
+ toCamelCase,
+ keysToCamelCase,
+ strAddWord,
+ strRemoveWord,
+ throttle
+};
+
+export default Utils;
diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js
index 12260fe9f..2068563a7 100644
--- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js
+++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForContinue.js
@@ -2,6 +2,9 @@ const onApprove = (context, errorHandler) => {
return (data, actions) => {
return fetch(context.config.ajax.approve_order.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: context.config.ajax.approve_order.nonce,
diff --git a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js
index 4e1000bfa..e8fcbde09 100644
--- a/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js
+++ b/modules/ppcp-button/resources/js/modules/OnApproveHandler/onApproveForPayNow.js
@@ -5,6 +5,9 @@ const onApprove = (context, errorHandler, spinner) => {
return fetch(context.config.ajax.approve_order.endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
nonce: context.config.ajax.approve_order.nonce,
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js
index 3eefb59ac..9cd6e345c 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/CreditCardRenderer.js
@@ -10,6 +10,7 @@ class CreditCardRenderer {
this.spinner = spinner;
this.cardValid = false;
this.formValid = false;
+ this.emptyFields = new Set(['number', 'cvv', 'expirationDate']);
this.currentHostedFieldsInstance = null;
}
@@ -121,22 +122,21 @@ class CreditCardRenderer {
const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
- if (event.fields.number.isValid) {
+ if (event.cards.length === 1) {
cardNumber.classList.add(className);
}
})
hostedFields.on('validityChange', (event) => {
- const formValid = Object.keys(event.fields).every(function (key) {
+ this.formValid = Object.keys(event.fields).every(function (key) {
return event.fields[key].isValid;
});
-
- const className = this._cardNumberFiledCLassNameByCardType(event.cards[0].type);
- event.fields.number.isValid
- ? cardNumber.classList.add(className)
- : this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
-
- this.formValid = formValid;
-
+ });
+ hostedFields.on('empty', (event) => {
+ this._recreateElementClassAttribute(cardNumber, cardNumberField.className);
+ this.emptyFields.add(event.emittedBy);
+ });
+ hostedFields.on('notEmpty', (event) => {
+ this.emptyFields.delete(event.emittedBy);
});
show(buttonSelector);
@@ -234,22 +234,31 @@ class CreditCardRenderer {
this.errorHandler.clear();
if (err.data?.details?.length) {
- this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join(' '), true);
+ this.errorHandler.message(err.data.details.map(d => `${d.issue} ${d.description}`).join(' '));
} else if (err.details?.length) {
- this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join(' '), true);
+ this.errorHandler.message(err.details.map(d => `${d.issue} ${d.description}`).join(' '));
} else if (err.data?.errors?.length > 0) {
this.errorHandler.messages(err.data.errors);
} else if (err.data?.message) {
- this.errorHandler.message(err.data.message, true);
+ this.errorHandler.message(err.data.message);
} else if (err.message) {
- this.errorHandler.message(err.message, true);
+ this.errorHandler.message(err.message);
} else {
this.errorHandler.genericError();
}
});
} else {
this.spinner.unblock();
- const message = ! this.cardValid ? this.defaultConfig.hosted_fields.labels.card_not_supported : this.defaultConfig.hosted_fields.labels.fields_not_valid;
+
+ let message = this.defaultConfig.labels.error.generic;
+ if (this.emptyFields.size > 0) {
+ message = this.defaultConfig.hosted_fields.labels.fields_empty;
+ } else if (!this.cardValid) {
+ message = this.defaultConfig.hosted_fields.labels.card_not_supported;
+ } else if (!this.formValid) {
+ message = this.defaultConfig.hosted_fields.labels.fields_not_valid;
+ }
+
this.errorHandler.message(message);
}
}
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js
index f7409c683..b1261a466 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/MessageRenderer.js
@@ -1,51 +1,55 @@
+import widgetBuilder from "./WidgetBuilder";
+
class MessageRenderer {
constructor(config) {
this.config = config;
- }
-
- render() {
- if (! this.shouldRender()) {
- return;
- }
-
- paypal.Messages({
- amount: this.config.amount,
- placement: this.config.placement,
- style: this.config.style
- }).render(this.config.wrapper);
-
- jQuery(document.body).on('updated_cart_totals', () => {
- paypal.Messages({
- amount: this.config.amount,
- placement: this.config.placement,
- style: this.config.style
- }).render(this.config.wrapper);
- });
+ this.optionsFingerprint = null;
+ this.currentNumber = 0;
}
renderWithAmount(amount) {
-
if (! this.shouldRender()) {
return;
}
- const newWrapper = document.createElement('div');
- newWrapper.setAttribute('id', this.config.wrapper.replace('#', ''));
-
- const sibling = document.querySelector(this.config.wrapper).nextSibling;
- document.querySelector(this.config.wrapper).parentElement.removeChild(document.querySelector(this.config.wrapper));
- sibling.parentElement.insertBefore(newWrapper, sibling);
- paypal.Messages({
+ const options = {
amount,
placement: this.config.placement,
style: this.config.style
- }).render(this.config.wrapper);
+ };
+
+ // sometimes the element is destroyed while the options stay the same
+ if (document.querySelector(this.config.wrapper).getAttribute('data-render-number') !== this.currentNumber.toString()) {
+ this.optionsFingerprint = null;
+ }
+
+ if (this.optionsEqual(options)) {
+ return;
+ }
+
+ const wrapper = document.querySelector(this.config.wrapper);
+ this.currentNumber++;
+ wrapper.setAttribute('data-render-number', this.currentNumber);
+
+ widgetBuilder.registerMessages(this.config.wrapper, options);
+ widgetBuilder.renderMessages(this.config.wrapper);
+ }
+
+ optionsEqual(options) {
+ const fingerprint = JSON.stringify(options);
+
+ if (this.optionsFingerprint === fingerprint) {
+ return true;
+ }
+
+ this.optionsFingerprint = fingerprint;
+ return false;
}
shouldRender() {
- if (typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
+ if (typeof paypal === 'undefined' || typeof paypal.Messages === 'undefined' || typeof this.config.wrapper === 'undefined' ) {
return false;
}
if (! document.querySelector(this.config.wrapper)) {
@@ -53,14 +57,5 @@ class MessageRenderer {
}
return true;
}
-
- hideMessages() {
- const domElement = document.querySelector(this.config.wrapper);
- if (! domElement ) {
- return false;
- }
- domElement.style.display = 'none';
- return true;
- }
}
export default MessageRenderer;
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js
index f07df8fe4..c40fcc384 100644
--- a/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js
+++ b/modules/ppcp-button/resources/js/modules/Renderer/Renderer.js
@@ -1,4 +1,7 @@
import merge from "deepmerge";
+import {loadScript} from "@paypal/paypal-js";
+import {keysToCamelCase} from "../Helper/Utils";
+import widgetBuilder from "./WidgetBuilder";
class Renderer {
constructor(creditCardRenderer, defaultSettings, onSmartButtonClick, onSmartButtonsInit) {
@@ -7,10 +10,15 @@ class Renderer {
this.onSmartButtonClick = onSmartButtonClick;
this.onSmartButtonsInit = onSmartButtonsInit;
+ this.buttonsOptions = {};
+ this.onButtonsInitListeners = {};
+
this.renderedSources = new Set();
+
+ this.reloadEventName = 'ppcp-reload-buttons';
}
- render(contextConfig, settingsOverride = {}) {
+ render(contextConfig, settingsOverride = {}, contextConfigOverride = () => {}) {
const settings = merge(this.defaultSettings, settingsOverride);
const enabledSeparateGateways = Object.fromEntries(Object.entries(
@@ -50,7 +58,7 @@ class Renderer {
}
if (this.creditCardRenderer) {
- this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfig);
+ this.creditCardRenderer.render(settings.hosted_fields.wrapper, contextConfigOverride);
}
for (const [fundingSource, data] of Object.entries(enabledSeparateGateways)) {
@@ -65,7 +73,9 @@ class Renderer {
}
renderButtons(wrapper, style, contextConfig, hasEnabledSeparateGateways, fundingSource = null) {
- if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) || 'undefined' === typeof paypal.Buttons ) {
+ if (! document.querySelector(wrapper) || this.isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) ) {
+ // Try to render registered buttons again in case they were removed from the DOM by an external source.
+ widgetBuilder.renderButtons([wrapper, fundingSource]);
return;
}
@@ -73,48 +83,50 @@ class Renderer {
contextConfig.fundingSource = fundingSource;
}
- const btn = paypal.Buttons({
- style,
- ...contextConfig,
- onClick: this.onSmartButtonClick,
- onInit: this.onSmartButtonsInit,
- });
- if (!btn.isEligible()) {
- return;
+ const buttonsOptions = () => {
+ return {
+ style,
+ ...contextConfig,
+ onClick: this.onSmartButtonClick,
+ onInit: (data, actions) => {
+ if (this.onSmartButtonsInit) {
+ this.onSmartButtonsInit(data, actions);
+ }
+ this.handleOnButtonsInit(wrapper, data, actions);
+ },
+ }
}
- btn.render(wrapper);
+ jQuery(document)
+ .off(this.reloadEventName, wrapper)
+ .on(this.reloadEventName, wrapper, (event, settingsOverride = {}, triggeredFundingSource) => {
- this.renderedSources.add(wrapper + fundingSource ?? '');
+ // Only accept events from the matching funding source
+ if (fundingSource && triggeredFundingSource && (triggeredFundingSource !== fundingSource)) {
+ return;
+ }
+
+ const settings = merge(this.defaultSettings, settingsOverride);
+ let scriptOptions = keysToCamelCase(settings.url_params);
+ scriptOptions = merge(scriptOptions, settings.script_attributes);
+
+ loadScript(scriptOptions).then((paypal) => {
+ widgetBuilder.setPaypal(paypal);
+ widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions());
+ widgetBuilder.renderAll();
+ });
+ });
+
+ this.renderedSources.add(wrapper + (fundingSource ?? ''));
+
+ if (typeof paypal !== 'undefined' && typeof paypal.Buttons !== 'undefined') {
+ widgetBuilder.registerButtons([wrapper, fundingSource], buttonsOptions());
+ widgetBuilder.renderButtons([wrapper, fundingSource]);
+ }
}
- isAlreadyRendered(wrapper, fundingSource, hasEnabledSeparateGateways) {
- // Simply check that has child nodes when we do not need to render buttons separately,
- // this will reduce the risk of breaking with different themes/plugins
- // and on the cart page (where we also do not need to render separately), which may fully reload this part of the page.
- // Ideally we should also find a way to detect such full reloads and remove the corresponding keys from the set.
- if (!hasEnabledSeparateGateways) {
- return document.querySelector(wrapper).hasChildNodes();
- }
- return this.renderedSources.has(wrapper + fundingSource ?? '');
- }
-
- hideButtons(element) {
- const domElement = document.querySelector(element);
- if (! domElement ) {
- return false;
- }
- domElement.style.display = 'none';
- return true;
- }
-
- showButtons(element) {
- const domElement = document.querySelector(element);
- if (! domElement ) {
- return false;
- }
- domElement.style.display = 'block';
- return true;
+ isAlreadyRendered(wrapper, fundingSource) {
+ return this.renderedSources.has(wrapper + (fundingSource ?? ''));
}
disableCreditCardFields() {
@@ -124,6 +136,52 @@ class Renderer {
enableCreditCardFields() {
this.creditCardRenderer.enableFields();
}
+
+ onButtonsInit(wrapper, handler, reset) {
+ this.onButtonsInitListeners[wrapper] = reset ? [] : (this.onButtonsInitListeners[wrapper] || []);
+ this.onButtonsInitListeners[wrapper].push(handler);
+ }
+
+ handleOnButtonsInit(wrapper, data, actions) {
+
+ this.buttonsOptions[wrapper] = {
+ data: data,
+ actions: actions
+ }
+
+ if (this.onButtonsInitListeners[wrapper]) {
+ for (let handler of this.onButtonsInitListeners[wrapper]) {
+ if (typeof handler === 'function') {
+ handler({
+ wrapper: wrapper,
+ ...this.buttonsOptions[wrapper]
+ });
+ }
+ }
+ }
+ }
+
+ disableSmartButtons(wrapper) {
+ if (!this.buttonsOptions[wrapper]) {
+ return;
+ }
+ try {
+ this.buttonsOptions[wrapper].actions.disable();
+ } catch (err) {
+ console.log('Failed to disable buttons: ' + err);
+ }
+ }
+
+ enableSmartButtons(wrapper) {
+ if (!this.buttonsOptions[wrapper]) {
+ return;
+ }
+ try {
+ this.buttonsOptions[wrapper].actions.enable();
+ } catch (err) {
+ console.log('Failed to enable buttons: ' + err);
+ }
+ }
}
export default Renderer;
diff --git a/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js
new file mode 100644
index 000000000..b094f6bdf
--- /dev/null
+++ b/modules/ppcp-button/resources/js/modules/Renderer/WidgetBuilder.js
@@ -0,0 +1,182 @@
+/**
+ * Handles the registration and rendering of PayPal widgets: Buttons and Messages.
+ * To have several Buttons per wrapper, an array should be provided, ex: [wrapper, fundingSource].
+ */
+class WidgetBuilder {
+
+ constructor() {
+ this.paypal = null;
+ this.buttons = new Map();
+ this.messages = new Map();
+
+ this.renderEventName = 'ppcp-render';
+
+ document.ppcpWidgetBuilderStatus = () => {
+ console.log({
+ buttons: this.buttons,
+ messages: this.messages,
+ });
+ }
+
+ jQuery(document)
+ .off(this.renderEventName)
+ .on(this.renderEventName, () => {
+ this.renderAll();
+ });
+ }
+
+ setPaypal(paypal) {
+ this.paypal = paypal;
+ jQuery(document).trigger('ppcp-paypal-loaded', paypal);
+ }
+
+ registerButtons(wrapper, options) {
+ wrapper = this.sanitizeWrapper(wrapper);
+
+ this.buttons.set(this.toKey(wrapper), {
+ wrapper: wrapper,
+ options: options,
+ });
+ }
+
+ renderButtons(wrapper) {
+ wrapper = this.sanitizeWrapper(wrapper);
+
+ if (!this.buttons.has(this.toKey(wrapper))) {
+ return;
+ }
+
+ if (this.hasRendered(wrapper)) {
+ return;
+ }
+
+ const entry = this.buttons.get(this.toKey(wrapper));
+ const btn = this.paypal.Buttons(entry.options);
+
+ if (!btn.isEligible()) {
+ this.buttons.delete(this.toKey(wrapper));
+ return;
+ }
+
+ let target = this.buildWrapperTarget(wrapper);
+
+ if (!target) {
+ return;
+ }
+
+ btn.render(target);
+ }
+
+ renderAllButtons() {
+ for (const [wrapper, entry] of this.buttons) {
+ this.renderButtons(wrapper);
+ }
+ }
+
+ registerMessages(wrapper, options) {
+ this.messages.set(wrapper, {
+ wrapper: wrapper,
+ options: options
+ });
+ }
+
+ renderMessages(wrapper) {
+ if (!this.messages.has(wrapper)) {
+ return;
+ }
+
+ const entry = this.messages.get(wrapper);
+
+ if (this.hasRendered(wrapper)) {
+ const element = document.querySelector(wrapper);
+ element.setAttribute('data-pp-amount', entry.options.amount);
+ return;
+ }
+
+ const btn = this.paypal.Messages(entry.options);
+
+ btn.render(entry.wrapper);
+
+ // watchdog to try to handle some strange cases where the wrapper may not be present
+ setTimeout(() => {
+ if (!this.hasRendered(wrapper)) {
+ btn.render(entry.wrapper);
+ }
+ }, 100);
+ }
+
+ renderAllMessages() {
+ for (const [wrapper, entry] of this.messages) {
+ this.renderMessages(wrapper);
+ }
+ }
+
+ renderAll() {
+ this.renderAllButtons();
+ this.renderAllMessages();
+ }
+
+ hasRendered(wrapper) {
+ let selector = wrapper;
+
+ if (Array.isArray(wrapper)) {
+ selector = wrapper[0];
+ for (const item of wrapper.slice(1)) {
+ selector += ' .item-' + item;
+ }
+ }
+
+ const element = document.querySelector(selector);
+ return element && element.hasChildNodes();
+ }
+
+ sanitizeWrapper(wrapper) {
+ if (Array.isArray(wrapper)) {
+ wrapper = wrapper.filter(item => !!item);
+ if (wrapper.length === 1) {
+ wrapper = wrapper[0];
+ }
+ }
+ return wrapper;
+ }
+
+ buildWrapperTarget(wrapper) {
+ let target = wrapper;
+
+ if (Array.isArray(wrapper)) {
+ const $wrapper = jQuery(wrapper[0]);
+
+ if (!$wrapper.length) {
+ return;
+ }
+
+ const itemClass = 'item-' + wrapper[1];
+
+ // Check if the parent element exists and it doesn't already have the div with the class
+ let $item = $wrapper.find('.' + itemClass);
+
+ if (!$item.length) {
+ $item = jQuery(`
`);
+ $wrapper.append($item);
+ }
+
+ target = $item.get(0);
+ }
+
+ if (!jQuery(target).length) {
+ return null;
+ }
+
+ return target;
+ }
+
+ toKey(wrapper) {
+ if (Array.isArray(wrapper)) {
+ return JSON.stringify(wrapper);
+ }
+ return wrapper;
+ }
+}
+
+window.widgetBuilder = window.widgetBuilder || new WidgetBuilder();
+export default window.widgetBuilder;
diff --git a/modules/ppcp-button/services.php b/modules/ppcp-button/services.php
index e3ff25cd5..92c3637e4 100644
--- a/modules/ppcp-button/services.php
+++ b/modules/ppcp-button/services.php
@@ -9,6 +9,14 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button;
+use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
+use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
+use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
+use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
+use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
+use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
+use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton;
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
@@ -61,24 +69,19 @@ return array(
return $dummy_ids[ $shop_country ] ?? $container->get( 'button.client_id' );
},
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
-
$state = $container->get( 'onboarding.state' );
- /**
- * The state.
- *
- * @var State $state
- */
if ( $state->current_state() !== State::STATE_ONBOARDED ) {
return new DisabledSmartButton();
}
+
$settings = $container->get( 'wcgateway.settings' );
$paypal_disabled = ! $settings->has( 'enabled' ) || ! $settings->get( 'enabled' );
if ( $paypal_disabled ) {
return new DisabledSmartButton();
}
+
$payer_factory = $container->get( 'api.factory.payer' );
$request_data = $container->get( 'button.request-data' );
-
$client_id = $container->get( 'button.client_id' );
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
$subscription_helper = $container->get( 'subscription.helper' );
@@ -104,6 +107,9 @@ return array(
$currency,
$container->get( 'wcgateway.all-funding-sources' ),
$container->get( 'button.basic-checkout-validation-enabled' ),
+ $container->get( 'button.early-wc-checkout-validation-enabled' ),
+ $container->get( 'button.pay-now-contexts' ),
+ $container->get( 'wcgateway.funding-sources-without-redirect' ),
$container->get( 'woocommerce.logger.woocommerce' ),
$container->get( 'wcgateway.paypal-locale' )
);
@@ -114,20 +120,34 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
+ 'button.pay-now-contexts' => static function ( ContainerInterface $container ): array {
+ return array( 'checkout', 'pay-now' );
+ },
'button.request-data' => static function ( ContainerInterface $container ): RequestData {
return new RequestData();
},
+ 'button.endpoint.simulate-cart' => static function ( ContainerInterface $container ): SimulateCartEndpoint {
+ if ( ! \WC()->cart ) {
+ throw new RuntimeException( 'cant initialize endpoint at this moment' );
+ }
+ $smart_button = $container->get( 'button.smart-button' );
+ $cart = WC()->cart;
+ $request_data = $container->get( 'button.request-data' );
+ $cart_products = $container->get( 'button.helper.cart-products' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $cart_products, $logger );
+ },
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
if ( ! \WC()->cart ) {
throw new RuntimeException( 'cant initialize endpoint at this moment' );
}
- $cart = WC()->cart;
- $shipping = WC()->shipping();
- $request_data = $container->get( 'button.request-data' );
+ $cart = WC()->cart;
+ $shipping = WC()->shipping();
+ $request_data = $container->get( 'button.request-data' );
$purchase_unit_factory = $container->get( 'api.factory.purchase-unit' );
- $data_store = \WC_Data_Store::load( 'product' );
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $data_store, $logger );
+ $cart_products = $container->get( 'button.helper.cart-products' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new ChangeCartEndpoint( $cart, $shipping, $request_data, $purchase_unit_factory, $cart_products, $logger );
},
'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
@@ -137,7 +157,7 @@ return array(
$session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' );
$early_order_handler = $container->get( 'button.helper.early-order-handler' );
- $registration_needed = $container->get( 'button.current-user-must-register' );
+ $registration_needed = $container->get( 'button.current-user-must-register' );
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new CreateOrderEndpoint(
$request_data,
@@ -151,6 +171,9 @@ return array(
$registration_needed,
$container->get( 'wcgateway.settings.card_billing_data_mode' ),
$container->get( 'button.early-wc-checkout-validation-enabled' ),
+ $container->get( 'button.pay-now-contexts' ),
+ $container->get( 'button.handle-shipping-in-paypal' ),
+ $container->get( 'wcgateway.funding-sources-without-redirect' ),
$logger
);
},
@@ -159,8 +182,7 @@ return array(
$state = $container->get( 'onboarding.state' );
$order_processor = $container->get( 'wcgateway.order-processor' );
$session_handler = $container->get( 'session.handler' );
- $prefix = $container->get( 'api.prefix' );
- return new EarlyOrderHandler( $state, $order_processor, $session_handler, $prefix );
+ return new EarlyOrderHandler( $state, $order_processor, $session_handler );
},
'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
@@ -182,6 +204,25 @@ return array(
$logger
);
},
+ 'button.endpoint.approve-subscription' => static function( ContainerInterface $container ): ApproveSubscriptionEndpoint {
+ return new ApproveSubscriptionEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'api.endpoint.order' ),
+ $container->get( 'session.handler' )
+ );
+ },
+ 'button.checkout-form-saver' => static function ( ContainerInterface $container ): CheckoutFormSaver {
+ return new CheckoutFormSaver(
+ $container->get( 'session.handler' )
+ );
+ },
+ 'button.endpoint.save-checkout-form' => static function ( ContainerInterface $container ): SaveCheckoutFormEndpoint {
+ return new SaveCheckoutFormEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'button.checkout-form-saver' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
'button.endpoint.data-client-id' => static function( ContainerInterface $container ) : DataClientIdEndpoint {
$request_data = $container->get( 'button.request-data' );
$identity_token = $container->get( 'api.endpoint.identity-token' );
@@ -199,6 +240,25 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
+ 'button.endpoint.validate-checkout' => static function ( ContainerInterface $container ): ValidateCheckoutEndpoint {
+ return new ValidateCheckoutEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'button.validation.wc-checkout-validator' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'button.endpoint.cart-script-params' => static function ( ContainerInterface $container ): CartScriptParamsEndpoint {
+ return new CartScriptParamsEndpoint(
+ $container->get( 'button.smart-button' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+
+ 'button.helper.cart-products' => static function ( ContainerInterface $container ): CartProductsHelper {
+ $data_store = \WC_Data_Store::load( 'product' );
+ return new CartProductsHelper( $data_store );
+ },
+
'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
return new ThreeDSecure( $logger );
@@ -235,4 +295,15 @@ return array(
*/
return (bool) apply_filters( 'woocommerce_paypal_payments_early_wc_checkout_validation_enabled', true );
},
+ 'button.validation.wc-checkout-validator' => static function ( ContainerInterface $container ): CheckoutFormValidator {
+ return new CheckoutFormValidator();
+ },
+
+ /**
+ * If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
+ * May result in slower popup performance, additional loading.
+ */
+ 'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool {
+ return false;
+ },
);
diff --git a/modules/ppcp-button/src/Assets/ButtonInterface.php b/modules/ppcp-button/src/Assets/ButtonInterface.php
new file mode 100644
index 000000000..ca42b2a4e
--- /dev/null
+++ b/modules/ppcp-button/src/Assets/ButtonInterface.php
@@ -0,0 +1,47 @@
+currency = $currency;
$this->all_funding_sources = $all_funding_sources;
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
+ $this->early_validation_enabled = $early_validation_enabled;
+ $this->pay_now_contexts = $pay_now_contexts;
+ $this->funding_sources_without_redirect = $funding_sources_without_redirect;
$this->logger = $logger;
$this->locale = $locale;
}
@@ -249,10 +287,6 @@ class SmartButton implements SmartButtonInterface {
$this->render_message_wrapper_registrar();
}
- if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
- return false;
- }
-
if (
$this->settings->has( 'dcc_enabled' )
&& $this->settings->get( 'dcc_enabled' )
@@ -279,7 +313,7 @@ class SmartButton implements SmartButtonInterface {
add_filter(
'woocommerce_credit_card_form_fields',
function ( array $default_fields, $id ) use ( $subscription_helper ) : array {
- if ( is_user_logged_in() && $this->settings->has( 'vault_enabled' ) && $this->settings->get( 'vault_enabled' ) && CreditCardGateway::ID === $id ) {
+ if ( is_user_logged_in() && $this->settings->has( 'vault_enabled_dcc' ) && $this->settings->get( 'vault_enabled_dcc' ) && CreditCardGateway::ID === $id ) {
$default_fields['card-vault'] = sprintf(
' %s
',
@@ -350,6 +384,8 @@ class SmartButton implements SmartButtonInterface {
);
}
+ $this->sanitize_woocommerce_filters();
+
return true;
}
@@ -357,54 +393,48 @@ class SmartButton implements SmartButtonInterface {
* Registers the hooks to render the credit messaging HTML depending on the settings.
*
* @return bool
- * @throws NotFoundException When a setting was not found.
*/
private function render_message_wrapper_registrar(): bool {
if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) {
return false;
}
- $selected_locations = $this->settings->has( 'pay_later_messaging_locations' ) ? $this->settings->get( 'pay_later_messaging_locations' ) : array();
+ $location = $this->location();
- $not_enabled_on_cart = ! in_array( 'cart', $selected_locations, true );
+ if ( ! $this->settings_status->is_pay_later_messaging_enabled_for_location( $location ) ) {
+ return false;
+ }
+
+ $get_hook = function ( string $location ): ?array {
+ switch ( $location ) {
+ case 'checkout':
+ return $this->messages_renderer_hook( $location, 'woocommerce_review_order_before_payment', 10 );
+ case 'cart':
+ return $this->messages_renderer_hook( $location, $this->proceed_to_checkout_button_renderer_hook(), 19 );
+ case 'pay-now':
+ return $this->messages_renderer_hook( 'pay_order', 'woocommerce_pay_order_before_submit', 10 );
+ case 'product':
+ return $this->messages_renderer_hook( $location, $this->single_product_renderer_hook(), 30 );
+ case 'shop':
+ return $this->messages_renderer_hook( $location, 'woocommerce_archive_description', 10 );
+ case 'home':
+ return $this->messages_renderer_hook( $location, 'loop_start', 20 );
+ default:
+ return null;
+ }
+ };
+
+ $hook = $get_hook( $location );
+ if ( ! $hook ) {
+ return false;
+ }
add_action(
- $this->proceed_to_checkout_button_renderer_hook(),
- function() use ( $not_enabled_on_cart ) {
- if ( ! is_cart() || $not_enabled_on_cart ) {
- return;
- }
- $this->message_renderer();
- },
- 19
+ $hook['name'],
+ array( $this, 'message_renderer' ),
+ $hook['priority']
);
- $not_enabled_on_product_page = ! in_array( 'product', $selected_locations, true );
- if (
- ( is_product() || wc_post_content_has_shortcode( 'product_page' ) )
- && ! $not_enabled_on_product_page
- && ! is_checkout()
- ) {
- add_action(
- $this->single_product_renderer_hook(),
- array( $this, 'message_renderer' ),
- 30
- );
- }
-
- $not_enabled_on_checkout = ! in_array( 'checkout', $selected_locations, true );
- if ( ! $not_enabled_on_checkout ) {
- add_action(
- $this->checkout_dcc_button_renderer_hook(),
- array( $this, 'message_renderer' ),
- 11
- );
- add_action(
- $this->pay_order_renderer_hook(),
- array( $this, 'message_renderer' ),
- 11
- );
- }
return true;
}
@@ -437,7 +467,7 @@ class SmartButton implements SmartButtonInterface {
return;
}
- $this->button_renderer( PayPalGateway::ID );
+ $this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_single_product_button_render' );
},
31
);
@@ -450,18 +480,14 @@ class SmartButton implements SmartButtonInterface {
add_action(
$this->mini_cart_button_renderer_hook(),
function () {
- if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
- return;
- }
-
if ( $this->is_cart_price_total_zero() || $this->is_free_trial_cart() ) {
return;
}
- echo '
';
+ echo '';
+ echo ' ';
+ do_action( 'woocommerce_paypal_payments_minicart_button_render' );
+ echo '
';
},
30
);
@@ -473,14 +499,15 @@ class SmartButton implements SmartButtonInterface {
add_action(
$this->pay_order_renderer_hook(),
function (): void {
- $this->button_renderer( PayPalGateway::ID );
+ $this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_payorder_button_render' );
$this->button_renderer( CardButtonGateway::ID );
- }
+ },
+ 20
);
add_action(
$this->checkout_button_renderer_hook(),
function (): void {
- $this->button_renderer( PayPalGateway::ID );
+ $this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_checkout_button_render' );
$this->button_renderer( CardButtonGateway::ID );
}
);
@@ -493,7 +520,7 @@ class SmartButton implements SmartButtonInterface {
return;
}
- $this->button_renderer( PayPalGateway::ID );
+ $this->button_renderer( PayPalGateway::ID, 'woocommerce_paypal_payments_cart_button_render' );
},
20
);
@@ -503,70 +530,129 @@ class SmartButton implements SmartButtonInterface {
}
/**
- * Enqueues the script.
- *
- * @return bool
- * @throws NotFoundException When a setting was not found.
+ * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
*/
- public function enqueue(): bool {
- $buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
- if ( ! is_checkout() && ! $buttons_enabled ) {
+ public function should_load_ppcp_script(): bool {
+ $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
+ if ( ! $pcp_gateway_enabled ) {
return false;
}
- $load_script = false;
- if ( is_checkout() && $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) {
- $load_script = true;
- }
- if ( $this->load_button_component() ) {
- $load_script = true;
+ if ( in_array( $this->context(), array( 'checkout-block', 'cart-block' ), true ) ) {
+ return false;
}
- if ( in_array( $this->context(), array( 'pay-now', 'checkout' ), true ) ) {
+ return $this->should_load_buttons() || $this->should_load_messages() || $this->can_render_dcc();
+ }
+
+ /**
+ * Determines whether the button component should be loaded.
+ */
+ public function should_load_buttons() : bool {
+ $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
+ if ( ! $pcp_gateway_enabled ) {
+ return false;
+ }
+
+ $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() );
+ $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
+
+ switch ( $this->context() ) {
+ case 'checkout':
+ case 'cart':
+ case 'pay-now':
+ case 'checkout-block':
+ case 'cart-block':
+ return $smart_button_enabled_for_current_location;
+ case 'product':
+ return $smart_button_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
+ default:
+ return $smart_button_enabled_for_mini_cart;
+ }
+ }
+
+ /**
+ * Determines whether the Pay Later messages component should be loaded.
+ */
+ public function should_load_messages() : bool {
+ $pcp_gateway_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
+ if ( ! $pcp_gateway_enabled ) {
+ return false;
+ }
+
+ if ( ! $this->messages_apply->for_country() || $this->is_free_trial_cart() ) {
+ return false;
+ }
+
+ $location = $this->location();
+
+ $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $location );
+
+ switch ( $location ) {
+ case 'checkout':
+ case 'cart':
+ case 'pay-now':
+ case 'product':
+ case 'shop':
+ case 'home':
+ return $messaging_enabled_for_current_location;
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * Whether DCC fields can be rendered.
+ */
+ public function can_render_dcc() : bool {
+ return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' )
+ && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' )
+ && $this->dcc_applies->for_country_currency()
+ && in_array( $this->context(), array( 'checkout', 'pay-now' ), true );
+ }
+
+ /**
+ * Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
+ */
+ public function enqueue(): void {
+ if ( $this->can_render_dcc() ) {
wp_enqueue_style(
- 'gateway',
- untrailingslashit( $this->module_url ) . '/assets/css/gateway.css',
+ 'ppcp-hosted-fields',
+ untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css',
array(),
$this->version
);
-
- if ( $this->can_render_dcc() ) {
- wp_enqueue_style(
- 'ppcp-hosted-fields',
- untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css',
- array(),
- $this->version
- );
- }
}
- if ( $load_script ) {
- wp_enqueue_script(
- 'ppcp-smart-button',
- untrailingslashit( $this->module_url ) . '/assets/js/button.js',
- array( 'jquery' ),
- $this->version,
- true
- );
- wp_localize_script(
- 'ppcp-smart-button',
- 'PayPalCommerceGateway',
- $this->localize_script()
- );
- }
- return true;
+ wp_enqueue_style(
+ 'gateway',
+ untrailingslashit( $this->module_url ) . '/assets/css/gateway.css',
+ array(),
+ $this->version
+ );
+
+ wp_enqueue_script(
+ 'ppcp-smart-button',
+ untrailingslashit( $this->module_url ) . '/assets/js/button.js',
+ array( 'jquery' ),
+ $this->version,
+ true
+ );
+
+ wp_localize_script(
+ 'ppcp-smart-button',
+ 'PayPalCommerceGateway',
+ $this->script_data()
+ );
}
/**
* Renders the HTML for the buttons.
*
- * @param string $gateway_id The gateway ID, like 'ppcp-gateway'.
+ * @param string $gateway_id The gateway ID, like 'ppcp-gateway'.
+ * @param string|null $action_name The action name to be called.
*/
- public function button_renderer( string $gateway_id ) {
-
- if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
- return;
- }
+ public function button_renderer( string $gateway_id, string $action_name = null ) {
$available_gateways = WC()->payment_gateways->get_available_payment_gateways();
@@ -576,21 +662,43 @@ class SmartButton implements SmartButtonInterface {
// The wrapper is needed for the loading spinner,
// otherwise jQuery block() prevents buttons rendering.
- echo '';
+ echo '';
}
/**
* Renders the HTML for the credit messaging.
*/
public function message_renderer() {
- if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
- return false;
- }
$product = wc_get_product();
+ $location = $this->location();
+
if (
- ! is_checkout() && is_a( $product, WC_Product::class )
+ $location === 'product' && is_a( $product, WC_Product::class )
/**
* The filter returning true if PayPal buttons can be rendered, or false otherwise.
*/
@@ -606,24 +714,47 @@ class SmartButton implements SmartButtonInterface {
* The values for the credit messaging.
*
* @return array
- * @throws NotFoundException When a setting was not found.
*/
private function message_values(): array {
if ( ! $this->settings_status->is_pay_later_messaging_enabled() ) {
return array();
}
- $placement = is_checkout() ? 'payment' : ( is_cart() ? 'cart' : 'product' );
- $product = wc_get_product();
- $amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0;
+ $location = $this->location();
+
+ switch ( $location ) {
+ case 'checkout':
+ case 'checkout-block':
+ case 'pay-now':
+ $placement = 'payment';
+ break;
+ case 'cart':
+ case 'cart-block':
+ $placement = 'cart';
+ break;
+ case 'product':
+ $placement = 'product';
+ break;
+ case 'shop':
+ $placement = 'product-list';
+ break;
+ case 'home':
+ $placement = 'home';
+ break;
+ default:
+ $placement = 'payment';
+ break;
+ }
+
+ $product = wc_get_product();
+ $amount = ( is_a( $product, WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0;
if ( is_checkout() || is_cart() ) {
$amount = WC()->cart->get_total( 'raw' );
}
$styling_per_location = $this->settings->has( 'pay_later_enable_styling_per_messaging_location' ) && $this->settings->get( 'pay_later_enable_styling_per_messaging_location' );
- $per_location = is_checkout() ? 'checkout' : ( is_cart() ? 'cart' : 'product' );
- $location = $styling_per_location ? $per_location : 'general';
+ $location = $styling_per_location ? $location : 'general';
$setting_name_prefix = "pay_later_{$location}_message";
$layout = $this->settings->has( "{$setting_name_prefix}_layout" ) ? $this->settings->get( "{$setting_name_prefix}_layout" ) : 'text';
@@ -635,6 +766,7 @@ class SmartButton implements SmartButtonInterface {
return array(
'wrapper' => '#ppcp-messages',
+ 'is_hidden' => ! $this->is_pay_later_filter_enabled_for_location( $this->context() ),
'amount' => $amount,
'placement' => $placement,
'style' => array(
@@ -653,18 +785,6 @@ class SmartButton implements SmartButtonInterface {
}
- /**
- * Whether DCC fields can be rendered.
- *
- * @return bool
- * @throws NotFoundException When a setting was not found.
- */
- private function can_render_dcc() : bool {
-
- $can_render = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) && $this->dcc_applies->for_country_currency();
- return $can_render;
- }
-
/**
* Renders the HTML for the DCC fields.
*/
@@ -697,7 +817,6 @@ class SmartButton implements SmartButtonInterface {
* Whether we can store vault tokens or not.
*
* @return bool
- * @throws NotFoundException If a setting hasn't been found.
*/
public function can_save_vault_token(): bool {
@@ -731,6 +850,21 @@ class SmartButton implements SmartButtonInterface {
return $this->subscription_helper->cart_contains_subscription();
}
+ /**
+ * Whether PayPal subscriptions is enabled or not.
+ *
+ * @return bool
+ */
+ private function paypal_subscriptions_enabled(): bool {
+ try {
+ $subscriptions_mode = $this->settings->get( 'subscriptions_mode' );
+ } catch ( NotFoundException $exception ) {
+ return false;
+ }
+
+ return $subscriptions_mode === 'subscriptions_api';
+ }
+
/**
* Retrieves the 3D Secure contingency settings.
*
@@ -748,58 +882,85 @@ class SmartButton implements SmartButtonInterface {
}
/**
- * The localized data for the smart button.
+ * The configuration for the smart buttons.
*
* @return array
- * @throws NotFoundException If a setting hasn't been found.
*/
- private function localize_script(): array {
- global $wp;
-
+ public function script_data(): array {
$is_free_trial_cart = $this->is_free_trial_cart();
+ $url_params = $this->url_params();
+
$this->request_data->enqueue_nonce_fix();
$localize = array(
- 'script_attributes' => $this->attributes(),
- 'data_client_id' => array(
- 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
- 'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
- 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
- 'user' => get_current_user_id(),
- 'has_subscriptions' => $this->has_subscriptions(),
+ 'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ),
+ 'url_params' => $url_params,
+ 'script_attributes' => $this->attributes(),
+ 'client_id' => $this->client_id,
+ 'currency' => $this->currency,
+ 'data_client_id' => array(
+ 'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
+ 'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
+ 'user' => get_current_user_id(),
+ 'has_subscriptions' => $this->has_subscriptions(),
+ 'paypal_subscriptions_enabled' => $this->paypal_subscriptions_enabled(),
),
- 'redirect' => wc_get_checkout_url(),
- 'context' => $this->context(),
- 'ajax' => array(
- 'change_cart' => array(
+ 'redirect' => wc_get_checkout_url(),
+ 'context' => $this->context(),
+ 'ajax' => array(
+ 'simulate_cart' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( SimulateCartEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( SimulateCartEndpoint::nonce() ),
+ ),
+ 'change_cart' => array(
'endpoint' => \WC_AJAX::get_endpoint( ChangeCartEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ChangeCartEndpoint::nonce() ),
),
- 'create_order' => array(
+ 'create_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( CreateOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( CreateOrderEndpoint::nonce() ),
),
- 'approve_order' => array(
+ 'approve_order' => array(
'endpoint' => \WC_AJAX::get_endpoint( ApproveOrderEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
),
- 'vault_paypal' => array(
+ 'approve_subscription' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( ApproveSubscriptionEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( ApproveSubscriptionEndpoint::nonce() ),
+ ),
+ 'vault_paypal' => array(
'endpoint' => \WC_AJAX::get_endpoint( StartPayPalVaultingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( StartPayPalVaultingEndpoint::nonce() ),
),
+ 'save_checkout_form' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( SaveCheckoutFormEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( SaveCheckoutFormEndpoint::nonce() ),
+ ),
+ 'validate_checkout' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( ValidateCheckoutEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( ValidateCheckoutEndpoint::nonce() ),
+ ),
+ 'cart_script_params' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
+ ),
),
- 'enforce_vault' => $this->has_subscriptions(),
- 'can_save_vault_token' => $this->can_save_vault_token(),
- 'is_free_trial_cart' => $is_free_trial_cart,
- 'vaulted_paypal_email' => ( is_checkout() && $is_free_trial_cart ) ? $this->get_vaulted_paypal_email() : '',
- 'bn_codes' => $this->bn_codes(),
- 'payer' => $this->payerData(),
- 'button' => array(
- 'wrapper' => '#ppc-button-' . PayPalGateway::ID,
- 'mini_cart_wrapper' => '#ppc-button-minicart',
- 'cancel_wrapper' => '#ppcp-cancel',
- 'url' => $this->url(),
- 'mini_cart_style' => array(
+ 'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
+ 'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
+ 'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),
+ 'enforce_vault' => $this->has_subscriptions(),
+ 'can_save_vault_token' => $this->can_save_vault_token(),
+ 'is_free_trial_cart' => $is_free_trial_cart,
+ 'vaulted_paypal_email' => ( is_checkout() && $is_free_trial_cart ) ? $this->get_vaulted_paypal_email() : '',
+ 'bn_codes' => $this->bn_codes(),
+ 'payer' => $this->payerData(),
+ 'button' => array(
+ 'wrapper' => '#ppc-button-' . PayPalGateway::ID,
+ 'is_disabled' => $this->is_button_disabled(),
+ 'mini_cart_wrapper' => '#ppc-button-minicart',
+ 'is_mini_cart_disabled' => $this->is_button_disabled( 'mini-cart' ),
+ 'cancel_wrapper' => '#ppcp-cancel',
+ 'mini_cart_style' => array(
'layout' => $this->style_for_context( 'layout', 'mini-cart' ),
'color' => $this->style_for_context( 'color', 'mini-cart' ),
'shape' => $this->style_for_context( 'shape', 'mini-cart' ),
@@ -807,7 +968,7 @@ class SmartButton implements SmartButtonInterface {
'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ),
'height' => $this->settings->has( 'button_mini-cart_height' ) && $this->settings->get( 'button_mini-cart_height' ) ? $this->normalize_height( (int) $this->settings->get( 'button_mini-cart_height' ) ) : 35,
),
- 'style' => array(
+ 'style' => array(
'layout' => $this->style_for_context( 'layout', $this->context() ),
'color' => $this->style_for_context( 'color', $this->context() ),
'shape' => $this->style_for_context( 'shape', $this->context() ),
@@ -815,7 +976,7 @@ class SmartButton implements SmartButtonInterface {
'tagline' => $this->style_for_context( 'tagline', $this->context() ),
),
),
- 'separate_buttons' => array(
+ 'separate_buttons' => array(
'card' => array(
'id' => CardButtonGateway::ID,
'wrapper' => '#ppc-button-' . CardButtonGateway::ID,
@@ -825,12 +986,16 @@ class SmartButton implements SmartButtonInterface {
),
),
),
- 'hosted_fields' => array(
+ 'hosted_fields' => array(
'wrapper' => '#ppcp-hosted-fields',
'labels' => array(
'credit_card_number' => '',
'cvv' => '',
'mm_yy' => __( 'MM/YY', 'woocommerce-paypal-payments' ),
+ 'fields_empty' => __(
+ 'Card payment details are missing. Please fill in all required fields.',
+ 'woocommerce-paypal-payments'
+ ),
'fields_not_valid' => __(
'Unfortunately, your credit card details are not valid.',
'woocommerce-paypal-payments'
@@ -844,8 +1009,8 @@ class SmartButton implements SmartButtonInterface {
'valid_cards' => $this->dcc_applies->valid_cards(),
'contingency' => $this->get_3ds_contingency(),
),
- 'messages' => $this->message_values(),
- 'labels' => array(
+ 'messages' => $this->message_values(),
+ 'labels' => array(
'error' => array(
'generic' => __(
'Something went wrong. Please try again or choose another payment source.',
@@ -872,12 +1037,22 @@ class SmartButton implements SmartButtonInterface {
// phpcs:ignore WordPress.WP.I18n
'shipping_field' => _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ),
),
- 'order_id' => 'pay-now' === $this->context() ? absint( $wp->query_vars['order-pay'] ) : 0,
- 'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
- 'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
- 'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
+ 'simulate_cart' => array(
+ 'enabled' => apply_filters( 'woocommerce_paypal_payments_simulate_cart_enabled', true ),
+ 'throttling' => apply_filters( 'woocommerce_paypal_payments_simulate_cart_throttling', 5000 ),
+ ),
+ 'order_id' => 'pay-now' === $this->context() ? $this->get_order_pay_id() : 0,
+ 'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
+ 'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
+ 'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
+ 'early_checkout_validation_enabled' => $this->early_validation_enabled,
+ 'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
);
+ if ( 'pay-now' === $this->context() ) {
+ $localize['pay_now'] = $this->pay_now_script_data();
+ }
+
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
$localize['button']['mini_cart_style']['tagline'] = false;
}
@@ -885,10 +1060,45 @@ class SmartButton implements SmartButtonInterface {
$localize['button']['style']['tagline'] = false;
}
+ if ( $this->is_paypal_continuation() ) {
+ $order = $this->session_handler->order();
+ assert( $order !== null );
+
+ $localize['continuation'] = array(
+ 'order_id' => $order->id(),
+ );
+ }
+
$this->request_data->dequeue_nonce_fix();
return $localize;
}
+ /**
+ * Returns pay-now payment data.
+ *
+ * @return array
+ */
+ private function pay_now_script_data(): array {
+ $order_id = $this->get_order_pay_id();
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'] ?? '';
+ $currency_code = get_woocommerce_currency();
+
+ $wc_order = wc_get_order( $order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return array();
+ }
+
+ $total = (float) $wc_order->get_total( 'numeric' );
+
+ return array(
+ 'total' => $total,
+ 'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
+ 'currency_code' => $currency_code,
+ 'country_code' => $shop_country_code,
+ );
+ }
+
/**
* If we can find the payer data for a current customer, we will return it.
*
@@ -904,34 +1114,50 @@ class SmartButton implements SmartButtonInterface {
}
/**
- * The JavaScript SDK url to load.
+ * The JavaScript SDK url parameters.
*
- * @return string
- * @throws NotFoundException If a setting was not found.
+ * @return array
*/
- private function url(): string {
- $intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture';
- $product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent;
- $other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent;
+ private function url_params(): array {
+ $context = $this->context();
+ try {
+ $intent = $this->intent();
+ } catch ( NotFoundException $exception ) {
+ $intent = 'capture';
+ }
- $params = array(
+ $subscription_mode = $this->settings->has( 'subscriptions_mode' ) ? $this->settings->get( 'subscriptions_mode' ) : '';
+ $params = array(
'client-id' => $this->client_id,
'currency' => $this->currency,
'integration-date' => PAYPAL_INTEGRATION_DATE,
'components' => implode( ',', $this->components() ),
- 'vault' => $this->can_save_vault_token() ? 'true' : 'false',
- 'commit' => is_checkout() ? 'true' : 'false',
- 'intent' => $this->context() === 'product' ? $product_intent : $other_context_intent,
+ 'vault' => ( $this->can_save_vault_token() || $this->subscription_helper->need_subscription_intent( $subscription_mode ) ) ? 'true' : 'false',
+ 'commit' => in_array( $context, $this->pay_now_contexts, true ) ? 'true' : 'false',
+ 'intent' => $intent,
);
if (
$this->environment->current_environment_is( Environment::SANDBOX )
- && defined( 'WP_DEBUG' ) && \WP_DEBUG && is_user_logged_in()
+ && defined( 'WP_DEBUG' ) && \WP_DEBUG
&& WC()->customer instanceof \WC_Customer && WC()->customer->get_billing_country()
&& 2 === strlen( WC()->customer->get_billing_country() )
) {
$params['buyer-country'] = WC()->customer->get_billing_country();
}
+ if ( 'pay-now' === $this->context() ) {
+ $wc_order_id = $this->get_order_pay_id();
+ if ( $wc_order_id ) {
+ $wc_order = wc_get_order( $wc_order_id );
+ if ( $wc_order instanceof WC_Order ) {
+ $currency = $wc_order->get_currency();
+ if ( $currency ) {
+ $params['currency'] = $currency;
+ }
+ }
+ }
+ }
+
$disable_funding = $this->settings->has( 'disable_funding' )
? $this->settings->get( 'disable_funding' )
: array();
@@ -952,6 +1178,13 @@ class SmartButton implements SmartButtonInterface {
}
}
+ if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) {
+ $disable_funding = array_diff(
+ array_keys( $this->all_funding_sources ),
+ array( 'venmo', 'paylater' )
+ );
+ }
+
if ( $this->is_free_trial_cart() ) {
$all_sources = array_keys( $this->all_funding_sources );
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
@@ -960,19 +1193,18 @@ class SmartButton implements SmartButtonInterface {
$disable_funding = $all_sources;
}
- if ( ! $this->settings_status->is_pay_later_button_enabled_for_context( $this->context() ) ) {
- $disable_funding[] = 'credit';
+ $enable_funding = array( 'venmo' );
+
+ if ( $this->is_pay_later_button_enabled_for_location( $context ) ) {
+ $enable_funding[] = 'paylater';
+ } else {
+ $disable_funding[] = 'paylater';
}
if ( count( $disable_funding ) > 0 ) {
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
}
- $enable_funding = array( 'venmo' );
- if ( $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() ) || ! in_array( 'credit', $disable_funding, true ) ) {
- $enable_funding[] = 'paylater';
- }
-
if ( $this->is_free_trial_cart() ) {
$enable_funding = array();
}
@@ -985,8 +1217,7 @@ class SmartButton implements SmartButtonInterface {
$params['locale'] = $this->locale;
}
- $smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' );
- return $smart_button_url;
+ return $params;
}
/**
@@ -1036,93 +1267,30 @@ class SmartButton implements SmartButtonInterface {
private function components(): array {
$components = array();
- if ( $this->load_button_component() ) {
+ if ( $this->should_load_buttons() ) {
$components[] = 'buttons';
$components[] = 'funding-eligibility';
}
- if (
- $this->messages_apply->for_country()
- && ! $this->is_free_trial_cart()
- ) {
+ if ( $this->should_load_messages() ) {
$components[] = 'messages';
}
if ( $this->dcc_is_enabled() ) {
$components[] = 'hosted-fields';
}
- return $components;
- }
-
- /**
- * Determines whether the button component should be loaded.
- *
- * @return bool
- * @throws NotFoundException If a setting has not been found.
- */
- private function load_button_component() : bool {
- $smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() );
- $smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
- $messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() );
-
- switch ( $this->context() ) {
- case 'checkout':
- case 'cart':
- return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location;
- case 'product':
- return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
- case 'pay-now':
- return true;
- default:
- return $smart_button_enabled_for_mini_cart;
- }
- }
-
- /**
- * The current context.
- *
- * @return string
- */
- private function context(): string {
- $context = 'mini-cart';
- if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
- $context = 'product';
- }
- if ( is_cart() ) {
- $context = 'cart';
- }
- if ( is_checkout() && ! $this->is_paypal_continuation() ) {
- $context = 'checkout';
- }
- if ( is_checkout_pay_page() ) {
- $context = 'pay-now';
- }
- return $context;
- }
-
- /**
- * Checks if PayPal payment was already initiated (on the product or cart pages).
- *
- * @return bool
- */
- private function is_paypal_continuation(): bool {
- $order = $this->session_handler->order();
- if ( ! $order ) {
- return false;
- }
- $source = $order->payment_source();
- if ( $source && $source->card() ) {
- return false; // Ignore for DCC.
- }
- if ( 'card' === $this->session_handler->funding_source() ) {
- return false; // Ignore for card buttons.
- }
- return true;
+ /**
+ * Filter to add further components from the extensions.
+ *
+ * @internal Matches filter name in APM extension.
+ *
+ * @param array $components The array of components already registered.
+ */
+ return apply_filters( 'woocommerce_paypal_payments_sdk_components_hook', $components );
}
/**
* Whether DCC is enabled or not.
*
* @return bool
- * @throws NotFoundException If a setting has not been found.
*/
private function dcc_is_enabled(): bool {
if ( ! is_checkout() ) {
@@ -1151,9 +1319,11 @@ class SmartButton implements SmartButtonInterface {
* @param string $context The context.
*
* @return string
- * @throws NotFoundException When a setting hasn't been found.
*/
private function style_for_context( string $style, string $context ): string {
+ // Use the cart/checkout styles for blocks.
+ $context = str_replace( '-block', '', $context );
+
$defaults = array(
'layout' => 'vertical',
'size' => 'responsive',
@@ -1234,6 +1404,35 @@ class SmartButton implements SmartButtonInterface {
return (string) apply_filters( 'woocommerce_paypal_payments_pay_order_dcc_renderer_hook', 'woocommerce_pay_order_after_submit' );
}
+ /**
+ * Returns the action name that will be used for rendering Pay Later messages.
+ *
+ * @param string $location The location name like 'checkout', 'shop'. See render_message_wrapper_registrar.
+ * @param string $default_hook The default name of the hook.
+ * @param int $default_priority The default priority of the hook.
+ * @return array An array with 'name' and 'priority' keys.
+ */
+ private function messages_renderer_hook( string $location, string $default_hook, int $default_priority ): array {
+ /**
+ * The filter returning the action name that will be used for rendering Pay Later messages.
+ */
+ $hook = (string) apply_filters(
+ "woocommerce_paypal_payments_${location}_messages_renderer_hook",
+ $default_hook
+ );
+ /**
+ * The filter returning the action priority that will be used for rendering Pay Later messages.
+ */
+ $priority = (int) apply_filters(
+ "woocommerce_paypal_payments_${location}_messages_renderer_priority",
+ $default_priority
+ );
+ return array(
+ 'name' => $hook,
+ 'priority' => $priority,
+ );
+ }
+
/**
* Returns action name that PayPal button will use for rendering next to Proceed to checkout button (normally displayed in cart).
*
@@ -1321,6 +1520,114 @@ class SmartButton implements SmartButtonInterface {
);
}
+ /**
+ * Fills and returns the product context_data array to be used in filters.
+ *
+ * @param array $context_data The context data for this filter.
+ * @return array
+ */
+ private function product_filter_context_data( array $context_data = array() ): array {
+ if ( ! isset( $context_data['product'] ) ) {
+ $context_data['product'] = wc_get_product();
+ }
+ if ( ! $context_data['product'] ) {
+ return array();
+ }
+ if ( ! isset( $context_data['order_total'] ) && ( $context_data['product'] instanceof WC_Product ) ) {
+ $context_data['order_total'] = (float) $context_data['product']->get_price( 'raw' );
+ }
+
+ return $context_data;
+ }
+
+ /**
+ * Checks if PayPal buttons/messages should be rendered for the current page.
+ *
+ * @param string|null $context The context that should be checked, use default otherwise.
+ * @param array $context_data The context data for this filter.
+ * @return bool
+ */
+ public function is_button_disabled( string $context = null, array $context_data = array() ): bool {
+ if ( null === $context ) {
+ $context = $this->context();
+ }
+
+ if ( 'product' === $context ) {
+ /**
+ * Allows to decide if the button should be disabled for a given product.
+ */
+ return apply_filters(
+ 'woocommerce_paypal_payments_product_buttons_disabled',
+ false,
+ $this->product_filter_context_data( $context_data )
+ );
+ }
+
+ /**
+ * Allows to decide if the button should be disabled globally or on a given context.
+ */
+ return apply_filters(
+ 'woocommerce_paypal_payments_buttons_disabled',
+ false,
+ $context
+ );
+ }
+
+ /**
+ * Checks a filter if pay_later/messages should be rendered on a given location / context.
+ *
+ * @param string $location The location.
+ * @param array $context_data The context data for this filter.
+ * @return bool
+ */
+ private function is_pay_later_filter_enabled_for_location( string $location, array $context_data = array() ): bool {
+
+ if ( 'product' === $location ) {
+ /**
+ * Allows to decide if the button should be disabled for a given product.
+ */
+ return ! apply_filters(
+ 'woocommerce_paypal_payments_product_buttons_paylater_disabled',
+ false,
+ $this->product_filter_context_data( $context_data )
+ );
+ }
+
+ /**
+ * Allows to decide if the button should be disabled on a given context.
+ */
+ return ! apply_filters(
+ 'woocommerce_paypal_payments_buttons_paylater_disabled',
+ false,
+ $location
+ );
+ }
+
+ /**
+ * Check whether Pay Later button is enabled for a given location.
+ *
+ * @param string $location The location.
+ * @param array $context_data The context data for this filter.
+ * @return bool true if is enabled, otherwise false.
+ */
+ public function is_pay_later_button_enabled_for_location( string $location, array $context_data = array() ): bool {
+ return $this->is_pay_later_filter_enabled_for_location( $location, $context_data )
+ && $this->settings_status->is_pay_later_button_enabled_for_location( $location );
+
+ }
+
+ /**
+ * Check whether Pay Later message is enabled for a given location.
+ *
+ * @param string $location The location setting name.
+ * @param array $context_data The context data for this filter.
+ * @return bool true if is enabled, otherwise false.
+ */
+ public function is_pay_later_messaging_enabled_for_location( string $location, array $context_data = array() ): bool {
+ return $this->is_pay_later_filter_enabled_for_location( $location, $context_data )
+ && $this->settings_status->is_pay_later_messaging_enabled_for_location( $location );
+ }
+
/**
* Retrieves all payment tokens for the user, via API or cached if already queried.
*
@@ -1369,4 +1676,66 @@ class SmartButton implements SmartButtonInterface {
return false;
}
+
+ /**
+ * Returns the intent.
+ *
+ * @return string
+ * @throws NotFoundException If intent is not found.
+ */
+ private function intent(): string {
+ $subscription_mode = $this->settings->has( 'subscriptions_mode' ) ? $this->settings->get( 'subscriptions_mode' ) : '';
+ if ( $this->subscription_helper->need_subscription_intent( $subscription_mode ) ) {
+ return 'subscription';
+ }
+
+ $intent = $this->settings->has( 'intent' ) ? $this->settings->get( 'intent' ) : 'capture';
+
+ return strtolower( apply_filters( 'woocommerce_paypal_payments_order_intent', $intent ) );
+ }
+
+ /**
+ * Returns the ID of WC order on the order-pay page, or 0.
+ *
+ * @return int
+ */
+ protected function get_order_pay_id(): int {
+ global $wp;
+
+ if ( ! isset( $wp->query_vars['order-pay'] ) ) {
+ return 0;
+ }
+
+ return absint( $wp->query_vars['order-pay'] );
+ }
+
+ /**
+ * Sanitize woocommerce filter on unexpected states.
+ *
+ * @return void
+ */
+ private function sanitize_woocommerce_filters(): void {
+
+ add_filter(
+ 'woocommerce_widget_cart_is_hidden',
+ /**
+ * Sometimes external plugins like "woocommerce-one-page-checkout" set the $value to null, handle that case here.
+ * Here we also disable the mini-cart on cart-block and checkout-block pages where our buttons aren't supported yet.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $value ) {
+ if ( null === $value ) {
+ if ( is_product() ) {
+ return false;
+ }
+ return in_array( $this->context(), array( 'cart', 'checkout', 'cart-block', 'checkout-block' ), true );
+ }
+ return in_array( $this->context(), array( 'cart-block', 'checkout-block' ), true ) ? true : $value;
+ },
+ 11
+ );
+
+ }
+
}
diff --git a/modules/ppcp-button/src/Assets/SmartButtonInterface.php b/modules/ppcp-button/src/Assets/SmartButtonInterface.php
index a574bc649..3bd6ddb32 100644
--- a/modules/ppcp-button/src/Assets/SmartButtonInterface.php
+++ b/modules/ppcp-button/src/Assets/SmartButtonInterface.php
@@ -22,16 +22,19 @@ interface SmartButtonInterface {
public function render_wrapper(): bool;
/**
- * Enqueues the necessary scripts.
- *
- * @return bool
+ * Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
*/
- public function enqueue(): bool;
+ public function should_load_ppcp_script(): bool;
/**
- * Whether the running installation could save vault tokens or not.
- *
- * @return bool
+ * Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
*/
- public function can_save_vault_token(): bool;
+ public function enqueue(): void;
+
+ /**
+ * The configuration for the smart buttons.
+ *
+ * @return array
+ */
+ public function script_data(): array;
}
diff --git a/modules/ppcp-button/src/ButtonModule.php b/modules/ppcp-button/src/ButtonModule.php
index 004071293..67dec01f3 100644
--- a/modules/ppcp-button/src/ButtonModule.php
+++ b/modules/ppcp-button/src/ButtonModule.php
@@ -9,6 +9,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Button;
+use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
+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;
@@ -60,14 +65,12 @@ class ButtonModule implements ModuleInterface {
add_action(
'wp_enqueue_scripts',
static function () use ( $c ) {
-
$smart_button = $c->get( 'button.smart-button' );
- /**
- * The Smart Button.
- *
- * @var SmartButtonInterface $smart_button
- */
- $smart_button->enqueue();
+ assert( $smart_button instanceof SmartButtonInterface );
+
+ if ( $smart_button->should_load_ppcp_script() ) {
+ $smart_button->enqueue();
+ }
}
);
@@ -95,7 +98,7 @@ class ButtonModule implements ModuleInterface {
*
* @param ContainerInterface $container The Container.
*/
- private function register_ajax_endpoints( ContainerInterface $container ) {
+ private function register_ajax_endpoints( ContainerInterface $container ): void {
add_action(
'wc_ajax_' . DataClientIdEndpoint::ENDPOINT,
static function () use ( $container ) {
@@ -118,6 +121,19 @@ class ButtonModule implements ModuleInterface {
}
);
+ add_action(
+ 'wc_ajax_' . SimulateCartEndpoint::ENDPOINT,
+ static function () use ( $container ) {
+ $endpoint = $container->get( 'button.endpoint.simulate-cart' );
+ /**
+ * The Simulate Cart Endpoint.
+ *
+ * @var SimulateCartEndpoint $endpoint
+ */
+ $endpoint->handle_request();
+ }
+ );
+
add_action(
'wc_ajax_' . ChangeCartEndpoint::ENDPOINT,
static function () use ( $container ) {
@@ -144,6 +160,16 @@ class ButtonModule implements ModuleInterface {
}
);
+ add_action(
+ 'wc_ajax_' . ApproveSubscriptionEndpoint::ENDPOINT,
+ static function () use ( $container ) {
+ $endpoint = $container->get( 'button.endpoint.approve-subscription' );
+ assert( $endpoint instanceof ApproveSubscriptionEndpoint );
+
+ $endpoint->handle_request();
+ }
+ );
+
add_action(
'wc_ajax_' . CreateOrderEndpoint::ENDPOINT,
static function () use ( $container ) {
@@ -156,6 +182,34 @@ class ButtonModule implements ModuleInterface {
$endpoint->handle_request();
}
);
+
+ add_action(
+ 'wc_ajax_' . SaveCheckoutFormEndpoint::ENDPOINT,
+ static function () use ( $container ) {
+ $endpoint = $container->get( 'button.endpoint.save-checkout-form' );
+ assert( $endpoint instanceof SaveCheckoutFormEndpoint );
+
+ $endpoint->handle_request();
+ }
+ );
+
+ add_action(
+ 'wc_ajax_' . ValidateCheckoutEndpoint::ENDPOINT,
+ static function () use ( $container ) {
+ $endpoint = $container->get( 'button.endpoint.validate-checkout' );
+ assert( $endpoint instanceof ValidateCheckoutEndpoint );
+ $endpoint->handle_request();
+ }
+ );
+
+ add_action(
+ 'wc_ajax_' . CartScriptParamsEndpoint::ENDPOINT,
+ static function () use ( $container ) {
+ $endpoint = $container->get( 'button.endpoint.cart-script-params' );
+ assert( $endpoint instanceof CartScriptParamsEndpoint );
+ $endpoint->handle_request();
+ }
+ );
}
/**
diff --git a/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
new file mode 100644
index 000000000..604c25124
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
@@ -0,0 +1,186 @@
+handle_data();
+ } catch ( Exception $error ) {
+ $this->logger->error( 'Cart ' . $this->logger_tag . ' failed: ' . $error->getMessage() );
+
+ wp_send_json_error(
+ array(
+ 'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
+ 'message' => $error->getMessage(),
+ 'code' => $error->getCode(),
+ 'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
+ )
+ );
+ return false;
+ }
+ }
+
+ /**
+ * Handles the request data.
+ *
+ * @return bool
+ * @throws Exception On error.
+ */
+ abstract protected function handle_data(): bool;
+
+ /**
+ * Adds products to cart.
+ *
+ * @param array $products Array of products to be added to cart.
+ * @return bool
+ * @throws Exception Add to cart methods throw an exception on fail.
+ */
+ protected function add_products( array $products ): bool {
+ $this->cart->empty_cart( false );
+
+ try {
+ $this->cart_products->add_products( $products );
+ } catch ( Exception $e ) {
+ $this->handle_error();
+ }
+
+ return true;
+ }
+
+ /**
+ * Handles errors.
+ *
+ * @param bool $send_response If this error handling should return the response.
+ * @return void
+ */
+ protected function handle_error( bool $send_response = true ): void {
+
+ $message = __(
+ 'Something went wrong. Action aborted',
+ 'woocommerce-paypal-payments'
+ );
+ $errors = wc_get_notices( 'error' );
+ if ( count( $errors ) ) {
+ $message = array_reduce(
+ $errors,
+ static function ( string $add, array $error ): string {
+ return $add . $error['notice'] . ' ';
+ },
+ ''
+ );
+ wc_clear_notices();
+ }
+
+ if ( $send_response ) {
+ wp_send_json_error(
+ array(
+ 'name' => '',
+ 'message' => $message,
+ 'code' => 0,
+ 'details' => array(),
+ )
+ );
+ }
+ }
+
+ /**
+ * Returns product information from request data.
+ *
+ * @return array|false
+ */
+ protected function products_from_request() {
+ $data = $this->request_data->read_request( $this->nonce() );
+ $products = $this->cart_products->products_from_data( $data );
+ if ( ! $products ) {
+ wp_send_json_error(
+ array(
+ 'name' => '',
+ 'message' => __(
+ 'Necessary fields not defined. Action aborted.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'code' => 0,
+ 'details' => array(),
+ )
+ );
+ return false;
+ }
+
+ return $products;
+ }
+
+ /**
+ * Removes stored cart items from WooCommerce cart.
+ *
+ * @return void
+ */
+ protected function remove_cart_items(): void {
+ $this->cart_products->remove_cart_items();
+ }
+}
diff --git a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php
index 7554f1731..7e4f7c4ee 100644
--- a/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/ApproveOrderEndpoint.php
@@ -180,7 +180,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
);
}
$this->session_handler->replace_order( $order );
- wp_send_json_success( $order );
+ wp_send_json_success();
}
if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) {
@@ -198,7 +198,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
$this->session_handler->replace_funding_source( $funding_source );
$this->session_handler->replace_order( $order );
- wp_send_json_success( $order );
+ wp_send_json_success();
return true;
} catch ( Exception $error ) {
$this->logger->error( 'Order approve failed: ' . $error->getMessage() );
diff --git a/modules/ppcp-button/src/Endpoint/ApproveSubscriptionEndpoint.php b/modules/ppcp-button/src/Endpoint/ApproveSubscriptionEndpoint.php
new file mode 100644
index 000000000..d2ca73b83
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/ApproveSubscriptionEndpoint.php
@@ -0,0 +1,94 @@
+request_data = $request_data;
+ $this->order_endpoint = $order_endpoint;
+ $this->session_handler = $session_handler;
+ }
+
+ /**
+ * The nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ * @throws RuntimeException When order not found or handling failed.
+ */
+ public function handle_request(): bool {
+ $data = $this->request_data->read_request( $this->nonce() );
+ if ( ! isset( $data['order_id'] ) ) {
+ throw new RuntimeException(
+ __( 'No order id given', 'woocommerce-paypal-payments' )
+ );
+ }
+
+ $order = $this->order_endpoint->order( $data['order_id'] );
+ $this->session_handler->replace_order( $order );
+
+ if ( isset( $data['subscription_id'] ) ) {
+ WC()->session->set( 'ppcp_subscription_id', $data['subscription_id'] );
+ }
+
+ wp_send_json_success();
+ return true;
+ }
+}
diff --git a/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php
new file mode 100644
index 000000000..83d4c74cf
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php
@@ -0,0 +1,104 @@
+smart_button = $smart_button;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ */
+ public function handle_request(): bool {
+ try {
+ if ( is_callable( 'wc_maybe_define_constant' ) ) {
+ wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
+ }
+
+ $script_data = $this->smart_button->script_data();
+
+ $total = (float) WC()->cart->get_total( 'numeric' );
+
+ // Shop settings.
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'] ?? '';
+ $currency_code = get_woocommerce_currency();
+
+ wp_send_json_success(
+ array(
+ 'url_params' => $script_data['url_params'],
+ 'button' => $script_data['button'],
+ 'messages' => $script_data['messages'],
+ 'amount' => WC()->cart->get_total( 'raw' ),
+
+ 'total' => $total,
+ 'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
+ 'currency_code' => $currency_code,
+ 'country_code' => $shop_country_code,
+ )
+ );
+
+ return true;
+ } catch ( Throwable $error ) {
+ $this->logger->error( "CartScriptParamsEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
+
+ wp_send_json_error();
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
index 1c64ddb0d..701076bed 100644
--- a/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/ChangeCartEndpoint.php
@@ -11,26 +11,16 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
-use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
-use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
/**
* Class ChangeCartEndpoint
*/
-class ChangeCartEndpoint implements EndpointInterface {
-
+class ChangeCartEndpoint extends AbstractCartEndpoint {
const ENDPOINT = 'ppc-change-cart';
- /**
- * The current cart object.
- *
- * @var \WC_Cart
- */
- private $cart;
-
/**
* The current shipping object.
*
@@ -38,13 +28,6 @@ class ChangeCartEndpoint implements EndpointInterface {
*/
private $shipping;
- /**
- * The request data helper.
- *
- * @var RequestData
- */
- private $request_data;
-
/**
* The PurchaseUnit factory.
*
@@ -52,20 +35,6 @@ class ChangeCartEndpoint implements EndpointInterface {
*/
private $purchase_unit_factory;
- /**
- * The product data store.
- *
- * @var \WC_Data_Store
- */
- private $product_data_store;
-
- /**
- * The logger.
- *
- * @var LoggerInterface
- */
- protected $logger;
-
/**
* ChangeCartEndpoint constructor.
*
@@ -73,7 +42,7 @@ class ChangeCartEndpoint implements EndpointInterface {
* @param \WC_Shipping $shipping The current WC shipping object.
* @param RequestData $request_data The request data helper.
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
- * @param \WC_Data_Store $product_data_store The data store for products.
+ * @param CartProductsHelper $cart_products The cart products helper.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@@ -81,7 +50,7 @@ class ChangeCartEndpoint implements EndpointInterface {
\WC_Shipping $shipping,
RequestData $request_data,
PurchaseUnitFactory $purchase_unit_factory,
- \WC_Data_Store $product_data_store,
+ CartProductsHelper $cart_products,
LoggerInterface $logger
) {
@@ -89,40 +58,10 @@ class ChangeCartEndpoint implements EndpointInterface {
$this->shipping = $shipping;
$this->request_data = $request_data;
$this->purchase_unit_factory = $purchase_unit_factory;
- $this->product_data_store = $product_data_store;
+ $this->cart_products = $cart_products;
$this->logger = $logger;
- }
- /**
- * The nonce.
- *
- * @return string
- */
- public static function nonce(): string {
- return self::ENDPOINT;
- }
-
- /**
- * Handles the request.
- *
- * @return bool
- */
- public function handle_request(): bool {
- try {
- return $this->handle_data();
- } catch ( Exception $error ) {
- $this->logger->error( 'Cart updating failed: ' . $error->getMessage() );
-
- wp_send_json_error(
- array(
- 'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
- 'message' => $error->getMessage(),
- 'code' => $error->getCode(),
- 'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
- )
- );
- return false;
- }
+ $this->logger_tag = 'updating';
}
/**
@@ -131,161 +70,25 @@ class ChangeCartEndpoint implements EndpointInterface {
* @return bool
* @throws Exception On error.
*/
- private function handle_data(): bool {
- $data = $this->request_data->read_request( $this->nonce() );
- $products = $this->products_from_data( $data );
+ protected function handle_data(): bool {
+ $this->cart_products->set_cart( $this->cart );
+
+ $products = $this->products_from_request();
+
if ( ! $products ) {
- wp_send_json_error(
- array(
- 'name' => '',
- 'message' => __(
- 'Necessary fields not defined. Action aborted.',
- 'woocommerce-paypal-payments'
- ),
- 'code' => 0,
- 'details' => array(),
- )
- );
return false;
}
$this->shipping->reset_shipping();
- $this->cart->empty_cart( false );
- $success = true;
- foreach ( $products as $product ) {
- $success = $success && ( ! $product['product']->is_type( 'variable' ) ) ?
- $this->add_product( $product['product'], $product['quantity'] )
- : $this->add_variable_product(
- $product['product'],
- $product['quantity'],
- $product['variations']
- );
- }
- if ( ! $success ) {
- $this->handle_error();
- return $success;
+
+ if ( ! $this->add_products( $products ) ) {
+ return false;
}
wp_send_json_success( $this->generate_purchase_units() );
- return $success;
- }
-
- /**
- * Handles errors.
- *
- * @return bool
- */
- private function handle_error(): bool {
-
- $message = __(
- 'Something went wrong. Action aborted',
- 'woocommerce-paypal-payments'
- );
- $errors = wc_get_notices( 'error' );
- if ( count( $errors ) ) {
- $message = array_reduce(
- $errors,
- static function ( string $add, array $error ): string {
- return $add . $error['notice'] . ' ';
- },
- ''
- );
- wc_clear_notices();
- }
-
- wp_send_json_error(
- array(
- 'name' => '',
- 'message' => $message,
- 'code' => 0,
- 'details' => array(),
- )
- );
return true;
}
- /**
- * Returns product information from an data array.
- *
- * @param array $data The data array.
- *
- * @return array|null
- */
- private function products_from_data( array $data ) {
-
- $products = array();
-
- if (
- ! isset( $data['products'] )
- || ! is_array( $data['products'] )
- ) {
- return null;
- }
- foreach ( $data['products'] as $product ) {
- if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
- return null;
- }
-
- $wc_product = wc_get_product( (int) $product['id'] );
-
- if ( ! $wc_product ) {
- return null;
- }
- $products[] = array(
- 'product' => $wc_product,
- 'quantity' => (int) $product['quantity'],
- 'variations' => isset( $product['variations'] ) ? $product['variations'] : null,
- );
- }
- return $products;
- }
-
- /**
- * Adds a product to the cart.
- *
- * @param \WC_Product $product The Product.
- * @param int $quantity The Quantity.
- *
- * @return bool
- * @throws Exception When product could not be added.
- */
- private function add_product( \WC_Product $product, int $quantity ): bool {
- return false !== $this->cart->add_to_cart( $product->get_id(), $quantity );
- }
-
-
- /**
- * Adds variations to the cart.
- *
- * @param \WC_Product $product The Product.
- * @param int $quantity The Quantity.
- * @param array $post_variations The variations.
- *
- * @return bool
- * @throws Exception When product could not be added.
- */
- private function add_variable_product(
- \WC_Product $product,
- int $quantity,
- array $post_variations
- ): bool {
-
- $variations = array();
- foreach ( $post_variations as $key => $value ) {
- $variations[ $value['name'] ] = $value['value'];
- }
-
- $variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
-
- // ToDo: Check stock status for variation.
- return false !== $this->cart->add_to_cart(
- $product->get_id(),
- $quantity,
- $variation_id,
- $variations
- );
- }
-
/**
* Based on the cart contents, the purchase units are created.
*
diff --git a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
index 205505c5c..4ee88c3c7 100644
--- a/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
+++ b/modules/ppcp-button/src/Endpoint/CreateOrderEndpoint.php
@@ -19,20 +19,19 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode;
-use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@@ -138,6 +137,27 @@ class CreateOrderEndpoint implements EndpointInterface {
*/
protected $early_validation_enabled;
+ /**
+ * The contexts that should have the Pay Now button.
+ *
+ * @var string[]
+ */
+ private $pay_now_contexts;
+
+ /**
+ * If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
+ *
+ * @var bool
+ */
+ private $handle_shipping_in_paypal;
+
+ /**
+ * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
+ *
+ * @var string[]
+ */
+ private $funding_sources_without_redirect;
+
/**
* The logger.
*
@@ -145,6 +165,13 @@ class CreateOrderEndpoint implements EndpointInterface {
*/
protected $logger;
+ /**
+ * The form data, or empty if not available.
+ *
+ * @var array
+ */
+ private $form = array();
+
/**
* CreateOrderEndpoint constructor.
*
@@ -159,6 +186,9 @@ class CreateOrderEndpoint implements EndpointInterface {
* @param bool $registration_needed Whether a new user must be registered during checkout.
* @param string $card_billing_data_mode The value of card_billing_data_mode from the settings.
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
+ * @param string[] $pay_now_contexts The contexts that should have the Pay Now button.
+ * @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
+ * @param string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@@ -173,21 +203,27 @@ class CreateOrderEndpoint implements EndpointInterface {
bool $registration_needed,
string $card_billing_data_mode,
bool $early_validation_enabled,
+ array $pay_now_contexts,
+ bool $handle_shipping_in_paypal,
+ array $funding_sources_without_redirect,
LoggerInterface $logger
) {
- $this->request_data = $request_data;
- $this->purchase_unit_factory = $purchase_unit_factory;
- $this->shipping_preference_factory = $shipping_preference_factory;
- $this->api_endpoint = $order_endpoint;
- $this->payer_factory = $payer_factory;
- $this->session_handler = $session_handler;
- $this->settings = $settings;
- $this->early_order_handler = $early_order_handler;
- $this->registration_needed = $registration_needed;
- $this->card_billing_data_mode = $card_billing_data_mode;
- $this->early_validation_enabled = $early_validation_enabled;
- $this->logger = $logger;
+ $this->request_data = $request_data;
+ $this->purchase_unit_factory = $purchase_unit_factory;
+ $this->shipping_preference_factory = $shipping_preference_factory;
+ $this->api_endpoint = $order_endpoint;
+ $this->payer_factory = $payer_factory;
+ $this->session_handler = $session_handler;
+ $this->settings = $settings;
+ $this->early_order_handler = $early_order_handler;
+ $this->registration_needed = $registration_needed;
+ $this->card_billing_data_mode = $card_billing_data_mode;
+ $this->early_validation_enabled = $early_validation_enabled;
+ $this->pay_now_contexts = $pay_now_contexts;
+ $this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
+ $this->funding_sources_without_redirect = $funding_sources_without_redirect;
+ $this->logger = $logger;
}
/**
@@ -226,7 +262,13 @@ class CreateOrderEndpoint implements EndpointInterface {
}
$this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
} else {
- $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart();
+ $this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->handle_shipping_in_paypal );
+
+ // Do not allow completion by webhooks when started via non-checkout buttons,
+ // it is needed only for some APMs in checkout.
+ if ( in_array( $data['context'], array( 'product', 'cart', 'cart-block' ), true ) ) {
+ $this->purchase_unit->set_custom_id( '' );
+ }
// The cart does not have any info about payment method, so we must handle free trial here.
if ( (
@@ -246,18 +288,20 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->set_bn_code( $data );
- $form_fields = $data['form'] ?? null;
+ if ( isset( $data['form'] ) ) {
+ $this->form = $data['form'];
+ }
if ( $this->early_validation_enabled
- && is_array( $form_fields )
+ && $this->form
&& 'checkout' === $data['context']
&& in_array( $payment_method, array( PayPalGateway::ID, CardButtonGateway::ID ), true )
) {
- $this->validate_form( $form_fields );
+ $this->validate_form( $this->form );
}
- if ( 'pay-now' === $data['context'] && is_array( $form_fields ) && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
- $this->validate_paynow_form( $form_fields );
+ if ( 'pay-now' === $data['context'] && $this->form && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
+ $this->validate_paynow_form( $this->form );
}
try {
@@ -268,11 +312,16 @@ class CreateOrderEndpoint implements EndpointInterface {
}
if ( 'checkout' === $data['context'] ) {
+ if ( $payment_method === PayPalGateway::ID && ! in_array( $funding_source, $this->funding_sources_without_redirect, true ) ) {
+ $this->session_handler->replace_order( $order );
+ $this->session_handler->replace_funding_source( $funding_source );
+ }
+
if (
! $this->early_order_handler->should_create_early_order()
|| $this->registration_needed
|| isset( $data['createaccount'] ) && '1' === $data['createaccount'] ) {
- wp_send_json_success( $order->to_array() );
+ wp_send_json_success( $this->make_response( $order ) );
}
$this->early_order_handler->register_for_order( $order );
@@ -282,18 +331,23 @@ class CreateOrderEndpoint implements EndpointInterface {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$wc_order->save_meta_data();
+
+ do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
}
- wp_send_json_success( $order->to_array() );
+ wp_send_json_success( $this->make_response( $order ) );
return true;
} catch ( ValidationException $error ) {
- wp_send_json_error(
- array(
- 'message' => $error->getMessage(),
- 'errors' => $error->errors(),
- )
+ $response = array(
+ 'message' => $error->getMessage(),
+ 'errors' => $error->errors(),
+ 'refresh' => isset( WC()->session->refresh_totals ),
);
+
+ unset( WC()->session->refresh_totals );
+
+ wp_send_json_error( $response );
} catch ( \RuntimeException $error ) {
$this->logger->error( 'Order creation failed: ' . $error->getMessage() );
@@ -339,7 +393,7 @@ class CreateOrderEndpoint implements EndpointInterface {
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->early_order_handler->should_create_early_order() ) {
- wp_send_json_success( $order->to_array() );
+ wp_send_json_success( $this->make_response( $order ) );
}
$this->early_order_handler->register_for_order( $order );
return $data;
@@ -382,6 +436,9 @@ class CreateOrderEndpoint implements EndpointInterface {
$funding_source
);
+ $action = in_array( $this->parsed_request_data['context'], $this->pay_now_contexts, true ) ?
+ ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE;
+
if ( 'card' === $funding_source ) {
if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) {
if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) {
@@ -407,7 +464,8 @@ class CreateOrderEndpoint implements EndpointInterface {
$shipping_preference,
$payer,
null,
- $this->payment_method()
+ '',
+ $action
);
} catch ( PayPalApiException $exception ) {
// Looks like currently there is no proper way to validate the shipping address for PayPal,
@@ -428,8 +486,7 @@ class CreateOrderEndpoint implements EndpointInterface {
array( $this->purchase_unit ),
$shipping_preference,
$payer,
- null,
- $this->payment_method()
+ null
);
}
@@ -467,11 +524,9 @@ class CreateOrderEndpoint implements EndpointInterface {
$payer = $this->payer_factory->from_paypal_response( json_decode( wp_json_encode( $data['payer'] ) ) );
}
- if ( ! $payer && isset( $data['form'] ) ) {
- $form_fields = $data['form'];
-
- if ( is_array( $form_fields ) && isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) {
- return $this->payer_factory->from_checkout_form( $form_fields );
+ if ( ! $payer && $this->form ) {
+ if ( isset( $this->form['billing_email'] ) && '' !== $this->form['billing_email'] ) {
+ return $this->payer_factory->from_checkout_form( $this->form );
}
}
@@ -493,24 +548,6 @@ class CreateOrderEndpoint implements EndpointInterface {
$this->api_endpoint->with_bn_code( $bn_code );
}
- /**
- * Returns the PaymentMethod object for the order.
- *
- * @return PaymentMethod
- */
- private function payment_method() : PaymentMethod {
- try {
- $payee_preferred = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ?
- PaymentMethod::PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED
- : PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
- } catch ( NotFoundException $exception ) {
- $payee_preferred = PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
- }
-
- $payment_method = new PaymentMethod( $payee_preferred );
- return $payment_method;
- }
-
/**
* Checks whether the form fields are valid.
*
@@ -541,4 +578,17 @@ class CreateOrderEndpoint implements EndpointInterface {
);
}
}
+
+ /**
+ * Returns the response data for success response.
+ *
+ * @param Order $order The PayPal order.
+ * @return array
+ */
+ private function make_response( Order $order ): array {
+ return array(
+ 'id' => $order->id(),
+ 'custom_id' => $order->purchase_units()[0]->custom_id(),
+ );
+ }
}
diff --git a/modules/ppcp-button/src/Endpoint/RequestData.php b/modules/ppcp-button/src/Endpoint/RequestData.php
index 60e053c00..a6716e77c 100644
--- a/modules/ppcp-button/src/Endpoint/RequestData.php
+++ b/modules/ppcp-button/src/Endpoint/RequestData.php
@@ -53,6 +53,11 @@ class RequestData {
}
$this->dequeue_nonce_fix();
+ if ( isset( $json['form_encoded'] ) ) {
+ $json['form'] = array();
+ parse_str( $json['form_encoded'], $json['form'] );
+ }
+
$sanitized = $this->sanitize( $json );
return $sanitized;
}
@@ -80,6 +85,10 @@ class RequestData {
private function sanitize( array $assoc_array ): array {
$data = array();
foreach ( (array) $assoc_array as $raw_key => $raw_value ) {
+ if ( $raw_key === 'form_encoded' ) {
+ $data[ $raw_key ] = $raw_value;
+ continue;
+ }
if ( ! is_array( $raw_value ) ) {
// Not sure if it is a good idea to sanitize everything at this level,
// but should be fine for now since we do not send any HTML or multi-line texts via ajax.
diff --git a/modules/ppcp-button/src/Endpoint/SaveCheckoutFormEndpoint.php b/modules/ppcp-button/src/Endpoint/SaveCheckoutFormEndpoint.php
new file mode 100644
index 000000000..52f5a32c8
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/SaveCheckoutFormEndpoint.php
@@ -0,0 +1,94 @@
+request_data = $request_data;
+ $this->checkout_form_saver = $checkout_form_saver;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ */
+ public function handle_request(): bool {
+ try {
+ $data = $this->request_data->read_request( $this->nonce() );
+
+ $this->checkout_form_saver->save( $data['form'] );
+
+ wp_send_json_success();
+ return true;
+ } catch ( Exception $error ) {
+ $this->logger->error( 'Checkout form saving failed: ' . $error->getMessage() );
+
+ wp_send_json_error(
+ array(
+ 'message' => $error->getMessage(),
+ )
+ );
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
new file mode 100644
index 000000000..1040cc680
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
@@ -0,0 +1,178 @@
+smart_button = $smart_button;
+ $this->cart = clone $cart;
+ $this->request_data = $request_data;
+ $this->cart_products = $cart_products;
+ $this->logger = $logger;
+
+ $this->logger_tag = 'simulation';
+ }
+
+ /**
+ * Handles the request data.
+ *
+ * @return bool
+ * @throws Exception On error.
+ */
+ protected function handle_data(): bool {
+ $products = $this->products_from_request();
+
+ if ( ! $products ) {
+ return false;
+ }
+
+ $this->replace_real_cart();
+
+ $this->add_products( $products );
+
+ $this->cart->calculate_totals();
+ $total = (float) $this->cart->get_total( 'numeric' );
+
+ $this->restore_real_cart();
+
+ // Process filters.
+ $pay_later_enabled = true;
+ $pay_later_messaging_enabled = true;
+ $button_enabled = true;
+
+ foreach ( $products as $product ) {
+ $context_data = array(
+ 'product' => $product['product'],
+ 'order_total' => $total,
+ );
+
+ $pay_later_enabled = $pay_later_enabled && $this->smart_button->is_pay_later_button_enabled_for_location( 'product', $context_data );
+ $pay_later_messaging_enabled = $pay_later_messaging_enabled && $this->smart_button->is_pay_later_messaging_enabled_for_location( 'product', $context_data );
+ $button_enabled = $button_enabled && ! $this->smart_button->is_button_disabled( 'product', $context_data );
+ }
+
+ // Shop settings.
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'];
+ $currency_code = get_woocommerce_currency();
+
+ wp_send_json_success(
+ array(
+ 'total' => $total,
+ 'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
+ 'currency_code' => $currency_code,
+ 'country_code' => $shop_country_code,
+ 'funding' => array(
+ 'paylater' => array(
+ 'enabled' => $pay_later_enabled,
+ ),
+ ),
+ 'button' => array(
+ 'is_disabled' => ! $button_enabled,
+ ),
+ 'messages' => array(
+ 'is_hidden' => ! $pay_later_messaging_enabled,
+ ),
+ )
+ );
+ return true;
+ }
+
+ /**
+ * Handles errors.
+ *
+ * @param bool $send_response If this error handling should return the response.
+ * @return void
+ *
+ * phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
+ */
+ protected function handle_error( bool $send_response = false ): void {
+ parent::handle_error( $send_response );
+ }
+
+ /**
+ * Replaces the real cart with the clone.
+ *
+ * @return void
+ */
+ private function replace_real_cart() {
+ // Set WC default cart as the clone.
+ // Store a reference to the real cart.
+ $this->real_cart = WC()->cart;
+ WC()->cart = $this->cart;
+ $this->cart_products->set_cart( $this->cart );
+ }
+
+ /**
+ * Restores the real cart.
+ * Currently, unsets the WC cart to prevent race conditions arising from it being persisted.
+ *
+ * @return void
+ */
+ private function restore_real_cart() {
+ // Remove from cart because some plugins reserve resources internally when adding to cart.
+ $this->remove_cart_items();
+
+ if ( apply_filters( 'woocommerce_paypal_payments_simulate_cart_prevent_updates', true ) ) {
+ // Removes shutdown actions to prevent persisting session, transients and save cookies.
+ remove_all_actions( 'shutdown' );
+ unset( WC()->cart );
+ } else {
+ // Restores cart, may lead to race conditions.
+ if ( null !== $this->real_cart ) {
+ WC()->cart = $this->real_cart;
+ }
+ }
+
+ unset( $this->cart );
+ }
+
+}
diff --git a/modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php b/modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
new file mode 100644
index 000000000..934c82cd4
--- /dev/null
+++ b/modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
@@ -0,0 +1,110 @@
+request_data = $request_data;
+ $this->checkout_form_validator = $checkout_form_validator;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ */
+ public function handle_request(): bool {
+ try {
+ $data = $this->request_data->read_request( $this->nonce() );
+
+ $form_fields = $data['form'];
+
+ $this->checkout_form_validator->validate( $form_fields );
+
+ wp_send_json_success();
+
+ return true;
+ } catch ( ValidationException $exception ) {
+ $response = array(
+ 'message' => $exception->getMessage(),
+ 'errors' => $exception->errors(),
+ 'refresh' => isset( WC()->session->refresh_totals ),
+ );
+
+ unset( WC()->session->refresh_totals );
+
+ wp_send_json_error( $response );
+ return false;
+ } catch ( Throwable $error ) {
+ $this->logger->error( "Form validation execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
+
+ wp_send_json_error(
+ array(
+ 'message' => $error->getMessage(),
+ )
+ );
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-button/src/Helper/CartProductsHelper.php b/modules/ppcp-button/src/Helper/CartProductsHelper.php
new file mode 100644
index 000000000..804c26598
--- /dev/null
+++ b/modules/ppcp-button/src/Helper/CartProductsHelper.php
@@ -0,0 +1,285 @@
+product_data_store = $product_data_store;
+ }
+
+ /**
+ * Sets a new cart instance.
+ *
+ * @param WC_Cart $cart The cart.
+ * @return void
+ */
+ public function set_cart( WC_Cart $cart ): void {
+ $this->cart = $cart;
+ }
+
+ /**
+ * Returns products information from a data array.
+ *
+ * @param array $data The data array.
+ *
+ * @return array|null
+ */
+ public function products_from_data( array $data ): ?array {
+
+ $products = array();
+
+ if (
+ ! isset( $data['products'] )
+ || ! is_array( $data['products'] )
+ ) {
+ return null;
+ }
+ foreach ( $data['products'] as $product ) {
+ $product = $this->product_from_data( $product );
+ if ( $product ) {
+ $products[] = $product;
+ }
+ }
+ return $products;
+ }
+
+ /**
+ * Returns product information from a data array.
+ *
+ * @param array $product The product data array, usually provided by the product page form.
+ * @return array|null
+ */
+ public function product_from_data( array $product ): ?array {
+ if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
+ return null;
+ }
+
+ $wc_product = wc_get_product( (int) $product['id'] );
+
+ if ( ! $wc_product ) {
+ return null;
+ }
+ return array(
+ 'product' => $wc_product,
+ 'quantity' => (int) $product['quantity'],
+ 'variations' => $product['variations'] ?? null,
+ 'booking' => $product['booking'] ?? null,
+ 'extra' => $product['extra'] ?? null,
+ );
+ }
+
+ /**
+ * Adds products to cart.
+ *
+ * @param array $products Array of products to be added to cart.
+ * @return bool
+ * @throws Exception Add to cart methods throw an exception on fail.
+ */
+ public function add_products( array $products ): bool {
+ $success = true;
+ foreach ( $products as $product ) {
+
+ // Add extras to POST, they are usually added by custom plugins.
+ if ( $product['extra'] && is_array( $product['extra'] ) ) {
+ // Handle cases like field[].
+ $query = http_build_query( $product['extra'] );
+ parse_str( $query, $extra );
+
+ foreach ( $extra as $key => $value ) {
+ $_POST[ $key ] = $value;
+ }
+ }
+
+ if ( $product['product']->is_type( 'booking' ) ) {
+ $success = $success && $this->add_booking_product(
+ $product['product'],
+ $product['booking']
+ );
+ } elseif ( $product['product']->is_type( 'variable' ) ) {
+ $success = $success && $this->add_variable_product(
+ $product['product'],
+ $product['quantity'],
+ $product['variations']
+ );
+ } else {
+ $success = $success && $this->add_product(
+ $product['product'],
+ $product['quantity']
+ );
+ }
+ }
+
+ if ( ! $success ) {
+ throw new Exception( 'Error adding products to cart.' );
+ }
+
+ return true;
+ }
+
+ /**
+ * Adds a product to the cart.
+ *
+ * @param \WC_Product $product The Product.
+ * @param int $quantity The Quantity.
+ *
+ * @return bool
+ * @throws Exception When product could not be added.
+ */
+ public function add_product( \WC_Product $product, int $quantity ): bool {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ $cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
+
+ if ( $cart_item_key ) {
+ $this->cart_item_keys[] = $cart_item_key;
+ }
+ return false !== $cart_item_key;
+ }
+
+ /**
+ * Adds variations to the cart.
+ *
+ * @param \WC_Product $product The Product.
+ * @param int $quantity The Quantity.
+ * @param array $post_variations The variations.
+ *
+ * @return bool
+ * @throws Exception When product could not be added.
+ */
+ public function add_variable_product(
+ \WC_Product $product,
+ int $quantity,
+ array $post_variations
+ ): bool {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ $variations = array();
+ foreach ( $post_variations as $key => $value ) {
+ $variations[ $value['name'] ] = $value['value'];
+ }
+
+ $variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
+
+ // ToDo: Check stock status for variation.
+ $cart_item_key = $this->cart->add_to_cart(
+ $product->get_id(),
+ $quantity,
+ $variation_id,
+ $variations
+ );
+
+ if ( $cart_item_key ) {
+ $this->cart_item_keys[] = $cart_item_key;
+ }
+ return false !== $cart_item_key;
+ }
+
+ /**
+ * Adds booking to the cart.
+ *
+ * @param \WC_Product $product The Product.
+ * @param array $data Data used by the booking plugin.
+ *
+ * @return bool
+ * @throws Exception When product could not be added.
+ */
+ public function add_booking_product(
+ \WC_Product $product,
+ array $data
+ ): bool {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
+ return false;
+ }
+
+ $cart_item_data = array(
+ 'booking' => wc_bookings_get_posted_data( $data, $product ),
+ );
+
+ $cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
+
+ if ( $cart_item_key ) {
+ $this->cart_item_keys[] = $cart_item_key;
+ }
+ return false !== $cart_item_key;
+ }
+
+ /**
+ * Removes stored cart items from WooCommerce cart.
+ *
+ * @return void
+ * @throws Exception Throws if there's a failure removing the cart items.
+ */
+ public function remove_cart_items(): void {
+ if ( ! $this->cart ) {
+ throw new Exception( 'Cart not set.' );
+ }
+
+ foreach ( $this->cart_item_keys as $cart_item_key ) {
+ if ( ! $cart_item_key ) {
+ continue;
+ }
+ $this->cart->remove_cart_item( $cart_item_key );
+ }
+ }
+
+ /**
+ * Returns the cart item keys of the items added to cart.
+ *
+ * @return array
+ */
+ public function cart_item_keys(): array {
+ return $this->cart_item_keys;
+ }
+
+}
diff --git a/modules/ppcp-button/src/Helper/CheckoutFormSaver.php b/modules/ppcp-button/src/Helper/CheckoutFormSaver.php
new file mode 100644
index 000000000..c2f10ca28
--- /dev/null
+++ b/modules/ppcp-button/src/Helper/CheckoutFormSaver.php
@@ -0,0 +1,53 @@
+session_handler = $session_handler;
+ }
+
+ /**
+ * Saves the form data to the WC customer and session.
+ *
+ * @param array $data The form data.
+ * @return void
+ */
+ public function save( array $data ) {
+ foreach ( $data as $key => $value ) {
+ $_POST[ $key ] = $value;
+ }
+ $data = $this->get_posted_data();
+
+ $this->update_session( $data );
+
+ $this->session_handler->replace_checkout_form( $data );
+ }
+}
diff --git a/modules/ppcp-button/src/Helper/ContextTrait.php b/modules/ppcp-button/src/Helper/ContextTrait.php
new file mode 100644
index 000000000..a7381b1b1
--- /dev/null
+++ b/modules/ppcp-button/src/Helper/ContextTrait.php
@@ -0,0 +1,128 @@
+is_checkout() && ! $this->is_paypal_continuation() ) {
+ return 'checkout';
+ }
+
+ return 'product';
+ }
+
+ // has_block may not work if called too early, such as during the block registration.
+ if ( has_block( 'woocommerce/cart' ) ) {
+ return 'cart-block';
+ }
+
+ if ( is_cart() ) {
+ return 'cart';
+ }
+
+ if ( is_checkout_pay_page() ) {
+ return 'pay-now';
+ }
+
+ if ( has_block( 'woocommerce/checkout' ) ) {
+ return 'checkout-block';
+ }
+
+ if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
+ return 'checkout';
+ }
+
+ return 'mini-cart';
+ }
+
+ /**
+ * The current location.
+ *
+ * @return string
+ */
+ protected function location(): string {
+ $context = $this->context();
+ if ( $context !== 'mini-cart' ) {
+ return $context;
+ }
+
+ if ( is_shop() || is_product_category() ) {
+ return 'shop';
+ }
+
+ if ( is_front_page() ) {
+ return 'home';
+ }
+
+ return '';
+ }
+
+ /**
+ * Checks if PayPal payment was already initiated (on the product or cart pages).
+ *
+ * @return bool
+ */
+ private function is_paypal_continuation(): bool {
+ $order = $this->session_handler->order();
+ if ( ! $order ) {
+ return false;
+ }
+
+ if ( ! $order->status()->is( OrderStatus::APPROVED )
+ && ! $order->status()->is( OrderStatus::COMPLETED )
+ ) {
+ return false;
+ }
+
+ $source = $order->payment_source();
+ if ( $source && $source->card() ) {
+ return false; // Ignore for DCC.
+ }
+
+ if ( 'card' === $this->session_handler->funding_source() ) {
+ return false; // Ignore for card buttons.
+ }
+
+ return true;
+ }
+}
diff --git a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
index 3776a6698..03d8fbeed 100644
--- a/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
+++ b/modules/ppcp-button/src/Helper/EarlyOrderHandler.php
@@ -11,19 +11,17 @@ namespace WooCommerce\PayPalCommerce\Button\Helper;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
-use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
/**
* Class EarlyOrderHandler
*/
class EarlyOrderHandler {
- use PrefixTrait;
-
/**
* The State.
*
@@ -51,19 +49,16 @@ class EarlyOrderHandler {
* @param State $state The State.
* @param OrderProcessor $order_processor The Order Processor.
* @param SessionHandler $session_handler The Session Handler.
- * @param string $prefix The Prefix.
*/
public function __construct(
State $state,
OrderProcessor $order_processor,
- SessionHandler $session_handler,
- string $prefix
+ SessionHandler $session_handler
) {
$this->state = $state;
$this->order_processor = $order_processor;
$this->session_handler = $session_handler;
- $this->prefix = $prefix;
}
/**
@@ -101,7 +96,7 @@ class EarlyOrderHandler {
$order_id = false;
foreach ( $order->purchase_units() as $purchase_unit ) {
if ( $purchase_unit->custom_id() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
- $order_id = (int) $this->sanitize_custom_id( $purchase_unit->custom_id() );
+ $order_id = (int) $purchase_unit->custom_id();
}
}
if ( $order_id === $resume_order_id ) {
@@ -169,6 +164,10 @@ class EarlyOrderHandler {
/**
* Patch Order so we have the \WC_Order id added.
*/
- return $this->order_processor->patch_order( $wc_order, $order );
+ $order = $this->order_processor->patch_order( $wc_order, $order );
+
+ do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
+
+ return $order;
}
}
diff --git a/modules/ppcp-button/src/Validation/CheckoutFormValidator.php b/modules/ppcp-button/src/Validation/CheckoutFormValidator.php
index fd686b953..6a1f9486d 100644
--- a/modules/ppcp-button/src/Validation/CheckoutFormValidator.php
+++ b/modules/ppcp-button/src/Validation/CheckoutFormValidator.php
@@ -27,20 +27,91 @@ class CheckoutFormValidator extends WC_Checkout {
public function validate( array $data ) {
$errors = new WP_Error();
- // Some plugins check their fields using $_POST,
+ // Some plugins check their fields using $_POST or $_REQUEST,
// also WC terms checkbox https://github.com/woocommerce/woocommerce/issues/35328 .
foreach ( $data as $key => $value ) {
- $_POST[ $key ] = $value;
+ $_POST[ $key ] = $value;
+ $_REQUEST[ $key ] = $value;
}
- // And we must call get_posted_data because it handles the shipping address.
- $data = $this->get_posted_data();
- // It throws some notices when checking fields etc., also from other plugins via hooks.
- // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
- @$this->validate_checkout( $data, $errors );
+ // Looks like without this WC()->shipping->get_packages() is empty which is used by some plugins.
+ WC()->cart->calculate_shipping();
- if ( $errors->has_errors() ) {
- throw new ValidationException( $errors->get_error_messages() );
+ // Some plugins/filters check is_checkout().
+ $is_checkout = function () {
+ return true;
+ };
+ add_filter( 'woocommerce_is_checkout', $is_checkout );
+ try {
+ // And we must call get_posted_data because it handles the shipping address.
+ $data = $this->get_posted_data();
+
+ // It throws some notices when checking fields etc., also from other plugins via hooks.
+ // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
+ @$this->validate_checkout( $data, $errors );
+ } finally {
+ remove_filter( 'woocommerce_is_checkout', $is_checkout );
+ }
+
+ if (
+ apply_filters( 'woocommerce_paypal_payments_early_wc_checkout_account_creation_validation_enabled', true ) &&
+ ! is_user_logged_in() && ( $this->is_registration_required() || ! empty( $data['createaccount'] ) )
+ ) {
+ $username = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
+ $email = $data['billing_email'] ?? '';
+
+ if ( email_exists( $email ) ) {
+ $errors->add(
+ 'registration-error-email-exists',
+ apply_filters(
+ 'woocommerce_registration_error_email_exists',
+ // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
+ __( 'An account is already registered with your email address. Please log in. ', 'woocommerce' ),
+ $email
+ )
+ );
+ }
+
+ if ( $username ) { // Empty username is already checked in validate_checkout, and it can be generated.
+ $username = sanitize_user( $username );
+ if ( empty( $username ) || ! validate_username( $username ) ) {
+ $errors->add(
+ 'registration-error-invalid-username',
+ // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
+ __( 'Please enter a valid account username.', 'woocommerce' )
+ );
+ }
+
+ if ( username_exists( $username ) ) {
+ $errors->add(
+ 'registration-error-username-exists',
+ // phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
+ __( 'An account is already registered with that username. Please choose another.', 'woocommerce' )
+ );
+ }
+ }
+ }
+
+ // Some plugins call wc_add_notice directly.
+ // We should retrieve such notices, and also clear them to avoid duplicates later.
+ // TODO: Normally WC converts the messages from validate_checkout into notices,
+ // maybe we should do the same for consistency, but it requires lots of changes in the way we handle/output errors.
+ $messages = array_merge(
+ $errors->get_error_messages(),
+ array_map(
+ function ( array $notice ): string {
+ return $notice['notice'];
+ },
+ wc_get_notices( 'error' )
+ )
+ );
+
+ if ( wc_notice_count( 'error' ) > 0 ) {
+ wc_clear_notices();
+ }
+
+ if ( $messages ) {
+ throw new ValidationException( $messages );
}
}
}
diff --git a/modules/ppcp-button/yarn.lock b/modules/ppcp-button/yarn.lock
index 5d7c6fab8..9d5ebef6e 100644
--- a/modules/ppcp-button/yarn.lock
+++ b/modules/ppcp-button/yarn.lock
@@ -956,6 +956,13 @@
"@jridgewell/resolve-uri" "^3.0.3"
"@jridgewell/sourcemap-codec" "^1.4.10"
+"@paypal/paypal-js@^6.0.0":
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.0.tgz#a5a9556af29e4a0124049bf9a093606f52b8a951"
+ integrity sha512-FYzjYby9F7tgg4tUxYNseZ6vkeDJcdcjoULsyNhfrWZZjicDpdj5932fZlyUlQXDSR9KlhjXH6H4nPIJ0Lq0Kw==
+ dependencies:
+ promise-polyfill "^8.3.0"
+
"@types/eslint-scope@^3.7.3":
version "3.7.4"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
@@ -1703,9 +1710,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ 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"
@@ -1723,9 +1730,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
- integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ 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"
@@ -1858,6 +1865,11 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0:
dependencies:
find-up "^4.0.0"
+promise-polyfill@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
+ integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
+
punycode@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
@@ -2191,10 +2203,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-compat/package.json b/modules/ppcp-compat/package.json
index 75351485e..9a4bde8e8 100644
--- a/modules/ppcp-compat/package.json
+++ b/modules/ppcp-compat/package.json
@@ -2,7 +2,7 @@
"name": "ppcp-compat",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
- "main": "resources/js/compat.js",
+ "main": "resources/js/tracking-compat.js",
"browserslist": [
"> 0.5%",
"Safari >= 8",
@@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-compat/resources/js/gzd-compat.js b/modules/ppcp-compat/resources/js/gzd-compat.js
deleted file mode 100644
index acf938b46..000000000
--- a/modules/ppcp-compat/resources/js/gzd-compat.js
+++ /dev/null
@@ -1,32 +0,0 @@
-document.addEventListener(
- 'DOMContentLoaded',
- () => {
- const orderTrackingContainerId = "ppcp_order-tracking";
- const orderTrackingContainerSelector = "#ppcp_order-tracking";
- const gzdSaveButton = document.getElementById('order-shipments-save');
- const loadLocation = location.href + " " + orderTrackingContainerSelector + ">*";
-
- const setEnabled = function (enabled) {
- let childNodes = document.getElementById(orderTrackingContainerId).getElementsByTagName('*');
- for (let node of childNodes) {
- node.disabled = !enabled;
- }
- }
-
- const waitForTrackingUpdate = function () {
- if (jQuery('#order-shipments-save').css('display') !== 'none') {
- setEnabled(false);
- setTimeout(waitForTrackingUpdate, 100)
- } else {
- jQuery(orderTrackingContainerSelector).load(loadLocation,"");
- }
- }
-
- if (typeof(gzdSaveButton) != 'undefined' && gzdSaveButton != null) {
- gzdSaveButton.addEventListener('click', function (event) {
- waitForTrackingUpdate();
- setEnabled(true);
- })
- }
- },
-);
diff --git a/modules/ppcp-compat/resources/js/tracking-compat.js b/modules/ppcp-compat/resources/js/tracking-compat.js
new file mode 100644
index 000000000..b62158ff1
--- /dev/null
+++ b/modules/ppcp-compat/resources/js/tracking-compat.js
@@ -0,0 +1,49 @@
+document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ const config = PayPalCommerceGatewayOrderTrackingCompat;
+
+ const orderTrackingContainerId = "ppcp_order-tracking";
+ const orderTrackingContainerSelector = "#ppcp_order-tracking .ppcp-tracking-column.shipments";
+ const gzdSaveButton = document.getElementById('order-shipments-save');
+ const loadLocation = location.href + " " + orderTrackingContainerSelector + ">*";
+ const gzdSyncEnabled = config.gzd_sync_enabled;
+ const wcShipmentSyncEnabled = config.wc_shipment_sync_enabled;
+ const wcShipmentSaveButton = document.querySelector('#woocommerce-shipment-tracking .button-save-form');
+
+ const toggleLoaderVisibility = function() {
+ const loader = document.querySelector('.ppcp-tracking-loader');
+ if (loader) {
+ if (loader.style.display === 'none' || loader.style.display === '') {
+ loader.style.display = 'block';
+ } else {
+ loader.style.display = 'none';
+ }
+ }
+ }
+
+ const waitForTrackingUpdate = function (elementToCheck) {
+ if (elementToCheck.css('display') !== 'none') {
+ setTimeout(() => waitForTrackingUpdate(elementToCheck), 100);
+ } else {
+ jQuery(orderTrackingContainerSelector).load(loadLocation, "", function(){
+ toggleLoaderVisibility();
+ });
+ }
+ }
+
+ if (gzdSyncEnabled && typeof(gzdSaveButton) != 'undefined' && gzdSaveButton != null) {
+ gzdSaveButton.addEventListener('click', function (event) {
+ toggleLoaderVisibility();
+ waitForTrackingUpdate(jQuery('#order-shipments-save'));
+ })
+ }
+
+ if (wcShipmentSyncEnabled && typeof(wcShipmentSaveButton) != 'undefined' && wcShipmentSaveButton != null) {
+ wcShipmentSaveButton.addEventListener('click', function (event) {
+ toggleLoaderVisibility();
+ waitForTrackingUpdate(jQuery('#shipment-tracking-form'));
+ })
+ }
+ },
+);
diff --git a/modules/ppcp-compat/services.php b/modules/ppcp-compat/services.php
index d345f2832..cb21e195b 100644
--- a/modules/ppcp-compat/services.php
+++ b/modules/ppcp-compat/services.php
@@ -11,11 +11,10 @@ namespace WooCommerce\PayPalCommerce\Compat;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
-use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
return array(
- 'compat.ppec.mock-gateway' => static function( $container ) {
+ 'compat.ppec.mock-gateway' => static function( $container ) {
$settings = $container->get( 'wcgateway.settings' );
$title = $settings->has( 'title' ) ? $settings->get( 'title' ) : __( 'PayPal', 'woocommerce-paypal-payments' );
$title = sprintf(
@@ -27,20 +26,20 @@ return array(
return new PPEC\MockGateway( $title );
},
- 'compat.ppec.subscriptions-handler' => static function ( ContainerInterface $container ) {
+ 'compat.ppec.subscriptions-handler' => static function ( ContainerInterface $container ) {
$ppcp_renewal_handler = $container->get( 'subscription.renewal-handler' );
$gateway = $container->get( 'compat.ppec.mock-gateway' );
return new PPEC\SubscriptionsHandler( $ppcp_renewal_handler, $gateway );
},
- 'compat.ppec.settings_importer' => static function( ContainerInterface $container ) : PPEC\SettingsImporter {
+ 'compat.ppec.settings_importer' => static function( ContainerInterface $container ) : PPEC\SettingsImporter {
$settings = $container->get( 'wcgateway.settings' );
return new PPEC\SettingsImporter( $settings );
},
- 'compat.plugin-script-names' => static function( ContainerInterface $container ) : array {
+ 'compat.plugin-script-names' => static function( ContainerInterface $container ) : array {
return array(
'ppcp-smart-button',
'ppcp-oxxo',
@@ -49,14 +48,25 @@ return array(
'ppcp-gateway-settings',
'ppcp-webhooks-status-page',
'ppcp-tracking',
+ 'ppcp-fraudnet',
+ 'ppcp-tracking-compat',
+ 'ppcp-clear-db',
);
},
- 'compat.gzd.is_supported_plugin_version_active' => function (): bool {
+ 'compat.gzd.is_supported_plugin_version_active' => function (): bool {
return function_exists( 'wc_gzd_get_shipments_by_order' ); // 3.0+
},
- 'compat.module.url' => static function ( ContainerInterface $container ): string {
+ 'compat.wc_shipment_tracking.is_supported_plugin_version_active' => function (): bool {
+ return class_exists( 'WC_Shipment_Tracking' );
+ },
+
+ 'compat.ywot.is_supported_plugin_version_active' => function (): bool {
+ return function_exists( 'yith_ywot_init' );
+ },
+
+ 'compat.module.url' => static function ( ContainerInterface $container ): string {
/**
* The path cannot be false.
*
@@ -68,22 +78,13 @@ return array(
);
},
- 'compat.assets' => function( ContainerInterface $container ) : CompatAssets {
+ 'compat.assets' => function( ContainerInterface $container ) : CompatAssets {
return new CompatAssets(
$container->get( 'compat.module.url' ),
$container->get( 'ppcp.asset-version' ),
- $container->get( 'order-tracking.is-paypal-order-edit-page' ) && $container->get( 'compat.should-initialize-gzd-compat-layer' )
+ $container->get( 'compat.gzd.is_supported_plugin_version_active' ),
+ $container->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' ),
+ $container->get( 'api.bearer' )
);
},
-
- 'compat.should-initialize-gzd-compat-layer' => function( ContainerInterface $container ) : bool {
- $settings = $container->get( 'wcgateway.settings' );
- assert( $settings instanceof Settings );
-
- $tracking_enabled = $settings->has( 'tracking_enabled' ) && $settings->get( 'tracking_enabled' );
- $is_gzd_active = $container->get( 'compat.gzd.is_supported_plugin_version_active' );
-
- return $tracking_enabled && $is_gzd_active;
- },
-
);
diff --git a/modules/ppcp-compat/src/AdminContextTrait.php b/modules/ppcp-compat/src/AdminContextTrait.php
new file mode 100644
index 000000000..ed5583774
--- /dev/null
+++ b/modules/ppcp-compat/src/AdminContextTrait.php
@@ -0,0 +1,39 @@
+get_meta( PayPalGateway::ORDER_ID_META_KEY ) || empty( $order->get_transaction_id() ) ) {
+ return false;
+ }
+
+ return true;
+ }
+}
diff --git a/modules/ppcp-compat/src/Assets/CompatAssets.php b/modules/ppcp-compat/src/Assets/CompatAssets.php
index ca6f15fe9..c1febf93f 100644
--- a/modules/ppcp-compat/src/Assets/CompatAssets.php
+++ b/modules/ppcp-compat/src/Assets/CompatAssets.php
@@ -9,10 +9,16 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat\Assets;
+use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
+use WooCommerce\PayPalCommerce\OrderTracking\TrackingAvailabilityTrait;
+
/**
* Class OrderEditPageAssets
*/
class CompatAssets {
+
+ use TrackingAvailabilityTrait;
+
/**
* The URL to the module.
*
@@ -28,23 +34,48 @@ class CompatAssets {
private $version;
/**
- * Whether Germanized synchronization scripts should be loaded.
+ * Whether Germanized plugin is active.
*
* @var bool
*/
- protected $should_enqueue_gzd_scripts;
+ protected $is_gzd_active;
+
+ /**
+ * Whether WC Shipments plugin is active
+ *
+ * @var bool
+ */
+ protected $is_wc_shipment_active;
+
+ /**
+ * The bearer.
+ *
+ * @var Bearer
+ */
+ protected $bearer;
/**
* Compat module assets constructor.
*
* @param string $module_url The URL to the module.
* @param string $version The assets version.
- * @param bool $should_enqueue_gzd_scripts Whether Germanized synchronization scripts should be loaded.
+ * @param bool $is_gzd_active Whether Germanized plugin is active.
+ * @param bool $is_wc_shipment_active Whether WC Shipments plugin is active.
+ * @param Bearer $bearer The bearer.
*/
- public function __construct( string $module_url, string $version, bool $should_enqueue_gzd_scripts ) {
- $this->module_url = $module_url;
- $this->version = $version;
- $this->should_enqueue_gzd_scripts = $should_enqueue_gzd_scripts;
+ public function __construct(
+ string $module_url,
+ string $version,
+ bool $is_gzd_active,
+ bool $is_wc_shipment_active,
+ Bearer $bearer
+ ) {
+
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->is_gzd_active = $is_gzd_active;
+ $this->is_wc_shipment_active = $is_wc_shipment_active;
+ $this->bearer = $bearer;
}
/**
@@ -53,15 +84,23 @@ class CompatAssets {
* @return void
*/
public function register(): void {
- $gzd_sync_enabled = apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true );
- if ( $this->should_enqueue_gzd_scripts && $gzd_sync_enabled ) {
+ if ( $this->is_tracking_enabled( $this->bearer ) ) {
wp_register_script(
- 'ppcp-gzd-compat',
- untrailingslashit( $this->module_url ) . '/assets/js/gzd-compat.js',
+ 'ppcp-tracking-compat',
+ untrailingslashit( $this->module_url ) . '/assets/js/tracking-compat.js',
array( 'jquery' ),
$this->version,
true
);
+
+ wp_localize_script(
+ 'ppcp-tracking-compat',
+ 'PayPalCommerceGatewayOrderTrackingCompat',
+ array(
+ 'gzd_sync_enabled' => apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) && $this->is_gzd_active,
+ 'wc_shipment_sync_enabled' => apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) && $this->is_wc_shipment_active,
+ )
+ );
}
}
@@ -71,9 +110,8 @@ class CompatAssets {
* @return void
*/
public function enqueue(): void {
- $gzd_sync_enabled = apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true );
- if ( $this->should_enqueue_gzd_scripts && $gzd_sync_enabled ) {
- wp_enqueue_script( 'ppcp-gzd-compat' );
+ if ( $this->is_tracking_enabled( $this->bearer ) ) {
+ wp_enqueue_script( 'ppcp-tracking-compat' );
}
}
}
diff --git a/modules/ppcp-compat/src/CompatModule.php b/modules/ppcp-compat/src/CompatModule.php
index 20cbecf36..790c3edb0 100644
--- a/modules/ppcp-compat/src/CompatModule.php
+++ b/modules/ppcp-compat/src/CompatModule.php
@@ -9,6 +9,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat;
+use Vendidero\Germanized\Shipments\ShipmentItem;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use Exception;
@@ -21,12 +23,13 @@ use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use WP_REST_Request;
+use WP_REST_Response;
/**
* Class CompatModule
*/
class CompatModule implements ModuleInterface {
-
/**
* Setup the compatibility module.
*
@@ -45,9 +48,10 @@ class CompatModule implements ModuleInterface {
* @throws NotFoundException
*/
public function run( ContainerInterface $c ): void {
+
$this->initialize_ppec_compat_layer( $c );
$this->fix_site_ground_optimizer_compatibility( $c );
- $this->initialize_gzd_compat_layer( $c );
+ $this->initialize_tracking_compat_layer( $c );
$asset_loader = $c->get( 'compat.assets' );
assert( $asset_loader instanceof CompatAssets );
@@ -57,6 +61,8 @@ class CompatModule implements ModuleInterface {
$this->migrate_pay_later_settings( $c );
$this->migrate_smart_button_settings( $c );
+
+ $this->fix_page_builders();
}
/**
@@ -111,6 +117,30 @@ class CompatModule implements ModuleInterface {
);
}
+ /**
+ * Sets up the 3rd party plugins compatibility layer for PayPal tracking.
+ *
+ * @param ContainerInterface $c The Container.
+ * @return void
+ */
+ protected function initialize_tracking_compat_layer( ContainerInterface $c ): void {
+ $is_gzd_active = $c->get( 'compat.gzd.is_supported_plugin_version_active' );
+ $is_wc_shipment_tracking_active = $c->get( 'compat.wc_shipment_tracking.is_supported_plugin_version_active' );
+ $is_ywot_active = $c->get( 'compat.ywot.is_supported_plugin_version_active' );
+
+ if ( $is_gzd_active ) {
+ $this->initialize_gzd_compat_layer( $c );
+ }
+
+ if ( $is_wc_shipment_tracking_active ) {
+ $this->initialize_wc_shipment_tracking_compat_layer( $c );
+ }
+
+ if ( $is_ywot_active ) {
+ $this->initialize_ywot_compat_layer( $c );
+ }
+ }
+
/**
* Sets up the Germanized for WooCommerce
* plugin compatibility layer.
@@ -121,60 +151,204 @@ class CompatModule implements ModuleInterface {
* @return void
*/
protected function initialize_gzd_compat_layer( ContainerInterface $c ): void {
- if ( ! $c->get( 'compat.should-initialize-gzd-compat-layer' ) ) {
- return;
- }
+ add_action(
+ 'woocommerce_gzd_shipment_status_shipped',
+ function( int $shipment_id, Shipment $shipment ) use ( $c ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
+ return;
+ }
+ $wc_order = $shipment->get_order();
+
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $order_id = $wc_order->get_id();
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = $shipment->get_tracking_id();
+ $carrier = $shipment->get_shipping_provider();
+ $items = array_map(
+ function ( ShipmentItem $item ): int {
+ return $item->get_order_item_id();
+ },
+ $shipment->get_items()
+ );
+
+ if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
+ return;
+ }
+
+ $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, $items );
+ },
+ 500,
+ 2
+ );
+ }
+
+ /**
+ * Sets up the Shipment Tracking
+ * plugin compatibility layer.
+ *
+ * @link https://woocommerce.com/document/shipment-tracking/
+ *
+ * @param ContainerInterface $c The Container.
+ * @return void
+ */
+ protected function initialize_wc_shipment_tracking_compat_layer( ContainerInterface $c ): void {
+ add_action(
+ 'wp_ajax_wc_shipment_tracking_save_form',
+ function() use ( $c ) {
+ check_ajax_referer( 'create-tracking-item', 'security', true );
+
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
+ return;
+ }
+
+ $order_id = (int) wc_clean( wp_unslash( $_POST['order_id'] ?? '' ) );
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = wc_clean( wp_unslash( $_POST['tracking_number'] ?? '' ) );
+ $carrier = wc_clean( wp_unslash( $_POST['tracking_provider'] ?? '' ) );
+ $carrier_other = wc_clean( wp_unslash( $_POST['custom_tracking_provider'] ?? '' ) );
+ $carrier = $carrier ?: $carrier_other ?: '';
+
+ if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
+ return;
+ }
+
+ $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
+ }
+ );
+
+ add_filter(
+ 'woocommerce_rest_prepare_order_shipment_tracking',
+ function( WP_REST_Response $response, array $tracking_item, WP_REST_Request $request ) use ( $c ): WP_REST_Response {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_wc_shipment_tracking', true ) ) {
+ return $response;
+ }
+
+ $callback = $request->get_attributes()['callback']['1'] ?? '';
+ if ( $callback !== 'create_item' ) {
+ return $response;
+ }
+
+ $order_id = $tracking_item['order_id'] ?? 0;
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return $response;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ $tracking_number = $tracking_item['tracking_number'] ?? '';
+ $carrier = $tracking_item['tracking_provider'] ?? '';
+ $carrier_other = $tracking_item['custom_tracking_provider'] ?? '';
+ $carrier = $carrier ?: $carrier_other ?: '';
+
+ if ( ! $tracking_number || ! $carrier || ! $transaction_id ) {
+ return $response;
+ }
+
+ $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
+
+ return $response;
+ },
+ 10,
+ 3
+ );
+ }
+
+ /**
+ * Sets up the YITH WooCommerce Order & Shipment Tracking
+ * plugin compatibility layer.
+ *
+ * @link https://wordpress.org/plugins/yith-woocommerce-order-tracking/
+ *
+ * @param ContainerInterface $c The Container.
+ * @return void
+ */
+ protected function initialize_ywot_compat_layer( ContainerInterface $c ): void {
+ add_action(
+ 'woocommerce_process_shop_order_meta',
+ function( int $order_id ) use ( $c ) {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_sync_ywot_tracking', true ) ) {
+ return;
+ }
+
+ $wc_order = wc_get_order( $order_id );
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ return;
+ }
+
+ $transaction_id = $wc_order->get_transaction_id();
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $tracking_number = wc_clean( wp_unslash( $_POST['ywot_tracking_code'] ?? '' ) );
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $carrier = wc_clean( wp_unslash( $_POST['ywot_carrier_name'] ?? '' ) );
+
+ if ( ! $tracking_number || ! is_string( $tracking_number ) || ! $carrier || ! is_string( $carrier ) || ! $transaction_id ) {
+ return;
+ }
+
+ $this->create_tracking( $c, $order_id, $transaction_id, $tracking_number, $carrier, array() );
+ },
+ 500,
+ 1
+ );
+ }
+
+ /**
+ * Creates PayPal tracking.
+ *
+ * @param ContainerInterface $c The Container.
+ * @param int $wc_order_id The WC order ID.
+ * @param string $transaction_id The transaction ID.
+ * @param string $tracking_number The tracking number.
+ * @param string $carrier The shipment carrier.
+ * @param int[] $line_items The list of shipment line item IDs.
+ * @return void
+ */
+ protected function create_tracking(
+ ContainerInterface $c,
+ int $wc_order_id,
+ string $transaction_id,
+ string $tracking_number,
+ string $carrier,
+ array $line_items
+ ) {
$endpoint = $c->get( 'order-tracking.endpoint.controller' );
assert( $endpoint instanceof OrderTrackingEndpoint );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
- add_action(
- 'woocommerce_gzd_shipment_status_shipped',
- static function( int $shipment_id, Shipment $shipment ) use ( $endpoint, $logger ) {
- if ( ! apply_filters( 'woocommerce_paypal_payments_sync_gzd_tracking', true ) ) {
- return;
- }
+ $shipment_factory = $c->get( 'order-tracking.shipment.factory' );
+ assert( $shipment_factory instanceof ShipmentFactoryInterface );
- $wc_order = $shipment->get_order();
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return;
- }
+ try {
+ $ppcp_shipment = $shipment_factory->create_shipment(
+ $wc_order_id,
+ $transaction_id,
+ $tracking_number,
+ 'SHIPPED',
+ 'OTHER',
+ $carrier,
+ $line_items
+ );
- $transaction_id = $wc_order->get_transaction_id();
- if ( empty( $transaction_id ) ) {
- return;
- }
+ $tracking_information = $endpoint->get_tracking_information( $wc_order_id, $tracking_number );
- $tracking_data = array(
- 'transaction_id' => $transaction_id,
- 'status' => 'SHIPPED',
- );
+ $tracking_information
+ ? $endpoint->update_tracking_information( $ppcp_shipment, $wc_order_id )
+ : $endpoint->add_tracking_information( $ppcp_shipment, $wc_order_id );
- $provider = $shipment->get_shipping_provider();
- if ( ! empty( $provider ) && $provider !== 'none' ) {
- $tracking_data['carrier'] = 'DHL_DEUTSCHE_POST';
- }
-
- try {
- $tracking_information = $endpoint->get_tracking_information( $wc_order->get_id() );
-
- $tracking_data['tracking_number'] = $tracking_information['tracking_number'] ?? '';
-
- if ( $shipment->get_tracking_id() ) {
- $tracking_data['tracking_number'] = $shipment->get_tracking_id();
- }
-
- ! $tracking_information ? $endpoint->add_tracking_information( $tracking_data, $wc_order->get_id() ) : $endpoint->update_tracking_information( $tracking_data, $wc_order->get_id() );
- } catch ( Exception $exception ) {
- $logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
- }
- },
- 500,
- 2
- );
+ } catch ( Exception $exception ) {
+ $logger->error( "Couldn't sync tracking information: " . $exception->getMessage() );
+ }
}
/**
@@ -316,4 +490,46 @@ class CompatModule implements ModuleInterface {
}
);
}
+
+ /**
+ * Changes the button rendering place for page builders
+ * that do not work well with our default places.
+ *
+ * @return void
+ */
+ protected function fix_page_builders(): void {
+ add_action(
+ 'init',
+ function() {
+ if ( $this->is_elementor_pro_active() || $this->is_divi_theme_active() ) {
+ add_filter(
+ 'woocommerce_paypal_payments_single_product_renderer_hook',
+ function(): string {
+ return 'woocommerce_after_add_to_cart_form';
+ },
+ 5
+ );
+ }
+ }
+ );
+ }
+
+ /**
+ * Checks whether the Elementor Pro plugins (allowing integrations with WC) is active.
+ *
+ * @return bool
+ */
+ protected function is_elementor_pro_active(): bool {
+ return is_plugin_active( 'elementor-pro/elementor-pro.php' );
+ }
+
+ /**
+ * Checks whether the Divi theme is currently used.
+ *
+ * @return bool
+ */
+ protected function is_divi_theme_active(): bool {
+ $theme = wp_get_theme();
+ return $theme->get( 'Name' ) === 'Divi';
+ }
}
diff --git a/modules/ppcp-compat/src/PPEC/PPECHelper.php b/modules/ppcp-compat/src/PPEC/PPECHelper.php
index 71e16e544..791cb7a22 100644
--- a/modules/ppcp-compat/src/PPEC/PPECHelper.php
+++ b/modules/ppcp-compat/src/PPEC/PPECHelper.php
@@ -7,6 +7,8 @@
namespace WooCommerce\PayPalCommerce\Compat\PPEC;
+use Automattic\WooCommerce\Utilities\OrderUtil;
+
/**
* Helper class with various constants associated to the PayPal Express Checkout plugin, as well as methods for figuring
* out the status of the gateway.
@@ -67,17 +69,36 @@ class PPECHelper {
* @return bool
*/
public static function site_has_ppec_subscriptions() {
- global $wpdb;
+ $has_ppec_subscriptions = get_transient( 'ppcp_has_ppec_subscriptions' );
+ if ( $has_ppec_subscriptions !== false ) {
+ return $has_ppec_subscriptions === 'true';
+ }
- $result = $wpdb->get_var(
- $wpdb->prepare(
- "SELECT 1 FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
+ global $wpdb;
+ if ( class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
+ $result = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT 1 FROM {$wpdb->wc_orders} WHERE payment_method = %s",
+ self::PPEC_GATEWAY_ID
+ )
+ );
+ } else {
+ $result = $wpdb->get_var(
+ $wpdb->prepare(
+ "SELECT 1 FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
WHERE p.post_type = %s AND p.post_status != %s AND pm.meta_key = %s AND pm.meta_value = %s LIMIT 1",
- 'shop_subscription',
- 'trash',
- '_payment_method',
- self::PPEC_GATEWAY_ID
- )
+ 'shop_subscription',
+ 'trash',
+ '_payment_method',
+ self::PPEC_GATEWAY_ID
+ )
+ );
+ }
+
+ set_transient(
+ 'ppcp_has_ppec_subscriptions',
+ ! empty( $result ) ? 'true' : 'false',
+ MONTH_IN_SECONDS
);
return ! empty( $result );
@@ -92,7 +113,9 @@ class PPECHelper {
/**
* The filter returning whether the compatibility layer for PPEC Subscriptions should be initialized.
*/
- return ( ! self::is_gateway_available() ) && self::site_has_ppec_subscriptions() && apply_filters( 'woocommerce_paypal_payments_process_legacy_subscriptions', true );
+ return ( ! self::is_gateway_available() )
+ && self::site_has_ppec_subscriptions()
+ && apply_filters( 'woocommerce_paypal_payments_process_legacy_subscriptions', true );
}
}
diff --git a/modules/ppcp-compat/src/PPEC/SettingsImporter.php b/modules/ppcp-compat/src/PPEC/SettingsImporter.php
index a4f9e9e14..7d87de968 100644
--- a/modules/ppcp-compat/src/PPEC/SettingsImporter.php
+++ b/modules/ppcp-compat/src/PPEC/SettingsImporter.php
@@ -142,7 +142,7 @@ class SettingsImporter {
$value = array_values(
array_intersect(
array_map( 'strtolower', is_array( $option_value ) ? $option_value : array() ),
- array( 'card', 'sepa', 'bancontact', 'blik', 'eps', 'giropay', 'ideal', 'mercadopago', 'mybank', 'p24', 'sofort', 'venmo' )
+ array( 'card', 'sepa', 'bancontact', 'blik', 'eps', 'giropay', 'ideal', 'mercadopago', 'mybank', 'p24', 'sofort', 'venmo', 'trustly' )
)
);
diff --git a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php
index 8db9808a0..90e2b5493 100644
--- a/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php
+++ b/modules/ppcp-compat/src/PPEC/SubscriptionsHandler.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Compat\PPEC;
+use Automattic\WooCommerce\Utilities\OrderUtil;
use stdClass;
use WooCommerce\PayPalCommerce\Subscription\RenewalHandler;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
@@ -69,7 +70,7 @@ class SubscriptionsHandler {
/**
* Adds a mock gateway to disguise as PPEC when needed. Hooked onto `woocommerce_payment_gateways`.
* The mock gateway fixes display issues where subscriptions paid via PPEC appear as "via Manual Renewal" and also
- * prevents Subscriptions from automatically changing the payment method to "manual" when a subscription is edited.
+ * prevents subscriptions from automatically changing the payment method to "manual" when a subscription is edited.
*
* @param array $gateways List of gateways.
* @return array
@@ -183,21 +184,29 @@ class SubscriptionsHandler {
return true;
}
- if ( function_exists( 'get_current_screen' ) && get_current_screen() ) {
- // Are we on the WC > Subscriptions screen?
- if ( 'edit-shop_subscription' === get_current_screen()->id ) {
- return true;
- }
+ // Are we editing an order or subscription tied to PPEC?
+ // phpcs:ignore WordPress.Security.NonceVerification
+ $order_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? $_POST['post_ID'] ?? '' ) );
+ if ( $order_id ) {
+ $order = wc_get_order( $order_id );
+ return ( $order && PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() );
+ }
- // Are we editing an order or subscription tied to PPEC?
- if ( in_array( get_current_screen()->id, array( 'shop_subscription', 'shop_order' ), true ) ) {
- $order = wc_get_order( $GLOBALS['post']->ID );
-
- return ( $order && PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() );
- }
+ // Are we on the WC > Subscriptions screen?
+ /**
+ * Class exist in WooCommerce.
+ *
+ * @psalm-suppress UndefinedClass
+ */
+ $post_type_or_page = class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled()
+ // phpcs:ignore WordPress.Security.NonceVerification
+ ? wc_clean( wp_unslash( $_GET['page'] ?? '' ) )
+ // phpcs:ignore WordPress.Security.NonceVerification
+ : wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) );
+ if ( $post_type_or_page === 'shop_subscription' || $post_type_or_page === 'wc-orders--shop_subscription' ) {
+ return true;
}
return false;
}
-
}
diff --git a/modules/ppcp-compat/webpack.config.js b/modules/ppcp-compat/webpack.config.js
index be299aca8..567cd235b 100644
--- a/modules/ppcp-compat/webpack.config.js
+++ b/modules/ppcp-compat/webpack.config.js
@@ -6,7 +6,7 @@ module.exports = {
mode: isProduction ? 'production' : 'development',
target: 'web',
entry: {
- 'gzd-compat': path.resolve('./resources/js/gzd-compat.js'),
+ 'tracking-compat': path.resolve('./resources/js/tracking-compat.js'),
},
output: {
path: path.resolve(__dirname, 'assets/'),
diff --git a/modules/ppcp-compat/yarn.lock b/modules/ppcp-compat/yarn.lock
index c790c4b52..22ce5854e 100644
--- a/modules/ppcp-compat/yarn.lock
+++ b/modules/ppcp-compat/yarn.lock
@@ -1682,9 +1682,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ 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"
@@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
- integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ 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"
@@ -2155,10 +2155,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-googlepay/.babelrc b/modules/ppcp-googlepay/.babelrc
new file mode 100644
index 000000000..822778e6c
--- /dev/null
+++ b/modules/ppcp-googlepay/.babelrc
@@ -0,0 +1,14 @@
+{
+ "presets": [
+ [
+ "@babel/preset-env",
+ {
+ "useBuiltIns": "usage",
+ "corejs": "3.25.0"
+ }
+ ],
+ [
+ "@babel/preset-react"
+ ]
+ ]
+}
diff --git a/modules/ppcp-googlepay/.gitignore b/modules/ppcp-googlepay/.gitignore
new file mode 100644
index 000000000..0bd2b9f58
--- /dev/null
+++ b/modules/ppcp-googlepay/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+assets/js
+assets/css
diff --git a/modules/ppcp-googlepay/composer.json b/modules/ppcp-googlepay/composer.json
new file mode 100644
index 000000000..81698b434
--- /dev/null
+++ b/modules/ppcp-googlepay/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "woocommerce/ppcp-googlepay",
+ "type": "dhii-mod",
+ "description": "Googlepay module for PPCP",
+ "license": "GPL-2.0",
+ "require": {
+ "php": "^7.2 | ^8.0",
+ "dhii/module-interface": "^0.3.0-alpha1"
+ },
+ "autoload": {
+ "psr-4": {
+ "WooCommerce\\PayPalCommerce\\Googlepay\\": "src"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/modules/ppcp-googlepay/extensions.php b/modules/ppcp-googlepay/extensions.php
new file mode 100644
index 000000000..18c8955be
--- /dev/null
+++ b/modules/ppcp-googlepay/extensions.php
@@ -0,0 +1,204 @@
+ function ( ContainerInterface $container, array $fields ): array {
+
+ // Eligibility check.
+ if ( ! $container->has( 'googlepay.eligible' ) || ! $container->get( 'googlepay.eligible' ) ) {
+ return $fields;
+ }
+
+ $is_available = $container->get( 'googlepay.available' );
+ $is_referral = $container->get( 'googlepay.is_referral' );
+
+ $insert_after = function( array $array, string $key, array $new ): array {
+ $keys = array_keys( $array );
+ $index = array_search( $key, $keys, true );
+ $pos = false === $index ? count( $array ) : $index + 1;
+
+ return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
+ };
+
+ $display_manager = $container->get( 'wcgateway.display-manager' );
+ assert( $display_manager instanceof DisplayManager );
+
+ // Connection tab fields.
+ $fields = $insert_after(
+ $fields,
+ 'ppcp_dcc_status',
+ array(
+ 'googlepay_status' => array(
+ 'title' => __( 'Google Pay Payments', 'woocommerce-paypal-payments' ),
+ 'type' => 'ppcp-text',
+ 'text' => $container->get( 'googlepay.settings.connection.status-text' ),
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
+ ),
+ )
+ );
+
+ if ( ! $is_available && $is_referral ) {
+ $connection_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=ppcp-connection#field-credentials_feature_onboarding_heading' );
+ $connection_link = '';
+ return $insert_after(
+ $fields,
+ 'allow_card_button_gateway',
+ array(
+ 'googlepay_button_enabled' => array(
+ 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'class' => array( 'ppcp-grayed-out-text' ),
+ 'input_class' => array( 'ppcp-disabled-checkbox' ),
+ 'label' => __( 'Enable Google Pay button', 'woocommerce-paypal-payments' )
+ . ''
+ . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Your PayPal account %1$srequires additional permissions%2$s to enable Google Pay.', 'woocommerce-paypal-payments' ),
+ $connection_link,
+ ' '
+ )
+ . '
',
+ 'default' => 'yes',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_is_true( false )
+ ->action_enable( 'googlepay_button_enabled' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ )
+ );
+ }
+
+ // Standard Payments tab fields.
+ return $insert_after(
+ $fields,
+ 'allow_card_button_gateway',
+ array(
+ 'googlepay_button_enabled' => array(
+ 'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'label' => __( 'Enable Google Pay button', 'woocommerce-paypal-payments' )
+ . ''
+ . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Buyers can use %1$sGoogle Pay%2$s to make payments on the web using a web browser.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ )
+ . '
',
+ 'default' => 'yes',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_element( 'googlepay_button_enabled', '1' )
+ ->action_visible( 'googlepay_button_type' )
+ ->action_visible( 'googlepay_button_color' )
+ ->action_visible( 'googlepay_button_language' )
+ ->action_visible( 'googlepay_button_shipping_enabled' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ 'googlepay_button_type' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Label', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'This controls the label of the Google Pay button.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'pay',
+ 'options' => PropertiesDictionary::button_types(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ 'googlepay_button_color' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'Google Pay payment buttons exist in two styles: dark and light. To provide contrast, use dark buttons on light backgrounds and light buttons on dark or colorful backgrounds.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'label' => '',
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'class' => array(),
+ 'default' => 'black',
+ 'options' => PropertiesDictionary::button_colors(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ 'googlepay_button_language' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Button Language', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'The language and region used for the displayed Google Pay button. The default value is the current language and region setting in a browser.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'en',
+ 'options' => PropertiesDictionary::button_languages(),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ 'googlepay_button_shipping_enabled' => array(
+ 'title' => str_repeat( ' ', 6 ) . __( 'Shipping Callback', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'Synchronizes your available shipping options with Google Pay. Enabling this may impact the buyer experience.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'label' => __( 'Enable Google Pay shipping callback', 'woocommerce-paypal-payments' ),
+ 'default' => 'no',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'gateway' => 'paypal',
+ 'requirements' => array(),
+ ),
+ )
+ );
+ },
+
+);
diff --git a/modules/ppcp-googlepay/module.php b/modules/ppcp-googlepay/module.php
new file mode 100644
index 000000000..2e146435b
--- /dev/null
+++ b/modules/ppcp-googlepay/module.php
@@ -0,0 +1,16 @@
+ 0.5%",
+ "Safari >= 8",
+ "Chrome >= 41",
+ "Firefox >= 43",
+ "Edge >= 14"
+ ],
+ "dependencies": {
+ "@paypal/paypal-js": "^6.0.0",
+ "core-js": "^3.25.0"
+ },
+ "devDependencies": {
+ "@babel/core": "^7.19",
+ "@babel/preset-env": "^7.19",
+ "@babel/preset-react": "^7.18.6",
+ "@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
+ "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-googlepay/resources/css/styles.scss b/modules/ppcp-googlepay/resources/css/styles.scss
new file mode 100644
index 000000000..c7f8a80a1
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/css/styles.scss
@@ -0,0 +1,46 @@
+.ppcp-button-googlepay {
+ margin: 7px 0;
+ overflow: hidden;
+ min-height: 40px;
+ height: 45px;
+
+ &.ppcp-button-pill {
+ border-radius: 50px;
+ }
+
+ &.ppcp-button-minicart {
+ display: block;
+ height: 40px;
+ }
+}
+
+.woocommerce-checkout {
+ .ppcp-button-googlepay {
+ margin-top: 0;
+ }
+}
+
+.ppcp-has-googlepay-block {
+
+ .wp-block-woocommerce-checkout {
+ .ppcp-button-googlepay {
+ margin: 0;
+ height: 40px;
+ }
+ }
+
+ .wp-block-woocommerce-cart {
+ .ppcp-button-googlepay {
+ margin: 0;
+ height: 40px;
+ }
+ /* Workaround for blocks grid */
+ .wc-block-components-express-payment__event-buttons {
+ display: block;
+ li[id*="express-payment-method-ppcp-"] {
+ padding-bottom: 0;
+ }
+ }
+ }
+
+}
diff --git a/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js
new file mode 100644
index 000000000..1074c94ec
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/BaseHandler.js
@@ -0,0 +1,71 @@
+import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
+import CartActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler";
+
+class BaseHandler {
+
+ constructor(buttonConfig, ppcpConfig, externalHandler) {
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+ this.externalHandler = externalHandler;
+ }
+
+ shippingAllowed() {
+ return true;
+ }
+
+ transactionInfo() {
+ return new Promise((resolve, reject) => {
+
+ fetch(
+ this.ppcpConfig.ajax.cart_script_params.endpoint,
+ {
+ method: 'GET',
+ credentials: 'same-origin',
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (! result.success) {
+ return;
+ }
+
+ // handle script reload
+ const data = result.data;
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+
+ });
+ });
+ }
+
+ createOrder() {
+ return this.actionHandler().configuration().createOrder(null, null);
+ }
+
+ approveOrder(data, actions) {
+ return this.actionHandler().configuration().onApprove(data, actions);
+ }
+
+ actionHandler() {
+ return new CartActionHandler(
+ this.ppcpConfig,
+ this.errorHandler(),
+ );
+ }
+
+ errorHandler() {
+ return new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+ }
+
+}
+
+export default BaseHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js
new file mode 100644
index 000000000..451167584
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/CartBlockHandler.js
@@ -0,0 +1,15 @@
+import BaseHandler from "./BaseHandler";
+
+class CartBlockHandler extends BaseHandler {
+
+ createOrder() {
+ return this.externalHandler.createOrder();
+ }
+
+ approveOrder(data, actions) {
+ return this.externalHandler.onApprove(data, actions);
+ }
+
+}
+
+export default CartBlockHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/CartHandler.js b/modules/ppcp-googlepay/resources/js/Context/CartHandler.js
new file mode 100644
index 000000000..295bec718
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/CartHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class CartHandler extends BaseHandler {
+
+}
+
+export default CartHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js
new file mode 100644
index 000000000..3d24df9aa
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutBlockHandler.js
@@ -0,0 +1,19 @@
+import BaseHandler from "./BaseHandler";
+
+class CheckoutBlockHandler extends BaseHandler{
+
+ shippingAllowed() {
+ return false;
+ }
+
+ createOrder() {
+ return this.externalHandler.createOrder();
+ }
+
+ approveOrder(data, actions) {
+ return this.externalHandler.onApprove(data, actions);
+ }
+
+}
+
+export default CheckoutBlockHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js
new file mode 100644
index 000000000..3f773a875
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/CheckoutHandler.js
@@ -0,0 +1,64 @@
+import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
+import BaseHandler from "./BaseHandler";
+import CheckoutActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
+import FormValidator from "../../../../ppcp-button/resources/js/modules/Helper/FormValidator";
+
+class CheckoutHandler extends BaseHandler {
+
+ shippingAllowed() {
+ return false;
+ }
+
+ transactionInfo() {
+ return new Promise(async (resolve, reject) => {
+
+ try {
+ const spinner = new Spinner();
+ const errorHandler = this.errorHandler();
+
+ const formSelector = this.ppcpConfig.context === 'checkout' ? 'form.checkout' : 'form#order_review';
+ const formValidator = this.ppcpConfig.early_checkout_validation_enabled ?
+ new FormValidator(
+ this.ppcpConfig.ajax.validate_checkout.endpoint,
+ this.ppcpConfig.ajax.validate_checkout.nonce,
+ ) : null;
+
+ if (!formValidator) {
+ resolve(super.transactionInfo());
+ return;
+ }
+
+ formValidator.validate(document.querySelector(formSelector)).then((errors) => {
+ if (errors.length > 0) {
+ spinner.unblock();
+ errorHandler.clear();
+ errorHandler.messages(errors);
+
+ // fire WC event for other plugins
+ jQuery( document.body ).trigger( 'checkout_error' , [ errorHandler.currentHtml() ] );
+
+ reject();
+ } else {
+ resolve(super.transactionInfo());
+ }
+ });
+
+ } catch (error) {
+ console.error(error);
+ reject();
+ }
+ });
+ }
+
+ actionHandler() {
+ return new CheckoutActionHandler(
+ this.ppcpConfig,
+ this.errorHandler(),
+ new Spinner()
+ );
+ }
+
+}
+
+export default CheckoutHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js
new file mode 100644
index 000000000..8c6bc261d
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/ContextHandlerFactory.js
@@ -0,0 +1,34 @@
+import SingleProductHandler from "./SingleProductHandler";
+import CartHandler from "./CartHandler";
+import CheckoutHandler from "./CheckoutHandler";
+import CartBlockHandler from "./CartBlockHandler";
+import CheckoutBlockHandler from "./CheckoutBlockHandler";
+import MiniCartHandler from "./MiniCartHandler";
+import PayNowHandler from "./PayNowHandler";
+import PreviewHandler from "./PreviewHandler";
+
+class ContextHandlerFactory {
+
+ static create(context, buttonConfig, ppcpConfig, externalActionHandler) {
+ switch (context) {
+ case 'product':
+ return new SingleProductHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'cart':
+ return new CartHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'checkout':
+ return new CheckoutHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'pay-now':
+ return new PayNowHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'mini-cart':
+ return new MiniCartHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'cart-block':
+ return new CartBlockHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'checkout-block':
+ return new CheckoutBlockHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ case 'preview':
+ return new PreviewHandler(buttonConfig, ppcpConfig, externalActionHandler);
+ }
+ }
+}
+
+export default ContextHandlerFactory;
diff --git a/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js b/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js
new file mode 100644
index 000000000..1884203b7
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/MiniCartHandler.js
@@ -0,0 +1,7 @@
+import BaseHandler from "./BaseHandler";
+
+class MiniCartHandler extends BaseHandler {
+
+}
+
+export default MiniCartHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js
new file mode 100644
index 000000000..add275608
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/PayNowHandler.js
@@ -0,0 +1,35 @@
+import Spinner from "../../../../ppcp-button/resources/js/modules/Helper/Spinner";
+import BaseHandler from "./BaseHandler";
+import CheckoutActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler";
+
+class PayNowHandler extends BaseHandler {
+
+ shippingAllowed() {
+ return false;
+ }
+
+ transactionInfo() {
+ return new Promise(async (resolve, reject) => {
+ const data = this.ppcpConfig['pay_now'];
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+ });
+ }
+
+ actionHandler() {
+ return new CheckoutActionHandler(
+ this.ppcpConfig,
+ this.errorHandler(),
+ new Spinner()
+ );
+ }
+
+}
+
+export default PayNowHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
new file mode 100644
index 000000000..a637f078d
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/PreviewHandler.js
@@ -0,0 +1,31 @@
+import BaseHandler from "./BaseHandler";
+
+class CartHandler extends BaseHandler {
+
+ constructor(buttonConfig, ppcpConfig, externalHandler) {
+ super(buttonConfig, ppcpConfig, externalHandler);
+ }
+
+ transactionInfo() {
+ throw new Error('Transaction info fail. This is just a preview.');
+ }
+
+ createOrder() {
+ throw new Error('Create order fail. This is just a preview.');
+ }
+
+ approveOrder(data, actions) {
+ throw new Error('Approve order fail. This is just a preview.');
+ }
+
+ actionHandler() {
+ throw new Error('Action handler fail. This is just a preview.');
+ }
+
+ errorHandler() {
+ throw new Error('Error handler fail. This is just a preview.');
+ }
+
+}
+
+export default CartHandler;
diff --git a/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js
new file mode 100644
index 000000000..a014f2ae6
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Context/SingleProductHandler.js
@@ -0,0 +1,65 @@
+import SingleProductActionHandler
+ from "../../../../ppcp-button/resources/js/modules/ActionHandler/SingleProductActionHandler";
+import SimulateCart from "../../../../ppcp-button/resources/js/modules/Helper/SimulateCart";
+import ErrorHandler from "../../../../ppcp-button/resources/js/modules/ErrorHandler";
+import UpdateCart from "../../../../ppcp-button/resources/js/modules/Helper/UpdateCart";
+import BaseHandler from "./BaseHandler";
+
+class SingleProductHandler extends BaseHandler {
+
+ transactionInfo() {
+ const errorHandler = new ErrorHandler(
+ this.ppcpConfig.labels.error.generic,
+ document.querySelector('.woocommerce-notices-wrapper')
+ );
+
+ function form() {
+ return document.querySelector('form.cart');
+ }
+
+ const actionHandler = new SingleProductActionHandler(
+ null,
+ null,
+ form(),
+ errorHandler,
+ );
+
+ const hasSubscriptions = PayPalCommerceGateway.data_client_id.has_subscriptions
+ && PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled;
+
+ const products = hasSubscriptions
+ ? actionHandler.getSubscriptionProducts()
+ : actionHandler.getProducts();
+
+ return new Promise((resolve, reject) => {
+ (new SimulateCart(
+ this.ppcpConfig.ajax.simulate_cart.endpoint,
+ this.ppcpConfig.ajax.simulate_cart.nonce,
+ )).simulate((data) => {
+
+ resolve({
+ countryCode: data.country_code,
+ currencyCode: data.currency_code,
+ totalPriceStatus: 'FINAL',
+ totalPrice: data.total_str
+ });
+
+ }, products);
+ });
+ }
+
+ actionHandler() {
+ return new SingleProductActionHandler(
+ this.ppcpConfig,
+ new UpdateCart(
+ this.ppcpConfig.ajax.change_cart.endpoint,
+ this.ppcpConfig.ajax.change_cart.nonce,
+ ),
+ document.querySelector('form.cart'),
+ this.errorHandler(),
+ );
+ }
+
+}
+
+export default SingleProductHandler;
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayButton.js b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
new file mode 100644
index 000000000..9ecb778d5
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/GooglepayButton.js
@@ -0,0 +1,383 @@
+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";
+import UpdatePaymentData from "./Helper/UpdatePaymentData";
+
+class GooglepayButton {
+
+ constructor(context, externalHandler, buttonConfig, ppcpConfig) {
+ this.isInitialized = false;
+
+ this.context = context;
+ this.externalHandler = externalHandler;
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+
+ this.paymentsClient = null;
+
+ this.contextHandler = ContextHandlerFactory.create(
+ this.context,
+ this.buttonConfig,
+ this.ppcpConfig,
+ this.externalHandler
+ );
+
+ this.log = function() {
+ if ( this.buttonConfig.is_debug ) {
+ console.log('[GooglePayButton]', ...arguments);
+ }
+ }
+ }
+
+ init(config) {
+ if (this.isInitialized) {
+ return;
+ }
+ this.isInitialized = true;
+
+ if (!this.validateConfig()) {
+ return;
+ }
+
+ this.googlePayConfig = config;
+ 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);
+ }
+
+ 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';
+ }
+
+ 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();
+
+ 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);
+
+ 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
+ */
+ async onButtonClick() {
+ this.log('onButtonClick', this.context);
+
+ const paymentDataRequest = await 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);
+ }
+
+ async paymentDataRequest() {
+ let baseRequest = {
+ apiVersion: 2,
+ apiVersionMinor: 0
+ }
+
+ const googlePayConfig = this.googlePayConfig;
+ const paymentDataRequest = Object.assign({}, baseRequest);
+ paymentDataRequest.allowedPaymentMethods = googlePayConfig.allowedPaymentMethods;
+ paymentDataRequest.transactionInfo = await this.contextHandler.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) => {
+ let paymentDataRequestUpdate = {};
+
+ const updatedData = await (new UpdatePaymentData(this.buttonConfig.ajax.update_payment_data)).update(paymentData);
+ const transactionInfo = await this.contextHandler.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 || !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);
+ });
+ }
+
+ 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;
+ }
+
+}
+
+export default GooglepayButton;
diff --git a/modules/ppcp-googlepay/resources/js/GooglepayManager.js b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
new file mode 100644
index 000000000..72475cfe5
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/GooglepayManager.js
@@ -0,0 +1,49 @@
+import buttonModuleWatcher from "../../../ppcp-button/resources/js/modules/ButtonModuleWatcher";
+import GooglepayButton from "./GooglepayButton";
+
+class GooglepayManager {
+
+ constructor(buttonConfig, ppcpConfig) {
+
+ this.buttonConfig = buttonConfig;
+ this.ppcpConfig = ppcpConfig;
+ this.googlePayConfig = null;
+
+ this.buttons = [];
+
+ buttonModuleWatcher.watchContextBootstrap((bootstrap) => {
+ const button = new GooglepayButton(
+ bootstrap.context,
+ bootstrap.handler,
+ buttonConfig,
+ ppcpConfig,
+ );
+
+ this.buttons.push(button);
+
+ if (this.googlePayConfig) {
+ button.init(this.googlePayConfig);
+ }
+ });
+ }
+
+ init() {
+ (async () => {
+ // Gets GooglePay configuration of the PayPal merchant.
+ this.googlePayConfig = await paypal.Googlepay().config();
+
+ for (const button of this.buttons) {
+ button.init(this.googlePayConfig);
+ }
+ })();
+ }
+
+ reinit() {
+ for (const button of this.buttons) {
+ button.reinit();
+ }
+ }
+
+}
+
+export default GooglepayManager;
diff --git a/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js
new file mode 100644
index 000000000..3d56d9316
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/Helper/UpdatePaymentData.js
@@ -0,0 +1,38 @@
+
+class UpdatePaymentData {
+
+ constructor(config) {
+ this.config = config;
+ }
+
+ update(paymentData) {
+ return new Promise((resolve, reject) => {
+ fetch(
+ this.config.endpoint,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: this.config.nonce,
+ paymentData: paymentData,
+ })
+
+ }
+ )
+ .then(result => result.json())
+ .then(result => {
+ if (!result.success) {
+ return;
+ }
+
+ resolve(result.data);
+ });
+ });
+ }
+
+}
+
+export default UpdatePaymentData;
diff --git a/modules/ppcp-googlepay/resources/js/boot-admin.js b/modules/ppcp-googlepay/resources/js/boot-admin.js
new file mode 100644
index 000000000..577733b6b
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/boot-admin.js
@@ -0,0 +1,146 @@
+import {loadCustomScript} from "@paypal/paypal-js";
+import GooglepayButton from "./GooglepayButton";
+import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
+
+(function ({
+ buttonConfig,
+ jQuery
+}) {
+
+ let googlePayConfig;
+ let buttonQueue = [];
+ let activeButtons = {};
+ let bootstrapped = false;
+
+ // React to PayPal config changes.
+ jQuery(document).on('ppcp_paypal_render_preview', (ev, ppcpConfig) => {
+ if (bootstrapped) {
+ createButton(ppcpConfig);
+ } else {
+ buttonQueue.push({
+ ppcpConfig: JSON.parse(JSON.stringify(ppcpConfig))
+ });
+ }
+ });
+
+ // React to GooglePay config changes.
+ jQuery([
+ '#ppcp-googlepay_button_enabled',
+ '#ppcp-googlepay_button_type',
+ '#ppcp-googlepay_button_color',
+ '#ppcp-googlepay_button_language',
+ '#ppcp-googlepay_button_shipping_enabled'
+ ].join(',')).on('change', () => {
+ for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
+ createButton(ppcpConfig);
+ }
+ });
+
+ // Maybe we can find a more elegant reload method when transitioning from styling modes.
+ jQuery([
+ '#ppcp-smart_button_enable_styling_per_location'
+ ].join(',')).on('change', () => {
+ setTimeout(() => {
+ for (const [selector, ppcpConfig] of Object.entries(activeButtons)) {
+ createButton(ppcpConfig);
+ }
+ }, 100);
+ });
+
+ const applyConfigOptions = function (buttonConfig) {
+ buttonConfig.button = buttonConfig.button || {};
+ buttonConfig.button.style = buttonConfig.button.style || {};
+ buttonConfig.button.style.type = jQuery('#ppcp-googlepay_button_type').val();
+ buttonConfig.button.style.color = jQuery('#ppcp-googlepay_button_color').val();
+ buttonConfig.button.style.language = jQuery('#ppcp-googlepay_button_language').val();
+ }
+
+ const createButton = function (ppcpConfig) {
+ const selector = ppcpConfig.button.wrapper + 'GooglePay';
+
+ if (!jQuery('#ppcp-googlepay_button_enabled').is(':checked')) {
+ jQuery(selector).remove();
+ return;
+ }
+
+ buttonConfig = JSON.parse(JSON.stringify(buttonConfig));
+ buttonConfig.button.wrapper = selector;
+ applyConfigOptions(buttonConfig);
+
+ const wrapperElement = `
`;
+
+ if (!jQuery(selector).length) {
+ jQuery(ppcpConfig.button.wrapper).after(wrapperElement);
+ } else {
+ jQuery(selector).replaceWith(wrapperElement);
+ }
+
+ const button = new GooglepayButton(
+ 'preview',
+ null,
+ buttonConfig,
+ ppcpConfig,
+ );
+
+ button.init(googlePayConfig);
+
+ activeButtons[selector] = ppcpConfig;
+ }
+
+ const bootstrap = async function () {
+ if (!widgetBuilder.paypal) {
+ return;
+ }
+
+ googlePayConfig = await widgetBuilder.paypal.Googlepay().config();
+
+ // We need to set bootstrapped here otherwise googlePayConfig may not be set.
+ bootstrapped = true;
+
+ let options;
+ while (options = buttonQueue.pop()) {
+ createButton(options.ppcpConfig);
+ }
+ };
+
+ document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+
+ if (typeof (buttonConfig) === 'undefined') {
+ console.error('PayPal button could not be configured.');
+ return;
+ }
+
+ let paypalLoaded = false;
+ let googlePayLoaded = false;
+
+ const tryToBoot = () => {
+ if (!bootstrapped && paypalLoaded && googlePayLoaded) {
+ bootstrap();
+ }
+ }
+
+ // Load GooglePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ googlePayLoaded = true;
+ tryToBoot();
+ });
+
+ // Wait for PayPal to be loaded externally
+ if (typeof widgetBuilder.paypal !== 'undefined') {
+ paypalLoaded = true;
+ tryToBoot();
+ }
+
+ jQuery(document).on('ppcp-paypal-loaded', () => {
+ paypalLoaded = true;
+ tryToBoot();
+ });
+ },
+ );
+
+})({
+ buttonConfig: window.wc_ppcp_googlepay_admin,
+ jQuery: window.jQuery
+});
diff --git a/modules/ppcp-googlepay/resources/js/boot-block.js b/modules/ppcp-googlepay/resources/js/boot-block.js
new file mode 100644
index 000000000..8b7ab78fb
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/boot-block.js
@@ -0,0 +1,71 @@
+import {useEffect, useState} from '@wordpress/element';
+import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
+import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
+import GooglepayManager from "./GooglepayManager";
+import {loadCustomScript} from "@paypal/paypal-js";
+
+const ppcpData = wc.wcSettings.getSetting('ppcp-gateway_data');
+const ppcpConfig = ppcpData.scriptData;
+
+const buttonData = wc.wcSettings.getSetting('ppcp-googlepay_data');
+const buttonConfig = buttonData.scriptData;
+
+if (typeof window.PayPalCommerceGateway === 'undefined') {
+ window.PayPalCommerceGateway = ppcpConfig;
+}
+
+const GooglePayComponent = () => {
+ const [bootstrapped, setBootstrapped] = useState(false);
+ const [paypalLoaded, setPaypalLoaded] = useState(false);
+ const [googlePayLoaded, setGooglePayLoaded] = useState(false);
+
+ const bootstrap = function () {
+ const manager = new GooglepayManager(buttonConfig, ppcpConfig);
+ manager.init();
+ };
+
+ useEffect(() => {
+ const bodyClass = 'ppcp-has-googlepay-block';
+ if (!document.body.classList.contains(bodyClass)) {
+ document.body.classList.add(bodyClass);
+ }
+ }, []);
+
+ useEffect(() => {
+ // Load GooglePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ setGooglePayLoaded(true);
+ });
+
+ // Load PayPal
+ loadPaypalScript(ppcpConfig, () => {
+ setPaypalLoaded(true);
+ });
+ }, []);
+
+ useEffect(() => {
+ if (!bootstrapped && paypalLoaded && googlePayLoaded) {
+ setBootstrapped(true);
+ bootstrap();
+ }
+ }, [paypalLoaded, googlePayLoaded]);
+
+ return (
+
+ );
+}
+
+const features = ['products'];
+let registerMethod = registerExpressPaymentMethod;
+
+registerMethod({
+ name: buttonData.id,
+ label:
,
+ content: ,
+ edit: ,
+ ariaLabel: buttonData.title,
+ canMakePayment: () => buttonData.enabled,
+ supports: {
+ features: features,
+ },
+});
diff --git a/modules/ppcp-googlepay/resources/js/boot.js b/modules/ppcp-googlepay/resources/js/boot.js
new file mode 100644
index 000000000..731f78d80
--- /dev/null
+++ b/modules/ppcp-googlepay/resources/js/boot.js
@@ -0,0 +1,64 @@
+import {loadCustomScript} from "@paypal/paypal-js";
+import {loadPaypalScript} from "../../../ppcp-button/resources/js/modules/Helper/ScriptLoading";
+import GooglepayManager from "./GooglepayManager";
+
+(function ({
+ buttonConfig,
+ ppcpConfig,
+ jQuery
+}) {
+
+ let manager;
+
+ const bootstrap = function () {
+ manager = new GooglepayManager(buttonConfig, ppcpConfig);
+ manager.init();
+ };
+
+ jQuery(document.body).on('updated_cart_totals updated_checkout', () => {
+ if (manager) {
+ manager.reinit();
+ }
+ });
+
+ document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ if (
+ (typeof (buttonConfig) === 'undefined') ||
+ (typeof (ppcpConfig) === 'undefined')
+ ) {
+ // No PayPal buttons present on this page.
+ return;
+ }
+
+ let bootstrapped = false;
+ let paypalLoaded = false;
+ let googlePayLoaded = false;
+
+ const tryToBoot = () => {
+ if (!bootstrapped && paypalLoaded && googlePayLoaded) {
+ bootstrapped = true;
+ bootstrap();
+ }
+ }
+
+ // Load GooglePay SDK
+ loadCustomScript({ url: buttonConfig.sdk_url }).then(() => {
+ googlePayLoaded = true;
+ tryToBoot();
+ });
+
+ // Load PayPal
+ loadPaypalScript(ppcpConfig, () => {
+ paypalLoaded = true;
+ tryToBoot();
+ });
+ },
+ );
+
+})({
+ buttonConfig: window.wc_ppcp_googlepay,
+ ppcpConfig: window.PayPalCommerceGateway,
+ jQuery: window.jQuery
+});
diff --git a/modules/ppcp-googlepay/services.php b/modules/ppcp-googlepay/services.php
new file mode 100644
index 000000000..02a9ccc9d
--- /dev/null
+++ b/modules/ppcp-googlepay/services.php
@@ -0,0 +1,234 @@
+ static function ( ContainerInterface $container ): bool {
+ $apm_applies = $container->get( 'googlepay.helpers.apm-applies' );
+ assert( $apm_applies instanceof ApmApplies );
+
+ return $apm_applies->for_country_currency();
+ },
+
+ 'googlepay.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
+ return new ApmApplies(
+ $container->get( 'googlepay.supported-country-currency-matrix' ),
+ $container->get( 'api.shop.currency' ),
+ $container->get( 'api.shop.country' )
+ );
+ },
+
+ // If GooglePay is configured and onboarded.
+ '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 );
+ /**
+ * If merchant isn't onboarded via /v1/customer/partner-referrals this returns false as the API call fails.
+ */
+ return apply_filters( 'woocommerce_paypal_payments_googlepay_product_status', $status->is_active() );
+ }
+ return true;
+ },
+
+ // We assume it's a referral if we can check product status without API request failures.
+ '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 {
+ return new AvailabilityNotice(
+ $container->get( 'googlepay.helpers.apm-product-status' ),
+ $container->get( 'wcgateway.is-wc-gateways-list-page' ),
+ $container->get( 'wcgateway.is-ppcp-settings-page' )
+ );
+ },
+
+ 'googlepay.helpers.apm-product-status' => SingletonDecorator::make(
+ static function( ContainerInterface $container ): ApmProductStatus {
+ return new ApmProductStatus(
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'api.endpoint.partners' ),
+ $container->get( 'onboarding.state' ),
+ $container->get( 'api.helper.failure-registry' )
+ );
+ }
+ ),
+
+ /**
+ * The matrix which countries and currency combinations can be used for GooglePay.
+ */
+ 'googlepay.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
+ /**
+ * Returns which countries and currency combinations can be used for GooglePay.
+ */
+ return apply_filters(
+ 'woocommerce_paypal_payments_googlepay_supported_country_currency_matrix',
+ array(
+ 'GB' => array(
+ 'AUD',
+ 'CAD',
+ 'CHF',
+ 'CZK',
+ 'DKK',
+ 'EUR',
+ 'GBP',
+ 'HKD',
+ 'HUF',
+ 'JPY',
+ 'NOK',
+ 'NZD',
+ 'PLN',
+ 'SEK',
+ 'SGD',
+ 'USD',
+ ),
+ 'US' => array(
+ 'AUD',
+ 'CAD',
+ 'EUR',
+ 'GBP',
+ 'JPY',
+ 'USD',
+ ),
+ 'CA' => array(
+ 'AUD',
+ 'CAD',
+ 'CHF',
+ 'CZK',
+ 'DKK',
+ 'EUR',
+ 'GBP',
+ 'HKD',
+ 'HUF',
+ 'JPY',
+ 'NOK',
+ 'NZD',
+ 'PLN',
+ 'SEK',
+ 'SGD',
+ 'USD',
+ ),
+ )
+ );
+ },
+
+ 'googlepay.button' => static function ( ContainerInterface $container ): ButtonInterface {
+ return new Button(
+ $container->get( 'googlepay.url' ),
+ $container->get( 'googlepay.sdk_url' ),
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'session.handler' ),
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'onboarding.environment' ),
+ $container->get( 'wcgateway.settings.status' ),
+ $container->get( 'api.shop.currency' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+
+ 'googlepay.blocks-payment-method' => static function ( ContainerInterface $container ): PaymentMethodTypeInterface {
+ return new BlocksPaymentMethod(
+ 'ppcp-googlepay',
+ $container->get( 'googlepay.url' ),
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'googlepay.button' ),
+ $container->get( 'blocks.method' )
+ );
+ },
+
+ 'googlepay.url' => static function ( ContainerInterface $container ): string {
+ $path = realpath( __FILE__ );
+ if ( false === $path ) {
+ return '';
+ }
+ return plugins_url(
+ '/modules/ppcp-googlepay/',
+ dirname( $path, 3 ) . '/woocommerce-paypal-payments.php'
+ );
+ },
+
+ '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 {
+ return new UpdatePaymentDataEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+
+ '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 {
+ return 'https://www.paypal.com/bizsignup/add-product?product=payment_methods&capabilities=GOOGLE_PAY';
+ },
+
+ 'googlepay.settings.connection.status-text' => static function ( ContainerInterface $container ): string {
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() < State::STATE_ONBOARDED ) {
+ return '';
+ }
+
+ $product_status = $container->get( 'googlepay.helpers.apm-product-status' );
+ assert( $product_status instanceof ApmProductStatus );
+
+ $environment = $container->get( 'onboarding.environment' );
+ assert( $environment instanceof Environment );
+
+ $enabled = $product_status->is_active();
+
+ $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
+ $disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
+
+ $button_text = $enabled
+ ? esc_html__( 'Settings', 'woocommerce-paypal-payments' )
+ : esc_html__( 'Enable Google Pay', 'woocommerce-paypal-payments' );
+
+ $enable_url = $environment->current_environment_is( Environment::PRODUCTION )
+ ? $container->get( 'googlepay.enable-url-live' )
+ : $container->get( 'googlepay.enable-url-sandbox' );
+
+ $button_url = $enabled
+ ? admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway#field-alternative_payment_methods' )
+ : $enable_url;
+
+ return sprintf(
+ '%1$s %2$s
%5$s
',
+ $enabled ? $enabled_status_text : $disabled_status_text,
+ $enabled ? ' ' : ' ',
+ $enabled ? '_self' : '_blank',
+ esc_url( $button_url ),
+ esc_html( $button_text )
+ );
+ },
+
+);
diff --git a/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php b/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php
new file mode 100644
index 000000000..0f9ff871b
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Assets/BlocksPaymentMethod.php
@@ -0,0 +1,114 @@
+name = $name;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->button = $button;
+ $this->paypal_payment_method = $paypal_payment_method;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function initialize() { }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function is_active() {
+ return $this->paypal_payment_method->is_active();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_script_handles() {
+ $handle = $this->name . '-block';
+
+ wp_register_script(
+ $handle,
+ trailingslashit( $this->module_url ) . 'assets/js/boot-block.js',
+ array(),
+ $this->version,
+ true
+ );
+
+ return array( $handle );
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function get_payment_method_data() {
+ $paypal_data = $this->paypal_payment_method->get_payment_method_data();
+
+ return array(
+ 'id' => $this->name,
+ 'title' => $paypal_data['title'], // See if we should use another.
+ 'description' => $paypal_data['description'], // See if we should use another.
+ 'enabled' => $paypal_data['enabled'], // This button is enabled when PayPal buttons are.
+ 'scriptData' => $this->button->script_data(),
+ );
+ }
+}
diff --git a/modules/ppcp-googlepay/src/Assets/Button.php b/modules/ppcp-googlepay/src/Assets/Button.php
new file mode 100644
index 000000000..df482c73f
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Assets/Button.php
@@ -0,0 +1,477 @@
+module_url = $module_url;
+ $this->sdk_url = $sdk_url;
+ $this->version = $version;
+ $this->session_handler = $session_handler;
+ $this->settings = $settings;
+ $this->environment = $environment;
+ $this->settings_status = $settings_status;
+ $this->currency = $currency;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Initializes the button.
+ */
+ public function initialize(): void {
+ add_filter( 'ppcp_onboarding_options', array( $this, 'add_onboarding_options' ), 10, 1 );
+ add_filter( 'ppcp_partner_referrals_option', array( $this, 'filter_partner_referrals_option' ), 10, 1 );
+ add_filter( 'ppcp_partner_referrals_data', array( $this, 'add_partner_referrals_data' ), 10, 1 );
+ }
+
+ /**
+ * Adds the GooglePay onboarding option.
+ *
+ * @param string $options The options.
+ * @return string
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ public function add_onboarding_options( $options ): string {
+ if ( ! apply_filters( 'woocommerce_paypal_payments_google_pay_onboarding_option', false ) ) {
+ return $options;
+ }
+
+ $checked = '';
+ try {
+ $onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' );
+ if ( $onboard_with_google === '1' ) {
+ $checked = 'checked';
+ }
+ } catch ( NotFoundException $exception ) {
+ $checked = '';
+ }
+
+ return $options
+ . ' '
+ . __( 'Onboard with GooglePay', 'woocommerce-paypal-payments' )
+ . ' ';
+ }
+
+ /**
+ * Filters a partner referrals option.
+ *
+ * @param array $option The option data.
+ * @return array
+ */
+ public function filter_partner_referrals_option( array $option ): array {
+ if ( $option['valid'] ) {
+ return $option;
+ }
+ if ( $option['field'] === 'ppcp-onboarding-google' ) {
+ $option['valid'] = true;
+ $option['value'] = ( $option['value'] ? '1' : '' );
+ }
+ return $option;
+ }
+
+ /**
+ * Adds to partner referrals data.
+ *
+ * @param array $data The referrals data.
+ * @return array
+ */
+ public function add_partner_referrals_data( array $data ): array {
+ try {
+ $onboard_with_google = $this->settings->get( 'ppcp-onboarding-google' );
+ if ( ! wc_string_to_bool( $onboard_with_google ) ) {
+ return $data;
+ }
+ } catch ( NotFoundException $exception ) {
+ return $data;
+ }
+
+ if ( ! in_array( 'PAYMENT_METHODS', $data['products'], true ) ) {
+ if ( in_array( 'PPCP', $data['products'], true ) ) {
+ $data['products'][] = 'PAYMENT_METHODS';
+ } elseif ( in_array( 'EXPRESS_CHECKOUT', $data['products'], true ) ) { // A bit sketchy, maybe replace on the EXPRESS_CHECKOUT index.
+ $data['products'][0] = 'PAYMENT_METHODS';
+ }
+ }
+
+ $data['capabilities'][] = 'GOOGLE_PAY';
+
+ return $data;
+ }
+
+ /**
+ * Returns if Google Pay button is enabled
+ *
+ * @return bool
+ */
+ public function is_enabled(): bool {
+ try {
+ return $this->settings->has( 'googlepay_button_enabled' ) && $this->settings->get( 'googlepay_button_enabled' );
+ } catch ( Exception $e ) {
+ return false;
+ }
+ }
+
+ /**
+ * Registers the necessary action hooks to render the HTML depending on the settings.
+ *
+ * @return bool
+ *
+ * @psalm-suppress RedundantCondition
+ */
+ public function render(): bool {
+ if ( ! $this->is_enabled() ) {
+ return false;
+ }
+
+ $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_payorder = true;
+ $button_enabled_minicart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
+
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ add_filter(
+ 'woocommerce_paypal_payments_sdk_components_hook',
+ function( $components ) {
+ $components[] = 'googlepay';
+ return $components;
+ }
+ );
+
+ if ( $button_enabled_product ) {
+ $default_hook_name = 'woocommerce_paypal_payments_single_product_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_single_product_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->googlepay_button();
+ },
+ 32
+ );
+ }
+
+ if ( $button_enabled_cart ) {
+ $default_hook_name = 'woocommerce_paypal_payments_cart_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_cart_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->googlepay_button();
+ },
+ 21
+ );
+ }
+
+ if ( $button_enabled_checkout ) {
+ $default_hook_name = 'woocommerce_paypal_payments_checkout_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_checkout_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->googlepay_button();
+ },
+ 21
+ );
+ }
+
+ if ( $button_enabled_payorder ) {
+ $default_hook_name = 'woocommerce_paypal_payments_payorder_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_payorder_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ $this->googlepay_button();
+ },
+ 21
+ );
+ }
+
+ if ( $button_enabled_minicart ) {
+ $default_hook_name = 'woocommerce_paypal_payments_minicart_button_render';
+ $render_placeholder = apply_filters( 'woocommerce_paypal_payments_googlepay_minicart_button_render_hook', $default_hook_name );
+ $render_placeholder = is_string( $render_placeholder ) ? $render_placeholder : $default_hook_name;
+ add_action(
+ $render_placeholder,
+ function () {
+ echo ' ';
+ },
+ 21
+ );
+ }
+
+ return true;
+ }
+
+ /**
+ * GooglePay button markup
+ */
+ private function googlepay_button(): void {
+ ?>
+
+
+
+ is_enabled() ) {
+ return;
+ }
+
+ wp_register_script(
+ 'wc-ppcp-googlepay',
+ untrailingslashit( $this->module_url ) . '/assets/js/boot.js',
+ array(),
+ $this->version,
+ true
+ );
+ wp_enqueue_script( 'wc-ppcp-googlepay' );
+
+ $this->enqueue_styles();
+
+ wp_localize_script(
+ 'wc-ppcp-googlepay',
+ 'wc_ppcp_googlepay',
+ $this->script_data()
+ );
+ }
+
+ /**
+ * Enqueues styles.
+ */
+ public function enqueue_styles(): void {
+ if ( ! $this->is_enabled() ) {
+ return;
+ }
+
+ wp_register_style(
+ 'wc-ppcp-googlepay',
+ untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
+ array(),
+ $this->version
+ );
+ wp_enqueue_style( 'wc-ppcp-googlepay' );
+ }
+
+ /**
+ * Enqueues scripts/styles for admin.
+ */
+ public function enqueue_admin(): void {
+ wp_register_style(
+ 'wc-ppcp-googlepay-admin',
+ untrailingslashit( $this->module_url ) . '/assets/css/styles.css',
+ array(),
+ $this->version
+ );
+ wp_enqueue_style( 'wc-ppcp-googlepay-admin' );
+
+ wp_register_script(
+ 'wc-ppcp-googlepay-admin',
+ untrailingslashit( $this->module_url ) . '/assets/js/boot-admin.js',
+ array(),
+ $this->version,
+ true
+ );
+ wp_enqueue_script( 'wc-ppcp-googlepay-admin' );
+
+ wp_localize_script(
+ 'wc-ppcp-googlepay-admin',
+ 'wc_ppcp_googlepay_admin',
+ $this->script_data()
+ );
+ }
+
+ /**
+ * The configuration for the smart buttons.
+ *
+ * @return array
+ */
+ public function script_data(): array {
+ $shipping = array(
+ 'enabled' => $this->settings->has( 'googlepay_button_shipping_enabled' )
+ ? boolval( $this->settings->get( 'googlepay_button_shipping_enabled' ) )
+ : false,
+ );
+
+ if ( $shipping['enabled'] ) {
+ $shipping['countries'] = array_keys( $this->wc_countries()->get_shipping_countries() );
+ }
+
+ return array(
+ 'environment' => $this->environment->current_environment_is( Environment::SANDBOX ) ? 'TEST' : 'PRODUCTION',
+ 'is_debug' => defined( 'WP_DEBUG' ) && WP_DEBUG ? true : false,
+ '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(
+ 'update_payment_data' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( UpdatePaymentDataEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( UpdatePaymentDataEndpoint::nonce() ),
+ ),
+ ),
+ );
+ }
+
+ /**
+ * Determines the style for a given indicator in a given context.
+ *
+ * @param string $context The context.
+ *
+ * @return array
+ */
+ private function button_styles_for_context( string $context ): array {
+ // Use the cart/checkout styles for blocks.
+ $context = str_replace( '-block', '', $context );
+
+ $values = array(
+ 'color' => 'black',
+ 'type' => 'pay',
+ 'language' => 'en',
+ );
+
+ foreach ( $values as $style => $value ) {
+ if ( $this->settings->has( 'googlepay_button_' . $context . '_' . $style ) ) {
+ $values[ $style ] = $this->settings->get( 'googlepay_button_' . $context . '_' . $style );
+ } elseif ( $this->settings->has( 'googlepay_button_' . $style ) ) {
+ $values[ $style ] = $this->settings->get( 'googlepay_button_' . $style );
+ }
+ }
+
+ return $values;
+ }
+
+ /**
+ * Returns a WC_Countries instance to check shipping
+ *
+ * @return WC_Countries
+ */
+ private function wc_countries(): WC_Countries {
+ return new WC_Countries();
+ }
+}
diff --git a/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php
new file mode 100644
index 000000000..27da5ef48
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Endpoint/UpdatePaymentDataEndpoint.php
@@ -0,0 +1,219 @@
+request_data = $request_data;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Returns the nonce.
+ *
+ * @return string
+ */
+ public static function nonce(): string {
+ return self::ENDPOINT;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return bool
+ * @throws RuntimeException When a validation fails.
+ */
+ public function handle_request(): bool {
+ try {
+ $data = $this->request_data->read_request( $this->nonce() );
+
+ // Validate payment data.
+ if ( ! isset( $data['paymentData'] ) ) {
+ throw new RuntimeException(
+ __( 'No paymentData provided.', 'woocommerce-paypal-payments' )
+ );
+ }
+
+ $payment_data = $data['paymentData'];
+
+ // Set context as cart.
+ if ( is_callable( 'wc_maybe_define_constant' ) ) {
+ wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
+ }
+
+ $this->update_addresses( $payment_data );
+ $this->update_shipping_method( $payment_data );
+
+ WC()->cart->calculate_shipping();
+ WC()->cart->calculate_fees();
+ WC()->cart->calculate_totals();
+
+ $total = (float) WC()->cart->get_total( 'numeric' );
+
+ // Shop settings.
+ $base_location = wc_get_base_location();
+ $shop_country_code = $base_location['country'];
+ $currency_code = get_woocommerce_currency();
+
+ wp_send_json_success(
+ array(
+ 'total' => $total,
+ 'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
+ 'currency_code' => $currency_code,
+ 'country_code' => $shop_country_code,
+ 'shipping_options' => $this->get_shipping_options(),
+ )
+ );
+
+ return true;
+ } catch ( Throwable $error ) {
+ $this->logger->error( "UpdatePaymentDataEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
+
+ wp_send_json_error();
+ return false;
+ }
+ }
+
+ /**
+ * Returns the array of available shipping methods.
+ *
+ * @return array
+ */
+ public function get_shipping_options(): array {
+ $shipping_options = array();
+
+ $calculated_packages = WC()->shipping->calculate_shipping(
+ WC()->cart->get_shipping_packages()
+ );
+
+ if ( ! isset( $calculated_packages[0] ) && ! isset( $calculated_packages[0]['rates'] ) ) {
+ return array();
+ }
+
+ foreach ( $calculated_packages[0]['rates'] as $rate ) {
+ /**
+ * The shipping rate.
+ *
+ * @var \WC_Shipping_Rate $rate
+ */
+ $shipping_options[] = array(
+ 'id' => $rate->get_id(),
+ 'label' => $rate->get_label(),
+ 'description' => html_entity_decode(
+ wp_strip_all_tags(
+ wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) )
+ )
+ ),
+ );
+ }
+
+ if ( ! isset( $shipping_options[0] ) ) {
+ return array();
+ }
+
+ $chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods' );
+
+ return array(
+ 'defaultSelectedOptionId' => ( $chosen_shipping_methods[0] ?? null ) ? $chosen_shipping_methods[0] : $shipping_options[0]['id'],
+ 'shippingOptions' => $shipping_options,
+ );
+ }
+
+ /**
+ * Update addresses.
+ *
+ * @param array $payment_data The payment data.
+ * @return void
+ */
+ private function update_addresses( array $payment_data ): void {
+ if ( ! in_array( $payment_data['callbackTrigger'] ?? '', array( 'SHIPPING_ADDRESS', 'INITIALIZE' ), true ) ) {
+ return;
+ }
+
+ /**
+ * The shipping methods.
+ *
+ * @var \WC_Customer|null $customer
+ */
+ $customer = WC()->customer;
+
+ if ( ! $customer ) {
+ return;
+ }
+
+ $customer->set_billing_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' );
+ $customer->set_billing_country( $payment_data['shippingAddress']['countryCode'] ?? '' );
+ $customer->set_billing_state( '' );
+ $customer->set_billing_city( $payment_data['shippingAddress']['locality'] ?? '' );
+
+ $customer->set_shipping_postcode( $payment_data['shippingAddress']['postalCode'] ?? '' );
+ $customer->set_shipping_country( $payment_data['shippingAddress']['countryCode'] ?? '' );
+ $customer->set_shipping_state( '' );
+ $customer->set_shipping_city( $payment_data['shippingAddress']['locality'] ?? '' );
+
+ // Save the data.
+ $customer->save();
+
+ WC()->session->set( 'customer', WC()->customer->get_data() );
+ }
+
+ /**
+ * Update shipping method.
+ *
+ * @param array $payment_data The payment data.
+ * @return void
+ */
+ private function update_shipping_method( array $payment_data ): void {
+ $rate_id = $payment_data['shippingOptionData']['id'];
+ $calculated_packages = WC()->shipping->calculate_shipping(
+ WC()->cart->get_shipping_packages()
+ );
+
+ if ( $rate_id && isset( $calculated_packages[0]['rates'][ $rate_id ] ) ) {
+ WC()->session->set( 'chosen_shipping_methods', array( $rate_id ) );
+ }
+ }
+
+}
diff --git a/modules/ppcp-googlepay/src/GooglepayModule.php b/modules/ppcp-googlepay/src/GooglepayModule.php
new file mode 100644
index 000000000..94f70ea09
--- /dev/null
+++ b/modules/ppcp-googlepay/src/GooglepayModule.php
@@ -0,0 +1,169 @@
+get( 'googlepay.helpers.apm-product-status' );
+ assert( $apm_status instanceof ApmProductStatus );
+ $apm_status->clear( $settings );
+ }
+ );
+
+ add_action(
+ 'init',
+ static function () use ( $c ) {
+
+ // Check if the module is applicable, correct country, currency, ... etc.
+ if ( ! $c->get( 'googlepay.eligible' ) ) {
+ return;
+ }
+
+ // Load the button handler.
+ $button = $c->get( 'googlepay.button' );
+ assert( $button instanceof ButtonInterface );
+ $button->initialize();
+
+ // Show notice if there are product availability issues.
+ $availability_notice = $c->get( 'googlepay.availability_notice' );
+ assert( $availability_notice instanceof AvailabilityNotice );
+ $availability_notice->execute();
+
+ // Check if this merchant can activate / use the buttons.
+ // We allow non referral merchants as they can potentially still use GooglePay, we just have no way of checking the capability.
+ if ( ( ! $c->get( 'googlepay.available' ) ) && $c->get( 'googlepay.is_referral' ) ) {
+ return;
+ }
+
+ // Initializes button rendering.
+ add_action(
+ 'wp',
+ static function () use ( $c, $button ) {
+ if ( is_admin() ) {
+ return;
+ }
+ $button->render();
+ }
+ );
+
+ // Enqueue frontend scripts.
+ add_action(
+ 'wp_enqueue_scripts',
+ static function () use ( $c, $button ) {
+ $smart_button = $c->get( 'button.smart-button' );
+ assert( $smart_button instanceof SmartButtonInterface );
+ if ( $smart_button->should_load_ppcp_script() ) {
+ $button->enqueue();
+ }
+
+ if ( has_block( 'woocommerce/checkout' ) || has_block( 'woocommerce/cart' ) ) {
+ /**
+ * Should add this to the ButtonInterface.
+ *
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $button->enqueue_styles();
+ }
+ }
+ );
+
+ // Enqueue backend scripts.
+ add_action(
+ 'admin_enqueue_scripts',
+ static function () use ( $c, $button ) {
+ if ( ! is_admin() ) {
+ return;
+ }
+
+ /**
+ * Should add this to the ButtonInterface.
+ *
+ * @psalm-suppress UndefinedInterfaceMethod
+ */
+ $button->enqueue_admin();
+ }
+ );
+
+ // Registers buttons on blocks pages.
+ add_action(
+ 'woocommerce_blocks_payment_method_type_registration',
+ function( PaymentMethodRegistry $payment_method_registry ) use ( $c, $button ): void {
+ if ( $button->is_enabled() ) {
+ $payment_method_registry->register( $c->get( 'googlepay.blocks-payment-method' ) );
+ }
+ }
+ );
+
+ // Adds GooglePay component to the backend button preview settings.
+ add_action(
+ 'woocommerce_paypal_payments_admin_gateway_settings',
+ function( array $settings ) use ( $c ): array {
+ if ( is_array( $settings['components'] ) ) {
+ $settings['components'][] = 'googlepay';
+ }
+ return $settings;
+ }
+ );
+
+ // Initialize AJAX endpoints.
+ add_action(
+ 'wc_ajax_' . UpdatePaymentDataEndpoint::ENDPOINT,
+ static function () use ( $c ) {
+ $endpoint = $c->get( 'googlepay.endpoint.update-payment-data' );
+ assert( $endpoint instanceof UpdatePaymentDataEndpoint );
+ $endpoint->handle_request();
+ }
+ );
+
+ }
+ );
+ }
+
+ /**
+ * Returns the key for the module.
+ *
+ * @return string|void
+ */
+ public function getKey() {
+ }
+}
diff --git a/modules/ppcp-googlepay/src/Helper/ApmApplies.php b/modules/ppcp-googlepay/src/Helper/ApmApplies.php
new file mode 100644
index 000000000..c2077dd8b
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Helper/ApmApplies.php
@@ -0,0 +1,67 @@
+allowed_country_currency_matrix = $allowed_country_currency_matrix;
+ $this->currency = $currency;
+ $this->country = $country;
+ }
+
+ /**
+ * Returns whether GooglePay can be used in the current country and the current currency 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 );
+ }
+
+}
diff --git a/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php
new file mode 100644
index 000000000..cf1e6487c
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Helper/ApmProductStatus.php
@@ -0,0 +1,192 @@
+settings = $settings;
+ $this->partners_endpoint = $partners_endpoint;
+ $this->onboarding_state = $onboarding_state;
+ $this->api_failure_registry = $api_failure_registry;
+ }
+
+ /**
+ * Whether the active/subscribed products support Googlepay.
+ *
+ * @return bool
+ */
+ public function is_active() : bool {
+
+ // If not onboarded then makes no sense to check status.
+ if ( ! $this->is_onboarded() ) {
+ return false;
+ }
+
+ // If status was already checked on this request return the same result.
+ if ( null !== $this->current_status ) {
+ return $this->current_status;
+ }
+
+ // Check if status was checked on previous requests.
+ if ( $this->settings->has( self::SETTINGS_KEY ) && ( $this->settings->get( self::SETTINGS_KEY ) ) ) {
+ $this->current_status = wc_string_to_bool( $this->settings->get( self::SETTINGS_KEY ) );
+ return $this->current_status;
+ }
+
+ // Check API failure registry to prevent multiple failed API requests.
+ if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) {
+ $this->has_request_failure = true;
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ // Request seller status via PayPal API.
+ try {
+ $seller_status = $this->partners_endpoint->seller_status();
+ } catch ( Throwable $error ) {
+ $this->has_request_failure = true;
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ // Check the seller status for the intended capability.
+ foreach ( $seller_status->products() as $product ) {
+ if ( $product->name() !== 'PAYMENT_METHODS' ) {
+ continue;
+ }
+
+ if ( in_array( self::CAPABILITY_NAME, $product->capabilities(), true ) ) {
+ // Capability found, persist status and return true.
+ $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_ENABLED );
+ $this->settings->persist();
+
+ $this->current_status = true;
+ return $this->current_status;
+ }
+ }
+
+ // Capability not found, persist status and return false.
+ $this->settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_DISABLED );
+ $this->settings->persist();
+
+ $this->current_status = false;
+ return $this->current_status;
+ }
+
+ /**
+ * Returns if the seller is onboarded.
+ *
+ * @return bool
+ */
+ public function is_onboarded(): bool {
+ return $this->onboarding_state->current_state() >= State::STATE_ONBOARDED;
+ }
+
+ /**
+ * Returns if there was a request failure.
+ *
+ * @return bool
+ */
+ public function has_request_failure(): bool {
+ return $this->has_request_failure;
+ }
+
+ /**
+ * Clears the persisted result to force a recheck.
+ *
+ * @param Settings|null $settings The settings object.
+ * We accept a Settings object to don't override other sequential settings that are being updated elsewhere.
+ * @return void
+ */
+ public function clear( Settings $settings = null ): void {
+ if ( null === $settings ) {
+ $settings = $this->settings;
+ }
+
+ $this->current_status = null;
+
+ if ( $settings->has( self::SETTINGS_KEY ) ) {
+ $settings->set( self::SETTINGS_KEY, self::SETTINGS_VALUE_UNDEFINED );
+ $settings->persist();
+ }
+ }
+
+}
diff --git a/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php
new file mode 100644
index 000000000..df31bdf26
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Helper/AvailabilityNotice.php
@@ -0,0 +1,157 @@
+product_status = $product_status;
+ $this->is_wc_gateways_list_page = $is_wc_gateways_list_page;
+ $this->is_ppcp_settings_page = $is_ppcp_settings_page;
+ }
+
+ /**
+ * Adds availability notice if applicable.
+ *
+ * @return void
+ */
+ public function execute(): void {
+ if ( ! $this->should_display() ) {
+ return;
+ }
+
+ // We need to check is active before checking failure requests, otherwise failure status won't be set.
+ $is_active = $this->product_status->is_active();
+
+ if ( $this->product_status->has_request_failure() ) {
+ $this->add_seller_status_failure_notice();
+ } elseif ( ! $is_active ) {
+ $this->add_not_available_notice();
+ }
+ }
+
+ /**
+ * Whether the message should display.
+ *
+ * @return bool
+ */
+ protected function should_display(): bool {
+ if ( ! $this->product_status->is_onboarded() ) {
+ return false;
+ }
+ if ( ! $this->is_wc_gateways_list_page && ! $this->is_ppcp_settings_page ) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
+ * Adds seller status failure notice.
+ *
+ * @return void
+ */
+ private function add_seller_status_failure_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds seller status notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __(
+ 'Notice: We could not determine your PayPal seller status to list your available features. Disconnect and reconnect your PayPal account through our %1$sonboarding process%2$s to resolve this.
Don\'t worry if you cannot use the %1$sonboarding process%2$s; most functionalities available to your account should work.
',
+ 'woocommerce-paypal-payments'
+ ),
+ ' ',
+ ' '
+ );
+
+ // Name the key so it can be overridden in other modules.
+ $notices['error_product_status'] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+ /**
+ * Adds not available notice.
+ *
+ * @return void
+ */
+ private function add_not_available_notice(): void {
+ add_filter(
+ Repository::NOTICES_FILTER,
+ /**
+ * Adds GooglePay not available notice.
+ *
+ * @param array $notices The notices.
+ * @return array
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ static function ( $notices ): array {
+ $message = sprintf(
+ __(
+ 'Google Pay is not available on your PayPal seller account.',
+ 'woocommerce-paypal-payments'
+ )
+ );
+
+ $notices[] = new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
+ return $notices;
+ }
+ );
+ }
+
+}
diff --git a/modules/ppcp-googlepay/src/Helper/PropertiesDictionary.php b/modules/ppcp-googlepay/src/Helper/PropertiesDictionary.php
new file mode 100644
index 000000000..d76219895
--- /dev/null
+++ b/modules/ppcp-googlepay/src/Helper/PropertiesDictionary.php
@@ -0,0 +1,88 @@
+ __( 'White', 'woocommerce-paypal-payments' ),
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ );
+ }
+
+ /**
+ * Returns the possible list of button types.
+ *
+ * @return array
+ */
+ public static function button_types(): array {
+ return array(
+ 'book' => __( 'Book', 'woocommerce-paypal-payments' ),
+ 'buy' => __( 'Buy', 'woocommerce-paypal-payments' ),
+ 'checkout' => __( 'Checkout', 'woocommerce-paypal-payments' ),
+ 'donate' => __( 'Donate', 'woocommerce-paypal-payments' ),
+ 'order' => __( 'Order', 'woocommerce-paypal-payments' ),
+ 'pay' => __( 'Pay', 'woocommerce-paypal-payments' ),
+ 'plain' => __( 'Plain', 'woocommerce-paypal-payments' ),
+ 'subscribe' => __( 'Subscribe', 'woocommerce-paypal-payments' ),
+ );
+ }
+
+ /**
+ * Returns the possible list of button languages.
+ *
+ * @return array
+ */
+ public static function button_languages(): array {
+ return array(
+ '' => __( 'Browser language', 'woocommerce-paypal-payments' ),
+ 'ar' => __( 'Arabic', 'woocommerce-paypal-payments' ),
+ 'bg' => __( 'Bulgarian', 'woocommerce-paypal-payments' ),
+ 'ca' => __( 'Catalan', 'woocommerce-paypal-payments' ),
+ 'zh' => __( 'Chinese', 'woocommerce-paypal-payments' ),
+ 'hr' => __( 'Croatian', 'woocommerce-paypal-payments' ),
+ 'cs' => __( 'Czech', 'woocommerce-paypal-payments' ),
+ 'da' => __( 'Danish', 'woocommerce-paypal-payments' ),
+ 'nl' => __( 'Dutch', 'woocommerce-paypal-payments' ),
+ 'en' => __( 'English', 'woocommerce-paypal-payments' ),
+ 'et' => __( 'Estonian', 'woocommerce-paypal-payments' ),
+ 'fi' => __( 'Finnish', 'woocommerce-paypal-payments' ),
+ 'fr' => __( 'French', 'woocommerce-paypal-payments' ),
+ 'de' => __( 'German', 'woocommerce-paypal-payments' ),
+ 'el' => __( 'Greek', 'woocommerce-paypal-payments' ),
+ 'id' => __( 'Indonesian', 'woocommerce-paypal-payments' ),
+ 'it' => __( 'Italian', 'woocommerce-paypal-payments' ),
+ 'ja' => __( 'Japanese', 'woocommerce-paypal-payments' ),
+ 'ko' => __( 'Korean', 'woocommerce-paypal-payments' ),
+ 'ms' => __( 'Malay', 'woocommerce-paypal-payments' ),
+ 'no' => __( 'Norwegian', 'woocommerce-paypal-payments' ),
+ 'pl' => __( 'Polish', 'woocommerce-paypal-payments' ),
+ 'pt' => __( 'Portuguese', 'woocommerce-paypal-payments' ),
+ 'ru' => __( 'Russian', 'woocommerce-paypal-payments' ),
+ 'sr' => __( 'Serbian', 'woocommerce-paypal-payments' ),
+ 'sk' => __( 'Slovak', 'woocommerce-paypal-payments' ),
+ 'sl' => __( 'Slovenian', 'woocommerce-paypal-payments' ),
+ 'es' => __( 'Spanish', 'woocommerce-paypal-payments' ),
+ 'sv' => __( 'Swedish', 'woocommerce-paypal-payments' ),
+ 'th' => __( 'Thai', 'woocommerce-paypal-payments' ),
+ 'tr' => __( 'Turkish', 'woocommerce-paypal-payments' ),
+ 'uk' => __( 'Ukrainian', 'woocommerce-paypal-payments' ),
+ );
+ }
+}
diff --git a/modules/ppcp-googlepay/webpack.config.js b/modules/ppcp-googlepay/webpack.config.js
new file mode 100644
index 000000000..6805309e2
--- /dev/null
+++ b/modules/ppcp-googlepay/webpack.config.js
@@ -0,0 +1,41 @@
+const path = require('path');
+const isProduction = process.env.NODE_ENV === 'production';
+
+const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
+
+module.exports = {
+ devtool: isProduction ? 'source-map' : 'eval-source-map',
+ mode: isProduction ? 'production' : 'development',
+ target: 'web',
+ plugins: [ new DependencyExtractionWebpackPlugin() ],
+ entry: {
+ 'boot': path.resolve('./resources/js/boot.js'),
+ 'boot-block': path.resolve('./resources/js/boot-block.js'),
+ 'boot-admin': path.resolve('./resources/js/boot-admin.js'),
+ "styles": path.resolve('./resources/css/styles.scss')
+ },
+ 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-googlepay/yarn.lock b/modules/ppcp-googlepay/yarn.lock
new file mode 100644
index 000000000..ec5854d6c
--- /dev/null
+++ b/modules/ppcp-googlepay/yarn.lock
@@ -0,0 +1,2241 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.22.10", "@babel/code-frame@^7.22.5":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.10.tgz#1c20e612b768fefa75f6e90d6ecb86329247f0a3"
+ integrity sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==
+ dependencies:
+ "@babel/highlight" "^7.22.10"
+ chalk "^2.4.2"
+
+"@babel/compat-data@^7.22.5", "@babel/compat-data@^7.22.6", "@babel/compat-data@^7.22.9":
+ version "7.22.9"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730"
+ integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ==
+
+"@babel/core@^7.19":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35"
+ integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.22.10"
+ "@babel/generator" "^7.22.10"
+ "@babel/helper-compilation-targets" "^7.22.10"
+ "@babel/helper-module-transforms" "^7.22.9"
+ "@babel/helpers" "^7.22.10"
+ "@babel/parser" "^7.22.10"
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.10"
+ "@babel/types" "^7.22.10"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+ semver "^6.3.1"
+
+"@babel/generator@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.10.tgz#c92254361f398e160645ac58831069707382b722"
+ integrity sha512-79KIf7YiWjjdZ81JnLujDRApWtl7BxTqWD88+FFdQEIOG8LJ0etDOM7CXuIgGJa55sGOwZVwuEsaLEm0PJ5/+A==
+ dependencies:
+ "@babel/types" "^7.22.10"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz#e7f06737b197d580a01edf75d97e2c8be99d3882"
+ integrity sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.22.5":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.10.tgz#573e735937e99ea75ea30788b57eb52fab7468c9"
+ integrity sha512-Av0qubwDQxC56DoUReVDeLfMEjYYSN1nZrTUrWkXd7hpU73ymRANkbuDm3yni9npkn+RXy9nNbEJZEzXr7xrfQ==
+ dependencies:
+ "@babel/types" "^7.22.10"
+
+"@babel/helper-compilation-targets@^7.22.10", "@babel/helper-compilation-targets@^7.22.5", "@babel/helper-compilation-targets@^7.22.6":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.10.tgz#01d648bbc25dd88f513d862ee0df27b7d4e67024"
+ integrity sha512-JMSwHD4J7SLod0idLq5PKgI+6g/hLD/iuWBq08ZX49xE14VpVEojJ5rHWptpirV2j020MvypRLAXAO50igCJ5Q==
+ dependencies:
+ "@babel/compat-data" "^7.22.9"
+ "@babel/helper-validator-option" "^7.22.5"
+ browserslist "^4.21.9"
+ lru-cache "^5.1.1"
+ semver "^6.3.1"
+
+"@babel/helper-create-class-features-plugin@^7.22.5":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.10.tgz#dd2612d59eac45588021ac3d6fa976d08f4e95a3"
+ integrity sha512-5IBb77txKYQPpOEdUdIhBx8VrZyDCQ+H82H0+5dX1TmuscP5vJKEE3cKurjtIw/vFwzbVH48VweE78kVDBrqjA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.9"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ semver "^6.3.1"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.5":
+ version "7.22.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.9.tgz#9d8e61a8d9366fe66198f57c40565663de0825f6"
+ integrity sha512-+svjVa/tFwsNSG4NEy1h85+HQ5imbT92Q5/bgtS7P0GTQlP8WuFdqsiABmQouhiFGyV66oGxZFpeYHza1rNsKw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ regexpu-core "^5.3.1"
+ semver "^6.3.1"
+
+"@babel/helper-define-polyfill-provider@^0.4.2":
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.2.tgz#82c825cadeeeee7aad237618ebbe8fa1710015d7"
+ integrity sha512-k0qnnOqHn5dK9pZpfD5XXZ9SojAITdCKRn2Lp6rnDGzIbaP0rHyMPk/4wsSxVBVz4RfN0q6VpXWP2pDGIoQ7hw==
+ 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.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
+ integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
+
+"@babel/helper-function-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
+ integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
+ dependencies:
+ "@babel/template" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-hoist-variables@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+ integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-member-expression-to-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.5.tgz#0a7c56117cad3372fbf8d2fb4bf8f8d64a1e76b2"
+ integrity sha512-aBiH1NKMG0H2cGZqspNvsaBe6wNGjbJjuLy29aU+eDZjSbbN53BaxlpB02xm9v34pLTZ1nIQPFYn2qMZoa5BQQ==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-module-imports@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c"
+ integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-module-transforms@^7.22.5", "@babel/helper-module-transforms@^7.22.9":
+ version "7.22.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129"
+ integrity sha512-t+WA2Xn5K+rTeGtC8jCsdAH52bjggG5TKRuRrAGNM/mjIbO4GxvlLMFOEz9wXY5I2XQ60PMFsAG2WIcG82dQMQ==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-simple-access" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/helper-validator-identifier" "^7.22.5"
+
+"@babel/helper-optimise-call-expression@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e"
+ integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@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.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+ integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
+"@babel/helper-remap-async-to-generator@^7.22.5", "@babel/helper-remap-async-to-generator@^7.22.9":
+ version "7.22.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.9.tgz#53a25b7484e722d7efb9c350c75c032d4628de82"
+ integrity sha512-8WWC4oR4Px+tr+Fp0X3RHDVfINGpF3ad1HIbrc8A77epiR6eMMc6jsgozkzT2uDiOOdoS9cLIQ+XD2XvI2WSmQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-wrap-function" "^7.22.9"
+
+"@babel/helper-replace-supers@^7.22.5", "@babel/helper-replace-supers@^7.22.9":
+ version "7.22.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.9.tgz#cbdc27d6d8d18cd22c81ae4293765a5d9afd0779"
+ integrity sha512-LJIKvvpgPOPUThdYqcX6IXRuIcTkcAub0IaDRGCZH0p5GPUp7PhRU9QVgFcDDd51BaPkk77ZjqFwh6DZTAEmGg==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-member-expression-to-functions" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+
+"@babel/helper-simple-access@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
+ integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz#007f15240b5751c537c40e77abb4e89eeaaa8847"
+ integrity sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-split-export-declaration@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+ integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+ dependencies:
+ "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+ integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
+"@babel/helper-validator-identifier@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
+ integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
+
+"@babel/helper-validator-option@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac"
+ integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==
+
+"@babel/helper-wrap-function@^7.22.9":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.22.10.tgz#d845e043880ed0b8c18bd194a12005cb16d2f614"
+ integrity sha512-OnMhjWjuGYtdoO3FmsEFWvBStBAe2QOgwOLsLNDjN+aaiMD8InJk1/O3HSD8lkqTjCgg5YI34Tz15KNNA3p+nQ==
+ dependencies:
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/template" "^7.22.5"
+ "@babel/types" "^7.22.10"
+
+"@babel/helpers@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.10.tgz#ae6005c539dfbcb5cd71fb51bfc8a52ba63bc37a"
+ integrity sha512-a41J4NW8HyZa1I1vAndrraTlPZ/eZoga2ZgS7fEr0tZJGVU4xqdE80CEm0CcNjha5EZ8fTBYLKHF0kqDUuAwQw==
+ dependencies:
+ "@babel/template" "^7.22.5"
+ "@babel/traverse" "^7.22.10"
+ "@babel/types" "^7.22.10"
+
+"@babel/highlight@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.10.tgz#02a3f6d8c1cb4521b2fd0ab0da8f4739936137d7"
+ integrity sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.22.5"
+ chalk "^2.4.2"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.22.10", "@babel/parser@^7.22.5":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55"
+ integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ==
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.22.5":
+ version "7.22.5"
+ 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.22.5.tgz#87245a21cd69a73b0b81bcda98d443d6df08f05e"
+ integrity sha512-NP1M5Rf+u2Gw9qfSO4ihjcTGW5zXTi36ITLd4/EoAcEhIZ0yjMqmftDNl3QC19CX7olhrjpyU454g/2W7X0jvQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.5.tgz#fef09f9499b1f1c930da8a0c419db42167d792ca"
+ integrity sha512-31Bb65aZaUwqCbWMnZPduIZxCBngHFlzyN6Dq6KAJjtx+lx6ohKHubc61OomYi7XwVD4Ol0XCVz4h+pYFR048g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-transform-optional-chaining" "^7.22.5"
+
+"@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.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.22.5.tgz#07d252e2aa0bc6125567f742cd58619cb14dce98"
+ integrity sha512-rdV97N7KqsRzeNGoWUOK6yUsWarLjE5Su/Snk9IYPU9CwkWHs4t+rTGOvffTR8XGkJMTAdLfO0xVnXm8wugIJg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-syntax-import-attributes@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.5.tgz#ab840248d834410b829f569f5262b9e517555ecb"
+ integrity sha512-KwvoWDeNKPETmozyFE0P2rOLqh39EoQHNjqizrI5B8Vt0ZNS7M56s7dAiAqbYfiAYOuIzIh96z3iR2ktgu3tEg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@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-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.22.5.tgz#a6b68e84fb76e759fc3b93e901876ffabbe1d918"
+ integrity sha512-gvyP4hZrgrs/wWMaocvxZ44Hw0b3W8Pe+cMxc8V1ULQ07oh8VNbIRaoD1LRZVTvD+0nieDKjfgKg89sD7rrKrg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@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.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.22.5.tgz#e5ba566d0c58a5b2ba2a8b795450641950b71958"
+ integrity sha512-26lTNXoVRdAnsaDXPpvCNUq+OVWEVC6bx7Vvz9rC53F2bagUWW4u4ii2+h8Fejfh7RYqPxn+libeFBBck9muEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-async-generator-functions@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.10.tgz#45946cd17f915b10e65c29b8ed18a0a50fc648c8"
+ integrity sha512-eueE8lvKVzq5wIObKK/7dvoeKJ+xc6TvRn6aysIjS6pSCeLy7S/eVi7pEQknZqyqvzaNKdDtem8nUNTBgDVR2g==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
+"@babel/plugin-transform-async-to-generator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.22.5.tgz#c7a85f44e46f8952f6d27fe57c2ed3cc084c3775"
+ integrity sha512-b1A8D8ZzE/VhNDoV1MSJTnpKkCG5bJo+19R4o4oy03zM7ws8yEMK755j61Dc3EyvdysbqH5BOOTquJ7ZX9C6vQ==
+ dependencies:
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-remap-async-to-generator" "^7.22.5"
+
+"@babel/plugin-transform-block-scoped-functions@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.22.5.tgz#27978075bfaeb9fa586d3cb63a3d30c1de580024"
+ integrity sha512-tdXZ2UdknEKQWKJP1KMNmuF5Lx3MymtMN/pvA+p/VEkhK8jVcQ1fzSy8KM9qRYhAf2/lV33hoMPKI/xaI9sADA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-block-scoping@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.22.10.tgz#88a1dccc3383899eb5e660534a76a22ecee64faa"
+ integrity sha512-1+kVpGAOOI1Albt6Vse7c8pHzcZQdQKW+wJH+g8mCaszOdDVwRXa/slHPqIw+oJAJANTKDMuM2cBdV0Dg618Vg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.5.tgz#97a56e31ad8c9dc06a0b3710ce7803d5a48cca77"
+ integrity sha512-nDkQ0NfkOhPTq8YCLiWNxp1+f9fCobEjCb0n8WdbNUBc4IB5V7P1QnX9IjpSoquKrXF5SKojHleVNs2vGeHCHQ==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-class-static-block@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.5.tgz#3e40c46f048403472d6f4183116d5e46b1bff5ba"
+ integrity sha512-SPToJ5eYZLxlnp1UzdARpOGeC2GbHvr9d/UV0EukuVx8atktg194oe+C5BqQ8jRTkgLRVOPYeXRSBg1IlMoVRA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
+"@babel/plugin-transform-classes@^7.22.6":
+ version "7.22.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.22.6.tgz#e04d7d804ed5b8501311293d1a0e6d43e94c3363"
+ integrity sha512-58EgM6nuPNG6Py4Z3zSuu0xWu2VfodiMi72Jt5Kj2FECmaYk1RrTXA45z6KBFsu9tRgwQDwIiY4FXTt+YsSFAQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.6"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-optimise-call-expression" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.22.5.tgz#cd1e994bf9f316bd1c2dafcd02063ec261bb3869"
+ integrity sha512-4GHWBgRf0krxPX+AaPtgBAlTgTeZmqDynokHOX7aqqAB4tHs3U2Y02zH6ETFdLZGcg9UQSD1WCmkVrE9ErHeOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/template" "^7.22.5"
+
+"@babel/plugin-transform-destructuring@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.22.10.tgz#38e2273814a58c810b6c34ea293be4973c4eb5e2"
+ integrity sha512-dPJrL0VOyxqLM9sritNbMSGx/teueHF/htMKrPT7DNxccXxRDPYqlgPFFdr8u+F+qUZOkZoXue/6rL5O5GduEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dotall-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.22.5.tgz#dbb4f0e45766eb544e193fb00e65a1dd3b2a4165"
+ integrity sha512-5/Yk9QxCQCl+sOIB1WelKnVRxTJDSAIxtJLL2/pqL14ZVlbH0fUQUZa/T5/UnQtBNgghR7mfB8ERBKyKPCi7Vw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-duplicate-keys@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.22.5.tgz#b6e6428d9416f5f0bba19c70d1e6e7e0b88ab285"
+ integrity sha512-dEnYD+9BBgld5VBXHnF/DbYGp3fqGMsyxKbtD1mDyIA7AkTSpKXFhCVuj/oQVOoALfBs77DudA0BE4d5mcpmqw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-dynamic-import@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.5.tgz#d6908a8916a810468c4edff73b5b75bda6ad393e"
+ integrity sha512-0MC3ppTB1AMxd8fXjSrbPa7LT9hrImt+/fcj+Pg5YMD7UQyWp/02+JWpdnCymmsXwIx5Z+sYn1bwCn4ZJNvhqQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
+"@babel/plugin-transform-exponentiation-operator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.22.5.tgz#402432ad544a1f9a480da865fda26be653e48f6a"
+ integrity sha512-vIpJFNM/FjZ4rh1myqIya9jXwrwwgFRHPjT3DkUA9ZLHuzox8jiXkOLvwm1H+PQIP3CqfC++WPKeuDi0Sjdj1g==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-export-namespace-from@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.5.tgz#57c41cb1d0613d22f548fddd8b288eedb9973a5b"
+ integrity sha512-X4hhm7FRnPgd4nDA4b/5V280xCx6oL7Oob5+9qVS5C13Zq4bh1qq7LU0GgRU6b5dBWBvhGaXYVB4AcN6+ol6vg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-transform-for-of@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.22.5.tgz#ab1b8a200a8f990137aff9a084f8de4099ab173f"
+ integrity sha512-3kxQjX1dU9uudwSshyLeEipvrLjBCVthCgeTp6CzE/9JYrlAIaeekVxRpCWsDDfYTfRZRoCeZatCQvwo+wvK8A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-function-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.22.5.tgz#935189af68b01898e0d6d99658db6b164205c143"
+ integrity sha512-UIzQNMS0p0HHiQm3oelztj+ECwFnj+ZRV4KnguvlsD2of1whUeM6o7wGNj6oLwcDoAXQ8gEqfgC24D+VdIcevg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-json-strings@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.5.tgz#14b64352fdf7e1f737eed68de1a1468bd2a77ec0"
+ integrity sha512-DuCRB7fu8MyTLbEQd1ew3R85nx/88yMoqo2uPSjevMj3yoN7CDM8jkgrY0wmVxfJZyJ/B9fE1iq7EQppWQmR5A==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
+"@babel/plugin-transform-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.22.5.tgz#e9341f4b5a167952576e23db8d435849b1dd7920"
+ integrity sha512-fTLj4D79M+mepcw3dgFBTIDYpbcB9Sm0bpm4ppXPaO+U+PKFFyV9MGRvS0gvGw62sd10kT5lRMKXAADb9pWy8g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-logical-assignment-operators@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.5.tgz#66ae5f068fd5a9a5dc570df16f56c2a8462a9d6c"
+ integrity sha512-MQQOUW1KL8X0cDWfbwYP+TbVbZm16QmQXJQ+vndPtH/BoO0lOKpVoEDMI7+PskYxH+IiE0tS8xZye0qr1lGzSA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.22.5.tgz#4fcc9050eded981a468347dd374539ed3e058def"
+ integrity sha512-RZEdkNtzzYCFl9SE9ATaUMTj2hqMb4StarOJLrZRbqqU4HSBE7UlBw9WBWQiDzrJZJdUWiMTVDI6Gv/8DPvfew==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-amd@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.22.5.tgz#4e045f55dcf98afd00f85691a68fc0780704f526"
+ integrity sha512-R+PTfLTcYEmb1+kK7FNkhQ1gP4KgjpSO6HfH9+f8/yfp2Nt3ggBjiVpRwmwTlfqZLafYKJACy36yDXlEmI9HjQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-modules-commonjs@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.22.5.tgz#7d9875908d19b8c0536085af7b053fd5bd651bfa"
+ integrity sha512-B4pzOXj+ONRmuaQTg05b3y/4DuFz3WcCNAXPLb2Q0GT0TrGKGxNKV4jwsXts+StaM0LQczZbOpj8o1DLPDJIiA==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-simple-access" "^7.22.5"
+
+"@babel/plugin-transform-modules-systemjs@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.5.tgz#18c31410b5e579a0092638f95c896c2a98a5d496"
+ integrity sha512-emtEpoaTMsOs6Tzz+nbmcePl6AKVtS1yC4YNAeMun9U8YCsgadPNxnOPQ8GhHFB2qdx+LZu9LgoC0Lthuu05DQ==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+
+"@babel/plugin-transform-modules-umd@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.22.5.tgz#4694ae40a87b1745e3775b6a7fe96400315d4f98"
+ integrity sha512-+S6kzefN/E1vkSsKx8kmQuqeQsvCKCd1fraCM7zXm4SFoggI099Tr4G8U81+5gtMdUeMQ4ipdQffbKLX0/7dBQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz#67fe18ee8ce02d57c855185e27e3dc959b2e991f"
+ integrity sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-new-target@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.5.tgz#1b248acea54ce44ea06dfd37247ba089fcf9758d"
+ integrity sha512-AsF7K0Fx/cNKVyk3a+DW0JLo+Ua598/NxMRvxDnkpCIGFh43+h/v2xyhRUYf6oD8gE4QtL83C7zZVghMjHd+iw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.5.tgz#f8872c65776e0b552e0849d7596cddd416c3e381"
+ integrity sha512-6CF8g6z1dNYZ/VXok5uYkkBBICHZPiGEl7oDnAx2Mt1hlHVHOSIKWJaXHjQJA5VB43KZnXZDIexMchY4y2PGdA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-transform-numeric-separator@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.5.tgz#57226a2ed9e512b9b446517ab6fa2d17abb83f58"
+ integrity sha512-NbslED1/6M+sXiwwtcAB/nieypGw02Ejf4KtDeMkCEpP6gWFMX1wI9WKYua+4oBneCCEmulOkRpwywypVZzs/g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-transform-object-rest-spread@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.5.tgz#9686dc3447df4753b0b2a2fae7e8bc33cdc1f2e1"
+ integrity sha512-Kk3lyDmEslH9DnvCDA1s1kkd3YWQITiBOHngOtDL9Pt6BZjzqb6hiOlb8VfjiiQJ2unmegBqZu0rx5RxJb5vmQ==
+ dependencies:
+ "@babel/compat-data" "^7.22.5"
+ "@babel/helper-compilation-targets" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.22.5"
+
+"@babel/plugin-transform-object-super@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.22.5.tgz#794a8d2fcb5d0835af722173c1a9d704f44e218c"
+ integrity sha512-klXqyaT9trSjIUrcsYIfETAzmOEZL3cBYqOYLJxBHfMFFggmXOv+NYSX/Jbs9mzMVESw/WycLFPRx8ba/b2Ipw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-replace-supers" "^7.22.5"
+
+"@babel/plugin-transform-optional-catch-binding@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.5.tgz#842080be3076703be0eaf32ead6ac8174edee333"
+ integrity sha512-pH8orJahy+hzZje5b8e2QIlBWQvGpelS76C63Z+jhZKsmzfNaPQ+LaW6dcJ9bxTpo1mtXbgHwy765Ro3jftmUg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-transform-optional-chaining@^7.22.10", "@babel/plugin-transform-optional-chaining@^7.22.5":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.10.tgz#076d28a7e074392e840d4ae587d83445bac0372a"
+ integrity sha512-MMkQqZAZ+MGj+jGTG3OTuhKeBpNcO+0oCEbrGNEaOmiEn+1MzRyQlYsruGiU8RTK3zV6XwrVJTmwiDOyYK6J9g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-transform-parameters@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.5.tgz#c3542dd3c39b42c8069936e48717a8d179d63a18"
+ integrity sha512-AVkFUBurORBREOmHRKo06FjHYgjrabpdqRSwq6+C7R5iTCZOsM4QbcB27St0a4U6fffyAOqh3s/qEfybAhfivg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-methods@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.5.tgz#21c8af791f76674420a147ae62e9935d790f8722"
+ integrity sha512-PPjh4gyrQnGe97JTalgRGMuU4icsZFnWkzicB/fUtzlKUqvsWBKEpPPfr5a2JiyirZkHxnAqkQMO5Z5B2kK3fA==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-private-property-in-object@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.5.tgz#07a77f28cbb251546a43d175a1dda4cf3ef83e32"
+ integrity sha512-/9xnaTTJcVoBtSSmrVyhtSvO3kbqS2ODoh2juEU72c3aYonNF0OMGiaz2gjukyKM2wBBYJP38S4JiE0Wfb5VMQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-create-class-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-transform-property-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.22.5.tgz#b5ddabd73a4f7f26cd0e20f5db48290b88732766"
+ integrity sha512-TiOArgddK3mK/x1Qwf5hay2pxI6wCZnvQqrFSqbtg1GLl2JcNMitVH/YnqjP+M31pLUeTfzY1HAXFDnUBV30rQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-display-name@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-display-name/-/plugin-transform-react-display-name-7.22.5.tgz#3c4326f9fce31c7968d6cb9debcaf32d9e279a2b"
+ integrity sha512-PVk3WPYudRF5z4GKMEYUrLjPl38fJSKNaEOkFuoprioowGuWN6w2RKznuFNSlJx7pzzXXStPUnNSOEO0jL5EVw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx-development@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx-development/-/plugin-transform-react-jsx-development-7.22.5.tgz#e716b6edbef972a92165cd69d92f1255f7e73e87"
+ integrity sha512-bDhuzwWMuInwCYeDeMzyi7TaBgRQei6DqxhbyniL7/VG4RSS7HtSL2QbY4eESy1KJqlWt8g3xeEBGPuo+XqC8A==
+ dependencies:
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+
+"@babel/plugin-transform-react-jsx@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-jsx/-/plugin-transform-react-jsx-7.22.5.tgz#932c291eb6dd1153359e2a90cb5e557dcf068416"
+ integrity sha512-rog5gZaVbUip5iWDMTYbVM15XQq+RkUKhET/IHR6oizR+JEoN6CAfTTuHcK4vwUyzca30qqHqEpzBOnaRMWYMA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-module-imports" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/plugin-syntax-jsx" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/plugin-transform-react-pure-annotations@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-react-pure-annotations/-/plugin-transform-react-pure-annotations-7.22.5.tgz#1f58363eef6626d6fa517b95ac66fe94685e32c0"
+ integrity sha512-gP4k85wx09q+brArVinTXhWiyzLl9UpmGva0+mWyKxk6JZequ05x3eUcIUE+FyttPKJFRRVtAvQaJ6YF9h1ZpA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-regenerator@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.22.10.tgz#8ceef3bd7375c4db7652878b0241b2be5d0c3cca"
+ integrity sha512-F28b1mDt8KcT5bUyJc/U9nwzw6cV+UmTeRlXYIl2TNqMMJif0Jeey9/RQ3C4NOd2zp0/TRsDns9ttj2L523rsw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ regenerator-transform "^0.15.2"
+
+"@babel/plugin-transform-reserved-words@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.22.5.tgz#832cd35b81c287c4bcd09ce03e22199641f964fb"
+ integrity sha512-DTtGKFRQUDm8svigJzZHzb/2xatPc6TzNvAIJ5GqOKDsGFYgAskjRulbR/vGsPKq3OPqtexnz327qYpP57RFyA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-shorthand-properties@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.22.5.tgz#6e277654be82b5559fc4b9f58088507c24f0c624"
+ integrity sha512-vM4fq9IXHscXVKzDv5itkO1X52SmdFBFcMIBZ2FRn2nqVYqw6dBexUgMvAjHW+KXpPPViD/Yo3GrDEBaRC0QYA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-spread@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.22.5.tgz#6487fd29f229c95e284ba6c98d65eafb893fea6b"
+ integrity sha512-5ZzDQIGyvN4w8+dMmpohL6MBo+l2G7tfC/O2Dg7/hjpgeWvUx8FzfeOKxGog9IimPa4YekaQ9PlDqTLOljkcxg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.22.5"
+
+"@babel/plugin-transform-sticky-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.22.5.tgz#295aba1595bfc8197abd02eae5fc288c0deb26aa"
+ integrity sha512-zf7LuNpHG0iEeiyCNwX4j3gDg1jgt1k3ZdXBKbZSoA3BbGQGvMiSvfbZRR3Dr3aeJe3ooWFZxOOG3IRStYp2Bw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-template-literals@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.22.5.tgz#8f38cf291e5f7a8e60e9f733193f0bcc10909bff"
+ integrity sha512-5ciOehRNf+EyUeewo8NkbQiUs4d6ZxiHo6BcBcnFlgiJfu16q0bQUw9Jvo0b0gBKFG1SMhDSjeKXSYuJLeFSMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-typeof-symbol@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.22.5.tgz#5e2ba478da4b603af8673ff7c54f75a97b716b34"
+ integrity sha512-bYkI5lMzL4kPii4HHEEChkD0rkc+nvnlR6+o/qdqR6zrm0Sv/nodmyLhlq2DO0YKLUNd2VePmPRjJXSBh9OIdA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-escapes@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.22.10.tgz#c723f380f40a2b2f57a62df24c9005834c8616d9"
+ integrity sha512-lRfaRKGZCBqDlRU3UIFovdp9c9mEvlylmpod0/OatICsSfuQ9YFthRo1tpTkGsklEefZdqlEFdY4A2dwTb6ohg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-property-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.5.tgz#098898f74d5c1e86660dc112057b2d11227f1c81"
+ integrity sha512-HCCIb+CbJIAE6sXn5CjFQXMwkCClcOfPCzTlilJ8cUatfzwHlWQkbtV0zD338u9dZskwvuOYTuuaMaA8J5EI5A==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.22.5.tgz#ce7e7bb3ef208c4ff67e02a22816656256d7a183"
+ integrity sha512-028laaOKptN5vHJf9/Arr/HiJekMd41hOEZYvNsrsXqJ7YPYuX2bQxh31fkZzGmq3YqHRJzYFFAVYvKfMPKqyg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.5.tgz#77788060e511b708ffc7d42fdfbc5b37c3004e91"
+ integrity sha512-lhMfi4FC15j13eKrh3DnYHjpGj6UKQHtNKTbtc1igvAhRy4+kLhV07OpLcsN0VgDEw/MjAvJO4BdMJsHwMhzCg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.5"
+ "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/preset-env@^7.19":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.10.tgz#3263b9fe2c8823d191d28e61eac60a79f9ce8a0f"
+ integrity sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==
+ dependencies:
+ "@babel/compat-data" "^7.22.9"
+ "@babel/helper-compilation-targets" "^7.22.10"
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.5"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.22.5"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.5"
+ "@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.22.5"
+ "@babel/plugin-syntax-import-attributes" "^7.22.5"
+ "@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.22.5"
+ "@babel/plugin-transform-async-generator-functions" "^7.22.10"
+ "@babel/plugin-transform-async-to-generator" "^7.22.5"
+ "@babel/plugin-transform-block-scoped-functions" "^7.22.5"
+ "@babel/plugin-transform-block-scoping" "^7.22.10"
+ "@babel/plugin-transform-class-properties" "^7.22.5"
+ "@babel/plugin-transform-class-static-block" "^7.22.5"
+ "@babel/plugin-transform-classes" "^7.22.6"
+ "@babel/plugin-transform-computed-properties" "^7.22.5"
+ "@babel/plugin-transform-destructuring" "^7.22.10"
+ "@babel/plugin-transform-dotall-regex" "^7.22.5"
+ "@babel/plugin-transform-duplicate-keys" "^7.22.5"
+ "@babel/plugin-transform-dynamic-import" "^7.22.5"
+ "@babel/plugin-transform-exponentiation-operator" "^7.22.5"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.5"
+ "@babel/plugin-transform-for-of" "^7.22.5"
+ "@babel/plugin-transform-function-name" "^7.22.5"
+ "@babel/plugin-transform-json-strings" "^7.22.5"
+ "@babel/plugin-transform-literals" "^7.22.5"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.22.5"
+ "@babel/plugin-transform-member-expression-literals" "^7.22.5"
+ "@babel/plugin-transform-modules-amd" "^7.22.5"
+ "@babel/plugin-transform-modules-commonjs" "^7.22.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.22.5"
+ "@babel/plugin-transform-modules-umd" "^7.22.5"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.5"
+ "@babel/plugin-transform-new-target" "^7.22.5"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.5"
+ "@babel/plugin-transform-numeric-separator" "^7.22.5"
+ "@babel/plugin-transform-object-rest-spread" "^7.22.5"
+ "@babel/plugin-transform-object-super" "^7.22.5"
+ "@babel/plugin-transform-optional-catch-binding" "^7.22.5"
+ "@babel/plugin-transform-optional-chaining" "^7.22.10"
+ "@babel/plugin-transform-parameters" "^7.22.5"
+ "@babel/plugin-transform-private-methods" "^7.22.5"
+ "@babel/plugin-transform-private-property-in-object" "^7.22.5"
+ "@babel/plugin-transform-property-literals" "^7.22.5"
+ "@babel/plugin-transform-regenerator" "^7.22.10"
+ "@babel/plugin-transform-reserved-words" "^7.22.5"
+ "@babel/plugin-transform-shorthand-properties" "^7.22.5"
+ "@babel/plugin-transform-spread" "^7.22.5"
+ "@babel/plugin-transform-sticky-regex" "^7.22.5"
+ "@babel/plugin-transform-template-literals" "^7.22.5"
+ "@babel/plugin-transform-typeof-symbol" "^7.22.5"
+ "@babel/plugin-transform-unicode-escapes" "^7.22.10"
+ "@babel/plugin-transform-unicode-property-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-regex" "^7.22.5"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.22.5"
+ "@babel/preset-modules" "0.1.6-no-external-plugins"
+ "@babel/types" "^7.22.10"
+ babel-plugin-polyfill-corejs2 "^0.4.5"
+ babel-plugin-polyfill-corejs3 "^0.8.3"
+ babel-plugin-polyfill-regenerator "^0.5.2"
+ core-js-compat "^3.31.0"
+ 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/preset-react@^7.18.6":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-react/-/preset-react-7.22.5.tgz#c4d6058fbf80bccad02dd8c313a9aaa67e3c3dd6"
+ integrity sha512-M+Is3WikOpEJHgR385HbuCITPTaPRaNkibTEa9oiofmJvIsrceb4yp9RL9Kb+TE8LznmeyZqpP+Lopwcx59xPQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.22.5"
+ "@babel/helper-validator-option" "^7.22.5"
+ "@babel/plugin-transform-react-display-name" "^7.22.5"
+ "@babel/plugin-transform-react-jsx" "^7.22.5"
+ "@babel/plugin-transform-react-jsx-development" "^7.22.5"
+ "@babel/plugin-transform-react-pure-annotations" "^7.22.5"
+
+"@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.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.10.tgz#ae3e9631fd947cb7e3610d3e9d8fef5f76696682"
+ integrity sha512-21t/fkKLMZI4pqP2wlmsQAWnYW1PDyKyyUV4vCi+B25ydmdaYTKXPwCj0BzSUnZf4seIiYvSA3jcZ3gdsMFkLQ==
+ dependencies:
+ regenerator-runtime "^0.14.0"
+
+"@babel/template@^7.22.5":
+ version "7.22.5"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
+ integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
+ dependencies:
+ "@babel/code-frame" "^7.22.5"
+ "@babel/parser" "^7.22.5"
+ "@babel/types" "^7.22.5"
+
+"@babel/traverse@^7.22.10":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.10.tgz#20252acb240e746d27c2e82b4484f199cf8141aa"
+ integrity sha512-Q/urqV4pRByiNNpb/f5OSv28ZlGJiFiiTh+GAHktbIrkPhPbl90+uW6SmpoLyZqutrg9AEaEf3Q/ZBRHBXgxig==
+ dependencies:
+ "@babel/code-frame" "^7.22.10"
+ "@babel/generator" "^7.22.10"
+ "@babel/helper-environment-visitor" "^7.22.5"
+ "@babel/helper-function-name" "^7.22.5"
+ "@babel/helper-hoist-variables" "^7.22.5"
+ "@babel/helper-split-export-declaration" "^7.22.6"
+ "@babel/parser" "^7.22.10"
+ "@babel/types" "^7.22.10"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.22.10", "@babel/types@^7.22.5", "@babel/types@^7.4.4":
+ version "7.22.10"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03"
+ integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg==
+ dependencies:
+ "@babel/helper-string-parser" "^7.22.5"
+ "@babel/helper-validator-identifier" "^7.22.5"
+ 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.0", "@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@^3.1.0":
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
+ integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.5"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.5.tgz#a3bb4d5c6825aab0d281268f47f6ad5853431e91"
+ integrity sha512-UTYAUj/wviwdsMfzoSJspJxbkH5o1snzwX0//0ENX1u/55kkZZkcTZP6u9bwKGkv+dkk9at4m1Cpt0uY80kcpQ==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.14":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.19"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz#f8a3249862f91be48d3127c3cfe992f79b4b8811"
+ integrity sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==
+ dependencies:
+ "@jridgewell/resolve-uri" "^3.1.0"
+ "@jridgewell/sourcemap-codec" "^1.4.14"
+
+"@paypal/paypal-js@^6.0.0":
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/@paypal/paypal-js/-/paypal-js-6.0.1.tgz#5d68d5863a5176383fee9424bc944231668fcffd"
+ integrity sha512-bvYetmkg2GEC6onsUJQx1E9hdAJWff2bS3IPeiZ9Sh9U7h26/fIgMKm240cq/908sbSoDjHys75XXd8at9OpQA==
+ dependencies:
+ promise-polyfill "^8.3.0"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.44.2"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.44.2.tgz#0d21c505f98a89b8dd4d37fa162b09da6089199a"
+ integrity sha512-sdPRb9K6iL5XZOmBubg8yiFp5yS/JdUDQsq5e6h95km91MCYMuvp7mh1fjPEYUhvHepKpZOjnEaMBR4PxjWDzg==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+ integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
+"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
+ version "7.0.12"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
+
+"@types/node@*":
+ version "20.5.1"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30"
+ integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg==
+
+"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
+ integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
+ 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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
+ integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
+
+"@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
+ integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+
+"@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.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
+ integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/helper-wasm-section" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-opt" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+ "@webassemblyjs/wast-printer" "1.11.6"
+
+"@webassemblyjs/wasm-gen@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
+ integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
+ dependencies:
+ "@webassemblyjs/ast" "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/wasm-opt@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
+ integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+
+"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
+ integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
+ integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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==
+
+"@woocommerce/dependency-extraction-webpack-plugin@^2.2.0":
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/@woocommerce/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-2.2.0.tgz#230d674a67585bc32e31bc28485bec99b41dbd1f"
+ integrity sha512-0wDY3EIUwWrPm0KrWvt1cf2SZDSX7CzBXvv4TyCqWOPuVPvC/ajyY8kD1HTFI80q6/RHoxWf3BYCmhuBzPbe9A==
+ dependencies:
+ "@wordpress/dependency-extraction-webpack-plugin" "^3.3.0"
+
+"@wordpress/dependency-extraction-webpack-plugin@^3.3.0":
+ version "3.7.0"
+ resolved "https://registry.yarnpkg.com/@wordpress/dependency-extraction-webpack-plugin/-/dependency-extraction-webpack-plugin-3.7.0.tgz#e52ef31f66b8c4add3d773a87e11007375127b04"
+ integrity sha512-SHyp88D1ICSaRVMfs/kKEicjKXWf1y2wecUeZIiMtkfAi8Bnk3JsnUo11LH7drJIXfjmDoer2B2rrBMZmRm8VA==
+ dependencies:
+ json2php "^0.0.4"
+ webpack-sources "^3.2.2"
+
+"@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-assertions@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+ integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn@^8.7.1, acorn@^8.8.2:
+ version "8.10.0"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+ integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
+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.5:
+ version "0.4.5"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.5.tgz#8097b4cb4af5b64a1d11332b6fb72ef5e64a054c"
+ integrity sha512-19hwUH5FKl49JEsvyTcoHakh6BE0wgXLLptIyKZ3PijHc/Ci521wygORCUCCred+E/twuqRyAkE02BAWPmsHOg==
+ dependencies:
+ "@babel/compat-data" "^7.22.6"
+ "@babel/helper-define-polyfill-provider" "^0.4.2"
+ semver "^6.3.1"
+
+babel-plugin-polyfill-corejs3@^0.8.3:
+ version "0.8.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.3.tgz#b4f719d0ad9bb8e0c23e3e630c0c8ec6dd7a1c52"
+ integrity sha512-z41XaniZL26WLrvjy7soabMXrfPWARN25PZoriDEiLMxAp50AUW3t35BGQUMg5xK3UrpVTtagIDklxYa+MhiNA==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.2"
+ core-js-compat "^3.31.0"
+
+babel-plugin-polyfill-regenerator@^0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.2.tgz#80d0f3e1098c080c8b5a65f41e9427af692dc326"
+ integrity sha512-tAlOptU0Xj34V1Y2PNTL4Y0FOJMDB6bZmoW39FeCQIhigGLkqu3Fj6uiXpxIf6Ij274ENdYx64y6Au+ZKlb1IA==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.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.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browserslist@^4.14.5, browserslist@^4.21.10, browserslist@^4.21.9:
+ version "4.21.10"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0"
+ integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ==
+ dependencies:
+ caniuse-lite "^1.0.30001517"
+ electron-to-chromium "^1.4.477"
+ node-releases "^2.0.13"
+ update-browserslist-db "^1.0.11"
+
+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.30001517:
+ version "1.0.30001522"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001522.tgz#44b87a406c901269adcdb834713e23582dd71856"
+ integrity sha512-TKiyTVZxJGhsTszLuzb+6vUZSjVOAhClszBr2Ta2k9IwtNBT/4dzmL6aywt0HCgEZlmwJzXJd8yNiob6HgwTRg==
+
+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.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ 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.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+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@^1.7.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+core-js-compat@^3.31.0:
+ version "3.32.1"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.32.1.tgz#55f9a7d297c0761a8eb1d31b593e0f5b6ffae964"
+ integrity sha512-GSvKDv4wE0bPnQtjklV101juQ85g6H3rm5PDP20mqlS5j0kXF3pP97YvAu5hl+uFHqMictp3b2VxOHljWMAtuA==
+ dependencies:
+ browserslist "^4.21.10"
+
+core-js@^3.25.0:
+ version "3.32.1"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.32.1.tgz#a7d8736a3ed9dd05940c3c4ff32c591bb735be77"
+ integrity sha512-lqufgNn9NLnESg5mQeYsxQP5w7wrViSj0jr/kv6ECQiByzQkrn1MKvV0L3acttpDqfQrHLwr2KCMgX5b8X+lyQ==
+
+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:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+electron-to-chromium@^1.4.477:
+ version "1.4.496"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.496.tgz#a57534b70d2bdee7e1ad7dbd4c91e560cbd08db1"
+ integrity sha512-qeXC3Zbykq44RCrBa4kr8v/dWzYJA8rAwpyh9Qd+NKWoJfjG5vvJqy9XOJ9H4P/lqulZBCgUWAYi+FeK5AuJ8g==
+
+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.15.0:
+ version "5.15.0"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35"
+ integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+envinfo@^7.7.3:
+ version "7.10.0"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.10.0.tgz#55146e3909cc5fe63c22da63fb15b05aeac35b13"
+ integrity sha512-ZtUjZO6l5mwTHvc1L9+1q5p/R3wTopcfqMW8r5t8SJSKqeVI/LtajORwRFEKpEFuekjD0VBjwu1HMxL4UalIRw==
+
+es-module-lexer@^1.2.1:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.3.0.tgz#6be9c9e0b4543a60cd166ff6f8b4e9dae0b0c16f"
+ integrity sha512-vZK7T0N2CBmBOixhmjdqx2gWVbFZ4DXZ/NyRMZVlJXPa7CyFS+/a4QQsDGDQy9ZfEzxFuNEsMLeQJnKP2p5/JA==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+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.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ 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"
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+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.4, graceful-fs@^4.2.9:
+ 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==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+immutable@^4.0.0:
+ version "4.3.2"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.2.tgz#f89d910f8dfb6e15c03b2cae2faaf8c1f66455fe"
+ integrity sha512-oGXzbEDem9OOpDWZu88jGiYCvIsLHMvGw+8OXlpsvTFvIQplQbjg1B1cvKg8f7Hoch6+NGjpPsH1Fr+Mc2D1aA==
+
+import-local@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+ 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.13.0"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.0.tgz#bb52aa6e2cbd49a30c2ba68c42bf3435ba6072db"
+ integrity sha512-Z7dk6Qo8pOCp3l4tsX2C5ZVas4V+UxwQodwZhLopL91TX8UyyHEXafPcyoeeWuLrwzHcr3igO78wNLwHJHsMCQ==
+ dependencies:
+ has "^1.0.3"
+
+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==
+
+json2php@^0.0.4:
+ version "0.0.4"
+ resolved "https://registry.yarnpkg.com/json2php/-/json2php-0.0.4.tgz#6bd85a1dda6a5dd7e91022bb24403cc1b7c2ee34"
+ integrity sha512-hFzejhs28f70sGnutcsRS459MnAsjRVI85RgPAL1KQIZEpjiDitc27CZv4IgOtaR86vrqOVlu9vJNew2XyTH4g==
+
+json5@^2.1.2, json5@^2.2.2:
+ 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.13:
+ version "2.0.13"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
+ integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+
+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:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+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"
+
+promise-polyfill@^8.3.0:
+ version "8.3.0"
+ resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-8.3.0.tgz#9284810268138d103807b11f4e23d5e945a4db63"
+ integrity sha512-H5oELycFml5yto/atYqmjyigJoAo3+OXwolYiH7OfQuYlAqhxNvTfiNMbV9hsC6Yp83yE5r2KTVmtrG6R9i6Pg==
+
+punycode@^2.1.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+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.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
+ 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.0"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.0.tgz#5e19d68eb12d486f797e15a3c6a918f7cec5eb45"
+ integrity sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==
+
+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.4"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.4.tgz#1dc40df46554cdaf8948a486a10f6ba1e2026c34"
+ integrity sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==
+ 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.66.1"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.66.1.tgz#04b51c4671e4650aa393740e66a4e58b44d055b1"
+ integrity sha512-50c+zTsZOJVgFfTgwwEzkjA3/QACgdNsKueWPyAR0mRINIvLAStVQBbPg14iuqEQ74NPDbXzJARJ/O4SI1zftA==
+ 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.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
+ 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.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+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.7:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.8"
+
+terser@^5.16.8:
+ version "5.19.2"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e"
+ integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA==
+ 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"
+
+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.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
+ integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+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.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ 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.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
+ dependencies:
+ clone-deep "^4.0.1"
+ wildcard "^2.0.0"
+
+webpack-sources@^3.2.2, 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.88.2"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.88.2.tgz#f62b4b842f1c6ff580f3fcb2ed4f0b579f4c210e"
+ integrity sha512-JmcgNZ1iKj+aiR0OvTYtWQqJwq37Pf683dY9bVORwVbUrDhLhdn/PlO2sHsFHPkj7sHNQF3JwaAkp49V+Sq1tQ==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.9.0"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.15.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.9"
+ 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.7"
+ watchpack "^2.4.0"
+ 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-onboarding/package.json b/modules/ppcp-onboarding/package.json
index 7746a726e..3dd9aa04e 100644
--- a/modules/ppcp-onboarding/package.json
+++ b/modules/ppcp-onboarding/package.json
@@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-onboarding/resources/css/onboarding.scss b/modules/ppcp-onboarding/resources/css/onboarding.scss
index 9441ac5fa..97cb65e66 100644
--- a/modules/ppcp-onboarding/resources/css/onboarding.scss
+++ b/modules/ppcp-onboarding/resources/css/onboarding.scss
@@ -45,7 +45,7 @@ label.error {
font-size: 1.1em;
}
-.input-text[pattern]:invalid {
+.input-text[pattern]:invalid, .input-text[type=email]:invalid {
border: red solid 2px;
}
diff --git a/modules/ppcp-onboarding/resources/js/onboarding.js b/modules/ppcp-onboarding/resources/js/onboarding.js
index 630dcb811..57d30161b 100644
--- a/modules/ppcp-onboarding/resources/js/onboarding.js
+++ b/modules/ppcp-onboarding/resources/js/onboarding.js
@@ -13,81 +13,102 @@ const ppcp_onboarding = {
reload: function() {
const buttons = document.querySelectorAll(ppcp_onboarding.BUTTON_SELECTOR);
- if (0 === buttons.length) {
- return;
+ if (buttons.length > 0) {
+ // Add event listeners to buttons preventing link clicking if PayPal init failed.
+ buttons.forEach(
+ (element) => {
+ if (element.hasAttribute('data-ppcp-button-initialized')) {
+ return;
+ }
+
+ element.addEventListener(
+ 'click',
+ (e) => {
+ if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) {
+ e.preventDefault();
+ }
+ }
+ );
+ }
+ );
+
+ // Clear any previous PayPal scripts.
+ [ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach(
+ (scriptID) => {
+ const scriptTag = document.getElementById(scriptID);
+
+ if (scriptTag) {
+ scriptTag.parentNode.removeChild(scriptTag);
+ }
+
+ if ('undefined' !== typeof window.PAYPAL) {
+ delete window.PAYPAL;
+ }
+ }
+ );
+
+ // Load PayPal scripts.
+ const paypalScriptTag = document.createElement('script');
+ paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID;
+ paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url;
+ document.body.appendChild(paypalScriptTag);
+
+ if (ppcp_onboarding._timeout) {
+ clearTimeout(ppcp_onboarding._timeout);
+ }
+
+ ppcp_onboarding._timeout = setTimeout(
+ () => {
+ buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); });
+
+ if ('undefined' !== window.PAYPAL.apps.Signup) {
+ window.PAYPAL.apps.Signup.render();
+ }
+ },
+ 1000
+ );
}
- // Add event listeners to buttons preventing link clicking if PayPal init failed.
- buttons.forEach(
- (element) => {
- if (element.hasAttribute('data-ppcp-button-initialized')) {
- return;
- }
+ const $onboarding_inputs = function () {
+ return jQuery('*[data-onboarding-option]');
+ };
+ const onboarding_options = function () {
+ let options = {};
+ $onboarding_inputs().each((index, el) => {
+ const opt = jQuery(el).data('onboardingOption');
+ options[opt] = el.checked;
+ });
+ return options;
+ }
+ const disable_onboarding_options = function () {
+ $onboarding_inputs().each((index, el) => {
+ el.setAttribute('disabled', 'disabled');
+ });
+ }
+ const enable_onboarding_options = function () {
+ $onboarding_inputs().each((index, el) => {
+ el.removeAttribute('disabled');
+ });
+ }
+ const update_onboarding_options = function () {
+ const spinner = ' ';
- element.addEventListener(
- 'click',
- (e) => {
- if (!element.hasAttribute('data-ppcp-button-initialized') || 'undefined' === typeof window.PAYPAL) {
- e.preventDefault();
- }
- }
- );
- }
- );
-
- // Clear any previous PayPal scripts.
- [ppcp_onboarding.PAYPAL_JS_ID, 'signup-js', 'biz-js'].forEach(
- (scriptID) => {
- const scriptTag = document.getElementById(scriptID);
-
- if (scriptTag) {
- scriptTag.parentNode.removeChild(scriptTag);
- }
-
- if ('undefined' !== typeof window.PAYPAL) {
- delete window.PAYPAL;
- }
- }
- );
-
- // Load PayPal scripts.
- const paypalScriptTag = document.createElement('script');
- paypalScriptTag.id = ppcp_onboarding.PAYPAL_JS_ID;
- paypalScriptTag.src = PayPalCommerceGatewayOnboarding.paypal_js_url;
- document.body.appendChild(paypalScriptTag);
-
- if (ppcp_onboarding._timeout) {
- clearTimeout(ppcp_onboarding._timeout);
- }
-
- ppcp_onboarding._timeout = setTimeout(
- () => {
- buttons.forEach((element) => { element.setAttribute('data-ppcp-button-initialized', 'true'); });
-
- if ('undefined' !== window.PAYPAL.apps.Signup) {
- window.PAYPAL.apps.Signup.render();
- }
- },
- 1000
- );
-
- const onboard_pui = document.querySelector('#ppcp-onboarding-pui');
- const spinner = ' ';
- onboard_pui?.addEventListener('click', (event) => {
- event.preventDefault();
- onboard_pui.setAttribute('disabled', 'disabled');
+ disable_onboarding_options();
buttons.forEach((element) => {
element.removeAttribute('href');
element.setAttribute('disabled', 'disabled');
jQuery(spinner).insertAfter(element);
});
- fetch(PayPalCommerceGatewayOnboarding.pui_endpoint, {
+ fetch(PayPalCommerceGatewayOnboarding.update_signup_links_endpoint, {
method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
credentials: 'same-origin',
body: JSON.stringify({
- nonce: PayPalCommerceGatewayOnboarding.pui_nonce,
- checked: onboard_pui.checked
+ nonce: PayPalCommerceGatewayOnboarding.update_signup_links_nonce,
+ settings: onboarding_options()
})
}).then((res)=>{
return res.json();
@@ -96,7 +117,6 @@ const ppcp_onboarding = {
alert('Could not update signup buttons: ' + JSON.stringify(data));
return;
}
-
buttons.forEach((element) => {
for (let [key, value] of Object.entries(data.data.signup_links)) {
key = 'connect-to' + key.replace(/-/g, '');
@@ -107,9 +127,13 @@ const ppcp_onboarding = {
}
}
});
- onboard_pui.removeAttribute('disabled');
+ enable_onboarding_options();
});
- })
+ }
+ $onboarding_inputs().on('click', (event) => {
+ event.preventDefault();
+ update_onboarding_options();
+ });
},
loginSeller: function(env, authCode, sharedId) {
diff --git a/modules/ppcp-onboarding/resources/js/settings.js b/modules/ppcp-onboarding/resources/js/settings.js
index 2eaad8ed2..56b8ec82b 100644
--- a/modules/ppcp-onboarding/resources/js/settings.js
+++ b/modules/ppcp-onboarding/resources/js/settings.js
@@ -1,7 +1,7 @@
document.addEventListener(
'DOMContentLoaded',
() => {
- const payLaterMessagingSelectableLocations = ['product', 'cart', 'checkout'];
+ const payLaterMessagingSelectableLocations = ['product', 'cart', 'checkout', 'shop', 'home'];
const payLaterMessagingAllLocations = payLaterMessagingSelectableLocations.concat('general');
const payLaterMessagingLocationsSelector = '#field-pay_later_messaging_locations';
const payLaterMessagingLocationsSelect = payLaterMessagingLocationsSelector + ' select';
@@ -9,7 +9,7 @@ document.addEventListener(
const smartButtonLocationsSelector = '#field-smart_button_locations';
const smartButtonLocationsSelect = smartButtonLocationsSelector + ' select';
- const smartButtonSelectableLocations = payLaterMessagingSelectableLocations.concat('mini-cart');
+ const smartButtonSelectableLocations = ['product', 'cart', 'checkout', 'mini-cart'];
const groupToggle = (selector, group) => {
const toggleElement = document.querySelector(selector);
@@ -19,7 +19,10 @@ document.addEventListener(
if (! toggleElement.checked) {
group.forEach( (elementToHide) => {
- document.querySelector(elementToHide).style.display = 'none';
+ const element = document.querySelector(elementToHide);
+ if (element) {
+ element.style.display = 'none';
+ }
})
}
toggleElement.addEventListener(
@@ -27,7 +30,10 @@ document.addEventListener(
(event) => {
if (! event.target.checked) {
group.forEach( (elementToHide) => {
- document.querySelector(elementToHide).style.display = 'none';
+ const element = document.querySelector(elementToHide);
+ if (element) {
+ element.style.display = 'none';
+ }
});
return;
@@ -396,6 +402,10 @@ document.addEventListener(
{
value:'authorize',
selector:'#field-capture_for_virtual_only'
+ },
+ {
+ value:'authorize',
+ selector:'#field-capture_on_status_change'
}
]
);
diff --git a/modules/ppcp-onboarding/services.php b/modules/ppcp-onboarding/services.php
index 61c231f94..27ab1ad25 100644
--- a/modules/ppcp-onboarding/services.php
+++ b/modules/ppcp-onboarding/services.php
@@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
-use WooCommerce\PayPalCommerce\Onboarding\Endpoint\PayUponInvoiceEndpoint;
+use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\Onboarding\OnboardingRESTController;
@@ -187,8 +187,8 @@ return array(
$logger
);
},
- 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : PayUponInvoiceEndpoint {
- return new PayUponInvoiceEndpoint(
+ 'onboarding.endpoint.pui' => static function( ContainerInterface $container ) : UpdateSignupLinksEndpoint {
+ return new UpdateSignupLinksEndpoint(
$container->get( 'wcgateway.settings' ),
$container->get( 'button.request-data' ),
$container->get( 'onboarding.signup-link-cache' ),
@@ -229,13 +229,15 @@ return array(
$partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' );
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
$settings = $container->get( 'wcgateway.settings' );
- $signup_link_cache = $container->get( 'onboarding.signup-link-cache' );
+ $signup_link_cache = $container->get( 'onboarding.signup-link-cache' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
return new OnboardingRenderer(
$settings,
$partner_referrals,
$partner_referrals_sandbox,
$partner_referrals_data,
- $signup_link_cache
+ $signup_link_cache,
+ $logger
);
},
'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer {
diff --git a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
index 5cac972dc..4b0c9c165 100644
--- a/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
+++ b/modules/ppcp-onboarding/src/Assets/OnboardingAssets.php
@@ -10,6 +10,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding\Assets;
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
+use WooCommerce\PayPalCommerce\Onboarding\Endpoint\UpdateSignupLinksEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@@ -145,18 +146,18 @@ class OnboardingAssets {
*/
public function get_script_data() {
return array(
- 'endpoint' => \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ),
- 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ),
- 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js',
- 'sandbox_state' => State::get_state_name( $this->state->sandbox_state() ),
- 'production_state' => State::get_state_name( $this->state->production_state() ),
- 'current_state' => State::get_state_name( $this->state->current_state() ),
- 'current_env' => $this->environment->current_environment(),
- 'error_messages' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( LoginSellerEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( $this->login_seller_endpoint::nonce() ),
+ 'paypal_js_url' => 'https://www.paypal.com/webapps/merchantboarding/js/lib/lightbox/partner.js',
+ 'sandbox_state' => State::get_state_name( $this->state->sandbox_state() ),
+ 'production_state' => State::get_state_name( $this->state->production_state() ),
+ 'current_state' => State::get_state_name( $this->state->current_state() ),
+ 'current_env' => $this->environment->current_environment(),
+ 'error_messages' => array(
'no_credentials' => __( 'API credentials must be entered to save the settings.', 'woocommerce-paypal-payments' ),
),
- 'pui_endpoint' => \WC_AJAX::get_endpoint( 'ppc-pui' ),
- 'pui_nonce' => wp_create_nonce( 'ppc-pui' ),
+ 'update_signup_links_endpoint' => \WC_AJAX::get_endpoint( UpdateSignupLinksEndpoint::ENDPOINT ),
+ 'update_signup_links_nonce' => wp_create_nonce( UpdateSignupLinksEndpoint::ENDPOINT ),
);
}
diff --git a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php
index b7a6cdddb..20ab7b95a 100644
--- a/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php
+++ b/modules/ppcp-onboarding/src/Endpoint/LoginSellerEndpoint.php
@@ -123,12 +123,18 @@ class LoginSellerEndpoint implements EndpointInterface {
public function handle_request(): bool {
try {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return false;
+ }
+
$data = $this->request_data->read_request( $this->nonce() );
$is_sandbox = isset( $data['env'] ) && 'sandbox' === $data['env'];
$this->settings->set( 'sandbox_on', $is_sandbox );
$this->settings->set( 'products_dcc_enabled', null );
$this->settings->set( 'products_pui_enabled', null );
$this->settings->persist();
+ do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings );
$endpoint = $is_sandbox ? $this->login_seller_sandbox : $this->login_seller_production;
$credentials = $endpoint->credentials_for(
diff --git a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php
similarity index 79%
rename from modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php
rename to modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php
index 695e54b83..f26e6dc13 100644
--- a/modules/ppcp-onboarding/src/Endpoint/PayUponInvoiceEndpoint.php
+++ b/modules/ppcp-onboarding/src/Endpoint/UpdateSignupLinksEndpoint.php
@@ -14,14 +14,17 @@ use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
+use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
/**
- * Class PayUponInvoiceEndpoint
+ * Class UpdateSignupLinksEndpoint
*/
-class PayUponInvoiceEndpoint implements EndpointInterface {
+class UpdateSignupLinksEndpoint implements EndpointInterface {
+
+ const ENDPOINT = 'ppc-update-signup-links';
/**
* The settings.
@@ -66,7 +69,7 @@ class PayUponInvoiceEndpoint implements EndpointInterface {
protected $logger;
/**
- * PayUponInvoiceEndpoint constructor.
+ * UpdateSignupLinksEndpoint constructor.
*
* @param Settings $settings The settings.
* @param RequestData $request_data The request data.
@@ -97,7 +100,7 @@ class PayUponInvoiceEndpoint implements EndpointInterface {
* @return string
*/
public static function nonce(): string {
- return 'ppc-pui';
+ return self::ENDPOINT;
}
/**
@@ -107,17 +110,35 @@ class PayUponInvoiceEndpoint implements EndpointInterface {
* @throws NotFoundException When order not found or handling failed.
*/
public function handle_request(): bool {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return false;
+ }
+
$signup_links = array();
try {
$data = $this->request_data->read_request( $this->nonce() );
- $this->settings->set( 'ppcp-onboarding-pui', $data['checked'] );
+
+ foreach ( $data['settings'] ?? array() as $field => $value ) {
+ $option = apply_filters(
+ 'ppcp_partner_referrals_option',
+ array(
+ 'field' => $field,
+ 'value' => $value,
+ 'valid' => false,
+ )
+ );
+
+ if ( $option['valid'] ) {
+ $this->settings->set( $field, $value );
+ }
+ }
+
$this->settings->persist();
foreach ( $this->signup_link_ids as $key ) {
- if ( $this->signup_link_cache->has( $key ) ) {
- $this->signup_link_cache->delete( $key );
- }
+ ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete();
}
foreach ( $this->signup_link_ids as $key ) {
diff --git a/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php b/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php
new file mode 100644
index 000000000..b00c9b250
--- /dev/null
+++ b/modules/ppcp-onboarding/src/Helper/OnboardingUrl.php
@@ -0,0 +1,400 @@
+cache = $cache;
+ $this->cache_key_prefix = $cache_key_prefix;
+ $this->user_id = $user_id;
+ }
+
+ /**
+ * Instances the object with a $token.
+ *
+ * @param Cache $cache The cache object where the URL is stored.
+ * @param string $token The token to validate.
+ * @param int $user_id User ID to associate the link with.
+ * @return false|self
+ */
+ public static function make_from_token( Cache $cache, string $token, int $user_id ) {
+ if ( ! $token ) {
+ return false;
+ }
+
+ $token_data = json_decode( UrlHelper::url_safe_base64_decode( $token ) ?: '', true );
+
+ if ( ! $token_data ) {
+ return false;
+ }
+
+ if ( ! isset( $token_data['u'] ) || ! isset( $token_data['k'] ) ) {
+ return false;
+ }
+
+ if ( $token_data['u'] !== $user_id ) {
+ return false;
+ }
+
+ return new self( $cache, $token_data['k'], $token_data['u'] );
+ }
+
+ /**
+ * Validates the token, if it's valid then delete it.
+ * If it's invalid don't delete it, to prevent malicious requests from invalidating the token.
+ *
+ * @param Cache $cache The cache object where the URL is stored.
+ * @param string $token The token to validate.
+ * @param int $user_id User ID to associate the link with.
+ * @return bool
+ */
+ public static function validate_token_and_delete( Cache $cache, string $token, int $user_id ): bool {
+ $onboarding_url = self::make_from_token( $cache, $token, $user_id );
+
+ if ( $onboarding_url === false ) {
+ return false;
+ }
+
+ if ( ! $onboarding_url->load() ) {
+ return false;
+ }
+
+ if ( ( $onboarding_url->token() ?: '' ) !== $token ) {
+ return false;
+ }
+
+ $onboarding_url->replace_previous_token( $token );
+ $onboarding_url->delete();
+ return true;
+ }
+
+ /**
+ * Validates the token against the previous token.
+ * Useful to don't throw errors on burst calls to endpoints.
+ *
+ * @param Cache $cache The cache object where the URL is stored.
+ * @param string $token The token to validate.
+ * @param int $user_id User ID to associate the link with.
+ * @return bool
+ */
+ public static function validate_previous_token( Cache $cache, string $token, int $user_id ): bool {
+ $onboarding_url = self::make_from_token( $cache, $token, $user_id );
+
+ if ( $onboarding_url === false ) {
+ return false;
+ }
+
+ return $onboarding_url->check_previous_token( $token );
+ }
+
+ /**
+ * Load cached data if is valid and initialize object.
+ *
+ * @return bool
+ */
+ public function load(): bool {
+ if ( ! $this->cache->has( $this->cache_key() ) ) {
+ return false;
+ }
+
+ $cached_data = $this->cache->get( $this->cache_key() );
+
+ if ( ! $this->validate_cache_data( $cached_data ) ) {
+ return false;
+ }
+
+ $this->secret = $cached_data['secret'];
+ $this->time = $cached_data['time'];
+ $this->url = $cached_data['url'];
+
+ return true;
+ }
+
+ /**
+ * Initializes the object
+ *
+ * @return void
+ */
+ public function init(): void {
+ try {
+ $this->secret = bin2hex( random_bytes( 16 ) );
+ } catch ( \Exception $e ) {
+ $this->secret = wp_generate_password( 16 );
+ }
+
+ $this->time = time();
+ $this->url = null;
+ }
+
+ /**
+ * Validates data from cache
+ *
+ * @param array $cache_data The data retrieved from the cache.
+ * @return bool
+ */
+ private function validate_cache_data( $cache_data ): bool {
+ if ( ! is_array( $cache_data ) ) {
+ return false;
+ }
+
+ if (
+ ! ( $cache_data['user_id'] ?? false )
+ || ! ( $cache_data['hash_check'] ?? false )
+ || ! ( $cache_data['secret'] ?? false )
+ || ! ( $cache_data['time'] ?? false )
+ || ! ( $cache_data['url'] ?? false )
+ ) {
+ return false;
+ }
+
+ if ( $cache_data['user_id'] !== $this->user_id ) {
+ return false;
+ }
+
+ // Detect if salt has changed.
+ if ( $cache_data['hash_check'] !== wp_hash( '' ) ) {
+ return false;
+ }
+
+ // If we want we can also validate time for expiration eventually.
+
+ return true;
+ }
+
+ /**
+ * Returns the URL
+ *
+ * @return string
+ * @throws RuntimeException Throws in case the URL isn't initialized.
+ */
+ public function get(): string {
+ if ( null === $this->url ) {
+ throw new RuntimeException( 'Object not initialized.' );
+ }
+ return $this->url;
+ }
+
+ /**
+ * Returns the Token
+ *
+ * @return string
+ * @throws RuntimeException Throws in case the object isn't initialized.
+ */
+ public function token(): string {
+ if (
+ null === $this->secret
+ || null === $this->time
+ || null === $this->user_id
+ ) {
+ throw new RuntimeException( 'Object not initialized.' );
+ }
+
+ // Trim the hash to make sure the token isn't too long.
+ $hash = substr(
+ wp_hash(
+ implode(
+ '|',
+ array(
+ $this->cache_key_prefix,
+ $this->user_id,
+ $this->secret,
+ $this->time,
+ )
+ )
+ ),
+ 0,
+ 32
+ );
+
+ $token = wp_json_encode(
+ array(
+ 'k' => $this->cache_key_prefix,
+ 'u' => $this->user_id,
+ 'h' => $hash,
+ )
+ );
+
+ if ( ! $token ) {
+ throw new RuntimeException( 'Unable to generate token.' );
+ }
+
+ return UrlHelper::url_safe_base64_encode( $token );
+ }
+
+ /**
+ * Sets the URL
+ *
+ * @param string $url The URL to store in the cache.
+ * @return void
+ */
+ public function set( string $url ): void {
+ $this->url = $url;
+ }
+
+ /**
+ * Persists the URL and related data in cache
+ *
+ * @return void
+ */
+ public function persist(): void {
+ if (
+ null === $this->secret
+ || null === $this->time
+ || null === $this->user_id
+ || null === $this->url
+ ) {
+ return;
+ }
+
+ $this->cache->set(
+ $this->cache_key(),
+ array(
+ 'hash_check' => wp_hash( '' ), // To detect if salt has changed.
+ 'secret' => $this->secret,
+ 'time' => $this->time,
+ 'user_id' => $this->user_id,
+ 'url' => $this->url,
+ ),
+ $this->cache_ttl
+ );
+ }
+
+ /**
+ * Deletes the token from cache
+ *
+ * @return void
+ */
+ public function delete(): void {
+ $this->cache->delete( $this->cache_key() );
+ }
+
+ /**
+ * Returns the compiled cache key
+ *
+ * @return string
+ */
+ private function cache_key(): string {
+ return implode( '_', array( $this->cache_key_prefix, $this->user_id ) );
+ }
+
+ /**
+ * Returns the compiled cache key of the previous token
+ *
+ * @return string
+ */
+ private function previous_cache_key(): string {
+ return $this->cache_key() . '_previous';
+ }
+
+ /**
+ * Checks it the previous token matches the token provided.
+ *
+ * @param string $previous_token The previous token.
+ * @return bool
+ */
+ private function check_previous_token( string $previous_token ): bool {
+ if ( ! $this->cache->has( $this->previous_cache_key() ) ) {
+ return false;
+ }
+
+ $cached_token = $this->cache->get( $this->previous_cache_key() );
+
+ return $cached_token === $previous_token;
+ }
+
+ /**
+ * Replaces the previous token.
+ *
+ * @param string $previous_token The previous token.
+ * @return void
+ */
+ private function replace_previous_token( string $previous_token ): void {
+ $this->cache->set(
+ $this->previous_cache_key(),
+ $previous_token,
+ $this->previous_cache_ttl
+ );
+ }
+
+}
diff --git a/modules/ppcp-onboarding/src/Helper/UrlHelper.php b/modules/ppcp-onboarding/src/Helper/UrlHelper.php
new file mode 100644
index 000000000..b50402ead
--- /dev/null
+++ b/modules/ppcp-onboarding/src/Helper/UrlHelper.php
@@ -0,0 +1,42 @@
+get( 'onboarding.endpoint.login-seller' );
/**
- * The ChangeCartEndpoint.
+ * The LoginSellerEndpoint.
*
- * @var ChangeCartEndpoint $endpoint
+ * @var LoginSellerEndpoint $endpoint
*/
$endpoint->handle_request();
}
);
add_action(
- 'wc_ajax_ppc-pui',
+ 'wc_ajax_' . UpdateSignupLinksEndpoint::ENDPOINT,
static function () use ( $c ) {
$endpoint = $c->get( 'onboarding.endpoint.pui' );
$endpoint->handle_request();
diff --git a/modules/ppcp-onboarding/src/OnboardingRESTController.php b/modules/ppcp-onboarding/src/OnboardingRESTController.php
index 4eac5ba72..d95b0a9d3 100644
--- a/modules/ppcp-onboarding/src/OnboardingRESTController.php
+++ b/modules/ppcp-onboarding/src/OnboardingRESTController.php
@@ -12,6 +12,7 @@ namespace WooCommerce\PayPalCommerce\Onboarding;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
+use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
/**
* Exposes and handles REST routes related to onboarding.
@@ -237,6 +238,7 @@ class OnboardingRESTController {
$settings->set( 'products_dcc_enabled', null );
$settings->set( 'products_pui_enabled', null );
+ do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $settings );
if ( ! $settings->persist() ) {
return new \WP_Error(
@@ -249,7 +251,7 @@ class OnboardingRESTController {
}
$webhook_registrar = $this->container->get( 'webhook.registrar' );
- $webhook_registrar->unregister();
+ assert( $webhook_registrar instanceof WebhookRegistrar );
$webhook_registrar->register();
return array();
diff --git a/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php b/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php
index 844e78875..a5d7628ec 100644
--- a/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php
+++ b/modules/ppcp-onboarding/src/Render/OnboardingOptionsRenderer.php
@@ -57,19 +57,24 @@ class OnboardingOptionsRenderer {
*/
public function render( bool $is_shop_supports_dcc ): string {
$checked = $is_shop_supports_dcc ? '' : 'checked';
- return '
-';
+
+ $on_boarding_options = '
+
+ ' .
+ __( 'Enable PayPal Payments — includes PayPal, Venmo, Pay Later — with fraud protection', 'woocommerce-paypal-payments' ) . '
+
+
+
+ ' . __( 'Securely accept all major credit & debit cards on the strength of the PayPal network', 'woocommerce-paypal-payments' ) . '
+
+ ' .
+ $this->render_dcc( $is_shop_supports_dcc ) .
+ ' ' .
+ $this->render_pui_option();
+
+ return ' ' .
+ apply_filters( 'ppcp_onboarding_options', $on_boarding_options ) .
+ ' ';
}
/**
@@ -90,7 +95,7 @@ class OnboardingOptionsRenderer {
$checked = '';
}
- return ' ' .
+ return ' ' .
__( 'Onboard with Pay upon Invoice', 'woocommerce-paypal-payments' ) . '
';
}
diff --git a/modules/ppcp-onboarding/src/Render/OnboardingRenderer.php b/modules/ppcp-onboarding/src/Render/OnboardingRenderer.php
index 8bbbc238a..081f1599b 100644
--- a/modules/ppcp-onboarding/src/Render/OnboardingRenderer.php
+++ b/modules/ppcp-onboarding/src/Render/OnboardingRenderer.php
@@ -9,11 +9,14 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Onboarding\Render;
+use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
+use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
/**
* Class OnboardingRenderer
@@ -55,6 +58,13 @@ class OnboardingRenderer {
*/
protected $cache;
+ /**
+ * The logger
+ *
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* OnboardingRenderer constructor.
*
@@ -63,19 +73,22 @@ class OnboardingRenderer {
* @param PartnerReferrals $sandbox_partner_referrals The PartnerReferrals for sandbox.
* @param PartnerReferralsData $partner_referrals_data The default partner referrals data.
* @param Cache $cache The cache.
+ * @param ?LoggerInterface $logger The logger.
*/
public function __construct(
Settings $settings,
PartnerReferrals $production_partner_referrals,
PartnerReferrals $sandbox_partner_referrals,
PartnerReferralsData $partner_referrals_data,
- Cache $cache
+ Cache $cache,
+ LoggerInterface $logger = null
) {
$this->settings = $settings;
$this->production_partner_referrals = $production_partner_referrals;
$this->sandbox_partner_referrals = $sandbox_partner_referrals;
$this->partner_referrals_data = $partner_referrals_data;
$this->cache = $cache;
+ $this->logger = $logger ?: new NullLogger();
}
/**
@@ -96,14 +109,29 @@ class OnboardingRenderer {
$environment = $is_production ? 'production' : 'sandbox';
$product = 'PPCP' === $data['products'][0] ? 'ppcp' : 'express_checkout';
- if ( $this->cache->has( $environment . '-' . $product ) ) {
- return $this->cache->get( $environment . '-' . $product );
+ $cache_key = $environment . '-' . $product;
+
+ $onboarding_url = new OnboardingUrl( $this->cache, $cache_key, get_current_user_id() );
+
+ if ( $onboarding_url->load() ) {
+ $this->logger->debug( 'Loaded onbording URL from cache: ' . $cache_key );
+ return $onboarding_url->get() ?: '';
}
+ $this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
+
+ $onboarding_url->init();
+
+ $data = $this->partner_referrals_data
+ ->append_onboarding_token( $data, $onboarding_url->token() ?: '' );
+
$url = $is_production ? $this->production_partner_referrals->signup_link( $data ) : $this->sandbox_partner_referrals->signup_link( $data );
$url = add_query_arg( $args, $url );
- $this->cache->set( $environment . '-' . $product, $url, 3 * MONTH_IN_SECONDS );
+ $onboarding_url->set( $url );
+ $onboarding_url->persist();
+
+ $this->logger->info( 'Persisted onboarding URL for: ' . $cache_key );
return $url;
}
diff --git a/modules/ppcp-onboarding/yarn.lock b/modules/ppcp-onboarding/yarn.lock
index 84a6766b7..fdcec8c07 100644
--- a/modules/ppcp-onboarding/yarn.lock
+++ b/modules/ppcp-onboarding/yarn.lock
@@ -1682,9 +1682,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ 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"
@@ -1702,9 +1702,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
- integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ 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"
@@ -2155,10 +2155,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-order-tracking/carriers.php b/modules/ppcp-order-tracking/carriers.php
index 5af0c9403..429f12a51 100644
--- a/modules/ppcp-order-tracking/carriers.php
+++ b/modules/ppcp-order-tracking/carriers.php
@@ -9,604 +9,1586 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\OrderTracking;
-use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
+ return (array) apply_filters(
+ 'woocommerce_paypal_payments_tracking_carriers',
+ array(
+ 'global' => array(
+ 'name' => 'Global',
+ 'items' => array(
+ '99MINUTOS' => _x( '99minutos', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'A2B_BA' => _x( 'A2B Express Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ABCUSTOM_SFTP' => _x( 'AB Custom Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ACILOGISTIX' => _x( 'ACI Logistix', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ACOMMERCE' => _x( 'ACOMMERCE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ACTIVOS24_API' => _x( 'Activos24', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ADS' => _x( 'ADS Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AEROFLASH' => _x( 'AEROFLASH', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AGEDISS_SFTP' => _x( 'Agediss', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AIR_21' => _x( 'AIR 21', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AIRSPEED' => _x( 'AIRSPEED', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AIRTERRA' => _x( 'Airterra', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AITWORLDWIDE_API' => _x( 'AIT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AITWORLDWIDE_SFTP' => _x( 'AIT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ALLIED_EXPRESS_FTP' => _x( 'Allied Express (FTP)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ALLJOY' => _x( 'ALLJOY SUPPLY CHAIN', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AMAZON_EMAIL_PUSH' => _x( 'Amazon', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AMAZON_ORDER' => _x( 'Amazon order', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AMAZON_UK_API' => _x( 'amazon_uk_api', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AMS_GRP' => _x( 'AMS Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ANDREANI_API' => _x( 'Andreani', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ANTERAJA' => _x( 'Anteraja', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARAMEX' => _x( 'Aramex', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARAMEX_API' => _x( 'Aramex', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARASKARGO' => _x( 'Aras Cargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARGENTS_WEBHOOK' => _x( 'Argents Express Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ASENDIA_DE' => _x( 'asendia_de', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ATSHEALTHCARE_REFERENCE' => _x( 'ATS Healthcare', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ATSHEALTHCARE' => _x( 'ATS Healthcare', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AUEXPRESS' => _x( 'Au Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AUSTRALIA_POST_API' => _x( 'Australia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AVERITT' => _x( 'Averitt Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AXLEHIRE_FTP' => _x( 'Axlehire', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AXLEHIRE' => _x( 'AxleHire', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BARQEXP' => _x( 'Barq', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BDMNET' => _x( 'BDMnet', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BEL_BELGIUM_POST' => _x( 'bel_belgium_post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLR_BELPOST' => _x( 'Belpost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BERT' => _x( 'BERT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BESTTRANSPORT_SFTP' => _x( 'Best Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BETTERTRUCKS' => _x( 'Better Trucks', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BIGSMART' => _x( 'Big Smart', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BIOCAIR_FTP' => _x( 'BioCair', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BJSHOMEDELIVERY' => _x( 'BJS Distribution courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BJSHOMEDELIVERY_FTP' => _x( 'BJS Distribution, Storage & Couriers - FTP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLUEDART' => _x( 'BLUEDART', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLUEDART_API' => _x( 'Bluedart', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BOLLORE_LOGISTICS' => _x( 'Bollore Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BOMI' => _x( 'Bomi Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BE_BPOST' => _x( 'Bpost (www.bpost.be)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BPOST_API' => _x( 'Bpost API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BPOST_INT' => _x( 'Bpost international', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BRT_IT_API' => _x( 'BRT Bartolini API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BUFFALO' => _x( 'BUFFALO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BURD' => _x( 'Burd Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHROBINSON' => _x( 'C.H. Robinson Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CAGO' => _x( 'Cago', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CANPAR' => _x( 'CANPAR', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CAPITAL' => _x( 'Capital Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CARRY_FLAP' => _x( 'Carry-Flap Co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CBL_LOGISTICA_API' => _x( 'CBL Logistica (API)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CDLDELIVERS' => _x( 'CDL Last Mile', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CELERITAS' => _x( 'Celeritas Transporte', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CEVA' => _x( 'CEVA LOGISTICS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CEVA_TRACKING' => _x( 'CEVA Package', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHAZKI' => _x( 'Chazki', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHIENVENTURE_WEBHOOK' => _x( 'Chienventure', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHILEXPRESS' => _x( 'Chile Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CITY56_WEBHOOK' => _x( 'City Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_GLS' => _x( 'CJ GLS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_LOGISTICS' => _x( 'CJ Logistics International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_PHILIPPINES' => _x( 'cj_philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CLICKLINK_SFTP' => _x( 'ClickLink', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_LOGISTICS' => _x( 'CN Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COLLECTPLUS' => _x( 'COLLECTPLUS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COM1EXPRESS' => _x( 'ComOne Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CONCISE' => _x( 'Concise', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CONCISE_WEBHOOK' => _x( 'Concise', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CONCISE_API' => _x( 'Concise', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COORDINADORA_API' => _x( 'Coordinadora', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COPA_COURIER' => _x( 'Copa Airlines Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CORREOS_DE_ESPANA' => _x( 'CORREOS DE ESPANA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CORREOSEXPRESS_API' => _x( 'Correos Express (API)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CORREOS_ES' => _x( 'correos Express (www.correos.es)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COURANT_PLUS_API' => _x( 'Courant Plus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COURIER_POST' => _x( 'COURIER POST', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COURIERPLUS' => _x( 'COURIERPLUS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CRLEXPRESS' => _x( 'CRL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CROSSFLIGHT' => _x( 'Crossflight Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CRYOPDP_FTP' => _x( 'CryoPDP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CESKAPOSTA_API' => _x( 'Czech Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DEXPRESS_WEBHOOK' => _x( 'D Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DACHSER' => _x( 'DACHSER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DACHSER_WEB' => _x( 'DACHSER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAESHIN' => _x( 'Daeshin', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAIICHI' => _x( 'Daiichi Freight System Inc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DANNIAO' => _x( 'Danniao', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAO365' => _x( 'DAO365', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAYROSS' => _x( 'Day & Ross', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DYLT' => _x( 'Daylight Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DBSCHENKER_API' => _x( 'DB Schenker', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DBSCHENKER_B2B' => _x( 'DB Schenker B2B', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DBSCHENKER_ICELAND' => _x( 'DB Schenker Iceland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DDEXPRESS' => _x( 'DD Express Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DE_DHL' => _x( 'DE DHL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELCART_IN' => _x( 'delcart_in', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVERYOURPARCEL_ZA' => _x( 'Deliver Your Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVER_IT' => _x( 'Deliver-iT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVERE' => _x( 'delivere', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVERR_SFTP' => _x( 'Deliverr', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELTEC_DE' => _x( 'DELTEC DE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DEMANDSHIP' => _x( 'DemandShip', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DEUTSCHE_DE' => _x( 'deutsche_de', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_API' => _x( 'DHL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_REFERENCE_API' => _x( 'DHL (Reference number)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_ACTIVE_TRACING' => _x( 'DHL Active Tracing', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_ECOMMERCE_GC' => _x( 'DHL eCommerce Greater China', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_GLOBAL_MAIL_API' => _x( 'DHL eCommerce Solutions', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DE_DHL_EXPRESS' => _x( 'DHL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_SFTP' => _x( 'DHL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_FR' => _x( 'DHL France (www.dhl.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_FREIGHT' => _x( 'DHL Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL' => _x( 'dhl Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_GLOBAL_FORWARDING_API' => _x( 'DHL Global Forwarding API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_GT_API' => _x( 'DHL Global Forwarding Guatemala', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_PA_API' => _x( 'DHL GLOBAL FORWARDING PANAMÁ', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IT_DHL_ECOMMERCE' => _x( 'DHL International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_JP' => _x( 'DHL Japan', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_PARCEL_NL' => _x( 'DHL Parcel NL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_SG' => _x( 'dhl Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_ES_SFTP' => _x( 'DHL Spain Domestic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_SUPPLYCHAIN_IN' => _x( 'DHL supply chain India', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_SUPPLYCHAIN_ID' => _x( 'DHL Supply Chain Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_AT' => _x( 'dhl_at', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_GLOBAL_MAIL' => _x( 'dhl_global_mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_IT' => _x( 'dhl_it', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_PIECEID' => _x( 'dhl_pieceid', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_SUPPLY_CHAIN_AU' => _x( 'dhl_supply_chain_au', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHLPARCEL_UK' => _x( 'dhlparcel_uk', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIALOGO_LOGISTICA_API' => _x( 'Dialogo Logistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIALOGO_LOGISTICA' => _x( 'Dialogo Logistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIRECTFREIGHT_AU_REF' => _x( 'Direct Freight Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIREX' => _x( 'Direx', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DKSH' => _x( 'DKSH', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DMFGROUP' => _x( 'DMF', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DNJ_EXPRESS' => _x( 'DNJ Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DOTZOT' => _x( 'DOTZOT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD' => _x( 'DPD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_AT_SFTP' => _x( 'DPD Austria', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_DELISTRACK' => _x( 'DPD delistrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_NL' => _x( 'DPD Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_RU_API' => _x( 'DPD Russia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_SK_SFTP' => _x( 'DPD Slovakia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_CH_SFTP' => _x( 'DPD Switzerland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_UK_SFTP' => _x( 'DPD UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_DE' => _x( 'dpd_de', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_FR_REFERENCE' => _x( 'dpd_fr_reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_UK' => _x( 'dpd_uk', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_DPEX' => _x( 'DPEX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPEX' => _x( 'DPEX (www.dpex.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DSV' => _x( 'DSV courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DSV_REFERENCE' => _x( 'DSV Futurewave', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DX' => _x( 'DX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DX_B2B_CONNUM' => _x( 'DX (B2B)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DX_FREIGHT' => _x( 'DX Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DYNALOGIC' => _x( 'Dynamic Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EASTWESTCOURIER_FTP' => _x( 'East West Courier Pte Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EC_CN' => _x( 'EC_CN', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECARGO' => _x( 'ECARGO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECEXPRESS' => _x( 'ECexpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECMS' => _x( 'ECMS International Logistics Co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECOFREIGHT' => _x( 'Eco Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECOURIER' => _x( 'ecourier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECOUTIER' => _x( 'eCoutier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EFS' => _x( 'EFS (E-commerce Fulfillment Service)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ELITE_CO' => _x( 'Elite Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ELOGISTICA' => _x( 'elogistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ELTA_GR' => _x( 'elta_gr', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARE_EMIRATES_POST' => _x( 'Emirates Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EMS' => _x( 'EMS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EMS_CN' => _x( 'ems_cn', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ENSENDA' => _x( 'ENSENDA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EFWNOW_API' => _x( 'Estes Forwarding Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ETOMARS' => _x( 'Etomars', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ETOTAL' => _x( 'eTotal Solution Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EDF_FTP' => _x( 'Eurodifarm', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EURODIS' => _x( 'eurodis', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EUROPAKET_API' => _x( 'Europacket+', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYHERMES_UK_API' => _x( 'EVRi', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EWE' => _x( 'EWE Global Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EXELOT_FTP' => _x( 'Exelot Ltd.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EXPEDITORS' => _x( 'Expeditors', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EXPEDITORS_API_REF' => _x( 'Expeditors API Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EZSHIP' => _x( 'EZship', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FAIRSENDEN_API' => _x( 'fairsenden', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FXTRAN' => _x( 'Falcon Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FAN' => _x( 'FAN COURIER EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FNF_ZA' => _x( 'Fast & Furious', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTDESPATCH' => _x( 'Fast Despatch Logistics Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTBOX' => _x( 'Fastbox', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTSHIP' => _x( 'Fastship Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTTRACK' => _x( 'fasttrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTWAY_AU' => _x( 'fastway_au', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTWAY_UK' => _x( 'FASTWAY_UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTWAY_US' => _x( 'FASTWAY_US', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTWAY_ZA' => _x( 'fastway_za', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FAXECARGO' => _x( 'Faxe Cargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FEDEX_FR' => _x( 'FedEx® Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FEDEX_API' => _x( 'FedEx®', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FERCAM_IT' => _x( 'fercam_it', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FETCHR' => _x( 'Fetchr', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FIRST_LOGISTICS_API' => _x( 'First Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FIRST_LOGISITCS' => _x( 'first_logisitcs', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FITZMARK_API' => _x( 'FitzMark', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FLASHEXPRESS_WEBHOOK' => _x( 'Flash Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FLIGHTLG' => _x( 'Flight Logistics Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FLIPXP' => _x( 'FlipXpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FLYTEXPRESS' => _x( 'FLYTEXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FORWARDAIR' => _x( 'Forward Air', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FOUR_PX_EXPRESS' => _x( 'FOUR PX EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FR_COLISSIMO' => _x( 'fr_colissimo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FR_MONDIAL' => _x( 'fr_mondial', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FRAGILEPAK_SFTP' => _x( 'FragilePAK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FRONTDOORCORP' => _x( 'FRONTdoor Collective', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FUJEXP' => _x( 'FUJIE EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GWLOGIS_API' => _x( 'G.I.G', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GAC' => _x( 'GAC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GATI_KWE_API' => _x( 'Gati-KWE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GDPHARM' => _x( 'GDPharm Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GW_WORLD' => _x( 'Gebrüder Weiss', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEODIS' => _x( 'GEODIS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEODIS_API' => _x( 'GEODIS - Distribution & Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GPOST' => _x( 'Georgian Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GIAO_HANG' => _x( 'Giao hàng nhanh', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GIO_ECOURIER_API' => _x( 'GIO Express Ecourier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GIO_ECOURIER' => _x( 'GIO Express Inc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GOGLOBALPOST' => _x( 'Global Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBEGISTICS' => _x( 'GLOBEGISTICS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOVO' => _x( 'Glovo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS' => _x( 'GLS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_SPAIN_API' => _x( 'GLS Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_DE' => _x( 'GLS_DE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_ES' => _x( 'GLS_ES', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_FR' => _x( 'GLS_FR', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_ITALY_FTP' => _x( 'gls_italy_ftp', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_SPAIN' => _x( 'gls_spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GOLS' => _x( 'GO Logistics & Storage', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GOPEOPLE' => _x( 'Go People', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GORUSH' => _x( 'Go Rush', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GOJEK' => _x( 'Gojek', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GREYHOUND' => _x( 'GREYHOUND', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAZET' => _x( 'Groupe Mazet', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HANJIN' => _x( 'HanJin', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HELLENIC_POST' => _x( 'Hellenic (Greece) Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HELLMANN' => _x( 'Hellmann Worldwide Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HELTHJEM_API' => _x( 'Helthjem', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMES_DE_FTP' => _x( 'Hermes Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMES_UK_SFTP' => _x( 'Hermes UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMESWORLD_UK' => _x( 'hermesworld_uk', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HEROEXPRESS' => _x( 'Hero Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HFD' => _x( 'HFD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HK_RPX' => _x( 'hk_rpx', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOMELOGISTICS' => _x( 'Home Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOMERUNNER' => _x( 'HomeRunner', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMES_IT' => _x( 'HR Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HSDEXPRESS' => _x( 'HSDEXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HSM_GLOBAL' => _x( 'HSM Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUANTONG' => _x( 'HuanTong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUBBED' => _x( 'HUBBED', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUNTER_EXPRESS_SFTP' => _x( 'Hunter Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IBVENTURE_WEBHOOK' => _x( 'IB Venture', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTUR_IS' => _x( 'Iceland Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ICSCOURIER' => _x( 'ICS COURIER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IDEXPRESS_ID' => _x( 'iDexpress Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IDN_POS' => _x( 'idn_pos', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IDS_LOGISTICS' => _x( 'ids_logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ILYANGLOGIS' => _x( 'Ilyang logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IMEXGLOBALSOLUTIONS' => _x( 'imexglobalsolutions', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IMILE_API' => _x( 'iMile', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IML' => _x( 'IML courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IMX' => _x( 'IMX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INDIA_POST' => _x( 'India Post Domestic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INDIA_POST_INT' => _x( 'India Post International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INEXPOST' => _x( 'Inexpost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INNTRALOG_SFTP' => _x( 'Inntralog GmbH', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INPOST_UK' => _x( 'InPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INSTABOX_WEBHOOK' => _x( 'Instabox', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTERNATIONAL_SEUR_API' => _x( 'International Seur API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTERSMARTTRANS' => _x( 'INTERSMARTTRANS & SOLUTIONS SL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTEX_DE' => _x( 'INTEX Paketdienst GmbH', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTIME_FTP' => _x( 'InTime', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ITHINKLOGISTICS' => _x( 'iThink Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JTCARGO' => _x( 'J&T CARGO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JTEXPRESS_PH' => _x( 'J&T Express Philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JTEXPRESS_SG_API' => _x( 'J&T Express Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JT_LOGISTICS' => _x( 'J&T International logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JAVIT' => _x( 'Javit', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_JCEX' => _x( 'JCEX courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JD_EXPRESS' => _x( 'JD Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JD_WORLDWIDE' => _x( 'JD Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JETSHIP_MY' => _x( 'jetship_my', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JNE_API' => _x( 'JNE (API)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IDN_JNE' => _x( 'JNE Express (Jalur Nugraha Ekakurir)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JOYINGBOX' => _x( 'joyingbox', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KARGOMKOLAY' => _x( 'KargomKolay (CargoMini)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KEDAEX' => _x( 'KedaEX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HK_TGX' => _x( 'Kerry Express Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KERRY_EXPRESS_TW_API' => _x( 'Kerry Express TaiWan', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THA_KERRY' => _x( 'Kerry Express Thailand', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KERRY_EXPRESS_TH_WEBHOOK' => _x( 'Kerry Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KNG' => _x( 'Keuhne + Nagel Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BE_KIALA' => _x( 'Kiala', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISYSTEMS_SFTP' => _x( 'Kiitääjät', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KOMON_EXPRESS' => _x( 'Komon Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KRONOS_WEBHOOK' => _x( 'Kronos Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KRONOS' => _x( 'Kronos Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KUEHNE' => _x( 'Kuehne + Nagel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LALAMOVE_API' => _x( 'Lalamove', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LBCEXPRESS_FTP' => _x( 'LBC EXPRESS INC.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LBCEXPRESS_API' => _x( 'LBC EXPRESS INC.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LCTBR_API' => _x( 'LCT do Brasil', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LTU_LIETUVOS' => _x( 'Lietuvos pastas', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LINKBRIDGE' => _x( 'Link Bridge(BeiJing)international logistics co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LION_PARCEL' => _x( 'LION PARCEL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LIVRAPIDE' => _x( 'Livrapide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGGI' => _x( 'Loggi', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTICSWORLDWIDE_KR' => _x( 'LOGISTICSWORLDWIDE KR', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTICSWORLDWIDE_MY' => _x( 'LOGISTICSWORLDWIDE MY', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGWIN_LOGISTICS' => _x( 'Logwin Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGYSTO' => _x( 'Logysto', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LUWJISTIK' => _x( 'Luwjistik', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MX_CARGO' => _x( 'M&X cargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'M3LOGISTICS' => _x( 'M3 Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'REIMAGINEDELIVERY' => _x( 'maergo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAGYAR_POSTA_API' => _x( 'Magyar Posta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAIL_BOX_ETC' => _x( 'Mail Boxes Etc.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_EMS' => _x( 'Malaysia Post EMS / Pos Laju', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MALCA_AMIT_API' => _x( 'Malca Amit', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MALCA_AMIT' => _x( 'Malca-Amit', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MARKEN' => _x( 'Marken', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEDAFRICA' => _x( 'Med Africa Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEEST' => _x( 'Meest', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEGASAVE' => _x( 'megasave', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MENSAJEROSURBANOS_API' => _x( 'Mensajeros Urbanos', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MWD' => _x( 'Metropolitan Warehouse & Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MWD_API' => _x( 'Metropolitan Warehouse & Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MHI' => _x( 'Mhi', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MIKROPAKKET' => _x( 'Mikropakket', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MISUMI_CN' => _x( 'MISUMI Group Inc.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MNX' => _x( 'MNX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MOBI_BR' => _x( 'Mobi Logistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MONDIALRELAY_FR' => _x( 'Mondial Relay France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MONDIALRELAY_ES' => _x( 'Mondial Relay Spain(Punto Pack)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MONDIAL_BE' => _x( 'MONDIAL_BE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MOOVA' => _x( 'Moova', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MORNINGLOBAL' => _x( 'Morning Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MOTHERSHIP_API' => _x( 'Mothership', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MOVIANTO' => _x( 'Movianto', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MUDITA' => _x( 'MUDITA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYDYNALOGIC' => _x( 'My DynaLogic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYSENDLE_API' => _x( 'mySendle', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NMTRANSFER' => _x( 'N&M Transfer Co., Inc.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NACEX_SPAIN_REFERENCE' => _x( 'nacex_spain_reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NAEKO_FTP' => _x( 'Naeko Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NAQEL_EXPRESS' => _x( 'Naqel Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NEWZEALAND_COURIERS' => _x( 'NEW ZEALAND COURIERS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NEWGISTICS' => _x( 'Newgistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NEWGISTICSAPI' => _x( 'Newgistics API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NIGHTLINE_UK' => _x( 'nightline_uk', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NIMBUSPOST' => _x( 'NimbusPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NIPPON_EXPRESS_FTP' => _x( 'Nippon Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NIPPON_EXPRESS' => _x( 'Nippon Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NORTHLINE' => _x( 'Northline', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOVA_POSHTA_API' => _x( 'Nova Poshta API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOVOFARMA_WEBHOOK' => _x( 'Novofarma', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NTL' => _x( 'NTL logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NYTLOGISTICS' => _x( 'NYT SUPPLY CHAIN LOGISTICS Co., LTD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OHI_WEBHOOK' => _x( 'Ohi', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHOPOLIVE' => _x( 'Olive', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OMLOGISTICS_API' => _x( 'OM LOGISTICS LTD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OMNIRPS_WEBHOOK' => _x( 'Omni Returns', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ONTRAC' => _x( 'ONTRAC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ORANGECONNEX' => _x( 'orangeconnex', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ORANGE_DS' => _x( 'OrangeDS (Orange Distribution Solutions Inc)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OSM_WORLDWIDE_SFTP' => _x( 'OSM Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OZEPARTS_SHIPPING' => _x( 'Ozeparts Shipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'P2P_TRC' => _x( 'P2P TrakPak', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PACKETA' => _x( 'Packeta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PACKFLEET' => _x( 'PACKFLEET', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PACKS' => _x( 'Packs', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAKAJO' => _x( 'Pakajo World', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANDAGO_API' => _x( 'Pandago', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANDION' => _x( 'Pandion', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANDU' => _x( 'PANDU', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANTHER_REFERENCE_API' => _x( 'Panther Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANTHER_ORDER_NUMBER' => _x( 'panther_order_number', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAPA_WEBHOOK' => _x( 'Papa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELRIGHT' => _x( 'Parcel Right', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCEL_2_POST' => _x( 'Parcel To Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELFORCE' => _x( 'PARCELFORCE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELSTARS_WEBHOOK' => _x( 'Parcelstars', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCLL' => _x( 'PARCLL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PASSPORTSHIPPING' => _x( 'Passport Shipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PATHEON' => _x( 'Patheon Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAYO' => _x( 'Payo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PCHOME_API' => _x( 'Pchome Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PGEON_API' => _x( 'Pgeon', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PHSE_API' => _x( 'PHSE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PICKUPP_VNM' => _x( 'pickupp_vnm', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PIDGE' => _x( 'Pidge', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PIL_LOGISTICS' => _x( 'PIL Logistics (China) Co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PLYCONGROUP' => _x( 'Plycon Transportation Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POLARSPEED' => _x( 'PolarSpeed Inc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTONE' => _x( 'Post ONE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTAPLUS' => _x( 'Posta Plus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTE_ITALIANE_PACCOCELERE' => _x( 'Poste Italiane Paccocelere', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTEN_NORGE' => _x( 'Posten Norge (www.posten.no)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTI_API' => _x( 'Posti API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTNL_INT_3_S' => _x( 'PostNL International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NLD_POSTNL' => _x( 'PostNL International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTNL_INTERNATIONAL' => _x( 'PostNL International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SWE_POSTNORD' => _x( 'Postnord sweden', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTPLUS' => _x( 'PostPlus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PROCARRIER' => _x( 'Pro Carrier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRODUCTCAREGROUP_SFTP' => _x( 'Product Care Services Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PROFESSIONAL_COURIERS' => _x( 'PROFESSIONAL COURIERS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PPL' => _x( 'Professional Parcel Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PROMEDDELIVERY' => _x( 'ProMed Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PUROLATOR' => _x( 'purolator', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PUROLATOR_INTERNATIONAL' => _x( 'Purolator International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QTRACK' => _x( 'QTrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QUALITYPOST' => _x( 'qualitypost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QINTL_API' => _x( 'Quickstat Courier LLC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QUIQUP' => _x( 'Quiqup', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RANSA_WEBHOOK' => _x( 'Ransa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'REDJEPAKKETJE' => _x( 'Red je Pakketje', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RELAISCOLIS' => _x( 'Relais Colis', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RHENUS_GROUP' => _x( 'Rhenus Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RHENUS_UK_API' => _x( 'Rhenus Logistics UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AIR_CANADA' => _x( 'Rivo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RIXONHK_API' => _x( 'Rixon Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROCHE_INTERNAL_SFTP' => _x( 'Roche Internal Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROYAL_MAIL_FTP' => _x( 'Royal Mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROYALSHIPMENTS' => _x( 'royalshipments', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RRDONNELLEY' => _x( 'rrdonnelley', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RUSSIAN_POST' => _x( 'Russian post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAEE' => _x( 'saee', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAGAWA' => _x( 'SAGAWA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAGAWA_API' => _x( 'Sagawa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SBERLOGISTICS_RU' => _x( 'Sber Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SECRETLAB_WEBHOOK' => _x( 'Secretlab', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEINO_API' => _x( 'Seino', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEKO_SFTP' => _x( 'SEKO Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SENDING' => _x( 'Sending Transporte Urgente y Comunicacion', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHOWL' => _x( 'SENHONG INTERNATIONAL LOGISTICS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOWLOG_API' => _x( 'Sequoialog', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SERVIENTREGA' => _x( 'Servientrega', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SERVIP_WEBHOOK' => _x( 'SerVIP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SETEL' => _x( 'Setel Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SF_EX' => _x( 'SF Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SF_EXPRESS_CN' => _x( 'SF Express China', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SGT_IT' => _x( 'SGT_IT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHADOWFAX' => _x( 'Shadowfax', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHENZHEN' => _x( 'shenzhen 1st International Logistics(Group)Co', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOTSIN_CARGO' => _x( 'SHENZHEN HOTSIN CARGO INTL FORWARDING CO., LTD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KWT' => _x( 'Shenzhen Jinghuada Logistics Co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHERPA' => _x( 'Sherpa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPA' => _x( 'SHIPA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPPIE' => _x( 'Shippie', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPPIFY' => _x( 'Shippify, Inc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPROCKET' => _x( 'Shiprocket X', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPX' => _x( 'ShipX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPXPRES' => _x( 'SHIPXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPX' => _x( 'Shopee Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPX_TH' => _x( 'Shopee Xpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHUNBANG_EXPRESS' => _x( 'ShunBang Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHYPLITE' => _x( 'Shypmax', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIMPLETIRE_WEBHOOK' => _x( 'SimpleTire', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIMSGLOBAL' => _x( 'Sims Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIODEMKA' => _x( 'SIODEMKA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKynet_WORLDWIDE' => _x( 'SkyNet Worldwide Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKY_POSTAL' => _x( 'SkyPostal', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SK_POSTA' => _x( 'Slovenska pošta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMARTCAT' => _x( 'SMARTCAT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMARTKARGO' => _x( 'SmartKargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMG_EXPRESS' => _x( 'SMG Direct', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMSA_EXPRESS_WEBHOOK' => _x( 'SMSA Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SNTGLOBAL_API' => _x( 'Snt Global Etrax', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SOLISTICA_API' => _x( 'solistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPANISH_SEUR_FTP' => _x( 'Spanish Seur', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPECTRAN' => _x( 'Spectran', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPEEDEX' => _x( 'speedex', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPEEDY' => _x( 'Speedy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPREETAIL_API' => _x( 'Spreetail', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPRINT_PACK' => _x( 'SPRINT PACK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SRT_TRANSPORT' => _x( 'SRT Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STAR_TRACK_NEXT_FLIGHT' => _x( 'Star Track Next Flight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STARLINKS_API' => _x( 'Starlinks Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STARTRACK' => _x( 'startrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STAR_TRACK_WEBHOOK' => _x( 'StarTrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STARTRACK_EXPRESS' => _x( 'startrack_express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STATOVERNIGHT' => _x( 'Stat Overnight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_STO' => _x( 'STO Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SWISHIP' => _x( 'Swiship', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SWISS_POST' => _x( 'SWISS POST', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'T_CAT' => _x( 'T-cat', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'T_CAT_API' => _x( 'T-cat', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGINEXT_WEBHOOK' => _x( 'T&W Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TW_TAIWAN_POST' => _x( 'Taiwan Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAMERGROUP_WEBHOOK' => _x( 'Tamer Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAQBIN_HK' => _x( 'TAQBIN Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAQBIN_SG' => _x( 'taqbin_sg', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TCS_API' => _x( 'TCS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TECOR' => _x( 'tecor', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TELEPORT_WEBHOOK' => _x( 'Teleport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIC_TELIWAY' => _x( 'Teliway SIC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TESTING_COURIER_WEBHOOK' => _x( 'Testing Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TESTING_COURIER' => _x( 'Testing Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TH_CJ' => _x( 'TH_CJ', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THIJS_NL' => _x( 'Thijs Logistiek', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THUNDEREXPRESS' => _x( 'Thunder Express Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TIPSA_API' => _x( 'Tipsa API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TIPSA_REF' => _x( 'Tipsa Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_FR_REFERENCE' => _x( 'TNT France Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_REFR' => _x( 'TNT Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_AU' => _x( 'tnt_au', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_CN' => _x( 'TNT_CN', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_DE' => _x( 'TNT_DE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_ES' => _x( 'TNT_ES', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_IT' => _x( 'tnt_it', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_JP' => _x( 'TNT_JP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_PL' => _x( 'TNT_PL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLL_WEBHOOK' => _x( 'Toll Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLL_IPEC' => _x( 'TOLL IPEC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLL_PRIORITY' => _x( 'Toll Priority', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOMYDOOR' => _x( 'Tomydoor', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TONAMI_FTP' => _x( 'Tonami', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESDEX' => _x( 'Top Ideal Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOTAL_EXPRESS_API' => _x( 'Total Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOURLINE_REFERENCE' => _x( 'Tourline Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THAIPARCELS' => _x( 'TP Logistic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRANS2U' => _x( 'Trans2u', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRANSMISSION' => _x( 'TRANSMISSION', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TANET' => _x( 'Transport Ambientales', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRANSVIRTUAL' => _x( 'TransVirtual', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRUNKRS' => _x( 'Trunkrs', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRUSK' => _x( 'Trusk France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TUSKLOGISTICS' => _x( 'Tusk Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TYP' => _x( 'TYP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'U_ENVIOS' => _x( 'U-ENVIOS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UBER_WEBHOOK' => _x( 'Uber', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UCS' => _x( 'UCS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UDS' => _x( 'United Delivery Service', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPS' => _x( 'United Parcel Service', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UP_EXPRESS' => _x( 'up_express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPARCEL' => _x( 'uParcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPS_API' => _x( 'UPS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPS_FREIGHT' => _x( 'UPS Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPS_REFERENCE' => _x( 'UPS Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'URGENT_CARGUS' => _x( 'Urgent Cargus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'US_APC' => _x( 'us_apc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'USPS_API' => _x( 'USPS API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PB_USPSFLATS_FTP' => _x( 'USPS Flats (Pitney Bowes)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'USPS_WEBHOOK' => _x( 'USPS Informed Visibility - Webhook', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VALUE_WEBHOOK' => _x( 'Value Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIAXPRESS' => _x( 'ViaXpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VNPOST_API' => _x( 'Vietnam Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIRTRANSPORT_SFTP' => _x( 'Vir Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VNPOST_EMS' => _x( 'vnpost_ems', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VOX' => _x( 'VOX SOLUCION EMPRESARIAL SRL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WATKINS_SHEPARD' => _x( 'watkins_shepard', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WEWORLDEXPRESS' => _x( 'We World Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WESHIP_API' => _x( 'WeShip', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WESHIP' => _x( 'WeShip', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WHISTL_SFTP' => _x( 'Whistl', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WINESHIPPING_WEBHOOK' => _x( 'Wineshipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WISH_EMAIL_PUSH' => _x( 'Wish', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WOOYOUNG_LOGISTICS_SFTP' => _x( 'WOO YOUNG LOGISTICS CO., LTD.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WORLDCOURIER' => _x( 'World Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WORLDNET' => _x( 'Worldnet Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WSPEXPRESS' => _x( 'WSP Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XYY' => _x( 'Xingyunyi Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPEDIGO' => _x( 'Xpedigo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPRESSBEES' => _x( 'XPRESSBEES', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YAMATO' => _x( 'YAMATO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAQBIN_SG_API' => _x( 'Yamato Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YIFAN' => _x( 'YiFan Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YODEL' => _x( 'yodel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YODEL_API' => _x( 'Yodel API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YODEL_DIR' => _x( 'Yodel Direct', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YODEL_INTNL' => _x( 'Yodel International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YUSEN' => _x( 'Yusen Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YUSEN_SFTP' => _x( 'Yusen Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YYCOM' => _x( 'yycom', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YYEXPRESS' => _x( 'YYEXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZTO_DOMESTIC' => _x( 'ZTO Express China', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZUELLIGPHARMA_SFTP' => _x( 'Zuellig Pharma Korea', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
- return array(
- 'global' => array(
- 'name' => 'Global',
- 'items' => array(
- 'B_TWO_C_EUROPE' => _x( 'B2C Europe', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CJ_LOGISTICS' => _x( 'CJ Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CORREOS_EXPRESS' => _x( 'Correos Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_ACTIVE_TRACING' => _x( 'DHL Active Tracing', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_BENELUX' => _x( 'DHL Benelux', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_GLOBAL_MAIL' => _x( 'DHL ecCommerce US', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_GLOBAL_MAIL_ASIA' => _x( 'DHL eCommerce Asia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL' => _x( 'DHL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_GLOBAL_ECOMMERCE' => _x( 'DHL Global eCommerce', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_PACKET' => _x( 'DHL Packet', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD' => _x( 'DPD Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_LOCAL' => _x( 'DPD Local', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_LOCAL_REF' => _x( 'DPD Local Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPE_EXPRESS' => _x( 'DPE Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPEX' => _x( 'DPEX Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DTDC_EXPRESS' => _x( 'DTDC Express Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ESHOPWORLD' => _x( 'EShopWorld', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FEDEX' => _x( 'FedEx', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FLYT_EXPRESS' => _x( 'FLYT Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS' => _x( 'GLS', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'IMX' => _x( 'IMX France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INT_SUER' => _x( 'International SEUR', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LANDMARK_GLOBAL' => _x( 'Landmark Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MATKAHUOLTO' => _x( 'Matkahuoloto', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'OMNIPARCEL' => _x( 'Omni Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ONE_WORLD' => _x( 'One World', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTI' => _x( 'Posti', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RABEN_GROUP' => _x( 'Raben Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SF_EXPRESS' => _x( 'SF EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SKYNET_Worldwide' => _x( 'SkyNet Worldwide Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SPREADEL' => _x( 'Spreadel', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT' => _x( 'TNT Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'UPS' => _x( 'UPS', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'UPS_MI' => _x( 'UPS Mail Innovations', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'WEBINTERPRET' => _x( 'WebInterpret', 'Name of carrier', 'woocommerce-paypal-payments' ),
),
+ 'AG' => array(
+ 'name' => _x( 'Argentina', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'FASTRACK' => _x( 'Fasttrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ANDREANI' => _x( 'Grupo logistico Andreani', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARG_OCA' => _x( 'OCA Argentina', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'AU' => array(
+ 'name' => _x( 'Australia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ADSONE' => _x( 'Adsone', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ALLIEDEXPRESS' => _x( 'Allied Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARAMEX_AU' => _x( 'Aramex Australia (formerly Fastway AU)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AU_AU_POST' => _x( 'Australia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLUESTAR' => _x( 'Blue Star', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BONDSCOURIERS' => _x( 'Bonds Courier Service (bondscouriers.com.au)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BORDEREXPRESS' => _x( 'Border Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COPE' => _x( 'Cope Sensitive Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COURIERS_PLEASE' => _x( 'CouriersPlease (couriersplease.com.au)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVERE' => _x( 'deliverE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DESIGNERTRANSPORT_WEBHOOK' => _x( 'Designer Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_AU' => _x( 'DHL Supply Chain Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIRECTCOURIERS' => _x( 'Direct Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DTDC_AU' => _x( 'DTDC Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ENDEAVOUR_DELIVERY' => _x( 'Endeavour Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUNTER_EXPRESS' => _x( 'Hunter Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ICUMULUS' => _x( 'iCumulus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTERPARCEL_AU' => _x( 'Interparcel Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NEWAY' => _x( 'Neway Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELPOINT' => _x( 'Parcelpoint', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PFLOGISTICS' => _x( 'PFL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SENDLE' => _x( 'Sendle', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPPIT' => _x( 'Shippit', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THENILE_WEBHOOK' => _x( 'SortHub courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STAR_TRACK_EXPRESS' => _x( 'Star Track Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AUS_STARTRACK' => _x( 'StarTrack (startrack.com.au)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TFM' => _x( 'TFM Xpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TIGFREIGHT' => _x( 'TIG Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLL' => _x( 'Toll IPEC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UBI_LOGISTICS' => _x( 'UBI Smart Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XL_EXPRESS' => _x( 'XL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'AT' => array(
+ 'name' => _x( 'Austria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AUSTRIAN_POST_EXPRESS' => _x( 'Austrian Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AU_AUSTRIAN_POST' => _x( 'Austrian Post (Registered)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'BGD' => array(
+ 'name' => _x( 'Bangladesh', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'PAPERFLY' => _x( 'Paperfly Private Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'BE' => array(
+ 'name' => _x( 'Belgium', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'B_TWO_C_EUROPE' => _x( 'B2C courier Europe', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_BENELUX' => _x( 'dhl benelux', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BEL_DHL' => _x( 'DHL Benelux', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LANDMARK_GLOBAL' => _x( 'Landmark Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LANDMARK_GLOBAL_REFERENCE' => _x( 'Landmark Global Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MIKROPAKKET_BE' => _x( 'Mikropakket Belgium', 'Name of carrier', 'woocommerce-paypal-payments' ),
- ),
- 'AG' => array(
- 'name' => _x( 'Antigua and Barbuda', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CORREOS_AG' => _x( 'Correos Antigua and Barbuda', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'AR' => array(
- 'name' => _x( 'Argentina', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'EMIRATES_POST' => _x( 'Emirates Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'OCA_AR ' => _x( 'OCA Argentina', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BIH' => array(
+ 'name' => _x( 'Bosnia and Herzegovina', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BH_POSTA' => _x( 'BH Posta (www.posta.ba)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'AU' => array(
- 'name' => _x( 'Australia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ADSONE' => _x( 'Adsone', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AUSTRALIA_POST' => _x( 'Australia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TOLL_AU' => _x( 'Australia Toll', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'BONDS_COURIERS' => _x( 'Bonds Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'COURIERS_PLEASE' => _x( 'Couriers Please', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_AU' => _x( 'DHL Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DTDC_AU' => _x( 'DTDC Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FASTWAY_AU' => _x( 'Fastway Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HUNTER_EXPRESS ' => _x( 'Hunter Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SENDLE' => _x( 'Sendle', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'STARTRACK' => _x( 'Star Track', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'STARTRACK_EXPRESS' => _x( 'Star Track Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_AU ' => _x( 'TNT Australia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TOLL' => _x( 'Toll', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'UBI_LOGISTICS' => _x( 'UBI Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BR' => array(
+ 'name' => _x( 'Brazil', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BRA_CORREIOS' => _x( 'Correios Brazil', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIRECTLOG' => _x( 'Directlog', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FRETERAPIDO' => _x( 'Frete Rapido', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTELIPOST' => _x( 'Intelipost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOTAL_EXPRESS' => _x( 'Total Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'AT' => array(
- 'name' => _x( 'Austria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'AUSTRIAN_POST_EXPRESS' => _x( 'Austrian Post Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AUSTRIAN_POST' => _x( 'Austrian Post Registered', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_AT' => _x( 'DHL Austria', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BG' => array(
+ 'name' => _x( 'Bulgaria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'A1POST' => _x( 'A1Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BG_BULGARIAN_POST' => _x( 'Bulgarian Posts', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'BE' => array(
- 'name' => _x( 'Belgium', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'BPOST' => _x( 'bpost', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'BPOST_INT' => _x( 'bpost International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MONDIAL_BE' => _x( 'Mondial Belgium', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TAXIPOST' => _x( 'TaxiPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KHM' => array(
+ 'name' => _x( 'Cambodia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AFLLOG_FTP' => _x( 'AFL LOGISTICS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KHM_CAMBODIA_POST' => _x( 'Cambodia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROADRUNNER_FREIGHT' => _x( 'Roadbull Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+
+ ),
),
- ),
- 'BR' => array(
- 'name' => _x( 'Brazil', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CORREOS_BR' => _x( 'Correos Brazil', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DIRECTLOG_BR' => _x( 'Directlog', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CA' => array(
+ 'name' => _x( 'Canada', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CA_CANADA_POST' => _x( 'Canada Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHITCHATS' => _x( 'Chit Chats', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CORPORATECOURIERS_WEBHOOK' => _x( 'Corporate Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COURANT_PLUS' => _x( 'Courant Plus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBAL_ESTES' => _x( 'Estes Express Lines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DICOM' => _x( 'GLS Logistic Systems Canada Ltd./Dicom', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOCUS_WEBHOOK' => _x( 'Locus courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOOMIS_EXPRESS' => _x( 'Loomis Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MBW' => _x( 'MBW Courier Inc.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NATIONEX' => _x( 'Nationex courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELPAL_WEBHOOK' => _x( 'ParcelPal', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AIR_CANADA_GLOBAL' => _x( 'Rivo (Air canada)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROUTIFIC_WEBHOOK' => _x( 'Routific', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RPXLOGISTICS' => _x( 'RPX Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STALLIONEXPRESS' => _x( 'Stallion Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZIINGFINALMILE' => _x( 'Ziing Final Mile Inc', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'BG' => array(
- 'name' => _x( 'Bulgaria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'BULGARIAN_POST' => _x( 'Bulgarian Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CL' => array(
+ 'name' => _x( 'Chile', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BLUEX' => _x( 'Blue Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STARKEN' => _x( 'STARKEN couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CA' => array(
- 'name' => _x( 'Canada', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CANADA_POST' => _x( 'Canada Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CANPAR' => _x( 'Canpar', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GREYHOUND' => _x( 'Greyhound', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LOOMIS' => _x( 'Loomis', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PUROLATOR' => _x( 'Purolator', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN' => array(
+ 'name' => _x( 'China', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CN_17POST' => _x( '17 Post Service', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ACSWORLDWIDE' => _x( 'ACS Worldwide Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CAINIAO' => _x( 'AliExpress Standard Shipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ANJUN' => _x( 'Anjun couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ANSERX' => _x( 'ANSERX courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AUPOST_CN' => _x( 'AuPost China', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BEL_RS' => _x( 'BEL North Russia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_BESTEXPRESS' => _x( 'Best Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_BOXC' => _x( 'BoxC courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BUYLOGIC' => _x( 'buylogic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CPEX' => _x( 'Captain Express International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CGS_EXPRESS' => _x( 'CGS Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_CHINA_POST_EMS' => _x( 'China Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHUKOU1' => _x( 'Chukou1', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJPACKET' => _x( 'CJ Packet', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CLEVY_LINKS' => _x( 'Clevy Links', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CNDEXPRESS' => _x( 'CND Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CNEXPS' => _x( 'CNE Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COMET_TECH' => _x( 'CometTech', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CPACKET' => _x( 'Cpacket couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CUCKOOEXPRESS' => _x( 'Cuckoo Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DEX_I' => _x( 'DEX-I courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIDADI' => _x( 'DIDADI Logistics tech', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPE_EXPRESS' => _x( 'DPE Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DTD_EXPR' => _x( 'DTD Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EMPS_CN' => _x( 'EMPS Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_EQUICK' => _x( 'Equick China', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESHIPPING' => _x( 'Eshipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZES_EXPRESS' => _x( 'Eshun international Logistic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FAR_INTERNATIONAL' => _x( 'Far international', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FARGOOD' => _x( 'FarGood', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FULFILLME' => _x( 'Fulfillme', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GANGBAO' => _x( 'GANGBAO Supplychain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GESWL' => _x( 'GESWL Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_GOFLY' => _x( 'GoFly', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HDB' => _x( 'Haidaibao', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HDB_BOX' => _x( 'Haidaibao (BOX)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HH_EXP' => _x( 'Hua Han Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUAHAN_EXPRESS' => _x( 'HUAHANG EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HUODULL' => _x( 'Huodull', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HX_EXPRESS' => _x( 'HX Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IDEXPRESS' => _x( 'IDEX courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTEL_VALLEY' => _x( 'Intel-Valley Supply chain (ShenZhen) Co. Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'J_NET' => _x( 'J-Net', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JINDOUYUN' => _x( 'jindouyun courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JOOM_LOGIS' => _x( 'Joom Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JOYING_BOX' => _x( 'Joying Box', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'K1_EXPRESS' => _x( 'K1 Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KY_EXPRESS' => _x( 'Kua Yue Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LALAMOVE' => _x( 'Lalamove', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LEADER' => _x( 'leader', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SDH_SCM' => _x( 'lightning monkey', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTERS' => _x( 'Logisters', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LTIANEXP' => _x( 'LTIAN EXP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LTL' => _x( 'LTL COURIER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MORE_LINK' => _x( 'Morelink', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MXE' => _x( 'MXE Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NANJINGWOYUAN' => _x( 'Nanjing Woyuan', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ONEWORLDEXPRESS' => _x( 'One World Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PADTF' => _x( 'padtf.com', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAGO' => _x( 'Pago Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAN_ASIA' => _x( 'Pan-Asia International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_PAYPAL_PACKAGE' => _x( 'PayPal Package', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PFCEXPRESS' => _x( 'PFC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_POST56' => _x( 'Post56', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HKD' => _x( 'Qingdao HKD International Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ETS_EXPRESS' => _x( 'RETS express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RUSTON' => _x( 'Ruston', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_SF_EXPRESS' => _x( 'SF Express (www.sf-express.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SFB2C' => _x( 'SF International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SFC_LOGISTICS' => _x( 'SFC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SFCSERVICE' => _x( 'SFC Service', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAJIN' => _x( 'Shanghai Aqrum Chemical Logistics Co.Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SINOTRANS' => _x( 'Sinotrans', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STONE3PL' => _x( 'STONE3PL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SYPOST' => _x( 'Sunyou Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TARRIVE' => _x( 'TONDA GLOBAL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOPHATTEREXPRESS' => _x( 'Tophatter Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOPYOU' => _x( 'TopYou', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UC_EXPRE' => _x( 'ucexpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIWO' => _x( 'VIWO IoT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WANBEXPRESS' => _x( 'WanbExpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WEASHIP' => _x( 'Weaship', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_WEDO' => _x( 'WeDo Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WINIT' => _x( 'WinIt', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WISE_EXPRESS' => _x( 'Wise Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_WISHPOST' => _x( 'WishPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XQ_EXPRESS' => _x( 'XQ Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YANWEN' => _x( 'Yanwen Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YDH_EXPRESS' => _x( 'YDH express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ELIAN_POST' => _x( 'Yilian (Elian) Supply Chain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YINGNUO_LOGISTICS' => _x( 'yingnuo logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YTO' => _x( 'YTO Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CN_YUNDA' => _x( 'Yunda Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YUNEXPRESS' => _x( 'YunExpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZJS_EXPRESS' => _x( 'ZJS International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZTO_EXPRESS' => _x( 'ZTO Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CL' => array(
- 'name' => _x( 'Chile', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CORREOS_CL' => _x( 'Correos Chile', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COL' => array(
+ 'name' => _x( 'Colombia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'COORDINADORA' => _x( 'Coordinadora', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CN' => array(
- 'name' => _x( 'China', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'FOUR_PX_EXPRESS' => _x( 'Correos', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AUPOST_CN' => _x( 'AUPOST CHINA', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'BQC_EXPRESS' => _x( 'BQC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'BUYLOGIC' => _x( 'Buylogic', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CHINA_POST' => _x( 'China Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CNEXPS' => _x( 'CN Exps', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EC_CN' => _x( 'EC China', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EFS' => _x( 'EFS', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EMPS_CN' => _x( 'EMPS China', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EMS_CN' => _x( 'EMS China', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HUAHAN_EXPRESS' => _x( 'Huahan Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SFC_EXPRESS' => _x( 'SFC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_CN' => _x( 'TNT China', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'WINIT' => _x( 'WinIt', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'YANWEN_CN' => _x( 'Yanwen', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HRV' => array(
+ 'name' => _x( 'Croatia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'GLS_CROTIA' => _x( 'GLS Croatia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HRV_HRVATSKA' => _x( 'Hrvatska posta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OVERSE_EXP' => _x( 'Overseas Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CR' => array(
- 'name' => _x( 'Costa Rica', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CORREOS_CR' => _x( 'Correos De Costa Rica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CY' => array(
+ 'name' => _x( 'Cyprus', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CYPRUS_POST_CYP' => _x( 'Cyprus Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'HR' => array(
- 'name' => _x( 'Croatia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'HRVATSKA_HR' => _x( 'Hrvatska', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CZ' => array(
+ 'name' => _x( 'Czech Republic', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CESKA_CZ' => _x( 'Ceska Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_CZ' => _x( 'GLS Czech Republic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CY' => array(
- 'name' => _x( 'Cyprus', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CYPRUS_POST_CYP' => _x( 'Cyprus Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DNK' => array(
+ 'name' => _x( 'Denmark', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BUDBEE_WEBHOOK' => _x( 'Budbee courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DANSKE_FRAGT' => _x( 'Danske Fragtaend', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTNORD_LOGISTICS_DK' => _x( 'ostnord denmark', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTNORD_LOGISTICS' => _x( 'PostNord Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPRESSEN_DK' => _x( 'Xpressen courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CZ' => array(
- 'name' => _x( 'Czech Republic', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CESKA_CZ' => _x( 'Ceska', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_CZ' => _x( 'GLS Czech Republic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EST' => array(
+ 'name' => _x( 'Estonia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'OMNIVA' => _x( 'Omniva', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'FR' => array(
- 'name' => _x( 'France', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'BERT' => _x( 'BERT TRANSPORT', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CHRONOPOST_FR' => _x( 'Chronopost France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'COLIPOSTE' => _x( 'Coliposte', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'COLIS' => _x( 'Colis France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_FR' => _x( 'DHL France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_FR' => _x( 'DPD France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GEODIS' => _x( 'GEODIS - Distribution & Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_FR' => _x( 'GLS France', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LAPOSTE' => _x( 'LA Poste', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MONDIAL' => _x( 'Mondial Relay', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RELAIS_COLIS_FR' => _x( 'Relais Colis', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TELIWAY' => _x( 'Teliway', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_FR' => _x( 'TNT France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FIN' => array(
+ 'name' => _x( 'Finland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'MATKAHUOLTO' => _x( 'Matkahuolto', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTI' => _x( 'Posti courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'DE' => array(
- 'name' => _x( 'Germany', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ASENDIA_DE' => _x( 'Asendia Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DELTEC_DE' => _x( 'Deltec Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DEUTSCHE_DE' => _x( 'Deutsche', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_DEUTSCHE_POST' => _x( 'DHL Deutsche Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_DE' => _x( 'DPD Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_DE' => _x( 'GLS Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HERMES_DE' => _x( 'Hermes Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_DE' => _x( 'TNT Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FR' => array(
+ 'name' => _x( 'France', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CHRONOPOST_FR' => _x( 'Chronopost france (www.chronopost.fr)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COLIS_PRIVE' => _x( 'Colis Privé', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FR_COLIS' => _x( 'Colissimo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CUBYN' => _x( 'Cubyn', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_FR' => _x( 'DPD France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FR_EXAPAQ' => _x( 'DPD France (formerly exapaq)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEODIS_ESPACE' => _x( 'Geodis E-space', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HEPPNER_FR' => _x( 'Heppner France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LA_POSTE_SUIVI' => _x( 'La Poste', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_FR' => _x( 'TNT France', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIRTRANSPORT' => _x( 'VIR Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'GR' => array(
- 'name' => _x( 'Greece', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ELTA_GR' => _x( 'ELTA Greece', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GENIKI_GR' => _x( 'Geniki Greece', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ACS_GR' => _x( 'GRC Greece', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DE' => array(
+ 'name' => _x( 'Germany', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'HERMES_DE' => _x( 'Hermes Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AO_DEUTSCHLAND' => _x( 'AO Deutschland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DE_DPD_DELISTRACK' => _x( 'DPD Germany', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FIEGE' => _x( 'Fiege Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEIS' => _x( 'Geis CZ', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEL_EXPRESS' => _x( 'Gel Express Logistik', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GENERAL_OVERNIGHT' => _x( 'Go!Express and logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HEPPNER' => _x( 'Heppner Internationale Spedition GmbH & Co.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMES_2MANN_HANDLING' => _x( 'Hermes Einrichtungs Service GmbH & Co. KG', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOX_NACHTEXPRESS' => _x( 'Innight Express Germany GmbH (nox NachtExpress)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LIEFERY' => _x( 'liefery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOX_NIGHT_TIME_EXPRESS' => _x( 'NOX NightTimeExpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELONE' => _x( 'PARCEL ONE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRESSIODE' => _x( 'Pressio', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RABEN_GROUP' => _x( 'Raben Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STRECK_TRANSPORT' => _x( 'Streck Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SWISHIP_DE' => _x( 'Swiship DE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'HK' => array(
- 'name' => _x( 'Hong Kong', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ASENDIA_HK' => _x( 'Asendia Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_HK' => _x( 'DHL Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_HK' => _x( 'DPD Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HK_POST' => _x( 'Hong Kong Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KERRY_EXPRESS_HK' => _x( 'Kerry Express Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LOGISTICSWORLDWIDE_HK' => _x( 'Logistics Worldwide Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'QUANTIUM' => _x( 'Quantium', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SEKOLOGISTICS' => _x( 'Seko Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TAQBIN_HK' => _x( 'TA-Q-BIN Parcel Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GR' => array(
+ 'name' => _x( 'Greece', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ACS_GR' => _x( 'ACS Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EASY_MAIL' => _x( 'Easy Mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GENIKI_GR' => _x( 'Geniki Taxydromiki', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPEEDCOURIERS_GR' => _x( 'Speed Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPEEDEXCOURIER' => _x( 'SPEEDEX couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'HU' => array(
- 'name' => _x( 'Hungary', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'MAGYAR_HU' => _x( 'Magyar', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HK' => array(
+ 'name' => _x( 'Hong Kong', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CFL_LOGISTICS' => _x( 'CFL Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_HK_INTERNATIONAL' => _x( 'CJ Logistics International(Hong Kong)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CLE_LOGISTICS' => _x( 'CL E-Logistics Solutions Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CONTINENTAL' => _x( 'Continental', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COSTMETICSNOW' => _x( 'Cosmetics Now', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DEALERSEND' => _x( 'DealerSend', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_ECOMERCE_ASA' => _x( 'DHL eCommerce Asia (API)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_GLOBAL_MAIL_ASIA' => _x( 'DHL Global Mail Asia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_HK' => _x( 'DHL Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_HK' => _x( 'DPD Hong Kong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DTDC_EXPRESS' => _x( 'DTDC express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBAVEND' => _x( 'Globavend', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HK_POST' => _x( 'Hongkong Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JANCO' => _x( 'Janco Ecommerce', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JS_EXPRESS' => _x( 'JS EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KEC' => _x( 'KEC courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KERRY_ECOMMERCE' => _x( 'Kerry eCommerce', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LHT_EXPRESS' => _x( 'LHT Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTICSWORLDWIDE_HK' => _x( 'Logistic Worldwide Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAINWAY' => _x( 'Mainway', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MORNING_EXPRESS' => _x( 'Morning Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OKAYPARCEL' => _x( 'OkayParcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OMNIPARCEL' => _x( 'Omni Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PALEXPRESS' => _x( 'PAL Express Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PICKUP' => _x( 'Pickupp', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QUANTIUM' => _x( 'Quantium', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RPX' => _x( 'RPX Online', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEKOLOGISTICS' => _x( 'SEKO Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIP_IT_ASIA' => _x( 'Ship It Asia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMOOTH' => _x( 'Smooth Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'STEPFORWARDFS' => _x( 'STEP FORWARD FREIGHT SERVICE CO LTD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SFPLUS_WEBHOOK' => _x( 'Zeek courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZEEK_2_DOOR' => _x( 'Zeek2Door', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'IS' => array(
- 'name' => _x( 'Iceland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'POSTUR_IS' => _x( 'Postur', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HU' => array(
+ 'name' => _x( 'Hungary', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'DPD_HGRY' => _x( 'DPD Hungary', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAGYAR_HU' => _x( 'Magyar Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'IN' => array(
- 'name' => _x( 'India', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'BLUEDART' => _x( 'Bluedart', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DELHIVERY_IN' => _x( 'Delhivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DOTZOT' => _x( 'DotZot', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DTDC_IN' => _x( 'DTDC India', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EKART' => _x( 'Ekart', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INDIA_POST' => _x( 'India Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PROFESSIONAL_COURIERS' => _x( 'Professional Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'REDEXPRESS' => _x( 'Red Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SWIFTAIR' => _x( 'Swift Air', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'XPRESSBEES' => _x( 'Xpress Bees', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IN' => array(
+ 'name' => _x( 'India', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BOMBINOEXP' => _x( 'Bombino Express Pvt', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_DELHIVERY' => _x( 'Delhivery India', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELIVERYONTIME' => _x( 'DELIVERYONTIME LOGISTICS PVT LTD', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DTDC_IN' => _x( 'DTDC India', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_ECOM' => _x( 'Ecom Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EKART' => _x( 'Ekart logistics (ekartlogistics.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_FIRSTFLIGHT' => _x( 'First Flight Couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_GATI' => _x( 'Gati-KWE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_GOJAVAS' => _x( 'GoJavas', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOLISOL' => _x( 'Holisol', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LEXSHIP' => _x( 'LexShip', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OCS' => _x( 'OCS ANA Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELLED_IN' => _x( 'Parcelled.in', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PICKRR' => _x( 'Pickrr', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_SAFEEXPRESS' => _x( 'Safexpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SCUDEX_EXPRESS' => _x( 'Scudex Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHREE_ANJANI_COURIER' => _x( 'Shree Anjani Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHREE_MARUTI' => _x( 'Shree Maruti Courier Services Pvt Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHREENANDANCOURIER' => _x( 'SHREE NANDAN COURIER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHREETIRUPATI' => _x( 'SHREE TIRUPATI COURIER SERVICES PVT. LTD.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPOTON' => _x( 'SPOTON Logistics Pvt Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRACKON' => _x( 'Trackon Couriers Pvt. Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'ID' => array(
- 'name' => _x( 'Indonesia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'FIRST_LOGISITCS' => _x( 'First Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JNE_IDN' => _x( 'JNE Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LION_PARCEL' => _x( 'Lion Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NINJAVAN_ID' => _x( 'Ninjavan Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PANDU' => _x( 'Pandu Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POS_ID' => _x( 'Pos Indonesia Domestic', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POS_INT' => _x( 'Pos Indonesia International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RPX_ID' => _x( 'RPX Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RPX' => _x( 'RPX International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TIKI_ID' => _x( 'Tiki', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'WAHANA_ID' => _x( 'Wahana', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ID' => array(
+ 'name' => _x( 'Indonesia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ALFATREX' => _x( 'AlfaTrex', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CHOIR_EXP' => _x( 'Choir Express Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INDOPAKET' => _x( 'INDOPAKET', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JX' => _x( 'JX courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KURASI' => _x( 'KURASI', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NINJAVAN_ID' => _x( 'Ninja Van Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NINJAVAN_WB' => _x( 'Ninjavan Webhook', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MGLOBAL' => _x( 'PT MGLOBAL LOGISTICS INDONESIA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRIMAMULTICIPTA' => _x( 'PT Prima Multi Cipta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RCL' => _x( 'Red Carpet Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RPX_ID' => _x( 'RPX Indonesia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAP_EXPRESS' => _x( 'SAP EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIN_GLBL' => _x( 'Sin Global Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TIKI_ID' => _x( 'Tiki shipment', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRANS_KARGO' => _x( 'Trans Kargo Internasional', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WAHANA_ID' => _x( 'Wahana express (www.wahana.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'IE' => array(
- 'name' => _x( 'Ireland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'AN_POST' => _x( 'AN POST Ireland', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_IR' => _x( 'DPD Ireland', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MASTERLINK' => _x( 'Masterlink', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TPG' => _x( 'TPG', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'WISELOADS' => _x( 'Wiseloads', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IE' => array(
+ 'name' => _x( 'Ireland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AN_POST' => _x( 'An Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_IR' => _x( 'DPD Ireland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTWAY_IR' => _x( 'Fastway Ireland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WISELOADS' => _x( 'Wiseloads', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'IL' => array(
- 'name' => _x( 'Israel', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ISRAEL_POST' => _x( 'Israel Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IL' => array(
+ 'name' => _x( 'Israel', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ISRAEL_POST' => _x( 'Israel Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ISR_POST_DOMESTIC' => _x( 'Israel Post Domestic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'IT' => array(
- 'name' => _x( 'Italy', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'BRT_IT' => _x( 'BRT Bartolini', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_IT' => _x( 'DHL Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DMM_NETWORK' => _x( 'DMM Network', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FERCAM_IT' => _x( 'FERCAM Logistics & Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_IT' => _x( 'GLS Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HERMES_IT' => _x( 'Hermes Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTE_ITALIANE' => _x( 'Poste Italiane', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'REGISTER_MAIL_IT' => _x( 'Register Mail IT', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SDA_IT' => _x( 'SDA Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SGT_IT' => _x( 'SGT Corriere Espresso', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_CLICK_IT' => _x( 'TNT Click Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_IT' => _x( 'TNT Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IT' => array(
+ 'name' => _x( 'Italy', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BRT_IT_PARCELID' => _x( 'BRT Bartolini (Parcel ID)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BRT_IT' => _x( 'BRT Couriers Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARCO_SPEDIZIONI' => _x( 'Arco Spedizioni SP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLINKLASTMILE' => _x( 'Blink', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BRT_IT_SENDER_REF' => _x( 'BRT Bartolini (Sender Reference)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DMM_NETWORK' => _x( 'DMM Network', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLS_IT' => _x( 'GLS Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HRPARCEL' => _x( 'HR Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'I_DIKA' => _x( 'i-dika', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LICCARDI_EXPRESS' => _x( 'LICCARDI EXPRESS COURIER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MILKMAN' => _x( 'Milkman Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IT_NEXIVE' => _x( 'Nexive (TNT Post Italy)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IT_POSTE_ITALIA' => _x( 'Poste Italiane', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAILPOST' => _x( 'SAILPOST', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SDA_IT' => _x( 'SDA Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_CLICK_IT' => _x( 'TNT-Click Italy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'JP' => array(
- 'name' => _x( 'Japan', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DHL_JP' => _x( 'DHL Japan', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JP_POST' => _x( 'JP Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JAPAN_POST' => _x( 'Japan Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POCZTEX' => _x( 'Pocztex', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SAGAWA' => _x( 'Sagawa', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SAGAWA_JP' => _x( 'Sagawa JP', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_JP' => _x( 'TNT Japan', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'YAMATO' => _x( 'Yamato Japan', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JP' => array(
+ 'name' => _x( 'Japan', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'EFEX' => _x( 'eFEx (E-Commerce Fulfillment & Express)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JPN_JAPAN_POST' => _x( 'Japan Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KWE_GLOBAL' => _x( 'KWE Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAIL_PLUS' => _x( 'MailPlus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAILPLUS_JPN' => _x( 'MailPlus (Japan)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEINO' => _x( 'Seino', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'KR' => array(
- 'name' => _x( 'Korea', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ECARGO' => _x( 'Ecargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'EPARCEL_KR' => _x( 'eParcel Korea', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KOREA_POST' => _x( 'Korea Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KOR_KOREA_POST' => _x( 'KOR Korea Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CJ_KR' => _x( 'Korea Thai CJ', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LOGISTICSWORLDWIDE_KR' => _x( 'Logistics Worldwide Korea', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PANTOS' => _x( 'Pantos', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RINCOS' => _x( 'Rincos', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ROCKET_PARCEL' => _x( 'Rocket Parcel International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SRE_KOREA' => _x( 'SRE Korea', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JEY' => array(
+ 'name' => _x( 'Jersey', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'JERSEY_POST' => _x( 'Jersey Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'LT' => array(
- 'name' => _x( 'Lithuania', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'LIETUVOS_LT' => _x( 'Lietuvos Pastas', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KR' => array(
+ 'name' => _x( 'Korea', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CELLO_SQUARE' => _x( 'Cello Square', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CROSHOT' => _x( 'Croshot', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DOORA' => _x( 'Doora Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EPARCEL_KR' => _x( 'eParcel Korea', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KPOST' => _x( 'Korea Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KR_KOREA_POST' => _x( 'Koreapost (www.koreapost.go.kr)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KYUNGDONG_PARCEL' => _x( 'Kyungdong Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOTTE' => _x( 'Lotte Global Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RINCOS' => _x( 'Rincos', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROCKET_PARCEL' => _x( 'Rocket Parcel International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIP_GATE' => _x( 'ShipGate', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPTER' => _x( 'SHIPTER', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SRE_KOREA' => _x( 'SRE Korea (www.srekorea.co.kr)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLOS' => _x( 'Tolos courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'MY' => array(
- 'name' => _x( 'Malaysia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'AIRPAK_MY' => _x( 'Airpak', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CITYLINK_MY' => _x( 'CityLink Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CJ_MY' => _x( 'CJ Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CJ_INT_MY' => _x( 'CJ Malaysia International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CUCKOOEXPRESS' => _x( 'Cuckoo Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JETSHIP_MY' => _x( 'Jet Ship Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KANGAROO_MY' => _x( 'Kangaroo Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LOGISTICSWORLDWIDE_MY' => _x( 'Logistics Worldwide Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MALAYSIA_POST' => _x( 'Malaysia Post EMS / Pos Laju', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NATIONWIDE' => _x( 'Nationwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NINJAVAN_MY' => _x( 'Ninjavan Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SKYNET_MY' => _x( 'Skynet Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TAQBIN_MY' => _x( 'TA-Q-BIN Parcel Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KWT' => array(
+ 'name' => _x( 'Kuwait', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'POSTA_PLUS' => _x( 'Posta Plus', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'MX' => array(
- 'name' => _x( 'Mexico', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CORREOS_MX' => _x( 'Correos De Mexico', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ESTAFETA' => _x( 'Estafeta', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AEROFLASH' => _x( 'Mexico Aeroflash', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'REDPACK' => _x( 'Mexico Redpack', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SENDA_MX' => _x( 'Mexico Senda Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LAO' => array(
+ 'name' => _x( "Lao People's Democratic Republic (the)", 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'LAO_POST' => _x( 'Lao Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'NL' => array(
- 'name' => _x( 'Netherlands', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DHL_NL' => _x( 'DHL Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_PARCEL_NL' => _x( 'DHL Parcel Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_NL' => _x( 'GLS Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KIALA' => _x( 'Kiala', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNL' => _x( 'PostNL', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNL_INT' => _x( 'PostNl International', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNL_INT_3_S' => _x( 'PostNL International 3S', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_NL' => _x( 'TNT Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TRANSMISSION' => _x( 'Transmission Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LVA' => array(
+ 'name' => _x( 'Latvia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CDEK' => _x( 'CDEK courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LATVIJAS_PASTS' => _x( 'Latvijas Pasts', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'NZ' => array(
- 'name' => _x( 'New Zealand', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'COURIER_POST' => _x( 'Courier Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FASTWAY_NZ' => _x( 'Fastway New Zealand', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NZ_POST' => _x( 'New Zealand Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TOLL_IPEC' => _x( 'Toll IPEC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LT' => array(
+ 'name' => _x( 'Lithuania', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'VENIPAK' => _x( 'Venipak', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'NG' => array(
- 'name' => _x( 'Nigeria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'COURIERPLUS' => _x( 'Courier Plus', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NIPOST_NG' => _x( 'NiPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MY' => array(
+ 'name' => _x( 'Malaysia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ABXEXPRESS_MY' => _x( 'ABX Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_AIRPAK' => _x( 'Airpak Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CITYLINK_MY' => _x( 'City-Link Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_CENTURY' => _x( 'CJ Century', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_INT_MY' => _x( 'CJ International Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COLLECTCO' => _x( 'CollectCo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FMX' => _x( 'FMX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_GDEX' => _x( 'GDEX Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JTEXPRESS' => _x( 'J&T EXPRESS MALAYSIA', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JINSUNG' => _x( 'JINSUNG TRADING', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JOCOM' => _x( 'Jocom', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KANGAROO_MY' => _x( 'Kangaroo Worldwide Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LINE' => _x( 'Line Clear Express & Logistics Sdn Bhd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTIKA' => _x( 'Logistika', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'M_XPRESS' => _x( 'M Xpress Sdn Bhd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_MYS_POST' => _x( 'Malaysia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MATDESPATCH' => _x( 'Matdespatch', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_MYPOST_ONLINE' => _x( 'Mypostonline', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NATIONWIDE_MY' => _x( 'Nationwide Express Courier Services Bhd (www.nationwide.com.my)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NINJAVAN_MY' => _x( 'Ninja Van (www.ninjavan.co)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PICKUPP_MYS' => _x( 'PICK UPP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYS_SKYNET' => _x( 'Skynet Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAQBIN_MY' => _x( 'TAQBIN Malaysia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WEPOST' => _x( 'WePost Sdn Bhd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WYNGS' => _x( 'Wyngs', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZEPTO_EXPRESS' => _x( 'ZeptoExpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'NO' => array(
- 'name' => _x( 'Norway', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'POSTEN_NORGE' => _x( 'Posten Norge', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MX' => array(
+ 'name' => _x( 'Mexico', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CORREOS_DE_MEXICO' => _x( 'Correos Mexico', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEX_ESTAFETA' => _x( 'Estafeta (www.estafeta.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GRUPO' => _x( 'Grupo ampm', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOUNDEXPRESS' => _x( 'Hound Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IVOY_WEBHOOK' => _x( 'Ivoy courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEX_SENDA' => _x( 'Mexico Senda Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAQUETEXPRESS' => _x( 'Paquetexpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MEX_REDPACK' => _x( 'Redpack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'PH' => array(
- 'name' => _x( 'Philippines', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'TWO_GO' => _x( '2GO', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AIR_21' => _x( 'Air 21', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AIRSPEED' => _x( 'Airspeed', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JAMEXPRESS_PH' => _x( 'Jam Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'LBC_PH' => _x( 'LBC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NINJAVAN_PH' => _x( 'Ninjavan Philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RAF_PH' => _x( 'RAF Philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'XEND_EXPRESS_PH' => _x( 'Xend Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NL' => array(
+ 'name' => _x( 'Netherlands', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BROUWER_TRANSPORT' => _x( 'Brouwer Transport en Logistiek', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NLD_DHL' => _x( 'DHL Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FIEGE_NL' => _x( 'Fiege Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NLD_GLS' => _x( 'GLS Netherlands', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HAPPY2POINT' => _x( 'Happy 2ThePoint', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAPER_EXPRESS' => _x( 'Paper Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTNL_INTL_3S' => _x( 'PostNL International 3S', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRUNKRS_WEBHOOK' => _x( 'Trunkrs courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'PL' => array(
- 'name' => _x( 'Poland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DHL_PL' => _x( 'DHL Poland', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_PL' => _x( 'DPD Poland', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INPOST_PACZKOMATY' => _x( 'InPost Paczkomaty', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POCZTA_POLSKA' => _x( 'Poczta Polska', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SIODEMKA' => _x( 'Siodemka', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_PL' => _x( 'TNT Poland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NZ' => array(
+ 'name' => _x( 'New Zealand', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'FASTWAY_NZ' => _x( 'Fastway New Zealand', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTERPARCEL_NZ' => _x( 'Interparcel New Zealand', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAINFREIGHT' => _x( 'Mainfreight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NZ_NZ_POST' => _x( 'New Zealand Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOLL_NZ' => _x( 'Toll New Zealand', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'PT' => array(
- 'name' => _x( 'Portugal', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ADICIONAL_PT' => _x( 'Adicional Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CHRONOPOST_PT' => _x( 'Chronopost Portugal', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CTT_PT' => _x( 'Portugal PTT', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SEUR_PT' => _x( 'Portugal Seur', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NG' => array(
+ 'name' => _x( 'Nigeria', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'NIPOST_NG' => _x( 'NIpost (www.nipost.gov.ng)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'RO' => array(
- 'name' => _x( 'Romania', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DPD_RO' => _x( 'DPD Romania', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTA_RO' => _x( 'Postaromana', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NO' => array(
+ 'name' => _x( 'Norway', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'HELTHJEM' => _x( 'Helthjem', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'RU' => array(
- 'name' => _x( 'Russia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DPD_RU' => _x( 'DPD Russia', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RUSSIAN_POST' => _x( 'Russian Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAK' => array(
+ 'name' => _x( 'Pakistan', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'FORRUN' => _x( 'forrun Pvt Ltd (Arpatech Venture)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TCS' => _x( 'TCS courier ', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'SA' => array(
- 'name' => _x( 'Saudi Arabia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DAWN_WING' => _x( 'Dawn Wing', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RAM' => _x( 'Ram', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'THE_COURIER_GUY' => _x( 'The Courier Guy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRY' => array(
+ 'name' => _x( 'Paraguay', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AEX' => _x( 'AEX Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CS' => array(
- 'name' => _x( 'Serbia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'POST_SERBIA_CS' => _x( 'Serbia Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PH' => array(
+ 'name' => _x( 'Philippines', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'TWO_GO' => _x( '2GO Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PHL_JAMEXPRESS' => _x( 'Jam Express Philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PIXSELL' => _x( 'PIXSELL LOGISTICS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RAF_PH' => _x( 'RAF Philippines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XDE_WEBHOOK' => _x( 'Ximex Delivery Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPOST' => _x( 'Xpost.ph', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'SG' => array(
- 'name' => _x( 'Singapore', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DHL_SG' => _x( 'DHL Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'JETSHIP_SG' => _x( 'JetShip Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NINJAVAN_SG' => _x( 'Ninjavan Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PARCELPOST_SG' => _x( 'Parcel Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SINGPOST' => _x( 'Singapore Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TAQBIN_SG' => _x( 'TA-Q-BIN Parcel Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PL' => array(
+ 'name' => _x( 'Poland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'DHL_PL' => _x( 'DHL Poland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_POLAND' => _x( 'DPD Poland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FEDEX_POLAND' => _x( 'FedEx® Poland Domestic', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INPOST_PACZKOMATY' => _x( 'InPost Paczkomaty', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PL_POCZTA_POLSKA' => _x( 'Poczta Polska', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROYAL_MAIL' => _x( 'Royal Mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'ZA' => array(
- 'name' => _x( 'South Africa', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'FASTWAY_ZA' => _x( 'Fastway South Africa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PT' => array(
+ 'name' => _x( 'Portugal', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ADICIONAL' => _x( 'Adicional Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BNEED' => _x( 'Bneed courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CARRIERS' => _x( 'Carriers courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRT_CHRONOPOST' => _x( 'Chronopost Portugal', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRT_CTT' => _x( 'CTT Portugal', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELNEXT' => _x( 'Delnext', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'ES' => array(
- 'name' => _x( 'Spain', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ASM_ES' => _x( 'ASM', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CBL_LOGISTICA' => _x( 'CBL Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CORREOS_ES' => _x( 'Correos De Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_ES ' => _x( 'DHL Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_PARCEL_ES' => _x( 'DHL Parcel Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLS_ES' => _x( 'GLS Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INT_SEUR' => _x( 'International Suer', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ITIS' => _x( 'ITIS', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NACEX_ES' => _x( 'Nacex Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'REDUR_ES' => _x( 'Redur Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SEUR_ES' => _x( 'Spanish Seur', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_ES' => _x( 'TNT Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RO' => array(
+ 'name' => _x( 'Romania', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'DPD_RO' => _x( 'DPD Romania', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTA_RO' => _x( 'Post Roman (www.posta-romana.ro)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'SE' => array(
- 'name' => _x( 'Sweden', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'DBSCHENKER_SE' => _x( 'DB Schenker Sweden', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DIRECTLINK_SE' => _x( 'DirectLink Sweden', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNORD_LOGISTICS_GLOBAL' => _x( 'PostNord Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNORD_LOGISTICS_DK' => _x( 'PostNord Logistics Denmark', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'POSTNORD_LOGISTICS_SE' => _x( 'PostNord Logistics Sweden', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RUS' => array(
+ 'name' => _x( 'Russia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'BOX_BERRY' => _x( 'Boxberry courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CSE' => _x( 'CSE courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_PARCEL_RU' => _x( 'DHL Parcel Russia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DOBROPOST' => _x( 'DobroPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_RU' => _x( 'DPD Russia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EXPRESSSALE' => _x( 'Expresssale', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GBS_BROKER' => _x( 'GBS-Broker', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PONY_EXPRESS' => _x( 'Pony express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHOPFANS' => _x( 'ShopfansRU LLC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'CH' => array(
- 'name' => _x( 'Switzerland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'SWISS_POST' => _x( 'Swiss Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAU' => array(
+ 'name' => _x( 'Saudi Arabia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'SAU_SAUDI_POST' => _x( 'Saudi Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SMSA_EXPRESS' => _x( 'SMSA Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THABIT_LOGISTICS' => _x( 'Thabit Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZAJIL_EXPRESS' => _x( 'Zajil Express Company', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'TW' => array(
- 'name' => _x( 'Taiwan', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'CHUNGHWA_POST' => _x( 'Chunghwa Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TAIWAN_POST_TW' => _x( 'Taiwan Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SRB' => array(
+ 'name' => _x( 'Serbia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'POST_SERBIA' => _x( 'Posta Serbia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'TH' => array(
- 'name' => _x( 'Thailand', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ACOMMMERCE' => _x( 'Acommerce', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ALPHAFAST' => _x( 'Alphafast', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CJ_TH' => _x( 'CJ Thailand', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FASTRACK' => _x( 'FastTrack Thailand', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'KERRY_EXPRESS_TH' => _x( 'Kerry Express Thailand', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NIM_EXPRESS' => _x( 'NIM Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NINJAVAN_THAI' => _x( 'Ninjavan Thailand', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SENDIT' => _x( 'SendIt', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'THAILAND_POST' => _x( 'Thailand Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SG' => array(
+ 'name' => _x( 'Singapore', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CLOUDWISH_ASIA' => _x( 'Cloudwish Asia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SG_DETRACK' => _x( 'Detrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FONSEN' => _x( 'Fonsen Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GRAB_WEBHOOK' => _x( 'Grab courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SIMPLYPOST' => _x( 'J&T Express Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JANIO' => _x( 'Janio Asia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'IND_JAYONEXPRESS' => _x( 'Jayon Express (JEX)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'JET_SHIP' => _x( 'Jet-Ship Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KGMHUB' => _x( 'KGM Hub', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LEGION_EXPRESS' => _x( 'Legion Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NHANS_SOLUTIONS' => _x( 'Nhans Solutions', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NINJAVAN_SG' => _x( 'Ninja van Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELPOST_SG' => _x( 'Parcel Post Singapore', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARKNPARCEL' => _x( 'Park N Parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PICKUPP_SGP' => _x( 'PICK UPP (Singapore)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SG_QXPRESS' => _x( 'Qxpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RAIDEREX' => _x( 'RaidereX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ROADBULL' => _x( 'Red Carpet Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RZYEXPRESS' => _x( 'RZY Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SG_SG_POST' => _x( 'Singapore Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SG_SPEEDPOST' => _x( 'Singapore Speedpost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TCK_EXPRESS' => _x( 'TCK Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COUREX' => _x( 'Urbanfox', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WMG' => _x( 'WMG Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZYLLEM' => _x( 'Zyllem', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'TR' => array(
- 'name' => _x( 'Turkey', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'PTT_POST' => _x( 'PTT Posta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SVK' => array(
+ 'name' => _x( 'Slovakia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'GLS_SLOV' => _x( 'GLS General Logistics Systems Slovakia s.r.o.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'UA' => array(
- 'name' => _x( 'Ukraine', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'NOVA_POSHTA' => _x( 'Nova Poshta', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NOVA_POSHTA_INT' => _x( 'Nova Poshta International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SVN' => array(
+ 'name' => _x( 'Slovenia', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'GLS_SLOVEN' => _x( 'GLS Slovenia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POST_SLOVENIA' => _x( 'Post of Slovenia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'AE' => array(
- 'name' => _x( 'United Arab Emirates', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'AXL' => _x( 'AXL Express & Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CONTINENTAL' => _x( 'Continental', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SKYNET_UAE' => _x( 'Skynet Worldwide Express UAE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZA' => array(
+ 'name' => _x( 'South Africa', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ZA_COURIERIT' => _x( 'Courier IT', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAWN_WING' => _x( 'Dawn Wing', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPE_SOUTH_AFRC' => _x( 'DPE South Africa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTEXPRESS' => _x( 'Internet Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'COLLIVERY' => _x( 'MDS Collivery Pty (Ltd)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RAM' => _x( 'RAM courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKYNET_ZA' => _x( 'Skynet World Wide Express South Africa', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SOUTH_AFRICAN_POST_OFFICE' => _x( 'South African Post Office', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZA_SPECIALISED_FREIGHT' => _x( 'Specialised Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THECOURIERGUY' => _x( 'The Courier Guy', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'GB' => array(
- 'name' => _x( 'United Kingdom', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'AIRBORNE_EXPRESS_UK' => _x( 'Airborne Express UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AIRSURE' => _x( 'Airsure', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'APC_OVERNIGHT' => _x( 'APC Overnight', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ASENDIA_UK' => _x( 'Asendia UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'COLLECTPLUS' => _x( 'CollectPlus', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DELTEC_UK' => _x( 'Deltec UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DHL_UK' => _x( 'DHL UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_DELISTRACK' => _x( 'DPD Delistrack', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'DPD_UK' => _x( 'DPD UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FASTWAY_UK' => _x( 'Fastway UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'HERMESWORLD_UK' => _x( 'HermesWorld', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INTERLINK' => _x( 'Interlink Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'MYHERMES' => _x( 'MyHermes UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'NIGHTLINE_UK' => _x( 'Nightline UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'PARCELFORCE' => _x( 'Parcel Force', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ROYAL_MAIL' => _x( 'Royal Mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RPD_2_MAN' => _x( 'RPD2man Deliveries', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'SKYNET_UK' => _x( 'Skynet Worldwide Express UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'TNT_UK' => _x( 'TNT UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'UK_MAIL' => _x( 'UK Mail', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'YODEL' => _x( 'Yodel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ES' => array(
+ 'name' => _x( 'Spain', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ABCUSTOM' => _x( 'AB Custom Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ADERONLINE' => _x( 'Ader couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ASIGNA' => _x( 'ASIGNA courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_ASM' => _x( 'ASM(GLS Spain)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CBL_LOGISTICA' => _x( 'CBL Logistica', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CORREOS_EXPRESS' => _x( 'Correos Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_PARCEL_ES' => _x( 'DHL parcel Spain(www.dhl.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_ES' => _x( 'DHL Spain(www.dhl.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ECOSCOOTING' => _x( 'ECOSCOOTING', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_ENVIALIA' => _x( 'Envialia', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ENVIALIA_REFERENCE' => _x( 'Envialia Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTEGRA2_FTP' => _x( 'Integra2', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MRW_FTP' => _x( 'MRW courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_MRW' => _x( 'MRW spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NACEX' => _x( 'NACEX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NACEX_ES' => _x( 'NACEX Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_NACEX' => _x( 'NACEX Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PAACK_WEBHOOK' => _x( 'Paack courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_PACKLINK' => _x( 'Packlink', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ESP_REDUR' => _x( 'Redur Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRT_INT_SEUR' => _x( 'SEUR International', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRT_SEUR' => _x( 'SEUR portugal', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEUR_ES' => _x( 'Seur Spain', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEUR_SP_API' => _x( 'Spanish Seur API', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPRING_GDS' => _x( 'Spring GDS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SZENDEX' => _x( 'SZENDEX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_NL' => _x( 'THT Netherland', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TIPSA' => _x( 'TIPSA courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT' => _x( 'TNT Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBAL_TNT' => _x( 'TNT global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TOURLINE' => _x( 'tourline', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VAMOX' => _x( 'VAMOX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIA_EXPRESS' => _x( 'Viaxpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZELERIS' => _x( 'Zeleris', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'US' => array(
- 'name' => _x( 'United States', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'ABC_PACKAGE' => _x( 'ABC Package Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'AIRBORNE_EXPRESS' => _x( 'Airborne Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ASENDIA_US' => _x( 'Asendia USA', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'CPACKET' => _x( 'Cpacket', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ENSENDA' => _x( 'Ensenda USA', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ESTES' => _x( 'Estes', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'FASTWAY_US' => _x( 'Fastway USA', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'GLOBEGISTICS' => _x( 'Globegistics USA', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'INTERNATIONAL_BRIDGE' => _x( 'International Bridge', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'ONTRAC' => _x( 'OnTrac', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RL_US' => _x( 'RL Carriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'RRDONNELLEY' => _x( 'RR Donnelley', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'USPS' => _x( 'USPS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SE' => array(
+ 'name' => _x( 'Sweden', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AIRMEE_WEBHOOK' => _x( 'Airmee couriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BRING' => _x( 'Bring', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DBSCHENKER_SE' => _x( 'DB Schenker (www.dbschenker.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DBSCHENKER_SV' => _x( 'DB Schenker Sweden', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
- 'VN' => array(
- 'name' => _x( 'Vietnam', 'Name of carrier country', 'woocommerce-paypal-payments' ),
- 'items' => array(
- 'KERRY_EXPRESS_VN' => _x( 'Kerry Express Vietnam', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'VIETNAM_POST' => _x( 'Vietnam Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
- 'VNPOST_EMS' => _x( 'Vietnam Post EMS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CH' => array(
+ 'name' => _x( 'Switzerland', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ASENDIA_HK' => _x( 'Asendia HonKong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PLANZER' => _x( 'Planzer Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SWISS_POST_FTP' => _x( 'Swiss Post FTP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VIAEUROPE' => _x( 'ViaEurope', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
),
- ),
+ 'TW' => array(
+ 'name' => _x( 'Taiwan', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CNWANGTONG' => _x( 'cnwangtong', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CTC_EXPRESS' => _x( 'CTC Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIMERCO' => _x( 'Dimerco Express Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HCT_LOGISTICS' => _x( 'HCT LOGISTICS CO.LTD.', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KERRYTJ' => _x( 'Kerry TJ Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PRESIDENT_TRANS' => _x( 'PRESIDENT TRANSNET CORP', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBAL_EXPRESS' => _x( 'Tai Wan Global Business', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'TH' => array(
+ 'name' => _x( 'Thailand', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ALPHAFAST' => _x( 'Alphafast', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CJ_KR' => _x( 'CJ Korea Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THA_DYNAMIC_LOGISTICS' => _x( 'Dynamic Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FASTRK_SERV' => _x( 'Fastrak Services', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FLASHEXPRESS' => _x( 'Flash Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NIM_EXPRESS' => _x( 'Nim Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NINJAVAN_THAI' => _x( 'Ninja van Thai', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SENDIT' => _x( 'Sendit', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKYBOX' => _x( 'SKYBOX', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THA_THAILAND_POST' => _x( 'Thailand Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'TR' => array(
+ 'name' => _x( 'Turkey', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'ASE' => _x( 'ASE KARGO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CDEK_TR' => _x( 'CDEK TR', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PTS' => _x( 'PTS courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PTT_POST' => _x( 'PTT Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPENTEGRA' => _x( 'ShipEntegra', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YURTICI_KARGO' => _x( 'Yurtici Kargo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'UA' => array(
+ 'name' => _x( 'Ukraine', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'NOVA_POSHTA_INT' => _x( 'Nova Poshta (International)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NOVA_POSHTA' => _x( 'Nova Poshta (novaposhta.ua)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'POSTA_UKR' => _x( 'UkrPoshta', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'AE' => array(
+ 'name' => _x( 'United Arab Emirates', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'IBEONE' => _x( 'Beone Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MARA_XPRESS' => _x( 'Mara Xpress', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FETCHR_WEBHOOK' => _x( 'Mena 360 (Fetchr)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ONECLICK' => _x( 'One click delivery services', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKYNET_UAE' => _x( 'SKYNET UAE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'GB' => array(
+ 'name' => _x( 'United Kingdom', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'AMAZON' => _x( 'Amazon Shipping', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AO_COURIER' => _x( 'AO Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'APC_OVERNIGHT' => _x( 'APC overnight (apc-overnight.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'APC_OVERNIGHT_CONNUM' => _x( 'APC Overnight Consignment', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'APG' => _x( 'APG eCommerce Solutions', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ARK_LOGISTICS' => _x( 'ARK Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GB_ARROW' => _x( 'Arrow XL', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ASENDIA_UK' => _x( 'Asendia UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BH_WORLDWIDE' => _x( 'B&H Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BIRDSYSTEM' => _x( 'BirdSystem', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'BLUECARE' => _x( 'Bluecare Express Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CAE_DELIVERS' => _x( 'CAE Delivers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'CARIBOU' => _x( 'Caribou', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DAIGLOBALTRACK' => _x( 'DAI Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DELTEC_UK' => _x( 'Deltec Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_REFR' => _x( 'DHl (Reference number)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DHL_UK' => _x( 'dhl UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIAMOND_EUROGISTICS' => _x( 'Diamond Eurogistics Limited', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DIRECTPARCELS' => _x( 'Direct Parcels', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DMS_MATRIX' => _x( 'DMSMatrix', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_LOCAL' => _x( 'DPD Local', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DPD_LOCAL_REF' => _x( 'DPD Local reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DX_SFTP' => _x( 'DX (SFTP)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'EU_FLEET_SOLUTIONS' => _x( 'EU Fleet Solutions', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FEDEX_UK' => _x( 'FedEx® UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'FURDECO' => _x( 'Furdeco', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GBA' => _x( 'GBA Services Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GEMWORLDWIDE' => _x( 'GEM Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HERMES' => _x( 'HermesWorld UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HOME_DELIVERY_SOLUTIONS' => _x( 'Home Delivery Solutions Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'INTERPARCEL_UK' => _x( 'Interparcel UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MYHERMES' => _x( 'MyHermes UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NATIONAL_SAMEDAY' => _x( 'National Sameday', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GB_NORSK' => _x( 'Norsk Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OCS_WORLDWIDE' => _x( 'OCS WORLDWIDE', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PALLETWAYS' => _x( 'Palletways', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GB_PANTHER' => _x( 'Panther', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PANTHER_REFERENCE' => _x( 'Panther Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCEL2GO' => _x( 'Parcel2Go', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PARCELINKLOGISTICS' => _x( 'Parcelink Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PLUS_LOG_UK' => _x( 'Plus UK Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RPD2MAN' => _x( 'RPD2man Deliveries', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SKYNET_UK' => _x( 'Skynet UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'AMAZON_FBA_SWISHIP' => _x( 'Swiship UK', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'THEDELIVERYGROUP' => _x( 'TDG – The Delivery Group', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PALLET_NETWORK' => _x( 'The Pallet Network', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_UK' => _x( 'TNT UK Limited (www.tnt.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TNT_UK_REFR' => _x( 'TNT UK Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GB_TUFFNELLS' => _x( 'Tuffnells Parcels Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TUFFNELLS_REFERENCE' => _x( 'Tuffnells Parcels Express- Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UK_UK_MAIL' => _x( 'UK mail (ukmail.com)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WHISTL' => _x( 'Whistl', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WNDIRECT' => _x( 'wnDirect', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UK_XDP' => _x( 'XDP Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XDP_UK_REFERENCE' => _x( 'XDP Express Reference', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPERT_DELIVERY' => _x( 'Xpert Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UK_YODEL' => _x( 'Yodel (www.yodel.co.uk)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'US' => array(
+ 'name' => _x( 'United States', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'GIO_EXPRESS' => _x( 'Gio Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBALTRANZ' => _x( 'GlobalTranz', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GSI_EXPRESS' => _x( 'GSI EXPRESS', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GSO' => _x( 'GSO (GLS-USA)', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'HIPSHIPPER' => _x( 'Hipshipper', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'GLOBAL_IPARCEL' => _x( 'i-parcel', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'DESCARTES' => _x( 'Innovel courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'US_LASERSHIP' => _x( 'LaserShip', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LONESTAR' => _x( 'Lone Star Overnight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'MAILAMERICAS' => _x( 'MailAmericas', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NEWEGGEXPRESS' => _x( 'Newegg Express', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'US_OLD_DOMINION' => _x( 'Old Dominion Freight Line', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'OSM_WORLDWIDE' => _x( 'OSM Worldwide', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PCFCORP' => _x( 'PCF Final Mile', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PILOT_FREIGHT' => _x( 'Pilot Freight Services', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PITNEY_BOWES' => _x( 'Pitney Bowes', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'PITTOHIO' => _x( 'PITT OHIO', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'QWINTRY' => _x( 'Qwintry Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'RL_US' => _x( 'RL Carriers', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SAIA_FREIGHT' => _x( 'Saia LTL Freight', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SHIPTOR' => _x( 'Shiptor', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SONICTL' => _x( 'Sonic Transportation & Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SEFL' => _x( 'Southeastern Freight Lines', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SPEEDEE' => _x( 'Spee-Dee Delivery', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'SUTTON' => _x( 'Sutton Transport', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TAZMANIAN_FREIGHT' => _x( 'Tazmanian Freight Systems', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TFORCE_FINALMILE' => _x( 'TForce Final Mile', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'LOGISTYX_TRANSGROUP' => _x( 'Transgroup courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'TRUMPCARD' => _x( 'TRUMPCARD LLC', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'USPS' => _x( 'United States Postal Service', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'UPS_MAIL_INNOVATIONS' => _x( 'UPS Mail Innovations', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'USF_REDDAWAY' => _x( 'USF Reddaway', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'USHIP' => _x( 'uShip courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WESTBANK_COURIER' => _x( 'West Bank Courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WESTGATE_GL' => _x( 'Westgate Global', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'WIZMO' => _x( 'Wizmo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'XPO_LOGISTICS' => _x( 'XPO logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'YAKIT' => _x( 'Yakit courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'US_YRC' => _x( 'YRC courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'ZINC' => _x( 'Zinc courier', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'URY' => array(
+ 'name' => _x( 'Uruguay', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'CORREO_UY' => _x( 'Correo Uruguayo', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ 'VN' => array(
+ 'name' => _x( 'Vietnam', 'Name of carrier country', 'woocommerce-paypal-payments' ),
+ 'items' => array(
+ 'JTEXPRESS_VN' => _x( 'J&T Express Vietnam', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'KERRYTTC_VN' => _x( 'Kerry Express (Vietnam) Co Ltd', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'NTLOGISTICS_VN' => _x( 'Nhat Tin Logistics', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VNM_VIETNAM_POST' => _x( 'Vietnam Post', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ 'VNM_VIETTELPOST' => _x( 'ViettelPost', 'Name of carrier', 'woocommerce-paypal-payments' ),
+ ),
+ ),
+ )
);
diff --git a/modules/ppcp-order-tracking/package.json b/modules/ppcp-order-tracking/package.json
index 69607cfd9..080003865 100644
--- a/modules/ppcp-order-tracking/package.json
+++ b/modules/ppcp-order-tracking/package.json
@@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-order-tracking/resources/css/order-edit-page.scss b/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
index a7ad9a62e..c9a112ead 100644
--- a/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
+++ b/modules/ppcp-order-tracking/resources/css/order-edit-page.scss
@@ -1,8 +1,4 @@
#ppcp_order-tracking {
- .tracking-info-message {
- padding-left: 20px;
- }
-
.error {
color: red;
font-weight: bold;
@@ -13,8 +9,132 @@
font-weight: bold;
}
- input,select {
+ input:not([type="checkbox"]),select {
width: 100%;
}
+
+ #items-select-container {
+ display:none
+ }
+
+ .ppcp-tracking-columns-wrapper {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+ grid-gap: 60px;
+
+ .ppcp-tracking-column + .ppcp-tracking-column {
+ border-left: 1px solid #c3c4c7;
+ padding-left: 20px;
+ }
+ }
+
+ .ppcp-shipment {
+
+ border: 1px solid #ccc;
+ border-radius: 4px;
+ background: #fff;
+ margin: 20px 0px;
+
+ .wc-order-item-sku {
+ font-size: .92em!important;
+ color: #888;
+ }
+
+ .ppcp-shipment-header {
+
+ padding: 0px 10px;
+ cursor: pointer;
+ background: #f0f0f1;
+
+ h4 {
+ display: inline-block;
+ margin: 10px 0px;
+ }
+
+ button {
+ float: right;
+ border: 0;
+ background: 0 0;
+ cursor: pointer;
+ height: 38px;
+ }
+ }
+
+ .ppcp-shipment-info {
+ padding: 0px 10px 20px 10px;
+ }
+
+ .ppcp-shipment-info.hidden {
+ display: none;
+ }
+
+ .select2-container--default .select2-selection__rendered {
+ font-weight: bold;
+ }
+
+ .active .ppcp-shipment-header {
+ background-color: #e0e0e0;
+ border-bottom: 1px solid #ccc;
+ }
+
+ select {
+ width: auto;
+ }
+ }
+
+ .ppcp-shipment.closed {
+ .ppcp-shipment-header .shipment-toggle-indicator .toggle-indicator:before {
+ content: "\f140";
+ }
+ }
+
+ .ppcp-tracking-loader {
+ z-index: 1000;
+ border: none;
+ margin: 0px;
+ padding: 0px;
+ width: 100%;
+ height: 100%;
+ top: 0px;
+ left: 0px;
+ background: rgb(255, 255, 255);
+ opacity: 0.6;
+ cursor: wait;
+ position: absolute;
+ display: none;
+ }
+
+ @keyframes spin {
+ 0% { transform: rotate(0deg); }
+ 100% { transform: rotate(360deg); }
+ }
}
+.ppcp-remove-tracking-item {
+ color: #b32d2e;
+ font-size: 14px;
+ cursor: pointer;
+}
+
+#side-sortables #ppcp_order-tracking {
+ .ppcp-tracking-columns-wrapper {
+ display: block;
+ }
+
+ .ppcp-tracking-columns-wrapper .ppcp-tracking-column+.ppcp-tracking-column {
+ border-top: 1px solid #c3c4c7;
+ padding-left: 0px;
+ margin-top: 20px;
+ border-left: none;
+ }
+
+ .update_shipment {
+ margin-top: 20px;
+ }
+
+ .select2-container {
+ width: 100% !important;
+ }
+}
+
+
diff --git a/modules/ppcp-order-tracking/resources/js/order-edit-page.js b/modules/ppcp-order-tracking/resources/js/order-edit-page.js
index af86bf53e..baa6c16b3 100644
--- a/modules/ppcp-order-tracking/resources/js/order-edit-page.js
+++ b/modules/ppcp-order-tracking/resources/js/order-edit-page.js
@@ -3,49 +3,169 @@ document.addEventListener(
() => {
const config = PayPalCommerceGatewayOrderTrackingInfo;
if (!typeof (PayPalCommerceGatewayOrderTrackingInfo)) {
- console.error('trackign cannot be set.');
+ console.error('tracking cannot be set.');
return;
}
- jQuery(document).on('click', '.submit_tracking_info', function () {
- const transactionId = document.querySelector('.ppcp-tracking-transaction_id');
- const trackingNumber = document.querySelector('.ppcp-tracking-tracking_number');
- const status = document.querySelector('.ppcp-tracking-status');
- const carrier = document.querySelector('.ppcp-tracking-carrier');
- const orderId = document.querySelector('.ppcp-order_id');
- const submitButton = document.querySelector('.submit_tracking_info');
+ const includeAllItemsCheckbox = document.getElementById('include-all-items');
+ const shipmentsWrapper = '#ppcp_order-tracking .ppcp-tracking-column.shipments';
+ const transactionId = document.querySelector('.ppcp-tracking-transaction_id');
+ const orderId = document.querySelector('.ppcp-tracking-order_id');
+ const carrier = document.querySelector('.ppcp-tracking-carrier');
+ const carrierNameOther = document.querySelector('.ppcp-tracking-carrier_name_other');
- submitButton.setAttribute('disabled', 'disabled');
- fetch(config.ajax.tracking_info.endpoint, {
- method: 'POST',
- credentials: 'same-origin',
- body: JSON.stringify({
- nonce: config.ajax.tracking_info.nonce,
- transaction_id: transactionId ? transactionId.value : null,
- tracking_number: trackingNumber ? trackingNumber.value : null,
- status: status ? status.value : null,
- carrier: carrier ? carrier.value : null,
- order_id: orderId ? orderId.value : null,
- action: submitButton ? submitButton.dataset.action : null,
- })
- }).then(function (res) {
- return res.json();
- }).then(function (data) {
- if (!data.success) {
- jQuery( "" + data.data.message + " " ).insertAfter(submitButton);
- setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
- submitButton.removeAttribute('disabled');
- console.error(data);
- throw Error(data.data.message);
- }
+ function toggleLineItemsSelectbox() {
+ const selectContainer = document.getElementById('items-select-container');
+ includeAllItemsCheckbox?.addEventListener('change', function(){
+ selectContainer.style.display = includeAllItemsCheckbox.checked ? 'none' : 'block';
+ })
+ }
- jQuery( "" + data.data.message + " " ).insertAfter(submitButton);
- setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
+ function toggleShipment() {
+ jQuery(document).on('click', '.ppcp-shipment-header', function(event) {
+ const shipmentContainer = event.target.closest('.ppcp-shipment');
+ const shipmentInfo = shipmentContainer.querySelector('.ppcp-shipment-info');
- submitButton.dataset.action = 'update';
- submitButton.textContent = 'update';
- submitButton.removeAttribute('disabled');
+ shipmentContainer.classList.toggle('active');
+ shipmentContainer.classList.toggle('closed');
+ shipmentInfo.classList.toggle('hidden');
});
- })
+ }
+
+ function toggleShipmentUpdateButtonDisabled() {
+ jQuery(document).on('change', '.ppcp-shipment-status', function(event) {
+ const shipmentSelectbox = event.target;
+ const shipment = shipmentSelectbox.closest('.ppcp-shipment');
+ const updateShipmentButton = shipment.querySelector('.update_shipment');
+ const selectedValue = shipmentSelectbox.value;
+
+ updateShipmentButton.classList.remove('button-disabled');
+ });
+ }
+
+ function toggleLoaderVisibility() {
+ const loader = document.querySelector('.ppcp-tracking-loader');
+ if (loader) {
+ if (loader.style.display === 'none' || loader.style.display === '') {
+ loader.style.display = 'block';
+ } else {
+ loader.style.display = 'none';
+ }
+ }
+ }
+
+ function toggleOtherCarrierName() {
+ jQuery(carrier).on('change', function() {
+ const hiddenHtml = carrierNameOther.parentNode;
+ if (carrier.value === 'OTHER') {
+ hiddenHtml.classList.remove('hidden');
+ } else {
+ if (!hiddenHtml.classList.contains('hidden')) {
+ hiddenHtml.classList.add('hidden');
+ }
+ }
+ })
+ }
+
+ function handleAddShipment() {
+ jQuery(document).on('click', '.submit_tracking_info', function () {
+ const trackingNumber = document.querySelector('.ppcp-tracking-tracking_number');
+ const status = document.querySelector('.ppcp-tracking-status');
+ const submitButton = document.querySelector('.submit_tracking_info');
+ const items = document.querySelector('.ppcp-tracking-items');
+ const noShipemntsContainer = document.querySelector('.ppcp-tracking-no-shipments');
+
+ let checkedItems = includeAllItemsCheckbox?.checked || !items ? 0 : Array.from(items.selectedOptions).map(option => option.value)
+
+ toggleLoaderVisibility()
+ fetch(config.ajax.tracking_info.endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: config.ajax.tracking_info.nonce,
+ transaction_id: transactionId ? transactionId.value : null,
+ tracking_number: trackingNumber ? trackingNumber.value : null,
+ status: status ? status.value : null,
+ carrier: carrier ? carrier.value : null,
+ carrier_name_other: carrierNameOther ? carrierNameOther.value : null,
+ order_id: orderId ? orderId.value : null,
+ items: checkedItems
+ })
+ }).then(function (res) {
+ return res.json();
+ }).then(function (data) {
+ toggleLoaderVisibility()
+
+ if (!data.success || ! data.data.shipment) {
+ jQuery( "" + data.data.message + "
" ).insertAfter(submitButton);
+ setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
+ submitButton.removeAttribute('disabled');
+ console.error(data);
+ throw Error(data.data.message);
+ }
+
+ jQuery( "" + data.data.message + "
" ).insertAfter(submitButton);
+ setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
+ jQuery(data.data.shipment).appendTo(shipmentsWrapper);
+ if (noShipemntsContainer) {
+ noShipemntsContainer.parentNode.removeChild(noShipemntsContainer);
+ }
+ });
+ })
+ }
+
+ function handleUpdateShipment() {
+ jQuery(document).on('click', '.update_shipment', function (event) {
+ const updateShipment = event.target;
+ const parentElement = updateShipment.parentNode.parentNode;
+ const shipmentStatus = parentElement.querySelector('.ppcp-shipment-status');
+ const shipmentTrackingNumber = parentElement.querySelector('.ppcp-shipment-tacking_number');
+ const shipmentCarrier = parentElement.querySelector('.ppcp-shipment-carrier');
+ const shipmentCarrierNameOther = parentElement.querySelector('.ppcp-shipment-carrier-other');
+
+ toggleLoaderVisibility()
+ fetch(config.ajax.tracking_info.endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: config.ajax.tracking_info.nonce,
+ transaction_id: transactionId ? transactionId.value : null,
+ tracking_number: shipmentTrackingNumber ? shipmentTrackingNumber.value : null,
+ status: shipmentStatus ? shipmentStatus.value : null,
+ carrier: shipmentCarrier ? shipmentCarrier.value : null,
+ carrier_name_other: shipmentCarrierNameOther ? shipmentCarrierNameOther.value : null,
+ order_id: orderId ? orderId.value : null,
+ action: 'update'
+ })
+ }).then(function (res) {
+ return res.json();
+ }).then(function (data) {
+ toggleLoaderVisibility()
+
+ if (!data.success) {
+ jQuery( "" + data.data.message + "
" ).insertAfter(updateShipment);
+ setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
+ console.error(data);
+ throw Error(data.data.message);
+ }
+
+ jQuery( "" + data.data.message + "
" ).insertAfter(updateShipment);
+ setTimeout(()=> jQuery('.tracking-info-message').remove(),3000);
+ });
+ })
+ }
+
+ handleAddShipment();
+ handleUpdateShipment();
+ toggleLineItemsSelectbox();
+ toggleShipment();
+ toggleShipmentUpdateButtonDisabled();
+ toggleOtherCarrierName();
},
);
diff --git a/modules/ppcp-order-tracking/services.php b/modules/ppcp-order-tracking/services.php
index 8c52c4efa..144460571 100644
--- a/modules/ppcp-order-tracking/services.php
+++ b/modules/ppcp-order-tracking/services.php
@@ -9,6 +9,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\OrderTracking;
+use WC_Order;
+use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactory;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
@@ -21,12 +26,18 @@ return array(
$container->get( 'ppcp.asset-version' )
);
},
+ 'order-tracking.shipment.factory' => static function ( ContainerInterface $container ) : ShipmentFactoryInterface {
+ return new ShipmentFactory();
+ },
'order-tracking.endpoint.controller' => static function ( ContainerInterface $container ) : OrderTrackingEndpoint {
return new OrderTrackingEndpoint(
$container->get( 'api.host' ),
$container->get( 'api.bearer' ),
$container->get( 'woocommerce.logger.woocommerce' ),
- $container->get( 'button.request-data' )
+ $container->get( 'button.request-data' ),
+ $container->get( 'order-tracking.shipment.factory' ),
+ $container->get( 'order-tracking.allowed-shipping-statuses' ),
+ $container->get( 'order-tracking.is-merchant-country-us' )
);
},
'order-tracking.module.url' => static function ( ContainerInterface $container ): string {
@@ -42,17 +53,21 @@ return array(
},
'order-tracking.meta-box.renderer' => static function ( ContainerInterface $container ): MetaBoxRenderer {
return new MetaBoxRenderer(
- $container->get( 'order-tracking.endpoint.controller' ),
$container->get( 'order-tracking.allowed-shipping-statuses' ),
- $container->get( 'order-tracking.available-carriers' )
+ $container->get( 'order-tracking.available-carriers' ),
+ $container->get( 'order-tracking.endpoint.controller' ),
+ $container->get( 'order-tracking.is-merchant-country-us' )
);
},
'order-tracking.allowed-shipping-statuses' => static function ( ContainerInterface $container ): array {
- return array(
- 'SHIPPED' => 'SHIPPED',
- 'ON_HOLD' => 'ON_HOLD',
- 'DELIVERED' => 'DELIVERED',
- 'CANCELLED' => 'CANCELLED',
+ return (array) apply_filters(
+ 'woocommerce_paypal_payments_tracking_statuses',
+ array(
+ 'SHIPPED' => 'Shipped',
+ 'ON_HOLD' => 'On Hold',
+ 'DELIVERED' => 'Delivered',
+ 'CANCELLED' => 'Cancelled',
+ )
);
},
'order-tracking.allowed-carriers' => static function ( ContainerInterface $container ): array {
@@ -74,14 +89,7 @@ return array(
),
);
},
- 'order-tracking.is-paypal-order-edit-page' => static function ( ContainerInterface $container ): bool {
- $order_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- if ( empty( $order_id ) ) {
- return false;
- }
-
- $meta = get_post_meta( $order_id, PayPalGateway::ORDER_ID_META_KEY, true );
-
- return ! empty( $meta );
+ 'order-tracking.is-merchant-country-us' => static function ( ContainerInterface $container ): bool {
+ return $container->get( 'api.shop.country' ) === 'US';
},
);
diff --git a/modules/ppcp-order-tracking/src/Assets/OrderEditPageAssets.php b/modules/ppcp-order-tracking/src/Assets/OrderEditPageAssets.php
index cf9fab613..39706832b 100644
--- a/modules/ppcp-order-tracking/src/Assets/OrderEditPageAssets.php
+++ b/modules/ppcp-order-tracking/src/Assets/OrderEditPageAssets.php
@@ -78,11 +78,13 @@ class OrderEditPageAssets {
* @return array a map of script data.
*/
public function get_script_data(): array {
+
return array(
'ajax' => array(
'tracking_info' => array(
'endpoint' => \WC_AJAX::get_endpoint( OrderTrackingEndpoint::ENDPOINT ),
'nonce' => wp_create_nonce( OrderTrackingEndpoint::nonce() ),
+ 'url' => admin_url( 'admin-ajax.php' ),
),
),
);
diff --git a/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php b/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php
index fb281ecef..ae2ca54f0 100644
--- a/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php
+++ b/modules/ppcp-order-tracking/src/Endpoint/OrderTrackingEndpoint.php
@@ -11,20 +11,31 @@ namespace WooCommerce\PayPalCommerce\OrderTracking\Endpoint;
use Exception;
use Psr\Log\LoggerInterface;
+use stdClass;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\RequestTrait;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
+use WooCommerce\PayPalCommerce\OrderTracking\OrderTrackingModule;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentFactoryInterface;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentInterface;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
/**
* The OrderTrackingEndpoint.
*
* @psalm-type SupportedStatuses = 'SHIPPED'|'ON_HOLD'|'DELIVERED'|'CANCELLED'
- * @psalm-type TrackingInfo = array{transaction_id: string, status: SupportedStatuses, tracking_number?: string, carrier?: string}
- * @psalm-type RequestValues = array{transaction_id: string, status: SupportedStatuses, order_id: int, action: 'create'|'update', tracking_number?: string, carrier?: string}
+ * @psalm-type TrackingInfo = array{
+ * transaction_id: string,
+ * status: SupportedStatuses,
+ * tracking_number: string,
+ * carrier: string,
+ * items?: list,
+ * carrier_name_other?: string,
+ * }
* Class OrderTrackingEndpoint
*/
class OrderTrackingEndpoint {
@@ -45,61 +56,107 @@ class OrderTrackingEndpoint {
*
* @var string
*/
- private $host;
+ protected $host;
/**
* The bearer.
*
* @var Bearer
*/
- private $bearer;
+ protected $bearer;
/**
* The logger.
*
* @var LoggerInterface
*/
- private $logger;
+ protected $logger;
+
+ /**
+ * The ShipmentFactory.
+ *
+ * @var ShipmentFactoryInterface
+ */
+ protected $shipment_factory;
+
+ /**
+ * Allowed shipping statuses.
+ *
+ * @var string[]
+ */
+ protected $allowed_statuses;
+
+ /**
+ * Whether new API should be used.
+ *
+ * @var bool
+ */
+ protected $should_use_new_api;
/**
* PartnersEndpoint constructor.
*
- * @param string $host The host.
- * @param Bearer $bearer The bearer.
- * @param LoggerInterface $logger The logger.
- * @param RequestData $request_data The Request data.
+ * @param string $host The host.
+ * @param Bearer $bearer The bearer.
+ * @param LoggerInterface $logger The logger.
+ * @param RequestData $request_data The Request data.
+ * @param ShipmentFactoryInterface $shipment_factory The ShipmentFactory.
+ * @param string[] $allowed_statuses Allowed shipping statuses.
+ * @param bool $should_use_new_api Whether new API should be used.
*/
public function __construct(
string $host,
Bearer $bearer,
LoggerInterface $logger,
- RequestData $request_data
+ RequestData $request_data,
+ ShipmentFactoryInterface $shipment_factory,
+ array $allowed_statuses,
+ bool $should_use_new_api
) {
- $this->host = $host;
- $this->bearer = $bearer;
- $this->logger = $logger;
- $this->request_data = $request_data;
+ $this->host = $host;
+ $this->bearer = $bearer;
+ $this->logger = $logger;
+ $this->request_data = $request_data;
+ $this->shipment_factory = $shipment_factory;
+ $this->allowed_statuses = $allowed_statuses;
+ $this->should_use_new_api = $should_use_new_api;
}
/**
* Handles the request.
*/
public function handle_request(): void {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return;
+ }
+
try {
- $data = $this->request_data->read_request( $this->nonce() );
- $action = $data['action'];
- $request_body = $this->extract_tracking_information( $data );
- $order_id = (int) $data['order_id'];
- $action === 'create' ? $this->add_tracking_information( $request_body, $order_id ) : $this->update_tracking_information( $request_body, $order_id );
+ $data = $this->request_data->read_request( $this->nonce() );
+ $order_id = (int) $data['order_id'];
+ $action = $data['action'] ?? '';
- $action_message = $action === 'create' ? 'created' : 'updated';
- $message = sprintf(
- // translators: %1$s is the action message (created or updated).
- _x( 'successfully %1$s', 'tracking info success message', 'woocommerce-paypal-payments' ),
- esc_html( $action_message )
+ $this->validate_tracking_info( $data );
+ $shipment = $this->create_shipment( $order_id, $data );
+
+ $action === 'update'
+ ? $this->update_tracking_information( $shipment, $order_id )
+ : $this->add_tracking_information( $shipment, $order_id );
+
+ $message = $action === 'update'
+ ? _x( 'successfully updated', 'tracking info success message', 'woocommerce-paypal-payments' )
+ : _x( 'successfully created', 'tracking info success message', 'woocommerce-paypal-payments' );
+
+ ob_start();
+ $shipment->render( $this->allowed_statuses );
+ $shipment_html = ob_get_clean();
+
+ wp_send_json_success(
+ array(
+ 'message' => $message,
+ 'shipment' => $shipment_html,
+ )
);
-
- wp_send_json_success( array( 'message' => $message ) );
} catch ( Exception $error ) {
wp_send_json_error( array( 'message' => $error->getMessage() ), 500 );
}
@@ -108,99 +165,116 @@ class OrderTrackingEndpoint {
/**
* Creates the tracking information of a given order with the given data.
*
- * @param array $data The tracking information to add.
- * @psalm-param TrackingInfo $data
- * @param int $order_id The order ID.
- * @throws RuntimeException If problem creating.
+ * @param ShipmentInterface $shipment The shipment.
+ * @param int $order_id The order ID.
+ *
+ * @throws RuntimeException If problem adding.
*/
- public function add_tracking_information( array $data, int $order_id ) : void {
- $url = trailingslashit( $this->host ) . 'v1/shipping/trackers-batch';
+ public function add_tracking_information( ShipmentInterface $shipment, int $order_id ) : void {
+ $wc_order = wc_get_order( $order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return;
+ }
- $body = array(
- 'trackers' => array( (array) apply_filters( 'woocommerce_paypal_payments_tracking_data_before_sending', $data, $order_id ) ),
- );
+ $shipment_request_data = $this->generate_request_data( $wc_order, $shipment );
- $args = array(
- 'method' => 'POST',
- 'headers' => $this->request_headers(),
- 'body' => wp_json_encode( $body ),
- );
+ $url = $shipment_request_data['url'] ?? '';
+ $args = $shipment_request_data['args'] ?? array();
- do_action( 'woocommerce_paypal_payments_before_tracking_is_added', $order_id, $data );
+ if ( ! $url || empty( $args ) ) {
+ $this->throw_runtime_exception( $shipment_request_data, 'create' );
+ }
+
+ do_action( 'woocommerce_paypal_payments_before_tracking_is_added', $order_id, $shipment_request_data );
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
- $error = new RuntimeException(
- 'Could not create order tracking information.'
+ $args = array(
+ 'args' => $args,
+ 'response' => $response,
);
- $this->logger->log(
- 'warning',
- $error->getMessage(),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
- throw $error;
+ $this->throw_runtime_exception( $args, 'create' );
}
- /**
- * Need to ignore Method WP_Error::offsetGet does not exist
- *
- * @psalm-suppress UndefinedMethod
- */
- $json = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
- if ( 200 !== $status_code ) {
- $error = new PayPalApiException(
- $json,
- $status_code
- );
- $this->logger->log(
- 'warning',
- sprintf(
- 'Failed to create order tracking information. PayPal API response: %1$s',
- $error->getMessage()
- ),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
- throw $error;
+ if ( 201 !== $status_code && ! is_wp_error( $response ) ) {
+ /**
+ * Cannot be WP_Error because we check for it above.
+ *
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ $this->throw_paypal_api_exception( $status_code, $args, $response, 'create' );
}
- $wc_order = wc_get_order( $order_id );
- if ( is_a( $wc_order, WC_Order::class ) ) {
- $wc_order->update_meta_data( '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' );
- $wc_order->save();
- }
+ $this->save_tracking_metadata( $wc_order, $shipment->tracking_number(), array_keys( $shipment->line_items() ) );
do_action( 'woocommerce_paypal_payments_after_tracking_is_added', $order_id, $response );
}
/**
- * Gets the tracking information of a given order.
+ * Updates the tracking information of a given order with the given shipment.
*
- * @param int $wc_order_id The order ID.
- * @return array|null The tracking information.
- * @psalm-return TrackingInfo|null
- * @throws RuntimeException If problem getting.
+ * @param ShipmentInterface $shipment The shipment.
+ * @param int $order_id The order ID.
+ *
+ * @throws RuntimeException If problem updating.
*/
- public function get_tracking_information( int $wc_order_id ) : ?array {
- $wc_order = wc_get_order( $wc_order_id );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- throw new RuntimeException( 'wrong order ID' );
+ public function update_tracking_information( ShipmentInterface $shipment, int $order_id ) : void {
+ $host = trailingslashit( $this->host );
+ $tracker_id = $this->find_tracker_id( $shipment->transaction_id(), $shipment->tracking_number() );
+ $url = "{$host}v1/shipping/trackers/{$tracker_id}";
+ $shipment_data = $shipment->to_array();
+
+ $args = array(
+ 'method' => 'PUT',
+ 'headers' => $this->request_headers(),
+ 'body' => wp_json_encode( (array) apply_filters( 'woocommerce_paypal_payments_tracking_data_before_update', $shipment_data, $order_id ) ),
+ );
+
+ do_action( 'woocommerce_paypal_payments_before_tracking_is_updated', $order_id, $shipment_data );
+
+ $response = $this->request( $url, $args );
+
+ if ( is_wp_error( $response ) ) {
+ $args = array(
+ 'args' => $args,
+ 'response' => $response,
+ );
+ $this->throw_runtime_exception( $args, 'update' );
}
- if ( ! $wc_order->meta_exists( '_ppcp_paypal_tracking_number' ) ) {
+ $status_code = (int) wp_remote_retrieve_response_code( $response );
+ if ( 204 !== $status_code && ! is_wp_error( $response ) ) {
+ /**
+ * Cannot be WP_Error because we check for it above.
+ *
+ * @psalm-suppress PossiblyInvalidArgument
+ */
+ $this->throw_paypal_api_exception( $status_code, $args, $response, 'update' );
+ }
+
+ do_action( 'woocommerce_paypal_payments_after_tracking_is_updated', $order_id, $response );
+ }
+
+ /**
+ * Gets the tracking information of a given order.
+ *
+ * @param int $wc_order_id The order ID.
+ * @param string $tracking_number The tracking number.
+ *
+ * @return ShipmentInterface|null The tracking information.
+ * @throws RuntimeException If problem getting.
+ */
+ public function get_tracking_information( int $wc_order_id, string $tracking_number ) : ?ShipmentInterface {
+ $wc_order = wc_get_order( $wc_order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
return null;
}
- $transaction_id = $wc_order->get_transaction_id();
- $tracking_number = $wc_order->get_meta( '_ppcp_paypal_tracking_number', true );
- $url = trailingslashit( $this->host ) . 'v1/shipping/trackers/' . $this->find_tracker_id( $transaction_id, $tracking_number );
+ $host = trailingslashit( $this->host );
+ $tracker_id = $this->find_tracker_id( $wc_order->get_transaction_id(), $tracking_number );
+ $url = "{$host}v1/shipping/trackers/{$tracker_id}";
$args = array(
'method' => 'GET',
@@ -210,18 +284,11 @@ class OrderTrackingEndpoint {
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
- $error = new RuntimeException(
- 'Could not fetch the tracking information.'
+ $args = array(
+ 'args' => $args,
+ 'response' => $response,
);
- $this->logger->log(
- 'warning',
- $error->getMessage(),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
- throw $error;
+ $this->throw_runtime_exception( $args, 'fetch' );
}
/**
@@ -236,46 +303,39 @@ class OrderTrackingEndpoint {
return null;
}
- return $this->extract_tracking_information( (array) $data );
+ return $this->create_shipment( $wc_order_id, (array) $data );
}
/**
- * Updates the tracking information of a given order with the given data.
+ * Gets the list of shipments of a given order.
*
- * @param array $data The tracking information to update.
- * @psalm-param TrackingInfo $data
- * @param int $order_id The order ID.
- * @throws RuntimeException If problem updating.
+ * @param int $wc_order_id The order ID.
+ * @return ShipmentInterface[] The list of shipments.
+ * @throws RuntimeException If problem getting.
*/
- public function update_tracking_information( array $data, int $order_id ) : void {
- $tracking_info = $this->get_tracking_information( $order_id );
- $transaction_id = $tracking_info['transaction_id'] ?? '';
- $tracking_number = $tracking_info['tracking_number'] ?? '';
- $url = trailingslashit( $this->host ) . 'v1/shipping/trackers/' . $this->find_tracker_id( $transaction_id, $tracking_number );
+ public function list_tracking_information( int $wc_order_id ) : ?array {
+ $wc_order = wc_get_order( $wc_order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return array();
+ }
+
+ $host = trailingslashit( $this->host );
+ $transaction_id = $wc_order->get_transaction_id();
+ $url = "{$host}v1/shipping/trackers?transaction_id={$transaction_id}";
$args = array(
- 'method' => 'PUT',
+ 'method' => 'GET',
'headers' => $this->request_headers(),
- 'body' => wp_json_encode( (array) apply_filters( 'woocommerce_paypal_payments_tracking_data_before_update', $data, $order_id ) ),
);
- do_action( 'woocommerce_paypal_payments_before_tracking_is_updated', $order_id, $data );
-
$response = $this->request( $url, $args );
if ( is_wp_error( $response ) ) {
- $error = new RuntimeException(
- 'Could not update order tracking information.'
+ $args = array(
+ 'args' => $args,
+ 'response' => $response,
);
- $this->logger->log(
- 'warning',
- $error->getMessage(),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
- throw $error;
+ $this->throw_runtime_exception( $args, 'fetch' );
}
/**
@@ -283,34 +343,20 @@ class OrderTrackingEndpoint {
*
* @psalm-suppress UndefinedMethod
*/
- $json = json_decode( $response['body'] );
+ $data = json_decode( $response['body'] );
$status_code = (int) wp_remote_retrieve_response_code( $response );
- if ( 204 !== $status_code ) {
- $error = new PayPalApiException(
- $json,
- $status_code
- );
- $this->logger->log(
- 'warning',
- sprintf(
- 'Failed to update the order tracking information. PayPal API response: %1$s',
- $error->getMessage()
- ),
- array(
- 'args' => $args,
- 'response' => $response,
- )
- );
- throw $error;
+
+ if ( 200 !== $status_code ) {
+ return null;
}
- $wc_order = wc_get_order( $order_id );
- if ( is_a( $wc_order, WC_Order::class ) ) {
- $wc_order->update_meta_data( '_ppcp_paypal_tracking_number', $data['tracking_number'] ?? '' );
- $wc_order->save();
+ $shipments = array();
+
+ foreach ( $data->trackers as $shipment ) {
+ $shipments[] = $this->create_shipment( $wc_order_id, (array) $shipment );
}
- do_action( 'woocommerce_paypal_payments_after_tracking_is_updated', $order_id, $response );
+ return $shipments;
}
/**
@@ -323,33 +369,80 @@ class OrderTrackingEndpoint {
}
/**
- * Extracts the needed tracking information from given data.
+ * Creates the shipment based on requested data.
*
+ * @param int $wc_order_id The WC order ID.
* @param array $data The request data map.
- * @psalm-param RequestValues $data
- * @return array A map of tracking information keys to values.
- * @psalm-return TrackingInfo
- * @throws RuntimeException If problem extracting.
+ * @psalm-param TrackingInfo $data
+ *
+ * @return ShipmentInterface The shipment.
+ * @throws RuntimeException If problem creating.
*/
- protected function extract_tracking_information( array $data ): array {
- if ( empty( $data['transaction_id'] ) || empty( $data['status'] ) ) {
- $this->logger->log( 'warning', 'Missing transaction_id or status.' );
- throw new RuntimeException( 'Missing transaction_id or status.' );
- }
+ protected function create_shipment( int $wc_order_id, array $data ): ShipmentInterface {
+ $carrier = $data['carrier'] ?? '';
$tracking_info = array(
- 'transaction_id' => $data['transaction_id'],
- 'status' => $data['status'],
+ 'transaction_id' => $data['transaction_id'] ?? '',
+ 'status' => $data['status'] ?? '',
+ 'tracking_number' => $data['tracking_number'] ?? '',
+ 'carrier' => $carrier,
+ 'carrier_name_other' => $data['carrier_name_other'] ?? '',
);
- if ( ! empty( $data['tracking_number'] ) ) {
- $tracking_info['tracking_number'] = $data['tracking_number'];
+ if ( ! empty( $data['items'] ) ) {
+ $tracking_info['items'] = array_map( 'intval', $data['items'] );
}
- if ( ! empty( $data['carrier'] ) ) {
- $tracking_info['carrier'] = $data['carrier'];
+ return $this->shipment_factory->create_shipment(
+ $wc_order_id,
+ $tracking_info['transaction_id'],
+ $tracking_info['tracking_number'],
+ $tracking_info['status'],
+ $tracking_info['carrier'],
+ $tracking_info['carrier_name_other'],
+ $tracking_info['items'] ?? array()
+ );
+ }
+
+ /**
+ * Validates tracking info for given request values.
+ *
+ * @param array $request_values A map of request keys to values.
+ * @return void
+ * @throws RuntimeException If validation failed.
+ */
+ protected function validate_tracking_info( array $request_values ): void {
+ $error_message = __( 'Missing required information: ', 'woocommerce-paypal-payments' );
+ $empty_keys = array();
+
+ $carrier = $request_values['carrier'] ?? '';
+
+ $data_to_check = array(
+ 'transaction_id' => $request_values['transaction_id'] ?? '',
+ 'status' => $request_values['status'] ?? '',
+ 'tracking_number' => $request_values['tracking_number'] ?? '',
+ 'carrier' => $carrier,
+ );
+
+ if ( $carrier === 'OTHER' ) {
+ $data_to_check['carrier_name_other'] = $request_values['carrier_name_other'] ?? '';
}
- return $tracking_info;
+
+ foreach ( $data_to_check as $key => $value ) {
+ if ( ! empty( $value ) ) {
+ continue;
+ }
+
+ $empty_keys[] = ucwords( str_replace( '_', ' ', $key ) );
+ }
+
+ if ( empty( $empty_keys ) ) {
+ return;
+ }
+
+ $error_message .= implode( ' ,', $empty_keys );
+
+ throw new RuntimeException( $error_message );
}
/**
@@ -374,4 +467,111 @@ class OrderTrackingEndpoint {
protected function find_tracker_id( string $transaction_id, string $tracking_number ): string {
return ! empty( $tracking_number ) ? "{$transaction_id}-{$tracking_number}" : "{$transaction_id}-NOTRACKER";
}
+
+ /**
+ * Saves the tracking metadata for given line items.
+ *
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @param string $tracking_number The tracking number.
+ * @param int[] $line_items The list of shipment line items.
+ * @return void
+ */
+ protected function save_tracking_metadata( WC_Order $wc_order, string $tracking_number, array $line_items ): void {
+ $tracking_meta = $wc_order->get_meta( OrderTrackingModule::PPCP_TRACKING_INFO_META_NAME );
+
+ if ( ! is_array( $tracking_meta ) ) {
+ $tracking_meta = array();
+ }
+
+ foreach ( $line_items as $item ) {
+ $tracking_meta[ $tracking_number ][] = $item;
+ }
+
+ $wc_order->update_meta_data( OrderTrackingModule::PPCP_TRACKING_INFO_META_NAME, $tracking_meta );
+ $wc_order->save();
+ }
+
+ /**
+ * Generates the request data.
+ *
+ * @param WC_Order $wc_order The WC order.
+ * @param ShipmentInterface $shipment The shipment.
+ * @return array
+ */
+ protected function generate_request_data( WC_Order $wc_order, ShipmentInterface $shipment ): array {
+ $paypal_order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
+ $host = trailingslashit( $this->host );
+ $shipment_data = $shipment->to_array();
+
+ $old_api_data = $shipment_data;
+ unset( $old_api_data['items'] );
+ $request_shipment_data = array( 'trackers' => array( $old_api_data ) );
+
+ if ( $this->should_use_new_api ) {
+ unset( $shipment_data['transaction_id'] );
+ $shipment_data['capture_id'] = $shipment->transaction_id();
+ $request_shipment_data = $shipment_data;
+ }
+
+ $url = $this->should_use_new_api ? "{$host}v2/checkout/orders/{$paypal_order_id}/track" : "{$host}v1/shipping/trackers";
+ $args = array(
+ 'method' => 'POST',
+ 'headers' => $this->request_headers(),
+ 'body' => wp_json_encode( (array) apply_filters( 'woocommerce_paypal_payments_tracking_data_before_sending', $request_shipment_data, $wc_order->get_id() ) ),
+ );
+
+ return array(
+ 'url' => $url,
+ 'args' => $args,
+ );
+ }
+
+ /**
+ * Throws PayPal APi exception and logs the error message with given arguments.
+ *
+ * @param int $status_code The response status code.
+ * @param array $args The arguments.
+ * @param array $response The request response.
+ * @param string $message_part The part of the message.
+ * @return void
+ *
+ * @throws PayPalApiException PayPal APi exception.
+ */
+ protected function throw_paypal_api_exception( int $status_code, array $args, array $response, string $message_part ): void {
+ $error = new PayPalApiException(
+ json_decode( $response['body'] ),
+ $status_code
+ );
+ $this->logger->log(
+ 'warning',
+ sprintf(
+ "Failed to {$message_part} order tracking information. PayPal API response: %s",
+ $error->getMessage()
+ ),
+ array(
+ 'args' => $args,
+ 'response' => $response,
+ )
+ );
+ throw $error;
+ }
+
+ /**
+ * Throws the exception && logs the error message with given arguments.
+ *
+ * @param array $args The arguments.
+ * @param string $message_part The part of the message.
+ * @return void
+ *
+ * @throws RuntimeException The exception.
+ */
+ protected function throw_runtime_exception( array $args, string $message_part ): void {
+ $error = new RuntimeException( "Could not {$message_part} the order tracking information." );
+ $this->logger->log(
+ 'warning',
+ $error->getMessage(),
+ $args
+ );
+ throw $error;
+ }
}
diff --git a/modules/ppcp-order-tracking/src/MetaBoxRenderer.php b/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
index 730ddd4ca..916b9fbda 100644
--- a/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
+++ b/modules/ppcp-order-tracking/src/MetaBoxRenderer.php
@@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\OrderTracking;
use WC_Order;
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
+use WooCommerce\PayPalCommerce\OrderTracking\Shipment\ShipmentInterface;
use WP_Post;
/**
@@ -24,15 +25,6 @@ use WP_Post;
*/
class MetaBoxRenderer {
- public const NAME_PREFIX = 'ppcp-tracking';
-
- /**
- * The OrderTrackingEndpoint.
- *
- * @var OrderTrackingEndpoint
- */
- protected $order_tracking_endpoint;
-
/**
* Allowed shipping statuses.
*
@@ -48,83 +40,144 @@ class MetaBoxRenderer {
*/
protected $carriers;
+ /**
+ * The order tracking endpoint.
+ *
+ * @var OrderTrackingEndpoint
+ */
+ protected $order_tracking_endpoint;
+
+ /**
+ * Whether new API should be used.
+ *
+ * @var bool
+ */
+ protected $should_use_new_api;
+
/**
* MetaBoxRenderer constructor.
*
- * @param OrderTrackingEndpoint $order_tracking_endpoint The OrderTrackingEndpoint.
* @param string[] $allowed_statuses Allowed shipping statuses.
* @param array $carriers Available shipping carriers.
* @psalm-param Carriers $carriers
+ * @param OrderTrackingEndpoint $order_tracking_endpoint The order tracking endpoint.
+ * @param bool $should_use_new_api Whether new API should be used.
*/
public function __construct(
- OrderTrackingEndpoint $order_tracking_endpoint,
array $allowed_statuses,
- array $carriers
+ array $carriers,
+ OrderTrackingEndpoint $order_tracking_endpoint,
+ bool $should_use_new_api
) {
- $this->order_tracking_endpoint = $order_tracking_endpoint;
$this->allowed_statuses = $allowed_statuses;
$this->carriers = $carriers;
+ $this->order_tracking_endpoint = $order_tracking_endpoint;
+ $this->should_use_new_api = $should_use_new_api;
}
/**
* Renders the order tracking MetaBox.
*
- * @param WP_Post $post The post object.
+ * @param mixed $post_or_order_object Either WP_Post or WC_Order when COT is data source.
*/
- public function render( WP_Post $post ): void {
- $wc_order = wc_get_order( $post->ID );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
+ public function render( $post_or_order_object ): void {
+ $wc_order = ( $post_or_order_object instanceof WP_Post ) ? wc_get_order( $post_or_order_object->ID ) : $post_or_order_object;
+ if ( ! $wc_order instanceof WC_Order ) {
return;
}
- $tracking_info = $this->order_tracking_endpoint->get_tracking_information( $wc_order->get_id() );
+ $transaction_id = $wc_order->get_transaction_id() ?: '';
+ $order_items = $wc_order->get_items();
+ $order_item_count = ! empty( $order_items ) ? count( $order_items ) : 0;
- $transaction_id = $tracking_info['transaction_id'] ?? $wc_order->get_transaction_id() ?: '';
- $tracking_number = $tracking_info['tracking_number'] ?? '';
- $status_value = $tracking_info['status'] ?? 'SHIPPED';
- $carrier_value = $tracking_info['carrier'] ?? '';
-
- $carriers = (array) apply_filters( 'woocommerce_paypal_payments_tracking_carriers', $this->carriers, $wc_order->get_id() );
- $statuses = (array) apply_filters( 'woocommerce_paypal_payments_tracking_statuses', $this->allowed_statuses, $wc_order->get_id() );
- $tracking_number = (string) apply_filters( 'woocommerce_paypal_payments_tracking_number', $tracking_number, $wc_order->get_id() );
-
- $action = ! $tracking_info ? 'create' : 'update';
+ /**
+ * The shipments
+ *
+ * @var ShipmentInterface[] $shipments
+ */
+ $shipments = $this->order_tracking_endpoint->list_tracking_information( $wc_order->get_id() ) ?? array();
?>
-
-
-
-
-
-
-
-
-
- $status ) : ?>
- >
-
-
-
-
-
-
-
-
-
- $carrier_name ) : ?>
- >
+
+
+
+
+
+
+
+ 1 && $this->should_use_new_api ) : ?>
+
+
+
+
+
+
+
+ get_name() ); ?>
+
+
+
+
+
+
+
+
+
+
+
+
+ allowed_statuses as $status_key => $status ) : ?>
+
-
-
-
-
-
-
-
+
+
+
+
+
+ carriers as $carrier ) :
+ if ( empty( $carrier ) ) {
+ continue;
+ }
+ $country = $carrier['name'] ?? '';
+ $carrier_items = $carrier['items'] ?? array();
+ ?>
+
+ $carrier_name ) : ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ render( $this->allowed_statuses );
+ }
+ ?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
get( 'wcgateway.settings' );
- assert( $settings instanceof Settings );
+ $endpoint = $c->get( 'order-tracking.endpoint.controller' );
+ assert( $endpoint instanceof OrderTrackingEndpoint );
- $pui_helper = $c->get( 'wcgateway.pay-upon-invoice-helper' );
- assert( $pui_helper instanceof PayUponInvoiceHelper );
-
- if ( $pui_helper->is_pui_gateway_enabled() ) {
- $settings->set( 'tracking_enabled', true );
- $settings->persist();
- }
-
- $tracking_enabled = $settings->has( 'tracking_enabled' ) && $settings->get( 'tracking_enabled' );
-
- if ( ! $tracking_enabled ) {
- return;
- }
+ add_action( 'wc_ajax_' . OrderTrackingEndpoint::ENDPOINT, array( $endpoint, 'handle_request' ) );
$asset_loader = $c->get( 'order-tracking.assets' );
assert( $asset_loader instanceof OrderEditPageAssets );
- $is_paypal_order_edit_page = $c->get( 'order-tracking.is-paypal-order-edit-page' );
-
- $endpoint = $c->get( 'order-tracking.endpoint.controller' );
- assert( $endpoint instanceof OrderTrackingEndpoint );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
assert( $logger instanceof LoggerInterface );
+ $bearer = $c->get( 'api.bearer' );
+
add_action(
'init',
- static function () use ( $asset_loader, $is_paypal_order_edit_page ) {
- if ( ! $is_paypal_order_edit_page ) {
+ function() use ( $asset_loader, $bearer ) {
+ if ( ! $this->is_tracking_enabled( $bearer ) ) {
return;
}
$asset_loader->register();
}
);
-
add_action(
- 'admin_enqueue_scripts',
- static function () use ( $asset_loader, $is_paypal_order_edit_page ) {
- if ( ! $is_paypal_order_edit_page ) {
+ 'init',
+ function() use ( $asset_loader, $bearer ) {
+ if ( ! $this->is_tracking_enabled( $bearer ) ) {
return;
}
@@ -94,56 +79,34 @@ class OrderTrackingModule implements ModuleInterface {
}
);
- add_action(
- 'wc_ajax_' . OrderTrackingEndpoint::ENDPOINT,
- array( $endpoint, 'handle_request' )
- );
-
$meta_box_renderer = $c->get( 'order-tracking.meta-box.renderer' );
add_action(
'add_meta_boxes',
- static function() use ( $meta_box_renderer, $is_paypal_order_edit_page ) {
- if ( ! $is_paypal_order_edit_page ) {
+ function() use ( $meta_box_renderer, $bearer ) {
+ if ( ! $this->is_tracking_enabled( $bearer ) ) {
return;
}
- add_meta_box( 'ppcp_order-tracking', __( 'Tracking Information', 'woocommerce-paypal-payments' ), array( $meta_box_renderer, 'render' ), 'shop_order', 'side' );
+ /**
+ * Class and function exist in WooCommerce.
+ *
+ * @psalm-suppress UndefinedClass
+ * @psalm-suppress UndefinedFunction
+ */
+ $screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
+ ? wc_get_page_screen_id( 'shop-order' )
+ : 'shop_order';
+
+ add_meta_box(
+ 'ppcp_order-tracking',
+ __( 'PayPal Package Tracking', 'woocommerce-paypal-payments' ),
+ array( $meta_box_renderer, 'render' ),
+ $screen,
+ 'normal'
+ );
},
10,
2
);
-
- add_action(
- 'woocommerce_order_status_completed',
- static function( int $order_id ) use ( $endpoint, $logger ) {
- $tracking_information = $endpoint->get_tracking_information( $order_id );
-
- if ( $tracking_information ) {
- return;
- }
-
- $wc_order = wc_get_order( $order_id );
- if ( ! is_a( $wc_order, WC_Order::class ) ) {
- return;
- }
-
- $transaction_id = $wc_order->get_transaction_id();
- if ( empty( $transaction_id ) ) {
- return;
- }
-
- $tracking_data = array(
- 'transaction_id' => $transaction_id,
- 'status' => 'SHIPPED',
- );
-
- try {
- $endpoint->add_tracking_information( $tracking_data, $order_id );
- } catch ( Exception $exception ) {
- $logger->error( "Couldn't create tracking information: " . $exception->getMessage() );
- throw $exception;
- }
- }
- );
}
}
diff --git a/modules/ppcp-order-tracking/src/Shipment/Shipment.php b/modules/ppcp-order-tracking/src/Shipment/Shipment.php
new file mode 100644
index 000000000..155931d19
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Shipment/Shipment.php
@@ -0,0 +1,292 @@
+tracking_number = $tracking_number;
+ $this->status = $status;
+ $this->carrier = $carrier;
+ $this->carrier_name_other = $carrier_name_other;
+ $this->line_items = $line_items;
+ $this->transaction_id = $transaction_id;
+ $this->wc_order_id = $wc_order_id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function transaction_id(): string {
+ return $this->transaction_id;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function tracking_number(): string {
+ return $this->tracking_number;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function status(): string {
+ return $this->status;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function carrier(): string {
+ return $this->carrier;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function carrier_name_other(): string {
+ return $this->carrier_name_other;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function line_items(): array {
+ $wc_order = wc_get_order( $this->wc_order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return array();
+ }
+
+ $wc_order_items = $wc_order->get_items();
+ $tracking_meta = $wc_order->get_meta( OrderTrackingModule::PPCP_TRACKING_INFO_META_NAME );
+ $saved_line_items = $tracking_meta[ $this->tracking_number() ] ?? array();
+ $line_items = $this->line_items ?: $saved_line_items;
+
+ $tracking_items = array();
+ foreach ( $wc_order_items as $item ) {
+ assert( $item instanceof WC_Order_Item_Product );
+ if ( ! empty( $line_items ) && ! in_array( $item->get_id(), $line_items, true ) ) {
+ continue;
+ }
+
+ $product = $item->get_product();
+ if ( ! is_a( $product, WC_Product::class ) ) {
+ continue;
+ }
+
+ $currency = $wc_order->get_currency();
+ $quantity = (int) $item->get_quantity();
+ $price_without_tax = (float) $wc_order->get_item_subtotal( $item, false );
+ $price_without_tax_rounded = round( $price_without_tax, 2 );
+ $image = wp_get_attachment_image_src( (int) $product->get_image_id(), 'full' );
+
+ $ppcp_order_item = new Item(
+ mb_substr( $item->get_name(), 0, 127 ),
+ new Money( $price_without_tax_rounded, $currency ),
+ $quantity,
+ $this->prepare_description( $product->get_description() ),
+ null,
+ $product->get_sku(),
+ $product->is_virtual() ? Item::DIGITAL_GOODS : Item::PHYSICAL_GOODS,
+ $product->get_permalink(),
+ $image[0] ?? ''
+ );
+
+ $tracking_items[ $item->get_id() ] = $ppcp_order_item->to_array();
+ }
+
+ return $tracking_items;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public function render( array $allowed_statuses ): void {
+ $carrier = $this->carrier();
+ $tracking_number = $this->tracking_number();
+ $carrier_name_other = $this->carrier_name_other();
+ ?>
+
+
+
+
+
+
+
+
+ render_shipment_line_item_info(); ?>
+
+
+ $status ) : ?>
+ status() ); ?>>
+
+
+
+
+
+
+
+
+ $this->transaction_id(),
+ 'tracking_number' => $this->tracking_number(),
+ 'status' => $this->status(),
+ 'carrier' => $this->carrier(),
+ 'items' => array_values( $this->line_items() ),
+ );
+
+ if ( ! empty( $this->carrier_name_other() ) ) {
+ $shipment['carrier_name_other'] = $this->carrier_name_other();
+ }
+
+ return $shipment;
+ }
+
+ /**
+ * Cleanups the description and prepares it for sending to PayPal.
+ *
+ * @param string $description Item description.
+ * @return string
+ */
+ protected function prepare_description( string $description ): string {
+ $description = strip_shortcodes( wp_strip_all_tags( $description ) );
+ return substr( $description, 0, 127 ) ?: '';
+ }
+
+ /**
+ * Renders the shipment line items info.
+ *
+ * @return void
+ */
+ protected function render_shipment_line_item_info(): void {
+ $line_items = $this->line_items();
+ if ( empty( $line_items ) ) {
+ return;
+ }
+
+ $format = '%1$s %2$s
';
+ $order_items_info = array();
+
+ foreach ( $this->line_items() as $shipment_line_item ) {
+ $sku = $shipment_line_item['sku'] ?? '';
+ $name = $shipment_line_item['name'] ?? '';
+
+ $sku_markup = sprintf(
+ '#%1$s ',
+ esc_html( $sku )
+ );
+
+ $order_items_info_markup = sprintf(
+ '%1$s %2$s',
+ esc_html( $name ),
+ $sku ? $sku_markup : ''
+ );
+
+ $order_items_info[] = $order_items_info_markup;
+ }
+
+ printf(
+ // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
+ $format,
+ esc_html__( 'Shipped Products:', 'woocommerce-paypal-payments' ),
+ wp_kses_post( implode( ', ', $order_items_info ) )
+ );
+ }
+}
diff --git a/modules/ppcp-order-tracking/src/Shipment/ShipmentFactory.php b/modules/ppcp-order-tracking/src/Shipment/ShipmentFactory.php
new file mode 100644
index 000000000..cddae0b2b
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/Shipment/ShipmentFactory.php
@@ -0,0 +1,31 @@
+,
+ * carrier_name_other?: string,
+ * }
+ */
+interface ShipmentInterface {
+
+ /**
+ * The transaction ID.
+ *
+ * @return string
+ */
+ public function transaction_id(): string;
+
+ /**
+ * The tracking number.
+ *
+ * @return string
+ */
+ public function tracking_number(): string;
+
+ /**
+ * The shipment status.
+ *
+ * @return string
+ */
+ public function status(): string;
+
+ /**
+ * The shipment carrier.
+ *
+ * @return string
+ */
+ public function carrier(): string;
+
+ /**
+ * The shipment carrier name for "OTHER".
+ *
+ * @return string
+ */
+ public function carrier_name_other(): string;
+
+ /**
+ * The list of shipment line items.
+ *
+ * @return array> The map of shipment line item ID to line item map.
+ * @psalm-return array
+ */
+ public function line_items(): array;
+
+ /**
+ * Renders the shipment.
+ *
+ * @param string[] $allowed_statuses Allowed shipping statuses.
+ *
+ * @return void
+ */
+ public function render( array $allowed_statuses ): void;
+
+ /**
+ * Returns the object as array.
+ *
+ * @return array The map of shipment object.
+ * @psalm-return shipmentMap
+ */
+ public function to_array(): array;
+}
diff --git a/modules/ppcp-order-tracking/src/TrackingAvailabilityTrait.php b/modules/ppcp-order-tracking/src/TrackingAvailabilityTrait.php
new file mode 100644
index 000000000..b149259a3
--- /dev/null
+++ b/modules/ppcp-order-tracking/src/TrackingAvailabilityTrait.php
@@ -0,0 +1,51 @@
+get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY );
+ $is_captured = empty( $captured ) || wc_string_to_bool( $captured );
+ $is_paypal_order_edit_page = $order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) && ! empty( $order->get_transaction_id() );
+
+ try {
+ $token = $bearer->bearer();
+ return $is_paypal_order_edit_page
+ && $is_captured
+ && $token->is_tracking_available()
+ && apply_filters( 'woocommerce_paypal_payments_shipment_tracking_enabled', true );
+ } catch ( RuntimeException $exception ) {
+ return false;
+ }
+ }
+}
diff --git a/modules/ppcp-order-tracking/yarn.lock b/modules/ppcp-order-tracking/yarn.lock
index 54ef909ff..565a1b6b8 100644
--- a/modules/ppcp-order-tracking/yarn.lock
+++ b/modules/ppcp-order-tracking/yarn.lock
@@ -1242,12 +1242,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
-acorn@^8.5.0:
- version "8.7.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
- integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
-
-acorn@^8.7.1:
+acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -1785,9 +1780,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ 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"
@@ -1805,9 +1800,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
- integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ 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"
@@ -2263,10 +2258,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-saved-payment-checker/.gitignore b/modules/ppcp-saved-payment-checker/.gitignore
new file mode 100644
index 000000000..0bd2b9f58
--- /dev/null
+++ b/modules/ppcp-saved-payment-checker/.gitignore
@@ -0,0 +1,3 @@
+node_modules
+assets/js
+assets/css
diff --git a/modules/ppcp-saved-payment-checker/composer.json b/modules/ppcp-saved-payment-checker/composer.json
new file mode 100644
index 000000000..96c8931c3
--- /dev/null
+++ b/modules/ppcp-saved-payment-checker/composer.json
@@ -0,0 +1,17 @@
+{
+ "name": "woocommerce/ppcp-saved-payment-checker",
+ "type": "dhii-mod",
+ "description": "Saved payments checker module",
+ "license": "GPL-2.0",
+ "require": {
+ "php": "^7.2 | ^8.0",
+ "dhii/module-interface": "^0.3.0-alpha1"
+ },
+ "autoload": {
+ "psr-4": {
+ "WooCommerce\\PayPalCommerce\\SavedPaymentChecker\\": "src"
+ }
+ },
+ "minimum-stability": "dev",
+ "prefer-stable": true
+}
diff --git a/modules/ppcp-saved-payment-checker/extensions.php b/modules/ppcp-saved-payment-checker/extensions.php
new file mode 100644
index 000000000..dad4983b4
--- /dev/null
+++ b/modules/ppcp-saved-payment-checker/extensions.php
@@ -0,0 +1,56 @@
+ function ( ContainerInterface $container, array $fields ): array {
+ $subscription_helper = $container->get( 'subscription.helper' );
+ assert( $subscription_helper instanceof SubscriptionHelper );
+
+ $insert_after = function( array $array, string $key, array $new ): array {
+ $keys = array_keys( $array );
+ $index = array_search( $key, $keys, true );
+ $pos = false === $index ? count( $array ) : $index + 1;
+
+ return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
+ };
+
+ return $insert_after(
+ $fields,
+ 'vault_enabled',
+ array(
+ 'subscription_behavior_when_vault_fails' => array(
+ 'title' => __( 'Subscription capture behavior if Vault fails', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'classes' => $subscription_helper->plugin_is_active() ? array() : array( 'hide' ),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'void_auth',
+ 'desc_tip' => true,
+ 'description' => __( 'By default, subscription payments are captured only when saving the payment method was successful. Without a saved payment method, automatic renewal payments are not possible.', 'woocommerce-paypal-payments' ),
+ 'description_with_tip' => __( 'Determines whether authorized payments for subscription orders are captured or voided if there is no saved payment method. This only applies when the intent Capture is used for the subscription order.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'void_auth' => __( 'Void authorization & fail the order/subscription', 'woocommerce-paypal-payments' ),
+ 'capture_auth' => __( 'Capture authorized payment & set subscription to Manual Renewal', 'woocommerce-paypal-payments' ),
+ 'capture_auth_ignore' => __( 'Capture authorized payment & disregard missing payment method', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => array( 'paypal' ),
+ ),
+ )
+ );
+ },
+);
diff --git a/modules/ppcp-saved-payment-checker/module.php b/modules/ppcp-saved-payment-checker/module.php
new file mode 100644
index 000000000..9f5217ab8
--- /dev/null
+++ b/modules/ppcp-saved-payment-checker/module.php
@@ -0,0 +1,16 @@
+ function( ContainerInterface $container ) : PaymentTokenChecker {
+ return new PaymentTokenChecker(
+ $container->get( 'vaulting.repository.payment-token' ),
+ $container->get( 'api.repository.order' ),
+ $container->get( 'wcgateway.settings' ),
+ $container->get( 'wcgateway.processor.authorized-payments' ),
+ $container->get( 'api.endpoint.payments' ),
+ $container->get( 'api.endpoint.payment-token' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+);
diff --git a/modules/ppcp-vaulting/src/PaymentTokenChecker.php b/modules/ppcp-saved-payment-checker/src/PaymentTokenChecker.php
similarity index 75%
rename from modules/ppcp-vaulting/src/PaymentTokenChecker.php
rename to modules/ppcp-saved-payment-checker/src/PaymentTokenChecker.php
index cd57e9426..0da73b51b 100644
--- a/modules/ppcp-vaulting/src/PaymentTokenChecker.php
+++ b/modules/ppcp-saved-payment-checker/src/PaymentTokenChecker.php
@@ -7,15 +7,17 @@
declare(strict_types=1);
-namespace WooCommerce\PayPalCommerce\Vaulting;
+namespace WooCommerce\PayPalCommerce\SavedPaymentChecker;
use Exception;
use Psr\Log\LoggerInterface;
-use RuntimeException;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
+use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
@@ -67,6 +69,13 @@ class PaymentTokenChecker {
*/
protected $payments_endpoint;
+ /**
+ * The payment token endpoint.
+ *
+ * @var PaymentTokenEndpoint
+ */
+ protected $payment_token_endpoint;
+
/**
* The logger.
*
@@ -82,6 +91,7 @@ class PaymentTokenChecker {
* @param Settings $settings The settings.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payments processor.
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
+ * @param PaymentTokenEndpoint $payment_token_endpoint The payment token endpoint.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
@@ -90,6 +100,7 @@ class PaymentTokenChecker {
Settings $settings,
AuthorizedPaymentsProcessor $authorized_payments_processor,
PaymentsEndpoint $payments_endpoint,
+ PaymentTokenEndpoint $payment_token_endpoint,
LoggerInterface $logger
) {
$this->payment_token_repository = $payment_token_repository;
@@ -97,6 +108,7 @@ class PaymentTokenChecker {
$this->settings = $settings;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->payments_endpoint = $payments_endpoint;
+ $this->payment_token_endpoint = $payment_token_endpoint;
$this->logger = $logger;
}
@@ -130,7 +142,7 @@ class PaymentTokenChecker {
return;
}
- $tokens = $this->payment_token_repository->all_for_user_id( $customer_id );
+ $tokens = $this->tokens_for_user( $customer_id );
if ( $tokens ) {
try {
$this->capture_authorized_payment( $wc_order );
@@ -191,6 +203,32 @@ class PaymentTokenChecker {
}
}
+ /**
+ * Schedules the vaulted payment check.
+ *
+ * @param int $wc_order_id The WC order ID.
+ * @param int $customer_id The customer ID.
+ */
+ public function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void {
+ $timestamp = 3 * MINUTE_IN_SECONDS;
+ if (
+ $this->settings->has( 'subscription_behavior_when_vault_fails' )
+ && $this->settings->get( 'subscription_behavior_when_vault_fails' ) === 'capture_auth'
+ ) {
+ $timestamp = 0;
+ }
+
+ as_schedule_single_action(
+ time() + $timestamp,
+ 'woocommerce_paypal_payments_check_saved_payment',
+ array(
+ 'order_id' => $wc_order_id,
+ 'customer_id' => $customer_id,
+ 'intent' => $this->settings->has( 'intent' ) ? $this->settings->get( 'intent' ) : '',
+ )
+ );
+ }
+
/**
* Captures authorized payments for the given WC order.
*
@@ -215,11 +253,11 @@ class PaymentTokenChecker {
$wc_order->update_status( 'failed', $error_message );
/**
- * Function already exist in Subscription plugin
+ * Function already exist in WC Subscriptions plugin.
*
* @psalm-suppress UndefinedFunction
*/
- $subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id() );
+ $subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $wc_order->get_id() ) : array();
foreach ( $subscriptions as $key => $subscription ) {
if ( $subscription->get_parent_id() === $wc_order->get_id() ) {
try {
@@ -231,4 +269,32 @@ class PaymentTokenChecker {
}
}
}
+
+ /**
+ * Returns customer tokens either from guest or customer id.
+ *
+ * @param int $customer_id The customer id.
+ * @return PaymentToken[]
+ */
+ private function tokens_for_user( int $customer_id ): array {
+ $tokens = array();
+
+ $guest_customer_id = get_user_meta( $customer_id, 'ppcp_guest_customer_id', true );
+ if ( $guest_customer_id ) {
+ $tokens = $this->payment_token_endpoint->for_guest( $guest_customer_id );
+ }
+
+ if ( ! $tokens ) {
+ $guest_customer_id = get_user_meta( $customer_id, 'ppcp_customer_id', true );
+ if ( $guest_customer_id ) {
+ $tokens = $this->payment_token_endpoint->for_guest( $guest_customer_id );
+ }
+ }
+
+ if ( ! $tokens ) {
+ $tokens = $this->payment_token_repository->all_for_user_id( $customer_id );
+ }
+
+ return $tokens;
+ }
}
diff --git a/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php
new file mode 100644
index 000000000..f04143ebc
--- /dev/null
+++ b/modules/ppcp-saved-payment-checker/src/SavedPaymentCheckerModule.php
@@ -0,0 +1,142 @@
+get( 'subscription.helper' );
+ assert( $subscription_helper instanceof SubscriptionHelper );
+
+ if ( $subscription_helper->cart_contains_subscription() || $subscription_helper->current_product_is_subscription() ) {
+ return 'AUTHORIZE';
+ }
+
+ return $intent;
+ }
+ );
+
+ /**
+ * Schedules saved payment checker before payment success handler.
+ */
+ add_action(
+ 'woocommerce_paypal_payments_before_handle_payment_success',
+ function( WC_Order $wc_order ) use ( $c ) {
+ $subscription_helper = $c->get( 'subscription.helper' );
+ assert( $subscription_helper instanceof SubscriptionHelper );
+
+ if ( $subscription_helper->has_subscription( $wc_order->get_id() ) ) {
+ $payment_token_checker = $c->get( 'saved-payment-checker.payment-token-checker' );
+ assert( $payment_token_checker instanceof PaymentTokenChecker );
+
+ $payment_token_checker->schedule_saved_payment_check( $wc_order->get_id(), $wc_order->get_customer_id() );
+ }
+ }
+ );
+
+ /**
+ * Triggers a payment token check for the given order and customer id.
+ */
+ add_action(
+ 'woocommerce_paypal_payments_check_saved_payment',
+ function ( int $order_id, int $customer_id, string $intent ) use ( $c ) {
+ $payment_token_checker = $c->get( 'vaulting.payment-token-checker' );
+ assert( $payment_token_checker instanceof PaymentTokenChecker );
+
+ $payment_token_checker->check_and_update( $order_id, $customer_id, $intent );
+ },
+ 10,
+ 3
+ );
+
+ /**
+ * Adds email content for vaulting failure.
+ */
+ add_action(
+ 'woocommerce_email_before_order_table',
+ function( WC_Order $order ) use ( $c ) {
+ $subscription_helper = $c->get( 'subscription.helper' );
+ assert( $subscription_helper instanceof SubscriptionHelper );
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+
+ $vault_failed = $order->get_meta( PaymentTokenChecker::VAULTING_FAILED_META_KEY );
+ if ( $subscription_helper->has_subscription( $order->get_id() ) && ! empty( $vault_failed ) ) {
+ $logger->info( "Adding vaulting failure info to email for order #{$order->get_id()}." );
+
+ if ( $vault_failed === 'void_auth' ) {
+ echo wp_kses_post( '' . __( 'The subscription payment failed because the payment method could not be saved. Please try again with a different payment method.', 'woocommerce-paypal-payments' ) . '
' );
+ }
+
+ if ( $vault_failed === 'capture_auth' ) {
+ echo wp_kses_post( '' . __( 'The subscription has been activated, but the payment method could not be saved. Please contact the merchant to save a payment method for automatic subscription renewal payments.', 'woocommerce-paypal-payments' ) . '
' );
+ }
+ }
+ }
+ );
+
+ /**
+ * Adds email content for vaulting changing manual renewal order.
+ */
+ add_action(
+ 'woocommerce_email_after_order_table',
+ function( WC_Order $order ) use ( $c ) {
+ $subscription_helper = $c->get( 'subscription.helper' );
+ assert( $subscription_helper instanceof SubscriptionHelper );
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+
+ $vault_failed = $order->get_meta( PaymentTokenChecker::VAULTING_FAILED_META_KEY );
+ if ( $subscription_helper->has_subscription( $order->get_id() ) && ! empty( $vault_failed ) ) {
+ $logger->info( "Changing subscription auto-renewal status for order #{$order->get_id()}." );
+
+ if ( $vault_failed === 'capture_auth' ) {
+ $subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $order->get_id() ) : array();
+ foreach ( $subscriptions as $subscription ) {
+ $subscription->set_requires_manual_renewal( true );
+ $subscription->save();
+ }
+ }
+ }
+ }
+ );
+ }
+}
diff --git a/modules/ppcp-session/services.php b/modules/ppcp-session/services.php
index 0b36eb95b..0d9e25ccd 100644
--- a/modules/ppcp-session/services.php
+++ b/modules/ppcp-session/services.php
@@ -15,17 +15,7 @@ use WooCommerce\PayPalCommerce\Session\Cancellation\CancelView;
return array(
'session.handler' => function ( ContainerInterface $container ) : SessionHandler {
-
- if ( is_null( WC()->session ) ) {
- return new SessionHandler();
- }
- $result = WC()->session->get( SessionHandler::ID );
- if ( is_a( $result, SessionHandler::class ) ) {
- return $result;
- }
- $session_handler = new SessionHandler();
- WC()->session->set( SessionHandler::ID, $session_handler );
- return $session_handler;
+ return new SessionHandler();
},
'session.cancellation.view' => function ( ContainerInterface $container ) : CancelView {
return new CancelView(
diff --git a/modules/ppcp-session/src/Cancellation/CancelController.php b/modules/ppcp-session/src/Cancellation/CancelController.php
index 3c23983f5..a4723cd96 100644
--- a/modules/ppcp-session/src/Cancellation/CancelController.php
+++ b/modules/ppcp-session/src/Cancellation/CancelController.php
@@ -9,12 +9,16 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Session\Cancellation;
+use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
/**
* Class CancelController
*/
class CancelController {
+ use ContextTrait;
+
+ public const NONCE = 'ppcp-cancel';
/**
* The Session handler.
@@ -48,37 +52,27 @@ class CancelController {
/**
* Runs the controller.
*/
- public function run() {
- $param_name = 'ppcp-cancel';
- $nonce = 'ppcp-cancel-' . get_current_user_id();
+ public function run(): void {
+ $param_name = self::NONCE;
if ( isset( $_GET[ $param_name ] ) && // Input var ok.
wp_verify_nonce(
sanitize_text_field( wp_unslash( $_GET[ $param_name ] ) ), // Input var ok.
- $nonce
+ self::NONCE
)
) { // Input var ok.
$this->session_handler->destroy_session_data();
}
- $order = $this->session_handler->order();
- if ( ! $order ) {
+ if ( ! $this->is_paypal_continuation() ) {
return;
}
- $source = $order->payment_source();
- if ( $source && $source->card() ) {
- return; // Ignore for DCC.
- }
-
- if ( 'card' === $this->session_handler->funding_source() ) {
- return; // Ignore for card buttons.
- }
-
- $url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() );
+ $url = add_query_arg( array( $param_name => wp_create_nonce( self::NONCE ) ), wc_get_checkout_url() );
add_action(
'woocommerce_review_order_after_submit',
function () use ( $url ) {
- $this->view->render_session_cancellation( $url, $this->session_handler->funding_source() );
+ // phpcs:ignore WordPress.Security.EscapeOutput
+ echo $this->view->render_session_cancellation( $url, $this->session_handler->funding_source() );
}
);
}
diff --git a/modules/ppcp-session/src/Cancellation/CancelView.php b/modules/ppcp-session/src/Cancellation/CancelView.php
index cdaf9b41e..260bfcace 100644
--- a/modules/ppcp-session/src/Cancellation/CancelView.php
+++ b/modules/ppcp-session/src/Cancellation/CancelView.php
@@ -50,7 +50,8 @@ class CancelView {
* @param string $url The URL.
* @param string|null $funding_source The ID of the funding source, such as 'venmo'.
*/
- public function render_session_cancellation( string $url, ?string $funding_source ) {
+ public function render_session_cancellation( string $url, ?string $funding_source ): string {
+ ob_start();
?>
',
'
',
- esc_html( $name )
+ esc_html( $name ),
+ ' '
);
?>
session->get_session).
+ *
+ * @var array
+ */
+ private static $data;
+
+ /**
+ * The customer ID.
+ *
+ * @var string|int
+ */
+ private static $customer_id;
+
+ /**
+ * Enqueues this session handler with the given data to be used by WC.
+ *
+ * @param array $session_data The session data (from WC()->session->get_session).
+ * @param int|string $customer_id The customer ID.
+ */
+ public static function replace_session_handler( array $session_data, $customer_id ): void {
+ self::$data = $session_data;
+ self::$customer_id = $customer_id;
+
+ add_filter(
+ 'woocommerce_session_handler',
+ function () {
+ return MemoryWcSession::class;
+ }
+ );
+ }
+
+ /**
+ * @inerhitDoc
+ */
+ public function init_session_cookie() {
+ $this->_customer_id = self::$customer_id;
+ $this->_data = self::$data;
+ }
+
+ /**
+ * @inerhitDoc
+ */
+ public function get_session_data() {
+ return self::$data;
+ }
+
+ /**
+ * @inerhitDoc
+ */
+ public function forget_session() {
+ self::$data = array();
+
+ parent::forget_session();
+ }
+}
diff --git a/modules/ppcp-session/src/SessionHandler.php b/modules/ppcp-session/src/SessionHandler.php
index 7d3f7546b..d85c40b65 100644
--- a/modules/ppcp-session/src/SessionHandler.php
+++ b/modules/ppcp-session/src/SessionHandler.php
@@ -16,7 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
*/
class SessionHandler {
- const ID = 'ppcp';
+ private const SESSION_KEY = 'ppcp';
/**
* The Order.
@@ -33,7 +33,7 @@ class SessionHandler {
private $bn_code = '';
/**
- * If PayPal respondes with INSTRUMENT_DECLINED, we only
+ * If PayPal responds with INSTRUMENT_DECLINED, we only
* want to go max. three times through the process of trying again.
*
* @var int
@@ -47,12 +47,23 @@ class SessionHandler {
*/
private $funding_source = null;
+ /**
+ * The checkout form data.
+ *
+ * @var array
+ */
+ private $checkout_form = array();
+
/**
* Returns the order.
*
* @return Order|null
*/
public function order() {
+ $this->load_session();
+
+ do_action( 'ppcp_session_get_order', $this->order, $this );
+
return $this->order;
}
@@ -60,13 +71,37 @@ class SessionHandler {
* Replaces the current order.
*
* @param Order $order The new order.
- *
- * @return SessionHandler
*/
- public function replace_order( Order $order ) : SessionHandler {
+ public function replace_order( Order $order ): void {
+ $this->load_session();
+
$this->order = $order;
+
+ $this->store_session();
+ }
+
+ /**
+ * Returns the checkout form data.
+ *
+ * @return array
+ */
+ public function checkout_form(): array {
+ $this->load_session();
+
+ return $this->checkout_form;
+ }
+
+ /**
+ * Replaces the checkout form data.
+ *
+ * @param array $checkout_form The checkout form data.
+ */
+ public function replace_checkout_form( array $checkout_form ): void {
+ $this->load_session();
+
+ $this->checkout_form = $checkout_form;
+
$this->store_session();
- return $this;
}
/**
@@ -75,6 +110,8 @@ class SessionHandler {
* @return string
*/
public function bn_code() : string {
+ $this->load_session();
+
return $this->bn_code;
}
@@ -82,13 +119,13 @@ class SessionHandler {
* Replaces the BN Code.
*
* @param string $bn_code The new BN Code.
- *
- * @return SessionHandler
*/
- public function replace_bn_code( string $bn_code ) : SessionHandler {
+ public function replace_bn_code( string $bn_code ) : void {
+ $this->load_session();
+
$this->bn_code = $bn_code;
+
$this->store_session();
- return $this;
}
/**
@@ -97,6 +134,8 @@ class SessionHandler {
* @return string|null
*/
public function funding_source(): ?string {
+ $this->load_session();
+
return $this->funding_source;
}
@@ -104,13 +143,13 @@ class SessionHandler {
* Replaces the funding source of the current checkout.
*
* @param string|null $funding_source The funding source.
- *
- * @return SessionHandler
*/
- public function replace_funding_source( ?string $funding_source ): SessionHandler {
+ public function replace_funding_source( ?string $funding_source ): void {
+ $this->load_session();
+
$this->funding_source = $funding_source;
+
$this->store_session();
- return $this;
}
/**
@@ -119,18 +158,20 @@ class SessionHandler {
* @return int
*/
public function insufficient_funding_tries() : int {
+ $this->load_session();
+
return $this->insufficient_funding_tries;
}
/**
* Increments the number of tries, the customer has done in this session.
- *
- * @return SessionHandler
*/
- public function increment_insufficient_funding_tries() : SessionHandler {
+ public function increment_insufficient_funding_tries(): void {
+ $this->load_session();
+
$this->insufficient_funding_tries++;
+
$this->store_session();
- return $this;
}
/**
@@ -143,14 +184,60 @@ class SessionHandler {
$this->bn_code = '';
$this->insufficient_funding_tries = 0;
$this->funding_source = null;
+ $this->checkout_form = array();
$this->store_session();
return $this;
}
/**
- * Stores the session.
+ * Stores the data into the WC session.
*/
- private function store_session() {
- WC()->session->set( self::ID, $this );
+ private function store_session(): void {
+ WC()->session->set( self::SESSION_KEY, self::make_array( $this ) );
+ }
+
+ /**
+ * Loads the data from the session.
+ */
+ private function load_session(): void {
+ if ( isset( WC()->session ) ) {
+ $data = WC()->session->get( self::SESSION_KEY );
+ } else {
+ $data = array();
+ }
+
+ if ( $data instanceof SessionHandler ) {
+ $data = self::make_array( $data );
+ } elseif ( ! is_array( $data ) ) {
+ $data = array();
+ }
+
+ $this->order = $data['order'] ?? null;
+ if ( ! $this->order instanceof Order ) {
+ $this->order = null;
+ }
+ $this->bn_code = (string) ( $data['bn_code'] ?? '' );
+ $this->insufficient_funding_tries = (int) ( $data['insufficient_funding_tries'] ?? '' );
+ $this->funding_source = $data['funding_source'] ?? null;
+ if ( ! is_string( $this->funding_source ) ) {
+ $this->funding_source = null;
+ }
+ $this->checkout_form = $data['checkout_form'] ?? array();
+ }
+
+ /**
+ * Converts given SessionHandler object into an array.
+ *
+ * @param SessionHandler $obj The object to convert.
+ * @return array
+ */
+ private static function make_array( SessionHandler $obj ): array {
+ return array(
+ 'order' => $obj->order,
+ 'bn_code' => $obj->bn_code,
+ 'insufficient_funding_tries' => $obj->insufficient_funding_tries,
+ 'funding_source' => $obj->funding_source,
+ 'checkout_form' => $obj->checkout_form,
+ );
}
}
diff --git a/modules/ppcp-session/src/SessionModule.php b/modules/ppcp-session/src/SessionModule.php
index a27effb47..cecc1a98f 100644
--- a/modules/ppcp-session/src/SessionModule.php
+++ b/modules/ppcp-session/src/SessionModule.php
@@ -9,6 +9,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Session;
+use Psr\Log\LoggerInterface;
+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;
@@ -19,6 +24,12 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
* Class SessionModule
*/
class SessionModule implements ModuleInterface {
+ /**
+ * A flag to avoid multiple requests to reload order.
+ *
+ * @var bool
+ */
+ private $reloaded_order = false;
/**
* {@inheritDoc}
@@ -46,6 +57,45 @@ class SessionModule implements ModuleInterface {
$controller->run();
}
);
+
+ add_action(
+ 'ppcp_session_get_order',
+ function ( ?Order $order, SessionHandler $session_handler ) use ( $c ): void {
+ if ( ! isset( WC()->session ) ) {
+ return;
+ }
+
+ if ( $this->reloaded_order ) {
+ return;
+ }
+
+ if ( ! $order ) {
+ return;
+ }
+
+ if ( $order->status()->is( OrderStatus::APPROVED )
+ || $order->status()->is( OrderStatus::COMPLETED )
+ ) {
+ return;
+ }
+
+ $order_endpoint = $c->get( 'api.endpoint.order' );
+ assert( $order_endpoint instanceof OrderEndpoint );
+
+ $this->reloaded_order = true;
+
+ try {
+ $session_handler->replace_order( $order_endpoint->order( $order->id() ) );
+ } catch ( Throwable $exception ) {
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+
+ $logger->warning( 'Failed to reload PayPal order in the session: ' . $exception->getMessage() );
+ }
+ },
+ 10,
+ 2
+ );
}
/**
diff --git a/modules/ppcp-status-report/src/StatusReportModule.php b/modules/ppcp-status-report/src/StatusReportModule.php
index 3e380b069..4c2d3c8ae 100644
--- a/modules/ppcp-status-report/src/StatusReportModule.php
+++ b/modules/ppcp-status-report/src/StatusReportModule.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\StatusReport;
+use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
@@ -20,7 +21,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Compat\PPEC\PPECHelper;
use WooCommerce\PayPalCommerce\Onboarding\State;
-use WooCommerce\PayPalCommerce\Webhooks\WebhookInfoStorage;
+use WooCommerce\PayPalCommerce\Webhooks\WebhookEventStorage;
/**
* Class StatusReportModule
@@ -49,6 +50,8 @@ class StatusReportModule implements ModuleInterface {
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof ContainerInterface );
+ $subscriptions_mode_settings = $c->get( 'wcgateway.settings.fields.subscriptions_mode' ) ?: array();
+
/* @var State $state The state. */
$state = $c->get( 'onboarding.state' );
@@ -61,8 +64,11 @@ class StatusReportModule implements ModuleInterface {
/* @var MessagesApply $messages_apply The messages apply. */
$messages_apply = $c->get( 'button.helper.messages-apply' );
+ /* @var SubscriptionHelper $subscription_helper The subscription helper class. */
+ $subscription_helper = $c->get( 'subscription.helper' );
+
$last_webhook_storage = $c->get( 'webhook.last-webhook-storage' );
- assert( $last_webhook_storage instanceof WebhookInfoStorage );
+ assert( $last_webhook_storage instanceof WebhookEventStorage );
$billing_agreements_endpoint = $c->get( 'api.endpoint.billing-agreements' );
assert( $billing_agreements_endpoint instanceof BillingAgreementsEndpoint );
@@ -72,8 +78,6 @@ class StatusReportModule implements ModuleInterface {
$had_ppec_plugin = PPECHelper::is_plugin_configured();
- $is_tracking_available = $c->get( 'order-tracking.is-tracking-available' );
-
$items = array(
array(
'label' => esc_html__( 'Onboarded', 'woocommerce-paypal-payments' ),
@@ -120,11 +124,19 @@ class StatusReportModule implements ModuleInterface {
'value' => $this->bool_to_html( ! $last_webhook_storage->is_empty() ),
),
array(
- 'label' => esc_html__( 'Vault enabled', 'woocommerce-paypal-payments' ),
- 'exported_label' => 'Vault enabled',
- 'description' => esc_html__( 'Whether vaulting is enabled on PayPal account or not.', 'woocommerce-paypal-payments' ),
+ 'label' => esc_html__( 'PayPal Vault enabled', 'woocommerce-paypal-payments' ),
+ 'exported_label' => 'PayPal Vault enabled',
+ 'description' => esc_html__( 'Whether vaulting option is enabled on Standard Payments settings or not.', 'woocommerce-paypal-payments' ),
'value' => $this->bool_to_html(
- $this->vault_enabled( $bearer )
+ $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' )
+ ),
+ ),
+ array(
+ 'label' => esc_html__( 'ACDC Vault enabled', 'woocommerce-paypal-payments' ),
+ 'exported_label' => 'ACDC Vault enabled',
+ 'description' => esc_html__( 'Whether vaulting option is enabled on Advanced Card Processing settings or not.', 'woocommerce-paypal-payments' ),
+ 'value' => $this->bool_to_html(
+ $settings->has( 'vault_enabled_dcc' ) && $settings->get( 'vault_enabled_dcc' )
),
),
array(
@@ -152,10 +164,14 @@ class StatusReportModule implements ModuleInterface {
),
),
array(
- 'label' => esc_html__( 'Tracking enabled', 'woocommerce-paypal-payments' ),
- 'exported_label' => 'Tracking enabled',
- 'description' => esc_html__( 'Whether tracking is enabled on PayPal account or not.', 'woocommerce-paypal-payments' ),
- 'value' => $this->bool_to_html( $is_tracking_available ),
+ 'label' => esc_html__( 'Subscriptions Mode', 'woocommerce-paypal-payments' ),
+ 'exported_label' => 'Subscriptions Mode',
+ 'description' => esc_html__( 'Whether subscriptions are active and their mode.', 'woocommerce-paypal-payments' ),
+ 'value' => $this->subscriptions_mode_text(
+ $subscription_helper->plugin_is_active(),
+ $settings->has( 'subscriptions_mode' ) ? (string) $settings->get( 'subscriptions_mode' ) : '',
+ $subscriptions_mode_settings
+ ),
),
);
@@ -193,18 +209,24 @@ class StatusReportModule implements ModuleInterface {
}
/**
- * It returns whether vaulting is enabled or not.
+ * Returns the text associated with the subscriptions mode status.
*
- * @param Bearer $bearer The bearer.
- * @return bool
+ * @param bool $is_plugin_active Indicates if the WooCommerce Subscriptions plugin is active.
+ * @param string $subscriptions_mode The subscriptions mode stored in settings.
+ * @param array $field_settings The subscriptions mode field settings.
+ * @return string
*/
- private function vault_enabled( Bearer $bearer ): bool {
- try {
- $token = $bearer->bearer();
- return $token->vaulting_available();
- } catch ( RuntimeException $exception ) {
- return false;
+ private function subscriptions_mode_text( bool $is_plugin_active, string $subscriptions_mode, array $field_settings ): string {
+ if ( ! $is_plugin_active || ! $field_settings || $subscriptions_mode === 'disable_paypal_subscriptions' ) {
+ return 'Disabled';
}
+
+ if ( ! $subscriptions_mode ) {
+ $subscriptions_mode = $field_settings['default'] ?? '';
+ }
+
+ // Return the options value or if it's missing from options the settings value.
+ return $field_settings['options'][ $subscriptions_mode ] ?? $subscriptions_mode;
}
/**
diff --git a/modules/ppcp-vaulting/.babelrc b/modules/ppcp-subscription/.babelrc
similarity index 100%
rename from modules/ppcp-vaulting/.babelrc
rename to modules/ppcp-subscription/.babelrc
diff --git a/modules/ppcp-vaulting/.gitignore b/modules/ppcp-subscription/.gitignore
similarity index 61%
rename from modules/ppcp-vaulting/.gitignore
rename to modules/ppcp-subscription/.gitignore
index f69a89f4b..265c2208c 100644
--- a/modules/ppcp-vaulting/.gitignore
+++ b/modules/ppcp-subscription/.gitignore
@@ -1,2 +1,2 @@
node_modules
-/assets
+assets
diff --git a/modules/ppcp-vaulting/package.json b/modules/ppcp-subscription/package.json
similarity index 88%
rename from modules/ppcp-vaulting/package.json
rename to modules/ppcp-subscription/package.json
index c9954a8bb..9fd2f9305 100644
--- a/modules/ppcp-vaulting/package.json
+++ b/modules/ppcp-subscription/package.json
@@ -1,8 +1,7 @@
{
- "name": "ppcp-vaulting",
+ "name": "ppcp-subscription",
"version": "1.0.0",
"license": "GPL-3.0-or-later",
- "main": "resources/js/myaccount-payments.js",
"browserslist": [
"> 0.5%",
"Safari >= 8",
@@ -21,7 +20,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-subscription/resources/js/paypal-subscription.js b/modules/ppcp-subscription/resources/js/paypal-subscription.js
new file mode 100644
index 000000000..7d848091f
--- /dev/null
+++ b/modules/ppcp-subscription/resources/js/paypal-subscription.js
@@ -0,0 +1,102 @@
+document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+ const variations = document.querySelector('.woocommerce_variations');
+ const disableFields = (productId) => {
+ if(variations) {
+ const children = variations.children;
+ for(let i=0; i < children.length; i++) {
+ const variableId = children[i].querySelector('h3').getElementsByClassName('variable_post_id')[0].value
+ if (parseInt(variableId) === productId) {
+ children[i].querySelector('.woocommerce_variable_attributes')
+ .getElementsByClassName('wc_input_subscription_period_interval')[0]
+ .setAttribute('disabled', 'disabled');
+ children[i].querySelector('.woocommerce_variable_attributes')
+ .getElementsByClassName('wc_input_subscription_period')[0]
+ .setAttribute('disabled', 'disabled');
+ children[i].querySelector('.woocommerce_variable_attributes')
+ .getElementsByClassName('wc_input_subscription_trial_length')[0]
+ .setAttribute('disabled', 'disabled');
+ children[i].querySelector('.woocommerce_variable_attributes')
+ .getElementsByClassName('wc_input_subscription_trial_period')[0]
+ .setAttribute('disabled', 'disabled');
+ children[i].querySelector('.woocommerce_variable_attributes')
+ .getElementsByClassName('wc_input_subscription_length')[0]
+ .setAttribute('disabled', 'disabled');
+ }
+ }
+ }
+
+ const periodInterval = document.querySelector('#_subscription_period_interval');
+ periodInterval.setAttribute('disabled', 'disabled');
+
+ const subscriptionPeriod = document.querySelector('#_subscription_period');
+ subscriptionPeriod.setAttribute('disabled', 'disabled');
+
+ const subscriptionLength = document.querySelector('._subscription_length_field');
+ subscriptionLength.style.display = 'none';
+
+ const subscriptionTrial = document.querySelector('._subscription_trial_length_field');
+ subscriptionTrial.style.display = 'none';
+ }
+
+ const setupProducts = () => {
+ PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach((product) => {
+ if(product.product_connected === 'yes') {
+ disableFields(product.product_id);
+ }
+
+ const unlinkBtn = document.getElementById(`ppcp-unlink-sub-plan-${product.product_id}`);
+ unlinkBtn?.addEventListener('click', (event)=>{
+ event.preventDefault();
+ unlinkBtn.disabled = true;
+ const spinner = document.getElementById('spinner-unlink-plan');
+ spinner.style.display = 'inline-block';
+
+ fetch(product.ajax.deactivate_plan.endpoint, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json'
+ },
+ credentials: 'same-origin',
+ body: JSON.stringify({
+ nonce: product.ajax.deactivate_plan.nonce,
+ plan_id: product.plan_id,
+ product_id: product.product_id
+ })
+ }).then(function (res) {
+ return res.json();
+ }).then(function (data) {
+ if (!data.success) {
+ unlinkBtn.disabled = false;
+ spinner.style.display = 'none';
+ console.error(data);
+ throw Error(data.data.message);
+ }
+
+ const enableSubscription = document.getElementById('ppcp-enable-subscription');
+ const product = document.getElementById('pcpp-product');
+ const plan = document.getElementById('pcpp-plan');
+ enableSubscription.style.display = 'none';
+ product.style.display = 'none';
+ plan.style.display = 'none';
+
+ const enable_subscription_product = document.getElementById('ppcp_enable_subscription_product');
+ enable_subscription_product.disabled = true;
+
+ const planUnlinked = document.getElementById('pcpp-plan-unlinked');
+ planUnlinked.style.display = 'block';
+
+ setTimeout(() => {
+ location.reload();
+ }, 1000)
+ });
+ });
+ })
+ }
+
+ setupProducts();
+ jQuery( '#woocommerce-product-data' ).on('woocommerce_variations_loaded', () => {
+ setupProducts();
+ });
+ });
diff --git a/modules/ppcp-subscription/services.php b/modules/ppcp-subscription/services.php
index a24002869..8ddad1fe6 100644
--- a/modules/ppcp-subscription/services.php
+++ b/modules/ppcp-subscription/services.php
@@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
return array(
'subscription.helper' => static function ( ContainerInterface $container ): SubscriptionHelper {
- return new SubscriptionHelper();
+ return new SubscriptionHelper( $container->get( 'wcgateway.settings' ) );
},
'subscription.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
$logger = $container->get( 'woocommerce.logger.woocommerce' );
@@ -43,4 +43,32 @@ return array(
$endpoint = $container->get( 'api.endpoint.payment-token' );
return new PaymentTokenRepository( $factory, $endpoint );
},
+ 'subscription.api-handler' => static function( ContainerInterface $container ): SubscriptionsApiHandler {
+ return new SubscriptionsApiHandler(
+ $container->get( 'api.endpoint.catalog-products' ),
+ $container->get( 'api.factory.product' ),
+ $container->get( 'api.endpoint.billing-plans' ),
+ $container->get( 'api.factory.billing-cycle' ),
+ $container->get( 'api.factory.payment-preferences' ),
+ $container->get( 'api.shop.currency' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
+ 'subscription.module.url' => static function ( ContainerInterface $container ): string {
+ /**
+ * The path cannot be false.
+ *
+ * @psalm-suppress PossiblyFalseArgument
+ */
+ return plugins_url(
+ '/modules/ppcp-subscription/',
+ dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
+ );
+ },
+ 'subscription.deactivate-plan-endpoint' => static function ( ContainerInterface $container ): DeactivatePlanEndpoint {
+ return new DeactivatePlanEndpoint(
+ $container->get( 'button.request-data' ),
+ $container->get( 'api.endpoint.billing-plans' )
+ );
+ },
);
diff --git a/modules/ppcp-subscription/src/DeactivatePlanEndpoint.php b/modules/ppcp-subscription/src/DeactivatePlanEndpoint.php
new file mode 100644
index 000000000..fe10c0638
--- /dev/null
+++ b/modules/ppcp-subscription/src/DeactivatePlanEndpoint.php
@@ -0,0 +1,86 @@
+request_data = $request_data;
+ $this->billing_plans = $billing_plans;
+ }
+
+ /**
+ * Handles the request.
+ *
+ * @return void
+ */
+ public function handle_request(): void {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return;
+ }
+
+ try {
+ $data = $this->request_data->read_request( self::ENDPOINT );
+
+ $plan_id = $data['plan_id'] ?? '';
+ if ( $plan_id ) {
+ $this->billing_plans->deactivate_plan( $plan_id );
+ }
+
+ $product_id = $data['product_id'] ?? '';
+ if ( $product_id ) {
+ $product = wc_get_product( $product_id );
+ if ( is_a( $product, WC_Product::class ) && WC_Subscriptions_Product::is_subscription( $product ) ) {
+ $product->delete_meta_data( '_ppcp_enable_subscription_product' );
+ $product->delete_meta_data( '_ppcp_subscription_plan_name' );
+ $product->delete_meta_data( 'ppcp_subscription_product' );
+ $product->delete_meta_data( 'ppcp_subscription_plan' );
+ $product->save();
+ }
+ }
+
+ wp_send_json_success();
+ } catch ( Exception $error ) {
+ wp_send_json_error();
+ }
+ }
+}
diff --git a/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php b/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php
index 8e309f4ce..d528ff554 100644
--- a/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php
+++ b/modules/ppcp-subscription/src/FreeTrialHandlerTrait.php
@@ -10,8 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription;
use WC_Order;
-use WC_Product;
-use WC_Subscription;
use WC_Subscriptions_Product;
use WC_Subscriptions_Synchroniser;
@@ -58,7 +56,10 @@ trait FreeTrialHandlerTrait {
$product = wc_get_product();
- if ( ! $product || ! WC_Subscriptions_Product::is_subscription( $product ) ) {
+ if (
+ ! $product || ! WC_Subscriptions_Product::is_subscription( $product )
+ || $product->get_meta( '_ppcp_enable_subscription_product' ) === 'yes'
+ ) {
return false;
}
diff --git a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php
index cb4521f37..9f803c5c5 100644
--- a/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php
+++ b/modules/ppcp-subscription/src/Helper/SubscriptionHelper.php
@@ -12,13 +12,33 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription\Helper;
use WC_Product;
+use WC_Product_Subscription_Variation;
+use WC_Subscriptions;
use WC_Subscriptions_Product;
+use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
/**
* Class SubscriptionHelper
*/
class SubscriptionHelper {
+ /**
+ * The settings.
+ *
+ * @var Settings
+ */
+ private $settings;
+
+ /**
+ * SubscriptionHelper constructor.
+ *
+ * @param Settings $settings The settings.
+ */
+ public function __construct( Settings $settings ) {
+ $this->settings = $settings;
+ }
+
/**
* Whether the current product is a subscription.
*
@@ -50,7 +70,7 @@ class SubscriptionHelper {
if ( ! isset( $item['data'] ) || ! is_a( $item['data'], WC_Product::class ) ) {
continue;
}
- if ( $item['data']->is_type( 'subscription' ) || $item['data']->is_type( 'subscription_variation' ) ) {
+ if ( WC_Subscriptions_Product::is_subscription( $item['data'] ) ) {
return true;
}
}
@@ -101,7 +121,7 @@ class SubscriptionHelper {
*/
public function plugin_is_active(): bool {
- return class_exists( \WC_Subscriptions::class );
+ return class_exists( WC_Subscriptions::class ) && class_exists( WC_Subscriptions_Product::class );
}
/**
@@ -127,4 +147,128 @@ class SubscriptionHelper {
return true;
}
+
+ /**
+ * Checks whether subscription needs subscription intent.
+ *
+ * @param string $subscription_mode The subscription mode.
+ * @return bool
+ */
+ public function need_subscription_intent( string $subscription_mode ): bool {
+ if ( $subscription_mode === 'subscriptions_api' ) {
+ if (
+ $this->current_product_is_subscription()
+ || ( ( is_cart() || is_checkout() ) && $this->cart_contains_subscription() )
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Checks if subscription product is allowed.
+ *
+ * @return bool
+ * @throws NotFoundException If setting is not found.
+ */
+ public function checkout_subscription_product_allowed(): bool {
+ if (
+ ! $this->paypal_subscription_id()
+ || ! $this->cart_contains_only_one_item()
+ ) {
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Returns PayPal subscription plan id from WC subscription product.
+ *
+ * @return string
+ */
+ public function paypal_subscription_id(): string {
+ if ( $this->current_product_is_subscription() ) {
+ $product = wc_get_product();
+ assert( $product instanceof WC_Product );
+
+ if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
+ return $product->get_meta( 'ppcp_subscription_plan' )['id'];
+ }
+ }
+
+ $cart = WC()->cart ?? null;
+ if ( ! $cart || $cart->is_empty() ) {
+ return '';
+ }
+ $items = $cart->get_cart_contents();
+ foreach ( $items as $item ) {
+ $product = wc_get_product( $item['product_id'] );
+ assert( $product instanceof WC_Product );
+
+ if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
+ return $product->get_meta( 'ppcp_subscription_plan' )['id'];
+ }
+ }
+
+ return '';
+ }
+
+ /**
+ * Returns variations for variable PayPal subscription product.
+ *
+ * @return array
+ */
+ public function variable_paypal_subscription_variations(): array {
+ $variations = array();
+ if ( ! $this->current_product_is_subscription() ) {
+ return $variations;
+ }
+
+ $product = wc_get_product();
+ assert( $product instanceof WC_Product );
+ if ( $product->get_type() !== 'variable-subscription' ) {
+ return $variations;
+ }
+
+ $variation_ids = $product->get_children();
+ foreach ( $variation_ids as $id ) {
+ $product = wc_get_product( $id );
+ if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
+ continue;
+ }
+
+ $subscription_plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
+ $variations[] = array(
+ 'id' => $product->get_id(),
+ 'attributes' => $product->get_attributes(),
+ 'subscription_plan' => $subscription_plan['id'] ?? '',
+ );
+ }
+
+ return $variations;
+ }
+
+ /**
+ * Checks if cart contains only one item.
+ *
+ * @return bool
+ */
+ public function cart_contains_only_one_item(): bool {
+ if ( ! $this->plugin_is_active() ) {
+ return false;
+ }
+ $cart = WC()->cart;
+ if ( ! $cart || $cart->is_empty() ) {
+ return false;
+ }
+
+ if ( count( $cart->get_cart() ) > 1 ) {
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/modules/ppcp-subscription/src/RenewalHandler.php b/modules/ppcp-subscription/src/RenewalHandler.php
index 851ea5f7d..f4a9a6cbd 100644
--- a/modules/ppcp-subscription/src/RenewalHandler.php
+++ b/modules/ppcp-subscription/src/RenewalHandler.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription;
+use WC_Subscription;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
@@ -139,8 +140,16 @@ class RenewalHandler {
*
* @param \WC_Order $wc_order The WooCommerce order.
*/
- public function renew( \WC_Order $wc_order ) {
+ public function renew( \WC_Order $wc_order ): void {
try {
+ $subscription = wcs_get_subscription( $wc_order->get_id() );
+ if ( is_a( $subscription, WC_Subscription::class ) ) {
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id ) {
+ return;
+ }
+ }
+
$this->process_order( $wc_order );
} catch ( \Exception $exception ) {
$error = $exception->getMessage();
@@ -162,13 +171,6 @@ class RenewalHandler {
return;
}
-
- $this->logger->info(
- sprintf(
- 'Renewal for order %d is completed.',
- $wc_order->get_id()
- )
- );
}
/**
@@ -186,6 +188,7 @@ class RenewalHandler {
if ( ! $token ) {
return;
}
+
$purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
$payer = $this->payer_factory->from_customer( $customer );
$shipping_preference = $this->shipping_preference_factory->from_state(
@@ -210,6 +213,12 @@ class RenewalHandler {
$transaction_id = $this->get_paypal_order_transaction_id( $order );
if ( $transaction_id ) {
$this->update_transaction_id( $transaction_id, $wc_order );
+
+ $subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id(), array( 'order_type' => 'any' ) );
+ foreach ( $subscriptions as $id => $subscription ) {
+ $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
+ $subscription->save();
+ }
}
$this->handle_new_order_status( $order, $wc_order );
@@ -217,6 +226,13 @@ class RenewalHandler {
if ( $this->capture_authorized_downloads( $order ) ) {
$this->authorized_payments_processor->capture_authorized_payment( $wc_order );
}
+
+ $this->logger->info(
+ sprintf(
+ 'Renewal for order %d is completed.',
+ $wc_order->get_id()
+ )
+ );
}
/**
@@ -225,7 +241,7 @@ class RenewalHandler {
* @param \WC_Customer $customer The customer.
* @param \WC_Order $wc_order The current WooCommerce order we want to process.
*
- * @return PaymentToken|null
+ * @return PaymentToken|null|false
*/
private function get_token_for_customer( \WC_Customer $customer, \WC_Order $wc_order ) {
/**
@@ -254,8 +270,7 @@ class RenewalHandler {
$subscription = function_exists( 'wcs_get_subscription' ) ? wcs_get_subscription( $wc_order->get_meta( '_subscription_renewal' ) ) : null;
if ( $subscription ) {
- $subscription_id = $subscription->get_id();
- $token_id = get_post_meta( $subscription_id, 'payment_token_id', true );
+ $token_id = $subscription->get_meta( 'payment_token_id' );
if ( $token_id ) {
foreach ( $tokens as $token ) {
if ( $token_id === $token->id() ) {
diff --git a/modules/ppcp-subscription/src/SubscriptionModule.php b/modules/ppcp-subscription/src/SubscriptionModule.php
index ae21ad42a..88f5596a3 100644
--- a/modules/ppcp-subscription/src/SubscriptionModule.php
+++ b/modules/ppcp-subscription/src/SubscriptionModule.php
@@ -9,6 +9,18 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Subscription;
+use ActionScheduler_Store;
+use Exception;
+use WC_Product;
+use WC_Product_Subscription;
+use WC_Product_Subscription_Variation;
+use WC_Product_Variable;
+use WC_Product_Variable_Subscription;
+use WC_Subscriptions_Product;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
+use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
+use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use Psr\Log\LoggerInterface;
@@ -21,14 +33,18 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
+use WP_Post;
/**
* Class SubscriptionModule
*/
class SubscriptionModule implements ModuleInterface {
+ use TransactionIdHandlingTrait;
+
/**
* {@inheritDoc}
*/
@@ -64,10 +80,28 @@ class SubscriptionModule implements ModuleInterface {
add_action(
'woocommerce_subscription_payment_complete',
function ( $subscription ) use ( $c ) {
+ $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $paypal_subscription_id ) {
+ return;
+ }
+
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
$logger = $c->get( 'woocommerce.logger.woocommerce' );
$this->add_payment_token_id( $subscription, $payment_token_repository, $logger );
+
+ if ( count( $subscription->get_related_orders() ) === 1 ) {
+ $parent_order = $subscription->get_parent();
+ if ( is_a( $parent_order, WC_Order::class ) ) {
+ $order_repository = $c->get( 'api.repository.order' );
+ $order = $order_repository->for_wc_order( $parent_order );
+ $transaction_id = $this->get_paypal_order_transaction_id( $order );
+ if ( $transaction_id ) {
+ $subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
+ $subscription->save();
+ }
+ }
+ }
}
);
@@ -102,39 +136,212 @@ class SubscriptionModule implements ModuleInterface {
function( array $data ) use ( $c ) {
// phpcs:ignore WordPress.Security.NonceVerification.Missing
$wc_order_action = wc_clean( wp_unslash( $_POST['wc_order_action'] ?? '' ) );
+
+ // phpcs:ignore WordPress.Security.NonceVerification.Missing
+ $subscription_id = wc_clean( wp_unslash( $_POST['post_ID'] ?? '' ) );
+ if ( ! $subscription_id ) {
+ return $data;
+ }
+ $subscription = wc_get_order( $subscription_id );
+ if ( ! is_a( $subscription, WC_Subscription::class ) ) {
+ return $data;
+ }
+
if (
- $wc_order_action === 'wcs_process_renewal'
+ $wc_order_action === 'wcs_process_renewal' && $subscription->get_payment_method() === CreditCardGateway::ID
&& isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN'
&& isset( $data['payment_source']['token']['source']->card )
) {
- $renewal_order_id = absint( $data['purchase_units'][0]['custom_id'] );
- $subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order_id );
- $subscriptions_values = array_values( $subscriptions );
- $latest_subscription = array_shift( $subscriptions_values );
- if ( is_a( $latest_subscription, WC_Subscription::class ) ) {
- $related_renewal_orders = $latest_subscription->get_related_orders( 'ids', 'renewal' );
- $latest_order_id_with_transaction = array_slice( $related_renewal_orders, 1, 1, false );
- $order_id = ! empty( $latest_order_id_with_transaction ) ? $latest_order_id_with_transaction[0] : 0;
- if ( count( $related_renewal_orders ) === 1 ) {
- $order_id = $latest_subscription->get_parent_id();
- }
-
- $wc_order = wc_get_order( $order_id );
- if ( is_a( $wc_order, WC_Order::class ) ) {
- $transaction_id = $wc_order->get_transaction_id();
- $data['application_context']['stored_payment_source'] = array(
+ $data['payment_source'] = array(
+ 'card' => array(
+ 'vault_id' => $data['payment_source']['token']['id'],
+ 'stored_credential' => array(
'payment_initiator' => 'MERCHANT',
'payment_type' => 'RECURRING',
'usage' => 'SUBSEQUENT',
- 'previous_transaction_reference' => $transaction_id,
- );
- }
+ ),
+ ),
+ );
+
+ $previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
+ if ( $previous_transaction_reference ) {
+ $data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference;
}
}
return $data;
}
);
+
+ $this->subscriptions_api_integration( $c );
+
+ add_action(
+ 'admin_enqueue_scripts',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $hook ) use ( $c ) {
+ if ( ! is_string( $hook ) ) {
+ return;
+ }
+ $settings = $c->get( 'wcgateway.settings' );
+ $subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
+ if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) {
+ return;
+ }
+
+ //phpcs:disable WordPress.Security.NonceVerification.Recommended
+ $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
+ $product = wc_get_product( $post_id );
+ if ( ! ( is_a( $product, WC_Product::class ) ) ) {
+ return;
+ }
+
+ $subscriptions_helper = $c->get( 'subscription.helper' );
+ assert( $subscriptions_helper instanceof SubscriptionHelper );
+
+ if (
+ ! $subscriptions_helper->plugin_is_active()
+ || ! (
+ is_a( $product, WC_Product_Subscription::class )
+ || is_a( $product, WC_Product_Variable_Subscription::class )
+ || is_a( $product, WC_Product_Subscription_Variation::class )
+ )
+ || ! WC_Subscriptions_Product::is_subscription( $product )
+ ) {
+ return;
+ }
+
+ $module_url = $c->get( 'subscription.module.url' );
+ wp_enqueue_script(
+ 'ppcp-paypal-subscription',
+ untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js',
+ array( 'jquery' ),
+ $c->get( 'ppcp.asset-version' ),
+ true
+ );
+
+ $products = array( $this->set_product_config( $product ) );
+ if ( $product->get_type() === 'variable-subscription' ) {
+ $products = array();
+
+ /**
+ * Suppress pslam.
+ *
+ * @psalm-suppress TypeDoesNotContainType
+ *
+ * WC_Product_Variable_Subscription extends WC_Product_Variable.
+ */
+ assert( $product instanceof WC_Product_Variable );
+ $available_variations = $product->get_available_variations();
+ foreach ( $available_variations as $variation ) {
+ /**
+ * The method is defined in WooCommerce.
+ *
+ * @psalm-suppress UndefinedMethod
+ */
+ $variation = wc_get_product_object( 'variation', $variation['variation_id'] );
+ $products[] = $this->set_product_config( $variation );
+ }
+ }
+
+ wp_localize_script(
+ 'ppcp-paypal-subscription',
+ 'PayPalCommerceGatewayPayPalSubscriptionProducts',
+ $products
+ );
+ }
+ );
+
+ $endpoint = $c->get( 'subscription.deactivate-plan-endpoint' );
+ assert( $endpoint instanceof DeactivatePlanEndpoint );
+ add_action(
+ 'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT,
+ array( $endpoint, 'handle_request' )
+ );
+
+ add_action(
+ 'add_meta_boxes',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( string $post_type, $post_or_order_object ) use ( $c ) {
+ if ( ! function_exists( 'wcs_get_subscription' ) ) {
+ return;
+ }
+
+ $order = ( $post_or_order_object instanceof WP_Post )
+ ? wc_get_order( $post_or_order_object->ID )
+ : $post_or_order_object;
+
+ if ( ! is_a( $order, WC_Order::class ) ) {
+ return;
+ }
+
+ $subscription = wcs_get_subscription( $order->get_id() );
+ if ( ! is_a( $subscription, WC_Subscription::class ) ) {
+ return;
+ }
+
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( ! $subscription_id ) {
+ return;
+ }
+
+ $screen_id = wc_get_page_screen_id( 'shop_subscription' );
+ remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' );
+
+ $environment = $c->get( 'onboarding.environment' );
+ add_meta_box(
+ 'ppcp_paypal_subscription',
+ __( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
+ function() use ( $subscription_id, $environment ) {
+ $host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
+ $url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id;
+ echo '' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '
';
+ echo '' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . ' ' . esc_attr( $subscription_id ) . '
';
+ },
+ $post_type,
+ 'side'
+ );
+
+ },
+ 30,
+ 2
+ );
+
+ add_action(
+ 'action_scheduler_before_execute',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $action_id ) {
+ /**
+ * Class exist in WooCommerce.
+ *
+ * @psalm-suppress UndefinedClass
+ */
+ $store = ActionScheduler_Store::instance();
+ $action = $store->fetch_action( $action_id );
+
+ $subscription_id = $action->get_args()['subscription_id'] ?? null;
+ if ( $subscription_id ) {
+ $subscription = wcs_get_subscription( $subscription_id );
+ if ( is_a( $subscription, WC_Subscription::class ) ) {
+ $paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $paypal_subscription_id ) {
+ as_unschedule_action( $action->get_hook(), $action->get_args() );
+ }
+ }
+ }
+ }
+ );
}
/**
@@ -265,8 +472,8 @@ class SubscriptionModule implements ModuleInterface {
SubscriptionHelper $subscription_helper
) {
- if ( $settings->has( 'vault_enabled' )
- && $settings->get( 'vault_enabled' )
+ if ( $settings->has( 'vault_enabled_dcc' )
+ && $settings->get( 'vault_enabled_dcc' )
&& $subscription_helper->is_subscription_change_payment()
&& CreditCardGateway::ID === $id
) {
@@ -303,4 +510,570 @@ class SubscriptionModule implements ModuleInterface {
return $default_fields;
}
+
+ /**
+ * Adds PayPal subscriptions API integration.
+ *
+ * @param ContainerInterface $c The container.
+ * @return void
+ * @throws Exception When something went wrong.
+ */
+ protected function subscriptions_api_integration( ContainerInterface $c ): void {
+ add_action(
+ 'save_post',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $product_id ) use ( $c ) {
+ $settings = $c->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+
+ try {
+ $subscriptions_mode = $settings->get( 'subscriptions_mode' );
+ } catch ( NotFoundException $exception ) {
+ return;
+ }
+
+ $nonce = wc_clean( wp_unslash( $_POST['_wcsnonce'] ?? '' ) );
+ if (
+ $subscriptions_mode !== 'subscriptions_api'
+ || ! is_string( $nonce )
+ || ! wp_verify_nonce( $nonce, 'wcs_subscription_meta' ) ) {
+ return;
+ }
+
+ $product = wc_get_product( $product_id );
+ if ( ! is_a( $product, WC_Product::class ) ) {
+ return;
+ }
+
+ $subscriptions_api_handler = $c->get( 'subscription.api-handler' );
+ assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
+ $this->update_subscription_product_meta( $product, $subscriptions_api_handler );
+ },
+ 12
+ );
+
+ add_action(
+ 'woocommerce_save_product_variation',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $variation_id ) use ( $c ) {
+ $wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) );
+
+ $subscriptions_helper = $c->get( 'subscription.helper' );
+ assert( $subscriptions_helper instanceof SubscriptionHelper );
+
+ if (
+ ! $subscriptions_helper->plugin_is_active()
+ || ! WC_Subscriptions_Product::is_subscription( $variation_id )
+ || ! is_string( $wcsnonce_save_variations )
+ || ! wp_verify_nonce( $wcsnonce_save_variations, 'wcs_subscription_variations' )
+ ) {
+ return;
+ }
+
+ $product = wc_get_product( $variation_id );
+ if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
+ return;
+ }
+
+ $subscriptions_api_handler = $c->get( 'subscription.api-handler' );
+ assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
+ $this->update_subscription_product_meta( $product, $subscriptions_api_handler );
+ },
+ 30
+ );
+
+ add_action(
+ 'woocommerce_process_shop_subscription_meta',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $id, $post ) use ( $c ) {
+ $subscription = wcs_get_subscription( $id );
+ if ( ! is_a( $subscription, WC_Subscription::class ) ) {
+ return;
+ }
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( ! $subscription_id ) {
+ return;
+ }
+ $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
+ assert( $subscriptions_endpoint instanceof BillingSubscriptions );
+
+ if ( $subscription->get_status() === 'cancelled' ) {
+ try {
+ $subscriptions_endpoint->cancel( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not cancel subscription product on PayPal. ' . $error );
+ }
+ }
+
+ if ( $subscription->get_status() === 'pending-cancel' ) {
+ try {
+ $subscriptions_endpoint->suspend( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
+ }
+ }
+
+ if ( $subscription->get_status() === 'active' ) {
+ try {
+ $current_subscription = $subscriptions_endpoint->subscription( $subscription_id );
+ if ( $current_subscription->status === 'SUSPENDED' ) {
+ $subscriptions_endpoint->activate( $subscription_id );
+ }
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not reactivate subscription product on PayPal. ' . $error );
+ }
+ }
+ },
+ 20,
+ 2
+ );
+
+ add_filter(
+ 'woocommerce_order_actions',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $actions, $subscription ): array {
+ if ( ! is_array( $actions ) || ! is_a( $subscription, WC_Subscription::class ) ) {
+ return $actions;
+ }
+
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id && isset( $actions['wcs_process_renewal'] ) ) {
+ unset( $actions['wcs_process_renewal'] );
+ }
+
+ return $actions;
+ },
+ 20,
+ 2
+ );
+
+ add_filter(
+ 'wcs_view_subscription_actions',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $actions, $subscription ): array {
+ if ( ! is_a( $subscription, WC_Subscription::class ) ) {
+ return $actions;
+ }
+
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id && $subscription->get_status() === 'active' ) {
+ $url = wp_nonce_url(
+ add_query_arg(
+ array(
+ 'change_subscription_to' => 'cancelled',
+ 'ppcp_cancel_subscription' => $subscription->get_id(),
+ )
+ ),
+ 'ppcp_cancel_subscription_nonce'
+ );
+
+ array_unshift(
+ $actions,
+ array(
+ 'url' => esc_url( $url ),
+ 'name' => esc_html__( 'Cancel', 'woocommerce-paypal-payments' ),
+ )
+ );
+
+ $actions['cancel']['name'] = esc_html__( 'Suspend', 'woocommerce-paypal-payments' );
+ unset( $actions['subscription_renewal_early'] );
+ }
+
+ return $actions;
+ },
+ 11,
+ 2
+ );
+
+ add_action(
+ 'wp_loaded',
+ function() use ( $c ) {
+ if ( ! function_exists( 'wcs_get_subscription' ) ) {
+ return;
+ }
+
+ $cancel_subscription_id = wc_clean( wp_unslash( $_GET['ppcp_cancel_subscription'] ?? '' ) );
+ $subscription = wcs_get_subscription( absint( $cancel_subscription_id ) );
+ if ( ! wcs_is_subscription( $subscription ) || $subscription === false ) {
+ return;
+ }
+
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ $nonce = wc_clean( wp_unslash( $_GET['_wpnonce'] ?? '' ) );
+ if ( ! is_string( $nonce ) ) {
+ return;
+ }
+
+ if (
+ $subscription_id
+ && $cancel_subscription_id
+ && $nonce
+ ) {
+ if (
+ ! wp_verify_nonce( $nonce, 'ppcp_cancel_subscription_nonce' )
+ || ! user_can( get_current_user_id(), 'edit_shop_subscription_status', $subscription->get_id() )
+ ) {
+ return;
+ }
+
+ $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' );
+ try {
+ $subscriptions_endpoint->cancel( $subscription_id );
+
+ $subscription->update_status( 'cancelled' );
+ $subscription->add_order_note( __( 'Subscription cancelled by the subscriber from their account page.', 'woocommerce-paypal-payments' ) );
+ wc_add_notice( __( 'Your subscription has been cancelled.', 'woocommerce-paypal-payments' ) );
+
+ wp_safe_redirect( $subscription->get_view_order_url() );
+ exit;
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not cancel subscription product on PayPal. ' . $error );
+ }
+ }
+ },
+ 100
+ );
+
+ add_action(
+ 'woocommerce_subscription_before_actions',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $subscription ) use ( $c ) {
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id ) {
+ $environment = $c->get( 'onboarding.environment' );
+ $host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
+ ?>
+
+
+
+ " id="ppcp-subscription-id" target="_blank">
+
+
+ 'ppcp_subscription',
+ 'value' => esc_attr( $query_vars['ppcp_subscription'] ),
+ );
+ }
+
+ return $query;
+ },
+ 10,
+ 2
+ );
+
+ add_action(
+ 'woocommerce_customer_changed_subscription_to_cancelled',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $subscription ) use ( $c ) {
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id ) {
+ $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
+ assert( $subscriptions_endpoint instanceof BillingSubscriptions );
+
+ try {
+ $subscriptions_endpoint->suspend( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not suspend subscription product on PayPal. ' . $error );
+ }
+ }
+ }
+ );
+
+ add_action(
+ 'woocommerce_customer_changed_subscription_to_active',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $subscription ) use ( $c ) {
+ $subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
+ if ( $subscription_id ) {
+ $subscriptions_endpoint = $c->get( 'api.endpoint.billing-subscriptions' );
+ assert( $subscriptions_endpoint instanceof BillingSubscriptions );
+
+ try {
+ $subscriptions_endpoint->activate( $subscription_id );
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ $logger->error( 'Could not active subscription product on PayPal. ' . $error );
+ }
+ }
+ }
+ );
+
+ add_action(
+ 'woocommerce_product_options_general_product_data',
+ function() use ( $c ) {
+ $settings = $c->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+
+ try {
+ $subscriptions_mode = $settings->get( 'subscriptions_mode' );
+ if ( $subscriptions_mode === 'subscriptions_api' ) {
+ /**
+ * Needed for getting global post object.
+ *
+ * @psalm-suppress InvalidGlobal
+ */
+ global $post;
+ $product = wc_get_product( $post->ID );
+ if ( ! is_a( $product, WC_Product::class ) ) {
+ return;
+ }
+
+ $environment = $c->get( 'onboarding.environment' );
+ echo '';
+ $this->render_paypal_subscription_fields( $product, $environment );
+ echo '
';
+
+ }
+ } catch ( NotFoundException $exception ) {
+ return;
+ }
+ }
+ );
+
+ add_action(
+ 'woocommerce_variation_options_pricing',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $loop, $variation_data, $variation ) use ( $c ) {
+ $settings = $c->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+
+ try {
+ $subscriptions_mode = $settings->get( 'subscriptions_mode' );
+ if ( $subscriptions_mode === 'subscriptions_api' ) {
+ $product = wc_get_product( $variation->ID );
+ if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
+ return;
+ }
+
+ $environment = $c->get( 'onboarding.environment' );
+ $this->render_paypal_subscription_fields( $product, $environment );
+
+ }
+ } catch ( NotFoundException $exception ) {
+ return;
+ }
+ },
+ 10,
+ 3
+ );
+ }
+
+ /**
+ * Render PayPal Subscriptions fields.
+ *
+ * @param WC_Product $product WC Product.
+ * @param Environment $environment The environment.
+ * @return void
+ */
+ private function render_paypal_subscription_fields( WC_Product $product, Environment $environment ): void {
+ $enable_subscription_product = $product->get_meta( '_ppcp_enable_subscription_product' );
+ $style = $product->get_type() === 'subscription_variation' ? 'float:left; width:150px;' : '';
+
+ echo '';
+ echo sprintf(
+ // translators: %1$s and %2$s are label open and close tags.
+ esc_html__( '%1$sConnect to PayPal%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ ' '
+ );
+ echo ' ';
+ echo sprintf(
+ // translators: %1$s and %2$s are label open and close tags.
+ esc_html__( '%1$sConnect Product to PayPal Subscriptions Plan%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ ' '
+ );
+
+ echo wc_help_tip( esc_html__( 'Create a subscription product and plan to bill customers at regular intervals. Be aware that certain subscription settings cannot be modified once the PayPal Subscription is linked to this product. Unlink the product to edit disabled fields.', 'woocommerce-paypal-payments' ) );
+ echo '
';
+
+ $subscription_product = $product->get_meta( 'ppcp_subscription_product' );
+ $subscription_plan = $product->get_meta( 'ppcp_subscription_plan' );
+ $subscription_plan_name = $product->get_meta( '_ppcp_subscription_plan_name' );
+ if ( $subscription_product && $subscription_plan ) {
+ if ( $enable_subscription_product !== 'yes' ) {
+ echo sprintf(
+ // translators: %1$s and %2$s are button and wrapper html tags.
+ esc_html__( '%1$sUnlink PayPal Subscription Plan%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ '
'
+ );
+ echo sprintf(
+ // translators: %1$s and %2$s is open and closing paragraph tag.
+ esc_html__( '%1$sPlan unlinked successfully ✔️%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ '
'
+ );
+ }
+
+ $host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
+ echo sprintf(
+ // translators: %1$s and %2$s are wrapper html tags.
+ esc_html__( '%1$sProduct%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ ' ' . esc_attr( $subscription_product['id'] ) . '
'
+ );
+ echo sprintf(
+ // translators: %1$s and %2$s are wrapper html tags.
+ esc_html__( '%1$sPlan%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ ' ' . esc_attr( $subscription_plan['id'] ) . '
'
+ );
+ } else {
+ echo sprintf(
+ // translators: %1$s and %2$s are wrapper html tags.
+ esc_html__( '%1$sPlan Name%2$s', 'woocommerce-paypal-payments' ),
+ '',
+ '
'
+ );
+ }
+ }
+
+ /**
+ * Updates subscription product meta.
+ *
+ * @param WC_Product $product The product.
+ * @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
+ * @return void
+ */
+ private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
+ // phpcs:ignore WordPress.Security.NonceVerification
+ $enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
+ $product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
+ $product->save();
+
+ if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
+ if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
+ $subscriptions_api_handler->update_product( $product );
+ $subscriptions_api_handler->update_plan( $product );
+ return;
+ }
+
+ if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) {
+ $subscriptions_api_handler->create_product( $product );
+ }
+
+ if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) {
+ // phpcs:ignore WordPress.Security.NonceVerification
+ $subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) );
+ if ( ! is_string( $subscription_plan_name ) ) {
+ return;
+ }
+
+ $product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name );
+ $product->save();
+
+ $subscriptions_api_handler->create_plan( $subscription_plan_name, $product );
+ }
+ }
+ }
+
+ /**
+ * Returns subscription product configuration.
+ *
+ * @param WC_Product $product The product.
+ * @return array
+ */
+ private function set_product_config( WC_Product $product ): array {
+ $plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
+ $plan_id = $plan['id'] ?? '';
+
+ return array(
+ 'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '',
+ 'plan_id' => $plan_id,
+ 'product_id' => $product->get_id(),
+ 'ajax' => array(
+ 'deactivate_plan' => array(
+ 'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ),
+ 'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ),
+ ),
+ ),
+ );
+ }
}
diff --git a/modules/ppcp-subscription/src/SubscriptionsApiHandler.php b/modules/ppcp-subscription/src/SubscriptionsApiHandler.php
new file mode 100644
index 000000000..2d8bb1d7b
--- /dev/null
+++ b/modules/ppcp-subscription/src/SubscriptionsApiHandler.php
@@ -0,0 +1,283 @@
+products_endpoint = $products_endpoint;
+ $this->product_factory = $product_factory;
+ $this->billing_plans_endpoint = $billing_plans_endpoint;
+ $this->billing_cycle_factory = $billing_cycle_factory;
+ $this->payment_preferences_factory = $payment_preferences_factory;
+ $this->currency = $currency;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Creates a Catalog Product and adds it as WC product meta.
+ *
+ * @param WC_Product $product The WC product.
+ * @return void
+ */
+ public function create_product( WC_Product $product ) {
+ try {
+ $subscription_product = $this->products_endpoint->create( $product->get_title(), $this->prepare_description( $product->get_description() ) );
+ $product->update_meta_data( 'ppcp_subscription_product', $subscription_product->to_array() );
+ $product->save();
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $this->logger->error( 'Could not create catalog product on PayPal. ' . $error );
+ }
+ }
+
+ /**
+ * Creates a subscription plan.
+ *
+ * @param string $plan_name The plan name.
+ * @param WC_Product $product The WC product.
+ * @return void
+ */
+ public function create_plan( string $plan_name, WC_Product $product ): void {
+ try {
+ $subscription_plan = $this->billing_plans_endpoint->create(
+ $plan_name ?: $product->get_title(),
+ $product->get_meta( 'ppcp_subscription_product' )['id'] ?? '',
+ $this->billing_cycles( $product ),
+ $this->payment_preferences_factory->from_wc_product( $product )->to_array()
+ );
+
+ $product->update_meta_data( 'ppcp_subscription_plan', $subscription_plan->to_array() );
+ $product->save();
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $this->logger->error( 'Could not create subscription plan on PayPal. ' . $error );
+ }
+ }
+
+ /**
+ * Updates a product.
+ *
+ * @param WC_Product $product The WC product.
+ * @return void
+ */
+ public function update_product( WC_Product $product ): void {
+ try {
+ $catalog_product_id = $product->get_meta( 'ppcp_subscription_product' )['id'] ?? '';
+ if ( $catalog_product_id ) {
+ $catalog_product = $this->products_endpoint->product( $catalog_product_id );
+ $catalog_product_name = $catalog_product->name() ?: '';
+ $catalog_product_description = $catalog_product->description() ?: '';
+
+ $wc_product_description = $this->prepare_description( $product->get_description() ) ?: $product->get_title();
+
+ if ( $catalog_product_name !== $product->get_title() || $catalog_product_description !== $wc_product_description ) {
+ $data = array();
+ if ( $catalog_product_name !== $product->get_title() ) {
+ $data[] = (object) array(
+ 'op' => 'replace',
+ 'path' => '/name',
+ 'value' => $product->get_title(),
+ );
+ }
+ if ( $catalog_product_description !== $wc_product_description ) {
+ $data[] = (object) array(
+ 'op' => 'replace',
+ 'path' => '/description',
+ 'value' => $wc_product_description,
+ );
+ }
+
+ $this->products_endpoint->update( $catalog_product_id, $data );
+ }
+ }
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $this->logger->error( 'Could not update catalog product on PayPal. ' . $error );
+ }
+ }
+
+ /**
+ * Updates a plan.
+ *
+ * @param WC_Product $product The WC product.
+ * @return void
+ */
+ public function update_plan( WC_Product $product ): void {
+ try {
+ $subscription_plan_id = $product->get_meta( 'ppcp_subscription_plan' )['id'] ?? '';
+ if ( $subscription_plan_id ) {
+ $subscription_plan = $this->billing_plans_endpoint->plan( $subscription_plan_id );
+
+ $price = $subscription_plan->billing_cycles()[0]->pricing_scheme()['fixed_price']['value'] ?? '';
+ if ( $price && round( (float) $price, 2 ) !== round( (float) $product->get_price(), 2 ) ) {
+ $this->billing_plans_endpoint->update_pricing(
+ $subscription_plan_id,
+ $this->billing_cycle_factory->from_wc_product( $product )
+ );
+ }
+ }
+ } catch ( RuntimeException $exception ) {
+ $error = $exception->getMessage();
+ if ( is_a( $exception, PayPalApiException::class ) ) {
+ $error = $exception->get_details( $error );
+ }
+
+ $this->logger->error( 'Could not update subscription plan on PayPal. ' . $error );
+ }
+ }
+
+ /**
+ * Returns billing cycles based on WC Subscription product.
+ *
+ * @param WC_Product $product The WC Subscription product.
+ * @return array
+ */
+ private function billing_cycles( WC_Product $product ): array {
+ $billing_cycles = array();
+ $sequence = 1;
+
+ $trial_length = $product->get_meta( '_subscription_trial_length' ) ?? '';
+ if ( $trial_length ) {
+ $billing_cycles[] = ( new BillingCycle(
+ array(
+ 'interval_unit' => $product->get_meta( '_subscription_trial_period' ),
+ 'interval_count' => $product->get_meta( '_subscription_trial_length' ),
+ ),
+ $sequence,
+ 'TRIAL',
+ array(
+ 'fixed_price' => array(
+ 'value' => '0',
+ 'currency_code' => $this->currency,
+ ),
+ ),
+ 1
+ ) )->to_array();
+
+ $sequence++;
+ }
+
+ $billing_cycles[] = ( new BillingCycle(
+ array(
+ 'interval_unit' => $product->get_meta( '_subscription_period' ),
+ 'interval_count' => $product->get_meta( '_subscription_period_interval' ),
+ ),
+ $sequence,
+ 'REGULAR',
+ array(
+ 'fixed_price' => array(
+ 'value' => $product->get_meta( '_subscription_price' ) ?: $product->get_price(),
+ 'currency_code' => $this->currency,
+ ),
+ ),
+ (int) $product->get_meta( '_subscription_length' )
+ ) )->to_array();
+
+ return $billing_cycles;
+ }
+}
diff --git a/modules/ppcp-vaulting/webpack.config.js b/modules/ppcp-subscription/webpack.config.js
similarity index 51%
rename from modules/ppcp-vaulting/webpack.config.js
rename to modules/ppcp-subscription/webpack.config.js
index a79f3d992..4a5f4a0fc 100644
--- a/modules/ppcp-vaulting/webpack.config.js
+++ b/modules/ppcp-subscription/webpack.config.js
@@ -6,7 +6,7 @@ module.exports = {
mode: isProduction ? 'production' : 'development',
target: 'web',
entry: {
- 'myaccount-payments': path.resolve('./resources/js/myaccount-payments.js'),
+ 'paypal-subscription': path.resolve('./resources/js/paypal-subscription.js'),
},
output: {
path: path.resolve(__dirname, 'assets/'),
@@ -17,6 +17,19 @@ module.exports = {
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-subscription/yarn.lock b/modules/ppcp-subscription/yarn.lock
new file mode 100644
index 000000000..08b8761f0
--- /dev/null
+++ b/modules/ppcp-subscription/yarn.lock
@@ -0,0 +1,2185 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@ampproject/remapping@^2.2.0":
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+ integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.21.4.tgz#d0fa9e4413aca81f2b23b9442797bda1826edb39"
+ integrity sha512-LYvhNKfwWSPpocw8GI7gpK2nq3HSDuEPC/uSYaALSJu9xjsalaaYFOq0Pwt5KmVqwEbZlDu81aLXwBOmD/Fv9g==
+ dependencies:
+ "@babel/highlight" "^7.18.6"
+
+"@babel/compat-data@^7.17.7", "@babel/compat-data@^7.22.0", "@babel/compat-data@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.3.tgz#cd502a6a0b6e37d7ad72ce7e71a7160a3ae36f7e"
+ integrity sha512-aNtko9OPOwVESUFp3MZfD8Uzxl7JzSeJpd7npIoxCasU37PFbAQRpKglkaKwlHOyeJdrREpo8TW8ldrkYWwvIQ==
+
+"@babel/core@^7.19":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.1.tgz#5de51c5206f4c6f5533562838337a603c1033cfd"
+ integrity sha512-Hkqu7J4ynysSXxmAahpN1jjRwVJ+NdpraFLIWflgjpVob3KNyK3/tIUc7Q7szed8WMp0JNa7Qtd1E9Oo22F9gA==
+ dependencies:
+ "@ampproject/remapping" "^2.2.0"
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.22.0"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-module-transforms" "^7.22.1"
+ "@babel/helpers" "^7.22.0"
+ "@babel/parser" "^7.22.0"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.2"
+ json5 "^2.2.2"
+ semver "^6.3.0"
+
+"@babel/generator@^7.22.0", "@babel/generator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.3.tgz#0ff675d2edb93d7596c5f6728b52615cfc0df01e"
+ integrity sha512-C17MW4wlk//ES/CJDL51kPNwl+qiBQyN7b9SKyVp11BLGFeSPoVaHrv+MNt8jwQFhQWowW88z1eeBx3pFz9v8A==
+ dependencies:
+ "@babel/types" "^7.22.3"
+ "@jridgewell/gen-mapping" "^0.3.2"
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jsesc "^2.5.1"
+
+"@babel/helper-annotate-as-pure@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
+ integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-builder-binary-assignment-operator-visitor@^7.18.6":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.3.tgz#c9b83d1ba74e163e023f008a3d3204588a7ceb60"
+ integrity sha512-ahEoxgqNoYXm0k22TvOke48i1PkavGu0qGCmcq9ugi6gnmvKNaMjKBSrZTnWUi1CFEeNAUiVba0Wtzm03aSkJg==
+ dependencies:
+ "@babel/types" "^7.22.3"
+
+"@babel/helper-compilation-targets@^7.17.7", "@babel/helper-compilation-targets@^7.18.9", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.1.tgz#bfcd6b7321ffebe33290d68550e2c9d7eb7c7a58"
+ integrity sha512-Rqx13UM3yVB5q0D/KwQ8+SPfX/+Rnsy1Lw1k/UwOC4KC6qrzIQoY3lYnBu5EHKBlEHHcj0M0W8ltPSkD8rqfsQ==
+ dependencies:
+ "@babel/compat-data" "^7.22.0"
+ "@babel/helper-validator-option" "^7.21.0"
+ browserslist "^4.21.3"
+ lru-cache "^5.1.1"
+ semver "^6.3.0"
+
+"@babel/helper-create-class-features-plugin@^7.21.0", "@babel/helper-create-class-features-plugin@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.22.1.tgz#ae3de70586cc757082ae3eba57240d42f468c41b"
+ integrity sha512-SowrZ9BWzYFgzUMwUmowbPSGu6CXL5MSuuCkG3bejahSpSymioPmuLdhPxNOc9MjuNGjy7M/HaXvJ8G82Lywlw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-member-expression-to-functions" "^7.22.0"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.22.1"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ semver "^6.3.0"
+
+"@babel/helper-create-regexp-features-plugin@^7.18.6", "@babel/helper-create-regexp-features-plugin@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.1.tgz#a7ed9a8488b45b467fca353cd1a44dc5f0cf5c70"
+ integrity sha512-WWjdnfR3LPIe+0EY8td7WmjhytxXtjKAEpnAxun/hkNiyOaPlvGK+NZaBFIdi9ndYV3Gav7BpFvtUwnaJlwi1w==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ regexpu-core "^5.3.1"
+ semver "^6.3.0"
+
+"@babel/helper-define-polyfill-provider@^0.4.0":
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.4.0.tgz#487053f103110f25b9755c5980e031e93ced24d8"
+ integrity sha512-RnanLx5ETe6aybRi1cO/edaRH+bNYWaryCEmjDDYyNr4wnSzyOp8T0dWipmqVHKEY3AbVKUom50AKSlj1zmKbg==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.17.7"
+ "@babel/helper-plugin-utils" "^7.16.7"
+ debug "^4.1.1"
+ lodash.debounce "^4.0.8"
+ resolve "^1.14.2"
+ semver "^6.1.2"
+
+"@babel/helper-environment-visitor@^7.18.9", "@babel/helper-environment-visitor@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.1.tgz#ac3a56dbada59ed969d712cf527bd8271fe3eba8"
+ integrity sha512-Z2tgopurB/kTbidvzeBrc2To3PUP/9i5MUe+fU6QJCQDyPwSH2oRapkLw3KGECDYSjhQZCNxEvNvZlLw8JjGwA==
+
+"@babel/helper-function-name@^7.18.9", "@babel/helper-function-name@^7.19.0", "@babel/helper-function-name@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz#d552829b10ea9f120969304023cd0645fa00b1b4"
+ integrity sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==
+ dependencies:
+ "@babel/template" "^7.20.7"
+ "@babel/types" "^7.21.0"
+
+"@babel/helper-hoist-variables@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
+ integrity sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-member-expression-to-functions@^7.22.0":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.22.3.tgz#4b77a12c1b4b8e9e28736ed47d8b91f00976911f"
+ integrity sha512-Gl7sK04b/2WOb6OPVeNy9eFKeD3L6++CzL3ykPOWqTn08xgYYK0wz4TUh2feIImDXxcVW3/9WQ1NMKY66/jfZA==
+ dependencies:
+ "@babel/types" "^7.22.3"
+
+"@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.21.4":
+ version "7.21.4"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.21.4.tgz#ac88b2f76093637489e718a90cec6cf8a9b029af"
+ integrity sha512-orajc5T2PsRYUN3ZryCEFeMDYwyw09c/pZeaQEZPH0MpKzSvn3e0uXsDBu3k03VI+9DBiRo+l22BfKTpKwa/Wg==
+ dependencies:
+ "@babel/types" "^7.21.4"
+
+"@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5", "@babel/helper-module-transforms@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.1.tgz#e0cad47fedcf3cae83c11021696376e2d5a50c63"
+ integrity sha512-dxAe9E7ySDGbQdCVOY/4+UcD8M9ZFqZcZhSPsPacvCG4M+9lwtDDQfI2EoaSvmf7W/8yCBkGU0m7Pvt1ru3UZw==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-module-imports" "^7.21.4"
+ "@babel/helper-simple-access" "^7.21.5"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
+
+"@babel/helper-optimise-call-expression@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe"
+ integrity sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@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.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.18.9", "@babel/helper-plugin-utils@^7.19.0", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.21.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56"
+ integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg==
+
+"@babel/helper-remap-async-to-generator@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.18.9.tgz#997458a0e3357080e54e1d79ec347f8a8cd28519"
+ integrity sha512-dI7q50YKd8BAv3VEfgg7PS7yD3Rtbi2J1XMXaalXO0W0164hYLnh8zpjRS0mte9MfVp/tltvr/cfdXPvJr1opA==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-wrap-function" "^7.18.9"
+ "@babel/types" "^7.18.9"
+
+"@babel/helper-replace-supers@^7.18.6", "@babel/helper-replace-supers@^7.20.7", "@babel/helper-replace-supers@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.1.tgz#38cf6e56f7dc614af63a21b45565dd623f0fdc95"
+ integrity sha512-ut4qrkE4AuSfrwHSps51ekR1ZY/ygrP1tp0WFm8oVq6nzc/hvfV/22JylndIbsf2U2M9LOMwiSddr6y+78j+OQ==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-member-expression-to-functions" "^7.22.0"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.0"
+
+"@babel/helper-simple-access@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.21.5.tgz#d697a7971a5c39eac32c7e63c0921c06c8a249ee"
+ integrity sha512-ENPDAMC1wAjR0uaCUwliBdiSl1KBJAVnMTzXqi64c2MG8MPR6ii4qf7bSXDqSFbr4W6W028/rf5ivoHop5/mkg==
+ dependencies:
+ "@babel/types" "^7.21.5"
+
+"@babel/helper-skip-transparent-expression-wrappers@^7.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz#fbe4c52f60518cab8140d77101f0e63a8a230684"
+ integrity sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==
+ dependencies:
+ "@babel/types" "^7.20.0"
+
+"@babel/helper-split-export-declaration@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz#7367949bc75b20c6d5a5d4a97bba2824ae8ef075"
+ integrity sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==
+ dependencies:
+ "@babel/types" "^7.18.6"
+
+"@babel/helper-string-parser@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.21.5.tgz#2b3eea65443c6bdc31c22d037c65f6d323b6b2bd"
+ integrity sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==
+
+"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.19.1":
+ version "7.19.1"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2"
+ integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==
+
+"@babel/helper-validator-option@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz#8224c7e13ace4bafdc4004da2cf064ef42673180"
+ integrity sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==
+
+"@babel/helper-wrap-function@^7.18.9":
+ version "7.20.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.20.5.tgz#75e2d84d499a0ab3b31c33bcfe59d6b8a45f62e3"
+ integrity sha512-bYMxIWK5mh+TgXGVqAtnu5Yn1un+v8DDZtqyzKRLUzrh70Eal2O3aZ7aPYiMADO4uKlkzOiRiZ6GX5q3qxvW9Q==
+ dependencies:
+ "@babel/helper-function-name" "^7.19.0"
+ "@babel/template" "^7.18.10"
+ "@babel/traverse" "^7.20.5"
+ "@babel/types" "^7.20.5"
+
+"@babel/helpers@^7.22.0":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.3.tgz#53b74351da9684ea2f694bf0877998da26dd830e"
+ integrity sha512-jBJ7jWblbgr7r6wYZHMdIqKc73ycaTcCaWRq4/2LpuPHcx7xMlZvpGQkOYc9HeSjn6rcx15CPlgVcBtZ4WZJ2w==
+ dependencies:
+ "@babel/template" "^7.21.9"
+ "@babel/traverse" "^7.22.1"
+ "@babel/types" "^7.22.3"
+
+"@babel/highlight@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.18.6.tgz#81158601e93e2563795adcbfbdf5d64be3f2ecdf"
+ integrity sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.18.6"
+ chalk "^2.0.0"
+ js-tokens "^4.0.0"
+
+"@babel/parser@^7.21.9", "@babel/parser@^7.22.0", "@babel/parser@^7.22.4":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.4.tgz#a770e98fd785c231af9d93f6459d36770993fb32"
+ integrity sha512-VLLsx06XkEYqBtE5YGPwfSGwfrjnyPP5oiGty3S8pQLFDFLaS8VwWSIxkTXpcvr5zeYLE6+MBNl2npl/YnfofA==
+
+"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
+ version "7.18.6"
+ 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.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
+ integrity sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.22.3.tgz#a75be1365c0c3188c51399a662168c1c98108659"
+ integrity sha512-6r4yRwEnorYByILoDRnEqxtojYKuiIv9FojW2E8GUKo9eWBwbKcd9IiZOZpdyXc64RmyGGyPu3/uAcrz/dq2kQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-transform-optional-chaining" "^7.22.3"
+
+"@babel/plugin-proposal-private-property-in-object@^7.21.0":
+ version "7.21.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.11.tgz#69d597086b6760c4126525cfa154f34631ff272c"
+ integrity sha512-0QZ8qP/3RLDVBwBFoWAwCtgcDZJVwA5LUJRZU8x2YFfKNuFq161wK3cuGrALu5yiPu+vzwTAg/sMWVNeWeNyaw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.21.0"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-proposal-unicode-property-regex@^7.4.4":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.18.6.tgz#af613d2cd5e643643b65cded64207b15c85cb78e"
+ integrity sha512-2BShG/d5yoZyXZfVePH91urL5wTG6ASZU9M4o03lKK8u8UW1y08OMttBSOADTcJrnPMpvDXRG3G8fyLh4ovs8w==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@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.20.0":
+ version "7.20.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.20.0.tgz#bb50e0d4bea0957235390641209394e87bdb9cc4"
+ integrity sha512-IUh1vakzNoWalR8ch/areW7qFopR2AEw03JlG7BbrDqmQ4X3q9uuipQwSGrUn7oGiemKjtSLDhNtQHzMHr1JdQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.19.0"
+
+"@babel/plugin-syntax-import-attributes@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.22.3.tgz#d7168f22b9b49a6cc1792cec78e06a18ad2e7b4b"
+ integrity sha512-i35jZJv6aO7hxEbIWQ41adVfOzjm9dcYDNeWlBMd8p0ZQRtNUCBrmGwZt+H5lb+oOC9a3svp956KP0oWGA1YsA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@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.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.21.5.tgz#9bb42a53de447936a57ba256fbf537fc312b6929"
+ integrity sha512-wb1mhwGOCaXHDTcsRYMKF9e5bbMgqwxtqa2Y1ifH96dXJPwbuLX9qHy3clhrxVqgMz7nyNXs8VkxdH8UBcjKqA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-async-generator-functions@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.22.3.tgz#3ed99924c354fb9e80dabb2cc8d002c702e94527"
+ integrity sha512-36A4Aq48t66btydbZd5Fk0/xJqbpg/v4QWI4AH4cYHBXy9Mu42UOupZpebKFiCFNT9S9rJFcsld0gsv0ayLjtA==
+ dependencies:
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+
+"@babel/plugin-transform-async-to-generator@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.20.7.tgz#dfee18623c8cb31deb796aa3ca84dda9cea94354"
+ integrity sha512-Uo5gwHPT9vgnSXQxqGtpdufUiWp96gk7yiP4Mp5bm1QMkEmLXBO7PAGYbKoJ6DhAwiNkcHFBol/x5zZZkL/t0Q==
+ dependencies:
+ "@babel/helper-module-imports" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-remap-async-to-generator" "^7.18.9"
+
+"@babel/plugin-transform-block-scoped-functions@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.18.6.tgz#9187bf4ba302635b9d70d986ad70f038726216a8"
+ integrity sha512-ExUcOqpPWnliRcPqves5HJcJOvHvIIWfuS4sroBUenPuMdmW+SMHDakmtS7qOo13sVppmUijqeTv7qqGsvURpQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-block-scoping@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.21.0.tgz#e737b91037e5186ee16b76e7ae093358a5634f02"
+ integrity sha512-Mdrbunoh9SxwFZapeHVrwFmri16+oYotcZysSzhNIVDwIAb1UV+kvnxULSYq9J3/q5MDG+4X6w8QVgD1zhBXNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
+"@babel/plugin-transform-class-properties@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.22.3.tgz#3407145e513830df77f0cef828b8b231c166fe4c"
+ integrity sha512-mASLsd6rhOrLZ5F3WbCxkzl67mmOnqik0zrg5W6D/X0QMW7HtvnoL1dRARLKIbMP3vXwkwziuLesPqWVGIl6Bw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-class-static-block@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.22.3.tgz#e352cf33567385c731a8f21192efeba760358773"
+ integrity sha512-5BirgNWNOx7cwbTJCOmKFJ1pZjwk5MUfMIwiBBvsirCJMZeQgs5pk6i1OlkVg+1Vef5LfBahFOrdCnAWvkVKMw==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-class-static-block" "^7.14.5"
+
+"@babel/plugin-transform-classes@^7.21.0":
+ version "7.21.0"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.21.0.tgz#f469d0b07a4c5a7dbb21afad9e27e57b47031665"
+ integrity sha512-RZhbYTCEUAe6ntPehC4hlslPWosNHDox+vAs4On/mCLRLfoDVHf6hVEd7kuxr1RnHwJmxFfUM3cZiZRmPxJPXQ==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-compilation-targets" "^7.20.7"
+ "@babel/helper-environment-visitor" "^7.18.9"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-optimise-call-expression" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-replace-supers" "^7.20.7"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ globals "^11.1.0"
+
+"@babel/plugin-transform-computed-properties@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.21.5.tgz#3a2d8bb771cd2ef1cd736435f6552fe502e11b44"
+ integrity sha512-TR653Ki3pAwxBxUe8srfF3e4Pe3FTA46uaNHYyQwIoM4oWKSoOZiDNyHJ0oIoDIUPSRQbQG7jzgVBX3FPVne1Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/template" "^7.20.7"
+
+"@babel/plugin-transform-destructuring@^7.21.3":
+ version "7.21.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.21.3.tgz#73b46d0fd11cd6ef57dea8a381b1215f4959d401"
+ integrity sha512-bp6hwMFzuiE4HqYEyoGJ/V2LeIWn+hLVKc4pnj++E5XQptwhtcGmSayM029d/j2X1bPKGTlsyPwAubuU22KhMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+
+"@babel/plugin-transform-dotall-regex@^7.18.6", "@babel/plugin-transform-dotall-regex@^7.4.4":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.18.6.tgz#b286b3e7aae6c7b861e45bed0a2fafd6b1a4fef8"
+ integrity sha512-6S3jpun1eEbAxq7TdjLotAsl4WpQI9DxfkycRcKrjhQYzU87qpXdknpBg/e+TdcMehqGnLFi7tnFUBR02Vq6wg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-duplicate-keys@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.18.9.tgz#687f15ee3cdad6d85191eb2a372c4528eaa0ae0e"
+ integrity sha512-d2bmXCtZXYc59/0SanQKbiWINadaJXqtvIQIzd4+hNwkWBgyCd5F/2t1kXoUdvPMrxzPvhK6EMQRROxsue+mfw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-dynamic-import@^7.22.1":
+ version "7.22.1"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.22.1.tgz#6c56afaf896a07026330cf39714532abed8d9ed1"
+ integrity sha512-rlhWtONnVBPdmt+jeewS0qSnMz/3yLFrqAP8hHC6EDcrYRSyuz9f9yQhHvVn2Ad6+yO9fHXac5piudeYrInxwQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-dynamic-import" "^7.8.3"
+
+"@babel/plugin-transform-exponentiation-operator@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.18.6.tgz#421c705f4521888c65e91fdd1af951bfefd4dacd"
+ integrity sha512-wzEtc0+2c88FVR34aQmiz56dxEkxr2g8DQb/KfaFa1JYXOFVsbhvAonFN6PwVWj++fKmku8NP80plJ5Et4wqHw==
+ dependencies:
+ "@babel/helper-builder-binary-assignment-operator-visitor" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-export-namespace-from@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.22.3.tgz#9b8700aa495007d3bebac8358d1c562434b680b9"
+ integrity sha512-5Ti1cHLTDnt3vX61P9KZ5IG09bFXp4cDVFJIAeCZuxu9OXXJJZp5iP0n/rzM2+iAutJY+KWEyyHcRaHlpQ/P5g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-export-namespace-from" "^7.8.3"
+
+"@babel/plugin-transform-for-of@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.21.5.tgz#e890032b535f5a2e237a18535f56a9fdaa7b83fc"
+ integrity sha512-nYWpjKW/7j/I/mZkGVgHJXh4bA1sfdFnJoOXwJuj4m3Q2EraO/8ZyrkCau9P5tbHQk01RMSt6KYLCsW7730SXQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-function-name@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.18.9.tgz#cc354f8234e62968946c61a46d6365440fc764e0"
+ integrity sha512-WvIBoRPaJQ5yVHzcnJFor7oS5Ls0PYixlTYE63lCj2RtdQEl15M68FXQlxnG6wdraJIXRdR7KI+hQ7q/9QjrCQ==
+ dependencies:
+ "@babel/helper-compilation-targets" "^7.18.9"
+ "@babel/helper-function-name" "^7.18.9"
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-json-strings@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.22.3.tgz#a181b8679cf7c93e9d0e3baa5b1776d65be601a9"
+ integrity sha512-IuvOMdeOOY2X4hRNAT6kwbePtK21BUyrAEgLKviL8pL6AEEVUVcqtRdN/HJXBLGIbt9T3ETmXRnFedRRmQNTYw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+
+"@babel/plugin-transform-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.18.9.tgz#72796fdbef80e56fba3c6a699d54f0de557444bc"
+ integrity sha512-IFQDSRoTPnrAIrI5zoZv73IFeZu2dhu6irxQjY9rNjTT53VmKg9fenjvoiOWOkJ6mm4jKVPtdMzBY98Fp4Z4cg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-logical-assignment-operators@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.22.3.tgz#9e021455810f33b0baccb82fb759b194f5dc36f0"
+ integrity sha512-CbayIfOw4av2v/HYZEsH+Klks3NC2/MFIR3QR8gnpGNNPEaq2fdlVCRYG/paKs7/5hvBLQ+H70pGWOHtlNEWNA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.10.4"
+
+"@babel/plugin-transform-member-expression-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.18.6.tgz#ac9fdc1a118620ac49b7e7a5d2dc177a1bfee88e"
+ integrity sha512-qSF1ihLGO3q+/g48k85tUjD033C29TNTVB2paCwZPVmOsjn9pClvYYrM2VeJpBY2bcNkuny0YUyTNRyRxJ54KA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-modules-amd@^7.20.11":
+ version "7.20.11"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.20.11.tgz#3daccca8e4cc309f03c3a0c4b41dc4b26f55214a"
+ integrity sha512-NuzCt5IIYOW0O30UvqktzHYR2ud5bOWbY0yaxWZ6G+aFzOMJvrs5YHNikrbdaT15+KNO31nPOy5Fim3ku6Zb5g==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.20.11"
+ "@babel/helper-plugin-utils" "^7.20.2"
+
+"@babel/plugin-transform-modules-commonjs@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.5.tgz#d69fb947eed51af91de82e4708f676864e5e47bc"
+ integrity sha512-OVryBEgKUbtqMoB7eG2rs6UFexJi6Zj6FDXx+esBLPTCxCNxAY9o+8Di7IsUGJ+AVhp5ncK0fxWUBd0/1gPhrQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.21.5"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-simple-access" "^7.21.5"
+
+"@babel/plugin-transform-modules-systemjs@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.22.3.tgz#cc507e03e88d87b016feaeb5dae941e6ef50d91e"
+ integrity sha512-V21W3bKLxO3ZjcBJZ8biSvo5gQ85uIXW2vJfh7JSWf/4SLUSr1tOoHX3ruN4+Oqa2m+BKfsxTR1I+PsvkIWvNw==
+ dependencies:
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-module-transforms" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+
+"@babel/plugin-transform-modules-umd@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.18.6.tgz#81d3832d6034b75b54e62821ba58f28ed0aab4b9"
+ integrity sha512-dcegErExVeXcRqNtkRU/z8WlBLnvD4MRnHgNs3MytRO1Mn1sHRyhbcpYbVMGclAqOjdW+9cfkdZno9dFdfKLfQ==
+ dependencies:
+ "@babel/helper-module-transforms" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-named-capturing-groups-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.3.tgz#db6fb77e6b3b53ec3b8d370246f0b7cf67d35ab4"
+ integrity sha512-c6HrD/LpUdNNJsISQZpds3TXvfYIAbo+efE9aWmY/PmSRD0agrJ9cPMt4BmArwUQ7ZymEWTFjTyp+yReLJZh0Q==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-new-target@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.22.3.tgz#deb0377d741cbee2f45305868b9026dcd6dd96e2"
+ integrity sha512-5RuJdSo89wKdkRTqtM9RVVJzHum9c2s0te9rB7vZC1zKKxcioWIy+xcu4OoIAjyFZhb/bp5KkunuLin1q7Ct+w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-nullish-coalescing-operator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.22.3.tgz#8c519f8bf5af94a9ca6f65cf422a9d3396e542b9"
+ integrity sha512-CpaoNp16nX7ROtLONNuCyenYdY/l7ZsR6aoVa7rW7nMWisoNoQNIH5Iay/4LDyRjKMuElMqXiBoOQCDLTMGZiw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+
+"@babel/plugin-transform-numeric-separator@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.22.3.tgz#02493070ca6685884b0eee705363ee4da2132ab0"
+ integrity sha512-+AF88fPDJrnseMh5vD9+SH6wq4ZMvpiTMHh58uLs+giMEyASFVhcT3NkoyO+NebFCNnpHJEq5AXO2txV4AGPDQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-numeric-separator" "^7.10.4"
+
+"@babel/plugin-transform-object-rest-spread@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.22.3.tgz#da6fba693effb8c203d8c3bdf7bf4e2567e802e9"
+ integrity sha512-38bzTsqMMCI46/TQnJwPPpy33EjLCc1Gsm2hRTF6zTMWnKsN61vdrpuzIEGQyKEhDSYDKyZHrrd5FMj4gcUHhw==
+ dependencies:
+ "@babel/compat-data" "^7.22.3"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-transform-parameters" "^7.22.3"
+
+"@babel/plugin-transform-object-super@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.18.6.tgz#fb3c6ccdd15939b6ff7939944b51971ddc35912c"
+ integrity sha512-uvGz6zk+pZoS1aTZrOvrbj6Pp/kK2mp45t2B+bTDre2UgsZZ8EZLSJtUg7m/no0zOJUWgFONpB7Zv9W2tSaFlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+ "@babel/helper-replace-supers" "^7.18.6"
+
+"@babel/plugin-transform-optional-catch-binding@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.22.3.tgz#e971a083fc7d209d9cd18253853af1db6d8dc42f"
+ integrity sha512-bnDFWXFzWY0BsOyqaoSXvMQ2F35zutQipugog/rqotL2S4ciFOKlRYUu9djt4iq09oh2/34hqfRR2k1dIvuu4g==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+
+"@babel/plugin-transform-optional-chaining@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.22.3.tgz#5fd24a4a7843b76da6aeec23c7f551da5d365290"
+ integrity sha512-63v3/UFFxhPKT8j8u1jTTGVyITxl7/7AfOqK8C5gz1rHURPUGe3y5mvIf68eYKGoBNahtJnTxBKug4BQOnzeJg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+"@babel/plugin-transform-parameters@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.22.3.tgz#24477acfd2fd2bc901df906c9bf17fbcfeee900d"
+ integrity sha512-x7QHQJHPuD9VmfpzboyGJ5aHEr9r7DsAsdxdhJiTB3J3j8dyl+NFZ+rX5Q2RWFDCs61c06qBfS4ys2QYn8UkMw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-private-methods@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.22.3.tgz#adac38020bab5047482d3297107c1f58e9c574f6"
+ integrity sha512-fC7jtjBPFqhqpPAE+O4LKwnLq7gGkD3ZmC2E3i4qWH34mH3gOg2Xrq5YMHUq6DM30xhqM1DNftiRaSqVjEG+ug==
+ dependencies:
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-private-property-in-object@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.22.3.tgz#031621b02c7b7d95389de1a3dba2fe9e8c548e56"
+ integrity sha512-C7MMl4qWLpgVCbXfj3UW8rR1xeCnisQ0cU7YJHV//8oNBS0aCIVg1vFnZXxOckHhEpQyqNNkWmvSEWnMLlc+Vw==
+ dependencies:
+ "@babel/helper-annotate-as-pure" "^7.18.6"
+ "@babel/helper-create-class-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/plugin-syntax-private-property-in-object" "^7.14.5"
+
+"@babel/plugin-transform-property-literals@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.18.6.tgz#e22498903a483448e94e032e9bbb9c5ccbfc93a3"
+ integrity sha512-cYcs6qlgafTud3PAzrrRNbQtfpQ8+y/+M5tKmksS9+M1ckbH6kzY8MrexEM9mcA6JDsukE19iIRvAyYl463sMg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-regenerator@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.21.5.tgz#576c62f9923f94bcb1c855adc53561fd7913724e"
+ integrity sha512-ZoYBKDb6LyMi5yCsByQ5jmXsHAQDDYeexT1Szvlmui+lADvfSecr5Dxd/PkrTC3pAD182Fcju1VQkB4oCp9M+w==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+ regenerator-transform "^0.15.1"
+
+"@babel/plugin-transform-reserved-words@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.18.6.tgz#b1abd8ebf8edaa5f7fe6bbb8d2133d23b6a6f76a"
+ integrity sha512-oX/4MyMoypzHjFrT1CdivfKZ+XvIPMFXwwxHp/r0Ddy2Vuomt4HDFGmft1TAY2yiTKiNSsh3kjBAzcM8kSdsjA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-shorthand-properties@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9"
+ integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-spread@^7.20.7":
+ version "7.20.7"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.20.7.tgz#c2d83e0b99d3bf83e07b11995ee24bf7ca09401e"
+ integrity sha512-ewBbHQ+1U/VnH1fxltbJqDeWBU1oNLG8Dj11uIv3xVf7nrQu0bPGe5Rf716r7K5Qz+SqtAOVswoVunoiBtGhxw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.20.2"
+ "@babel/helper-skip-transparent-expression-wrappers" "^7.20.0"
+
+"@babel/plugin-transform-sticky-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.18.6.tgz#c6706eb2b1524028e317720339583ad0f444adcc"
+ integrity sha512-kfiDrDQ+PBsQDO85yj1icueWMfGfJFKN1KCkndygtu/C9+XUfydLC8Iv5UYJqRwy4zk8EcplRxEOeLyjq1gm6Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-template-literals@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.18.9.tgz#04ec6f10acdaa81846689d63fae117dd9c243a5e"
+ integrity sha512-S8cOWfT82gTezpYOiVaGHrCbhlHgKhQt8XH5ES46P2XWmX92yisoZywf5km75wv5sYcXDUCLMmMxOLCtthDgMA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-typeof-symbol@^7.18.9":
+ version "7.18.9"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.18.9.tgz#c8cea68263e45addcd6afc9091429f80925762c0"
+ integrity sha512-SRfwTtF11G2aemAZWivL7PD+C9z52v9EvMqH9BuYbabyPuKUvSWks3oCg6041pT925L4zVFqaVBeECwsmlguEw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.18.9"
+
+"@babel/plugin-transform-unicode-escapes@^7.21.5":
+ version "7.21.5"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.21.5.tgz#1e55ed6195259b0e9061d81f5ef45a9b009fb7f2"
+ integrity sha512-LYm/gTOwZqsYohlvFUe/8Tujz75LqqVC2w+2qPHLR+WyWHGCZPN1KBpJCJn+4Bk4gOkQy/IXKIge6az5MqwlOg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-unicode-property-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.22.3.tgz#597b6a614dc93eaae605ee293e674d79d32eb380"
+ integrity sha512-5ScJ+OmdX+O6HRuMGW4kv7RL9vIKdtdAj9wuWUKy1wbHY3jaM/UlyIiC1G7J6UJiiyMukjjK0QwL3P0vBd0yYg==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/plugin-transform-unicode-regex@^7.18.6":
+ version "7.18.6"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.18.6.tgz#194317225d8c201bbae103364ffe9e2cea36cdca"
+ integrity sha512-gE7A6Lt7YLnNOL3Pb9BNeZvi+d8l7tcRrG4+pwJjK9hD2xX4mEvjlQW60G9EEmfXVYRPv9VRQcyegIVHCql/AA==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.18.6"
+ "@babel/helper-plugin-utils" "^7.18.6"
+
+"@babel/plugin-transform-unicode-sets-regex@^7.22.3":
+ version "7.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.22.3.tgz#7c14ee33fa69782b0101d0f7143d3fc73ce00700"
+ integrity sha512-hNufLdkF8vqywRp+P55j4FHXqAX2LRUccoZHH7AFn1pq5ZOO2ISKW9w13bFZVjBoTqeve2HOgoJCcaziJVhGNw==
+ dependencies:
+ "@babel/helper-create-regexp-features-plugin" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+
+"@babel/preset-env@^7.19":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.22.4.tgz#c86a82630f0e8c61d9bb9327b7b896732028cbed"
+ integrity sha512-c3lHOjbwBv0TkhYCr+XCR6wKcSZ1QbQTVdSkZUaVpLv8CVWotBMArWUi5UAJrcrQaEnleVkkvaV8F/pmc/STZQ==
+ dependencies:
+ "@babel/compat-data" "^7.22.3"
+ "@babel/helper-compilation-targets" "^7.22.1"
+ "@babel/helper-plugin-utils" "^7.21.5"
+ "@babel/helper-validator-option" "^7.21.0"
+ "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression" "^7.18.6"
+ "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining" "^7.22.3"
+ "@babel/plugin-proposal-private-property-in-object" "^7.21.0"
+ "@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.20.0"
+ "@babel/plugin-syntax-import-attributes" "^7.22.3"
+ "@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.21.5"
+ "@babel/plugin-transform-async-generator-functions" "^7.22.3"
+ "@babel/plugin-transform-async-to-generator" "^7.20.7"
+ "@babel/plugin-transform-block-scoped-functions" "^7.18.6"
+ "@babel/plugin-transform-block-scoping" "^7.21.0"
+ "@babel/plugin-transform-class-properties" "^7.22.3"
+ "@babel/plugin-transform-class-static-block" "^7.22.3"
+ "@babel/plugin-transform-classes" "^7.21.0"
+ "@babel/plugin-transform-computed-properties" "^7.21.5"
+ "@babel/plugin-transform-destructuring" "^7.21.3"
+ "@babel/plugin-transform-dotall-regex" "^7.18.6"
+ "@babel/plugin-transform-duplicate-keys" "^7.18.9"
+ "@babel/plugin-transform-dynamic-import" "^7.22.1"
+ "@babel/plugin-transform-exponentiation-operator" "^7.18.6"
+ "@babel/plugin-transform-export-namespace-from" "^7.22.3"
+ "@babel/plugin-transform-for-of" "^7.21.5"
+ "@babel/plugin-transform-function-name" "^7.18.9"
+ "@babel/plugin-transform-json-strings" "^7.22.3"
+ "@babel/plugin-transform-literals" "^7.18.9"
+ "@babel/plugin-transform-logical-assignment-operators" "^7.22.3"
+ "@babel/plugin-transform-member-expression-literals" "^7.18.6"
+ "@babel/plugin-transform-modules-amd" "^7.20.11"
+ "@babel/plugin-transform-modules-commonjs" "^7.21.5"
+ "@babel/plugin-transform-modules-systemjs" "^7.22.3"
+ "@babel/plugin-transform-modules-umd" "^7.18.6"
+ "@babel/plugin-transform-named-capturing-groups-regex" "^7.22.3"
+ "@babel/plugin-transform-new-target" "^7.22.3"
+ "@babel/plugin-transform-nullish-coalescing-operator" "^7.22.3"
+ "@babel/plugin-transform-numeric-separator" "^7.22.3"
+ "@babel/plugin-transform-object-rest-spread" "^7.22.3"
+ "@babel/plugin-transform-object-super" "^7.18.6"
+ "@babel/plugin-transform-optional-catch-binding" "^7.22.3"
+ "@babel/plugin-transform-optional-chaining" "^7.22.3"
+ "@babel/plugin-transform-parameters" "^7.22.3"
+ "@babel/plugin-transform-private-methods" "^7.22.3"
+ "@babel/plugin-transform-private-property-in-object" "^7.22.3"
+ "@babel/plugin-transform-property-literals" "^7.18.6"
+ "@babel/plugin-transform-regenerator" "^7.21.5"
+ "@babel/plugin-transform-reserved-words" "^7.18.6"
+ "@babel/plugin-transform-shorthand-properties" "^7.18.6"
+ "@babel/plugin-transform-spread" "^7.20.7"
+ "@babel/plugin-transform-sticky-regex" "^7.18.6"
+ "@babel/plugin-transform-template-literals" "^7.18.9"
+ "@babel/plugin-transform-typeof-symbol" "^7.18.9"
+ "@babel/plugin-transform-unicode-escapes" "^7.21.5"
+ "@babel/plugin-transform-unicode-property-regex" "^7.22.3"
+ "@babel/plugin-transform-unicode-regex" "^7.18.6"
+ "@babel/plugin-transform-unicode-sets-regex" "^7.22.3"
+ "@babel/preset-modules" "^0.1.5"
+ "@babel/types" "^7.22.4"
+ babel-plugin-polyfill-corejs2 "^0.4.3"
+ babel-plugin-polyfill-corejs3 "^0.8.1"
+ babel-plugin-polyfill-regenerator "^0.5.0"
+ core-js-compat "^3.30.2"
+ semver "^6.3.0"
+
+"@babel/preset-modules@^0.1.5":
+ version "0.1.5"
+ resolved "https://registry.yarnpkg.com/@babel/preset-modules/-/preset-modules-0.1.5.tgz#ef939d6e7f268827e1841638dc6ff95515e115d9"
+ integrity sha512-A57th6YRG7oR3cq/yt/Y84MvGgE0eJG2F1JLhKuyG+jFxEgrd/HAMJatiFtmOiZurz+0DkrvbheCLaV5f2JfjA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@babel/plugin-proposal-unicode-property-regex" "^7.4.4"
+ "@babel/plugin-transform-dotall-regex" "^7.4.4"
+ "@babel/types" "^7.4.4"
+ esutils "^2.0.2"
+
+"@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.22.3"
+ resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.3.tgz#0a7fce51d43adbf0f7b517a71f4c3aaca92ebcbb"
+ integrity sha512-XsDuspWKLUsxwCp6r7EhsExHtYfbe5oAGQ19kqngTdCPUoPQzOPdUbD/pB9PJiwb2ptYKQDjSJT3R6dC+EPqfQ==
+ dependencies:
+ regenerator-runtime "^0.13.11"
+
+"@babel/template@^7.18.10", "@babel/template@^7.20.7", "@babel/template@^7.21.9":
+ version "7.21.9"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.21.9.tgz#bf8dad2859130ae46088a99c1f265394877446fb"
+ integrity sha512-MK0X5k8NKOuWRamiEfc3KEJiHMTkGZNUjzMipqCGDDc6ijRl/B7RGSKVGncu4Ro/HdyzzY6cmoXuKI2Gffk7vQ==
+ dependencies:
+ "@babel/code-frame" "^7.21.4"
+ "@babel/parser" "^7.21.9"
+ "@babel/types" "^7.21.5"
+
+"@babel/traverse@^7.20.5", "@babel/traverse@^7.22.1":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.4.tgz#c3cf96c5c290bd13b55e29d025274057727664c0"
+ integrity sha512-Tn1pDsjIcI+JcLKq1AVlZEr4226gpuAQTsLMorsYg9tuS/kG7nuwwJ4AB8jfQuEgb/COBwR/DqJxmoiYFu5/rQ==
+ dependencies:
+ "@babel/code-frame" "^7.21.4"
+ "@babel/generator" "^7.22.3"
+ "@babel/helper-environment-visitor" "^7.22.1"
+ "@babel/helper-function-name" "^7.21.0"
+ "@babel/helper-hoist-variables" "^7.18.6"
+ "@babel/helper-split-export-declaration" "^7.18.6"
+ "@babel/parser" "^7.22.4"
+ "@babel/types" "^7.22.4"
+ debug "^4.1.0"
+ globals "^11.1.0"
+
+"@babel/types@^7.18.6", "@babel/types@^7.18.9", "@babel/types@^7.20.0", "@babel/types@^7.20.5", "@babel/types@^7.21.0", "@babel/types@^7.21.4", "@babel/types@^7.21.5", "@babel/types@^7.22.0", "@babel/types@^7.22.3", "@babel/types@^7.22.4", "@babel/types@^7.4.4":
+ version "7.22.4"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.4.tgz#56a2653ae7e7591365dabf20b76295410684c071"
+ integrity sha512-Tx9x3UBHTTsMSW85WB2kphxYQVvrZ/t1FxD88IpSgIjiUJlCm9z+xWIDwyo1vffTwSqteqyznB8ZE9vYYk16zA==
+ dependencies:
+ "@babel/helper-string-parser" "^7.21.5"
+ "@babel/helper-validator-identifier" "^7.19.1"
+ 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.0", "@jridgewell/gen-mapping@^0.3.2":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+ integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+ dependencies:
+ "@jridgewell/set-array" "^1.0.1"
+ "@jridgewell/sourcemap-codec" "^1.4.10"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+ integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+ integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/source-map@^0.3.3":
+ version "0.3.3"
+ resolved "https://registry.yarnpkg.com/@jridgewell/source-map/-/source-map-0.3.3.tgz#8108265659d4c33e72ffe14e33d6cc5eb59f2fda"
+ integrity sha512-b+fsZXeLYi9fEULmfBrhxn4IrPlINf8fiNarzTof004v3lFdntdwa9PF7vFJqm3mg7s+ScJMxXaE3Acp1irZcg==
+ dependencies:
+ "@jridgewell/gen-mapping" "^0.3.0"
+ "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/sourcemap-codec@1.4.14":
+ version "1.4.14"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+ integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+ version "1.4.15"
+ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+ integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+ version "0.3.18"
+ resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+ integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
+ dependencies:
+ "@jridgewell/resolve-uri" "3.1.0"
+ "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@types/eslint-scope@^3.7.3":
+ version "3.7.4"
+ resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
+ integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==
+ dependencies:
+ "@types/eslint" "*"
+ "@types/estree" "*"
+
+"@types/eslint@*":
+ version "8.40.0"
+ resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.40.0.tgz#ae73dc9ec5237f2794c4f79efd6a4c73b13daf23"
+ integrity sha512-nbq2mvc/tBrK9zQQuItvjJl++GTN5j06DaPtp3hZCpngmG6Q3xoyEmd0TwZI0gAy/G1X0zhGBbr2imsGFdFV0g==
+ dependencies:
+ "@types/estree" "*"
+ "@types/json-schema" "*"
+
+"@types/estree@*", "@types/estree@^1.0.0":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
+ integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
+
+"@types/json-schema@*", "@types/json-schema@^7.0.5", "@types/json-schema@^7.0.8":
+ version "7.0.12"
+ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+ integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
+
+"@types/node@*":
+ version "20.2.5"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.2.5.tgz#26d295f3570323b2837d322180dfbf1ba156fefb"
+ integrity sha512-JJulVEQXmiY9Px5axXHeYGLSjhkZEnD+MDPDGbCbIAbMslkKwmygtZFy1X6s/075Yo94sf8GuSlFfPzysQrWZQ==
+
+"@webassemblyjs/ast@1.11.6", "@webassemblyjs/ast@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.6.tgz#db046555d3c413f8966ca50a95176a0e2c642e24"
+ integrity sha512-IN1xI7PwOvLPgjcf180gC1bqn3q/QaOCwYUahIOhbYUu8KA/3tw2RT/T0Gidi1l7Hhj5D/INhJxiICObqpMu4Q==
+ 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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.6.tgz#b66d73c43e296fd5e88006f18524feb0f2c7c093"
+ integrity sha512-z3nFzdcp1mb8nEOFFk8DrYLpHvhKC3grJD2ardfKOzmbmJvEf/tPIqCY+sNcwZIY8ZD7IkB2l7/pqhUhqm7hLA==
+
+"@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.6.tgz#ff97f3863c55ee7f580fd5c41a381e9def4aa577"
+ integrity sha512-LPpZbSOwTpEC2cgn4hTydySy1Ke+XEu+ETXuoyvuyezHO3Kjdu90KK95Sh9xTbmjrCsUwvWwCOQQNta37VrS9g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+
+"@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.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.6.tgz#c72fa8220524c9b416249f3d94c2958dfe70ceab"
+ integrity sha512-Ybn2I6fnfIGuCR+Faaz7YcvtBKxvoLV3Lebn1tM4o/IAJzmi9AWYIPWpyBfU8cC+JxAO57bk4+zdsTjJR+VTOw==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/helper-wasm-bytecode" "1.11.6"
+ "@webassemblyjs/helper-wasm-section" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-opt" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+ "@webassemblyjs/wast-printer" "1.11.6"
+
+"@webassemblyjs/wasm-gen@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.6.tgz#fb5283e0e8b4551cc4e9c3c0d7184a65faf7c268"
+ integrity sha512-3XOqkZP/y6B4F0PBAXvI1/bky7GryoogUtfwExeP/v7Nzwo1QLcq5oQmpKlftZLbT+ERUOAZVQjuNVak6UXjPA==
+ dependencies:
+ "@webassemblyjs/ast" "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/wasm-opt@1.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.6.tgz#d9a22d651248422ca498b09aa3232a81041487c2"
+ integrity sha512-cOrKuLRE7PCe6AsOVl7WasYf3wbSo4CeOk6PkrjS7g57MFfVUF9u6ysQBBODX0LdgSvQqRiGz3CXvIDKcPNy4g==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@webassemblyjs/helper-buffer" "1.11.6"
+ "@webassemblyjs/wasm-gen" "1.11.6"
+ "@webassemblyjs/wasm-parser" "1.11.6"
+
+"@webassemblyjs/wasm-parser@1.11.6", "@webassemblyjs/wasm-parser@^1.11.5":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.6.tgz#bb85378c527df824004812bbdb784eea539174a1"
+ integrity sha512-6ZwPeGzMJM3Dqp3hCsLgESxBGtT/OeCvCZ4TA1JUPYgmhAx38tTPR9JaKy0S5H3evQpO/h2uWs2j6Yc/fjkpTQ==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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.11.6":
+ version "1.11.6"
+ resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.6.tgz#a7bf8dd7e362aeb1668ff43f35cb849f188eff20"
+ integrity sha512-JM7AhRcE+yW2GWYaKeHL5vt4xqee5N2WcezptmgyhNS+ScggqcT1OtXykhAb13Sn5Yas0j2uv9tHgrjwvzAP4A==
+ dependencies:
+ "@webassemblyjs/ast" "1.11.6"
+ "@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-assertions@^1.9.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac"
+ integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==
+
+acorn@^8.7.1, acorn@^8.8.2:
+ version "8.8.2"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.2.tgz#1b2f25db02af965399b9776b0c2c391276d37c4a"
+ integrity sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==
+
+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.3:
+ version "0.4.3"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.3.tgz#75044d90ba5043a5fb559ac98496f62f3eb668fd"
+ integrity sha512-bM3gHc337Dta490gg+/AseNB9L4YLHxq1nGKZZSHbhXv4aTYU2MD2cjza1Ru4S6975YLTaL1K8uJf6ukJhhmtw==
+ dependencies:
+ "@babel/compat-data" "^7.17.7"
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ semver "^6.1.1"
+
+babel-plugin-polyfill-corejs3@^0.8.1:
+ version "0.8.1"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.8.1.tgz#39248263c38191f0d226f928d666e6db1b4b3a8a"
+ integrity sha512-ikFrZITKg1xH6pLND8zT14UPgjKHiGLqex7rGEZCH2EvhsneJaJPemmpQaIZV5AL03II+lXylw3UmddDK8RU5Q==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+ core-js-compat "^3.30.1"
+
+babel-plugin-polyfill-regenerator@^0.5.0:
+ version "0.5.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.5.0.tgz#e7344d88d9ef18a3c47ded99362ae4a757609380"
+ integrity sha512-hDJtKjMLVa7Z+LwnTCxoDLQj6wdc+B8dun7ayF2fYieI6OzfuvcLMB32ihJZ4UhCBwNYGl5bg/x/P9cMdnkc2g==
+ dependencies:
+ "@babel/helper-define-polyfill-provider" "^0.4.0"
+
+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.2.0"
+ resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
+ integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==
+
+braces@~3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+ integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+ dependencies:
+ fill-range "^7.0.1"
+
+browserslist@^4.14.5, browserslist@^4.21.3, browserslist@^4.21.5:
+ version "4.21.7"
+ resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.7.tgz#e2b420947e5fb0a58e8f4668ae6e23488127e551"
+ integrity sha512-BauCXrQ7I2ftSqd2mvKHGo85XR0u7Ru3C/Hxsy/0TkfCtjrmAbPdzLGasmoiBxplpDXlPvdjX9u7srIMfgasNA==
+ dependencies:
+ caniuse-lite "^1.0.30001489"
+ electron-to-chromium "^1.4.411"
+ node-releases "^2.0.12"
+ update-browserslist-db "^1.0.11"
+
+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.30001489:
+ version "1.0.30001495"
+ resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001495.tgz#64a0ccef1911a9dcff647115b4430f8eff1ef2d9"
+ integrity sha512-F6x5IEuigtUfU5ZMQK2jsy5JqUUlEFRVZq8bO2a+ysq5K7jD6PPc9YXZj78xDNS3uNchesp1Jw47YXEqr+Viyg==
+
+chalk@^2.0.0:
+ 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.5.3"
+ resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd"
+ integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==
+ 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.3"
+ resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz#1015eced4741e15d06664a957dbbf50d041e26ac"
+ integrity sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==
+
+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@^1.7.0:
+ version "1.9.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+ integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+core-js-compat@^3.30.1, core-js-compat@^3.30.2:
+ version "3.30.2"
+ resolved "https://registry.yarnpkg.com/core-js-compat/-/core-js-compat-3.30.2.tgz#83f136e375babdb8c80ad3c22d67c69098c1dd8b"
+ integrity sha512-nriW1nuJjUgvkEjIot1Spwakz52V9YkYHZAQG6A1eCgC8AA1p0zngrQEP9R0+V6hji5XilWKG1Bd0YRppmGimA==
+ dependencies:
+ browserslist "^4.21.5"
+
+core-js@^3.25.0:
+ version "3.30.2"
+ resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.30.2.tgz#6528abfda65e5ad728143ea23f7a14f0dcf503fc"
+ integrity sha512-uBJiDmwqsbJCWHAwjrx3cvjbMXP7xD72Dmsn5LOJpiRmE3WbBbN5rCqQ2Qh6Ek6/eOrjlWngEynBWo4VxerQhg==
+
+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:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+ integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+ dependencies:
+ ms "2.1.2"
+
+electron-to-chromium@^1.4.411:
+ version "1.4.425"
+ resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.425.tgz#399df13091b836d28283a545c25c8e4d9da86da8"
+ integrity sha512-wv1NufHxu11zfDbY4fglYQApMswleE9FL/DSeyOyauVXDZ+Kco96JK/tPfBUaDqfRarYp2WH2hJ/5UnVywp9Jg==
+
+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.14.1:
+ version "5.14.1"
+ resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.14.1.tgz#de684b6803724477a4af5d74ccae5de52c25f6b3"
+ integrity sha512-Vklwq2vDKtl0y/vtwjSesgJ5MYS7Etuk5txS8VdKL4AOS1aUlD96zqIfsOSLQsdv3xgMRbtkWM8eG9XDfKUPow==
+ dependencies:
+ graceful-fs "^4.2.4"
+ tapable "^2.2.0"
+
+envinfo@^7.7.3:
+ version "7.8.1"
+ resolved "https://registry.yarnpkg.com/envinfo/-/envinfo-7.8.1.tgz#06377e3e5f4d379fea7ac592d5ad8927e0c4d475"
+ integrity sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==
+
+es-module-lexer@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527"
+ integrity sha512-9978wrXM50Y4rTMmW5kXIC09ZdXQZqkE4mxhwkd8VbzsGkXGPgV4zWuqQJgCEzYngdo2dYDa0l8xhX4fkSwJSg==
+
+escalade@^3.1.1:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+ integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+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.0.1:
+ version "7.0.1"
+ resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+ integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+ 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"
+
+fsevents@~2.3.2:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+ integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+ integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+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.4, graceful-fs@^4.2.9:
+ 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==
+
+has@^1.0.3:
+ version "1.0.3"
+ resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+ integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+ dependencies:
+ function-bind "^1.1.1"
+
+immutable@^4.0.0:
+ version "4.3.0"
+ resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.3.0.tgz#eb1738f14ffb39fd068b1dbe1296117484dd34be"
+ integrity sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==
+
+import-local@^3.0.2:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+ integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+ 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.11.0:
+ version "2.12.1"
+ resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+ integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
+ dependencies:
+ has "^1.0.3"
+
+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.2:
+ 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.12:
+ version "2.0.12"
+ resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.12.tgz#35627cc224a23bfb06fb3380f2b3afaaa7eb1039"
+ integrity sha512-QzsYKWhXTWx8h1kIvqfnC++o0pEmpRQA/aenALsL2F4pqNVr7YzcdMlDij5WBnwftRbJCNJL/O7zdKaxKPHqgQ==
+
+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:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+ integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+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.0"
+ resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+ integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+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.0"
+ resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-10.1.0.tgz#7c3192cab6dd24e21cb4461e5ddd7dd24fa8374c"
+ integrity sha512-d1VudCLoIGitcU/hEg2QqvyGZQmdC0Lf8BqdOMXGFSvJP4bNV1+XqbPQeHHLD51Jh4QJJ225dlIFvY4Ly6MXmQ==
+ 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.13.11:
+ version "0.13.11"
+ resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9"
+ integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==
+
+regenerator-transform@^0.15.1:
+ version "0.15.1"
+ resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.1.tgz#f6c4e99fc1b4591f780db2586328e4d9a9d8dc56"
+ integrity sha512-knzmNAcuyxV+gQCufkYcvOqX/qIIfHLv0u5x79kRxuGojfYVky1f15TzZEu2Avte8QGepvUNTnLskf8E6X6Vyg==
+ 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.2"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+ integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+ dependencies:
+ is-core-module "^2.11.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.63.2"
+ resolved "https://registry.yarnpkg.com/sass/-/sass-1.63.2.tgz#75f7d9a8e67d1d5b98a989507f4d98b6067b1f75"
+ integrity sha512-u56TU0AIFqMtauKl/OJ1AeFsXqRHkgO7nCWmHaDwfxDo9GUMSqBA4NEh6GMuh1CYVM7zuROYtZrHzPc2ixK+ww==
+ 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.1.2:
+ version "3.2.0"
+ resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-3.2.0.tgz#7dff4881064a4f22c09f0c6a1457feb820fd0636"
+ integrity sha512-0zTyLGyDJYd/MBxG1AhJkKa6fpEBds4OQO2ut0w7OYG+ZGhGea09lijvzsqegYSik88zc7cUtIlnnO+/BvD6gQ==
+ dependencies:
+ "@types/json-schema" "^7.0.8"
+ ajv "^6.12.5"
+ ajv-keywords "^3.5.2"
+
+semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0:
+ version "6.3.0"
+ resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+ integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+serialize-javascript@^6.0.1:
+ version "6.0.1"
+ resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.1.tgz#b206efb27c3da0b0ab6b52f48d170b7996458e5c"
+ integrity sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==
+ 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.0.2"
+ resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
+ integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==
+
+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.7:
+ version "5.3.9"
+ resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1"
+ integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA==
+ dependencies:
+ "@jridgewell/trace-mapping" "^0.3.17"
+ jest-worker "^27.4.5"
+ schema-utils "^3.1.1"
+ serialize-javascript "^6.0.1"
+ terser "^5.16.8"
+
+terser@^5.16.8:
+ version "5.17.7"
+ resolved "https://registry.yarnpkg.com/terser/-/terser-5.17.7.tgz#2a8b134826fe179b711969fd9d9a0c2479b2a8c3"
+ integrity sha512-/bi0Zm2C6VAexlGgLlVxA0P2lru/sdLyfCVaRMfKVo9nWxbmz7f/sD8VPybPeSUJaJcwmCJis9pBIhcVcG1QcQ==
+ 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"
+
+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.0.11:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
+ integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+ dependencies:
+ escalade "^3.1.1"
+ picocolors "^1.0.0"
+
+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.0:
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
+ integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
+ 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.9.0"
+ resolved "https://registry.yarnpkg.com/webpack-merge/-/webpack-merge-5.9.0.tgz#dc160a1c4cf512ceca515cc231669e9ddb133826"
+ integrity sha512-6NbRQw4+Sy50vYNTw7EyOn41OZItPiXB8GNv3INSoe3PSFaHJEz3SHTrYVaRm2LilNGnFUzh0FAwqPEmU/CwDg==
+ dependencies:
+ clone-deep "^4.0.1"
+ 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.86.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.86.0.tgz#b0eb81794b62aee0b7e7eb8c5073495217d9fc6d"
+ integrity sha512-3BOvworZ8SO/D4GVP+GoRC3fVeg5MO4vzmq8TJJEkdmopxyazGDxN8ClqN12uzrZW9Tv8EED8v5VSb6Sqyi0pg==
+ dependencies:
+ "@types/eslint-scope" "^3.7.3"
+ "@types/estree" "^1.0.0"
+ "@webassemblyjs/ast" "^1.11.5"
+ "@webassemblyjs/wasm-edit" "^1.11.5"
+ "@webassemblyjs/wasm-parser" "^1.11.5"
+ acorn "^8.7.1"
+ acorn-import-assertions "^1.9.0"
+ browserslist "^4.14.5"
+ chrome-trace-event "^1.0.2"
+ enhanced-resolve "^5.14.1"
+ 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.9"
+ 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.1.2"
+ tapable "^2.1.1"
+ terser-webpack-plugin "^5.3.7"
+ watchpack "^2.4.0"
+ 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-uninstall/package.json b/modules/ppcp-uninstall/package.json
index b7038cf3a..40bb2f18f 100644
--- a/modules/ppcp-uninstall/package.json
+++ b/modules/ppcp-uninstall/package.json
@@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-uninstall/services.php b/modules/ppcp-uninstall/services.php
index 472b64244..ca67dc71b 100644
--- a/modules/ppcp-uninstall/services.php
+++ b/modules/ppcp-uninstall/services.php
@@ -24,7 +24,6 @@ return array(
'uninstall.ppcp-all-option-names' => function( ContainerInterface $container ) : array {
return array(
$container->get( 'webhook.last-webhook-storage.key' ),
- PayPalRequestIdRepository::KEY,
'woocommerce_ppcp-is_pay_later_settings_migrated',
'woocommerce_' . PayPalGateway::ID . '_settings',
'woocommerce_' . CreditCardGateway::ID . '_settings',
@@ -34,6 +33,7 @@ return array(
'woocommerce-ppcp-version',
WebhookSimulation::OPTION_ID,
WebhookRegistrar::KEY,
+ 'ppcp_payment_tokens_migration_initialized',
);
},
@@ -41,6 +41,13 @@ return array(
return array(
'woocommerce_paypal_payments_check_pui_payment_captured',
'woocommerce_paypal_payments_check_saved_payment',
+ 'woocommerce_paypal_payments_payment_tokens_migration',
+ );
+ },
+
+ 'uninstall.ppcp-all-action-names' => function( ContainerInterface $container ) : array {
+ return array(
+ 'woocommerce_paypal_payments_uninstall',
);
},
diff --git a/modules/ppcp-uninstall/src/ClearDatabase.php b/modules/ppcp-uninstall/src/ClearDatabase.php
index fc09519a5..ed5d73bb6 100644
--- a/modules/ppcp-uninstall/src/ClearDatabase.php
+++ b/modules/ppcp-uninstall/src/ClearDatabase.php
@@ -31,4 +31,13 @@ class ClearDatabase implements ClearDatabaseInterface {
as_unschedule_action( $action_name );
}
}
+
+ /**
+ * {@inheritDoc}
+ */
+ public function clear_actions( array $action_names ): void {
+ foreach ( $action_names as $action_name ) {
+ do_action( $action_name );
+ }
+ }
}
diff --git a/modules/ppcp-uninstall/src/ClearDatabaseInterface.php b/modules/ppcp-uninstall/src/ClearDatabaseInterface.php
index 6d6aedb77..34d9b1469 100644
--- a/modules/ppcp-uninstall/src/ClearDatabaseInterface.php
+++ b/modules/ppcp-uninstall/src/ClearDatabaseInterface.php
@@ -29,4 +29,12 @@ interface ClearDatabaseInterface {
*/
public function clear_scheduled_actions( array $action_names ): void;
+ /**
+ * Clears the given actions.
+ *
+ * @param string[] $action_names The list of action names.
+ * @throws RuntimeException If problem clearing.
+ */
+ public function clear_actions( array $action_names ): void;
+
}
diff --git a/modules/ppcp-uninstall/src/UninstallModule.php b/modules/ppcp-uninstall/src/UninstallModule.php
index 1d740e4b2..25d65ae0f 100644
--- a/modules/ppcp-uninstall/src/UninstallModule.php
+++ b/modules/ppcp-uninstall/src/UninstallModule.php
@@ -47,8 +47,9 @@ class UninstallModule implements ModuleInterface {
$clear_db_endpoint = $container->get( 'uninstall.clear-db-endpoint' );
$option_names = $container->get( 'uninstall.ppcp-all-option-names' );
$scheduled_action_names = $container->get( 'uninstall.ppcp-all-scheduled-action-names' );
+ $action_names = $container->get( 'uninstall.ppcp-all-action-names' );
- $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names );
+ $this->handleClearDbAjaxRequest( $request_data, $clear_db, $clear_db_endpoint, $option_names, $scheduled_action_names, $action_names );
}
/**
@@ -69,22 +70,31 @@ class UninstallModule implements ModuleInterface {
* @param string $nonce The nonce.
* @param string[] $option_names The list of option names.
* @param string[] $scheduled_action_names The list of scheduled action names.
+ * @param string[] $action_names The list of action names.
*/
protected function handleClearDbAjaxRequest(
RequestData $request_data,
ClearDatabaseInterface $clear_db,
string $nonce,
array $option_names,
- array $scheduled_action_names
+ array $scheduled_action_names,
+ array $action_names
): void {
add_action(
"wc_ajax_{$nonce}",
- static function () use ( $request_data, $clear_db, $nonce, $option_names, $scheduled_action_names ) {
+ static function () use ( $request_data, $clear_db, $nonce, $option_names, $scheduled_action_names, $action_names ) {
try {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return false;
+ }
+
// Validate nonce.
$request_data->read_request( $nonce );
+
$clear_db->delete_options( $option_names );
$clear_db->clear_scheduled_actions( $scheduled_action_names );
+ $clear_db->clear_actions( $action_names );
wp_send_json_success();
return true;
diff --git a/modules/ppcp-uninstall/yarn.lock b/modules/ppcp-uninstall/yarn.lock
index 54ef909ff..565a1b6b8 100644
--- a/modules/ppcp-uninstall/yarn.lock
+++ b/modules/ppcp-uninstall/yarn.lock
@@ -1242,12 +1242,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz#ba2b5939ce62c238db6d93d81c9b111b29b855e9"
integrity sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==
-acorn@^8.5.0:
- version "8.7.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
- integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
-
-acorn@^8.7.1:
+acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -1785,9 +1780,9 @@ json-schema-traverse@^0.4.1:
integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
json5@^2.1.2, json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+ 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"
@@ -1805,9 +1800,9 @@ loader-runner@^4.2.0:
integrity sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==
loader-utils@^2.0.0:
- version "2.0.2"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.2.tgz#d6e3b4fb81870721ae4e0868ab11dd638368c129"
- integrity sha512-TM57VeHptv569d/GKh6TAYdzKblwDNiumOdkFnejjD0XwTH87K90w3O7AiJRqdQoXygvi1VQTJTLGhJl7WqA7A==
+ 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"
@@ -2263,10 +2258,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-vaulting/resources/js/myaccount-payments.js b/modules/ppcp-vaulting/resources/js/myaccount-payments.js
deleted file mode 100644
index 6023347d8..000000000
--- a/modules/ppcp-vaulting/resources/js/myaccount-payments.js
+++ /dev/null
@@ -1,42 +0,0 @@
-document.addEventListener(
- 'DOMContentLoaded',
- () => {
- jQuery('.ppcp-delete-payment-button').click(async (event) => {
- event.preventDefault();
- jQuery(this).prop('disabled', true);
- const token = event.target.id;
-
- const response = await fetch(
- PayPalCommerceGatewayVaulting.delete.endpoint,
- {
- method: 'POST',
- credentials: 'same-origin',
- headers: {
- 'content-type': 'application/json'
- },
- body: JSON.stringify(
- {
- nonce: PayPalCommerceGatewayVaulting.delete.nonce,
- token,
- }
- )
- }
- );
-
- const reportError = error => {
- alert(error);
- }
-
- if (!response.ok) {
- try {
- const result = await response.json();
- reportError(result.data);
- } catch (exc) {
- console.error(exc);
- reportError(response.status);
- }
- }
-
- window.location.reload();
- });
- });
diff --git a/modules/ppcp-vaulting/services.php b/modules/ppcp-vaulting/services.php
index dc03437ff..101e0bb4c 100644
--- a/modules/ppcp-vaulting/services.php
+++ b/modules/ppcp-vaulting/services.php
@@ -10,8 +10,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Vaulting;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
-use WooCommerce\PayPalCommerce\Vaulting\Assets\MyAccountPaymentsAssets;
-use WooCommerce\PayPalCommerce\Vaulting\Endpoint\DeletePaymentTokenEndpoint;
return array(
'vaulting.module-url' => static function ( ContainerInterface $container ): string {
@@ -20,37 +18,11 @@ return array(
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
);
},
- 'vaulting.assets.myaccount-payments' => function( ContainerInterface $container ) : MyAccountPaymentsAssets {
- return new MyAccountPaymentsAssets(
- $container->get( 'vaulting.module-url' ),
- $container->get( 'ppcp.asset-version' )
- );
- },
- 'vaulting.payment-tokens-renderer' => static function (): PaymentTokensRenderer {
- return new PaymentTokensRenderer();
- },
'vaulting.repository.payment-token' => static function ( ContainerInterface $container ): PaymentTokenRepository {
$factory = $container->get( 'api.factory.payment-token' );
$endpoint = $container->get( 'api.endpoint.payment-token' );
return new PaymentTokenRepository( $factory, $endpoint );
},
- 'vaulting.endpoint.delete' => function( ContainerInterface $container ) : DeletePaymentTokenEndpoint {
- return new DeletePaymentTokenEndpoint(
- $container->get( 'vaulting.repository.payment-token' ),
- $container->get( 'button.request-data' ),
- $container->get( 'woocommerce.logger.woocommerce' )
- );
- },
- 'vaulting.payment-token-checker' => function( ContainerInterface $container ) : PaymentTokenChecker {
- return new PaymentTokenChecker(
- $container->get( 'vaulting.repository.payment-token' ),
- $container->get( 'api.repository.order' ),
- $container->get( 'wcgateway.settings' ),
- $container->get( 'wcgateway.processor.authorized-payments' ),
- $container->get( 'api.endpoint.payments' ),
- $container->get( 'woocommerce.logger.woocommerce' )
- );
- },
'vaulting.customer-approval-listener' => function( ContainerInterface $container ) : CustomerApprovalListener {
return new CustomerApprovalListener(
$container->get( 'api.endpoint.payment-token' ),
@@ -70,4 +42,18 @@ return array(
$container->get( 'wcgateway.settings' )
);
},
+ 'vaulting.payment-token-factory' => function( ContainerInterface $container ): PaymentTokenFactory {
+ return new PaymentTokenFactory();
+ },
+ 'vaulting.payment-token-helper' => function( ContainerInterface $container ): PaymentTokenHelper {
+ return new PaymentTokenHelper();
+ },
+ 'vaulting.payment-tokens-migration' => function( ContainerInterface $container ): PaymentTokensMigration {
+ return new PaymentTokensMigration(
+ $container->get( 'vaulting.payment-token-factory' ),
+ $container->get( 'vaulting.repository.payment-token' ),
+ $container->get( 'vaulting.payment-token-helper' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
+ );
+ },
);
diff --git a/modules/ppcp-vaulting/src/Assets/MyAccountPaymentsAssets.php b/modules/ppcp-vaulting/src/Assets/MyAccountPaymentsAssets.php
deleted file mode 100644
index a6662bd02..000000000
--- a/modules/ppcp-vaulting/src/Assets/MyAccountPaymentsAssets.php
+++ /dev/null
@@ -1,77 +0,0 @@
-module_url = untrailingslashit( $module_url );
- $this->version = $version;
- }
-
- /**
- * Enqueues the necessary scripts.
- *
- * @return void
- */
- public function enqueue(): void {
- wp_enqueue_script(
- 'ppcp-vaulting-myaccount-payments',
- untrailingslashit( $this->module_url ) . '/assets/js/myaccount-payments.js',
- array( 'jquery' ),
- $this->version,
- true
- );
- }
-
- /**
- * Localize script.
- */
- public function localize() {
- wp_localize_script(
- 'ppcp-vaulting-myaccount-payments',
- 'PayPalCommerceGatewayVaulting',
- array(
- 'delete' => array(
- 'endpoint' => \WC_AJAX::get_endpoint( DeletePaymentTokenEndpoint::ENDPOINT ),
- 'nonce' => wp_create_nonce( DeletePaymentTokenEndpoint::nonce() ),
- ),
- )
- );
- }
-}
diff --git a/modules/ppcp-vaulting/src/Endpoint/DeletePaymentTokenEndpoint.php b/modules/ppcp-vaulting/src/Endpoint/DeletePaymentTokenEndpoint.php
deleted file mode 100644
index d6a232942..000000000
--- a/modules/ppcp-vaulting/src/Endpoint/DeletePaymentTokenEndpoint.php
+++ /dev/null
@@ -1,95 +0,0 @@
-repository = $repository;
- $this->request_data = $request_data;
- $this->logger = $logger;
- }
-
- /**
- * Returns the nonce for the endpoint.
- *
- * @return string
- */
- public static function nonce(): string {
- return self::ENDPOINT;
- }
-
- /**
- * Handles the incoming request.
- */
- public function handle_request() {
- try {
- $data = $this->request_data->read_request( $this->nonce() );
-
- $tokens = $this->repository->all_for_user_id( get_current_user_id() );
- if ( $tokens ) {
- foreach ( $tokens as $token ) {
- if ( isset( $data['token'] ) && $token->id() === $data['token'] ) {
- if ( $this->repository->delete_token( get_current_user_id(), $token ) ) {
- wp_send_json_success();
- return true;
- }
-
- wp_send_json_error( 'Could not delete payment token.' );
- return false;
- }
- }
- }
- } catch ( Exception $error ) {
- $this->logger->error( 'Failed to delete payment: ' . $error->getMessage() );
- wp_send_json_error( $error->getMessage(), 403 );
- return false;
- }
- }
-}
-
diff --git a/modules/ppcp-vaulting/src/PaymentTokenFactory.php b/modules/ppcp-vaulting/src/PaymentTokenFactory.php
new file mode 100644
index 000000000..86aa11bd5
--- /dev/null
+++ b/modules/ppcp-vaulting/src/PaymentTokenFactory.php
@@ -0,0 +1,30 @@
+get_token() === $token_id ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
diff --git a/modules/ppcp-vaulting/src/PaymentTokenPayPal.php b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php
new file mode 100644
index 000000000..1a5858118
--- /dev/null
+++ b/modules/ppcp-vaulting/src/PaymentTokenPayPal.php
@@ -0,0 +1,51 @@
+ '',
+ );
+
+ /**
+ * Get PayPal account email.
+ *
+ * @return string PayPal account email.
+ */
+ public function get_email() {
+ return $this->get_meta( 'email' );
+ }
+
+ /**
+ * Set PayPal account email.
+ *
+ * @param string $email PayPal account email.
+ */
+ public function set_email( $email ) {
+ $this->add_meta_data( 'email', $email, true );
+ }
+}
diff --git a/modules/ppcp-vaulting/src/PaymentTokenRepository.php b/modules/ppcp-vaulting/src/PaymentTokenRepository.php
index d159aae41..ef8a31311 100644
--- a/modules/ppcp-vaulting/src/PaymentTokenRepository.php
+++ b/modules/ppcp-vaulting/src/PaymentTokenRepository.php
@@ -20,8 +20,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
class PaymentTokenRepository {
- const USER_META = 'ppcp-vault-token';
-
/**
* The payment token factory.
*
@@ -51,27 +49,6 @@ class PaymentTokenRepository {
$this->endpoint = $endpoint;
}
- /**
- * Return a token for a user.
- *
- * @param int $id The user id.
- *
- * @return PaymentToken|null
- */
- public function for_user_id( int $id ) {
- try {
- $token = (array) get_user_meta( $id, self::USER_META, true );
- if ( ! $token || ! isset( $token['id'] ) ) {
- return $this->fetch_for_user_id( $id );
- }
-
- $token = $this->factory->from_array( $token );
- return $token;
- } catch ( RuntimeException $error ) {
- return null;
- }
- }
-
/**
* Return all tokens for a user.
*
@@ -81,7 +58,6 @@ class PaymentTokenRepository {
public function all_for_user_id( int $id ) {
try {
$tokens = $this->endpoint->for_user( $id );
- update_user_meta( $id, self::USER_META, $tokens );
return $tokens;
} catch ( RuntimeException $exception ) {
return array();
@@ -97,7 +73,6 @@ class PaymentTokenRepository {
* @return bool
*/
public function delete_token( int $user_id, PaymentToken $token ): bool {
- delete_user_meta( $user_id, self::USER_META );
return $this->endpoint->delete_token( $token );
}
@@ -121,21 +96,6 @@ class PaymentTokenRepository {
return $this->token_contains_source( $tokens, 'paypal' );
}
- /**
- * Fetch PaymentToken from PayPal for a user.
- *
- * @param int $id The user id.
- * @return PaymentToken
- */
- private function fetch_for_user_id( int $id ): PaymentToken {
-
- $tokens = $this->endpoint->for_user( $id );
- $token = current( $tokens );
- $token_array = $token->to_array();
- update_user_meta( $id, self::USER_META, $token_array );
- return $token;
- }
-
/**
* Checks if tokens has the given source.
*
diff --git a/modules/ppcp-vaulting/src/PaymentTokensMigration.php b/modules/ppcp-vaulting/src/PaymentTokensMigration.php
new file mode 100644
index 000000000..a7d3511cc
--- /dev/null
+++ b/modules/ppcp-vaulting/src/PaymentTokensMigration.php
@@ -0,0 +1,139 @@
+payment_token_factory = $payment_token_factory;
+ $this->payment_token_repository = $payment_token_repository;
+ $this->logger = $logger;
+ $this->payment_token_helper = $payment_token_helper;
+ }
+
+ /**
+ * Migrates user existing vaulted tokens into WC payment tokens API.
+ *
+ * @param int $id WooCommerce customer id.
+ */
+ public function migrate_payment_tokens_for_user( int $id ):void {
+ $tokens = $this->payment_token_repository->all_for_user_id( $id );
+ $total_tokens = count( $tokens );
+ $this->logger->info( 'Migrating ' . (string) $total_tokens . ' tokens for user ' . (string) $id );
+
+ foreach ( $tokens as $token ) {
+ if ( isset( $token->source()->card ) ) {
+ $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, CreditCardGateway::ID );
+ if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) {
+ $this->logger->info( 'Token already exist for user ' . (string) $id );
+ continue;
+ }
+
+ $payment_token_acdc = new WC_Payment_Token_CC();
+ $payment_token_acdc->set_token( $token->id() );
+ $payment_token_acdc->set_user_id( $id );
+ $payment_token_acdc->set_gateway_id( CreditCardGateway::ID );
+ $payment_token_acdc->set_last4( $token->source()->card->last_digits );
+ $payment_token_acdc->set_card_type( $token->source()->card->brand );
+ $payment_token_acdc->set_expiry_year( '0000' );
+ $payment_token_acdc->set_expiry_month( '00' );
+
+ try {
+ $payment_token_acdc->save();
+ } catch ( Exception $exception ) {
+ $this->logger->error(
+ "Could not save WC payment token credit card {$token->id()} for user {$id}. "
+ . $exception->getMessage()
+ );
+ continue;
+ }
+ } elseif ( $token->source()->paypal ) {
+ $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $id, PayPalGateway::ID );
+ if ( $this->payment_token_helper->token_exist( $wc_tokens, $token->id() ) ) {
+ $this->logger->info( 'Token already exist for user ' . (string) $id );
+ continue;
+ }
+
+ $payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
+ assert( $payment_token_paypal instanceof PaymentTokenPayPal );
+
+ $payment_token_paypal->set_token( $token->id() );
+ $payment_token_paypal->set_user_id( $id );
+ $payment_token_paypal->set_gateway_id( PayPalGateway::ID );
+
+ $email = $token->source()->paypal->payer->email_address ?? '';
+ if ( $email && is_email( $email ) ) {
+ $payment_token_paypal->set_email( $email );
+ }
+
+ try {
+ $payment_token_paypal->save();
+ } catch ( Exception $exception ) {
+ $this->logger->error(
+ "Could not save WC payment token PayPal {$token->id()} for user {$id}. "
+ . $exception->getMessage()
+ );
+ continue;
+ }
+ }
+ }
+ }
+}
diff --git a/modules/ppcp-vaulting/src/PaymentTokensRenderer.php b/modules/ppcp-vaulting/src/PaymentTokensRenderer.php
deleted file mode 100644
index 26126df14..000000000
--- a/modules/ppcp-vaulting/src/PaymentTokensRenderer.php
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
- source() ?? null;
- if ( $source && isset( $source->card ) ) {
- ?>
-
- card->brand ) . ' ...' . esc_attr( $source->card->last_digits ); ?>
-
-
-
-
- paypal ) ) {
- ?>
-
- paypal->payer->email_address ); ?>
-
-
-
-
-
-
-
-
-
-
-
-
- get( 'wcgateway.settings' );
- if ( ! $settings->has( 'vault_enabled' ) || ! $settings->get( 'vault_enabled' ) ) {
- return;
- }
-
$listener = $container->get( 'vaulting.customer-approval-listener' );
assert( $listener instanceof CustomerApprovalListener );
$listener->listen();
- add_filter(
- 'woocommerce_account_menu_items',
- function( $menu_links ) {
- $menu_links = array_slice( $menu_links, 0, 5, true )
- + array( 'ppcp-paypal-payment-tokens' => __( 'PayPal payments', 'woocommerce-paypal-payments' ) )
- + array_slice( $menu_links, 5, null, true );
-
- return $menu_links;
- },
- 40
- );
-
- add_action(
- 'init',
- function () {
- add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
- }
- );
-
- add_action(
- 'woocommerce_paypal_payments_gateway_migrate',
- function () {
- add_action(
- 'init',
- function () {
- add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
- flush_rewrite_rules();
- }
- );
- }
- );
-
- add_action(
- 'woocommerce_paypal_payments_gateway_activate',
- function () {
- add_rewrite_endpoint( 'ppcp-paypal-payment-tokens', EP_PAGES );
- flush_rewrite_rules();
- }
- );
-
- add_action(
- 'woocommerce_account_ppcp-paypal-payment-tokens_endpoint',
- function () use ( $container ) {
- $payment_token_repository = $container->get( 'vaulting.repository.payment-token' );
- $renderer = $container->get( 'vaulting.payment-tokens-renderer' );
-
- $tokens = $payment_token_repository->all_for_user_id( get_current_user_id() );
- if ( $tokens ) {
- echo wp_kses_post( $renderer->render( $tokens ) );
- } else {
- echo wp_kses_post( $renderer->render_no_tokens() );
- }
- }
- );
-
$subscription_helper = $container->get( 'subscription.helper' );
add_action(
'woocommerce_created_customer',
@@ -124,129 +66,198 @@ class VaultingModule implements ModuleInterface {
}
);
- $asset_loader = $container->get( 'vaulting.assets.myaccount-payments' );
+ add_filter(
+ 'woocommerce_payment_token_class',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $type ) {
+ if ( $type === 'WC_Payment_Token_PayPal' ) {
+ return PaymentTokenPayPal::class;
+ }
+
+ return $type;
+ }
+ );
+
+ add_filter(
+ 'woocommerce_payment_methods_list_item',
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function( $item, $payment_token ) {
+ if ( ! is_array( $item ) || ! is_a( $payment_token, WC_Payment_Token::class ) ) {
+ return $item;
+ }
+
+ if ( strtolower( $payment_token->get_type() ) === 'paypal' ) {
+ assert( $payment_token instanceof PaymentTokenPayPal );
+ $item['method']['brand'] = $payment_token->get_email();
+
+ return $item;
+ }
+
+ return $item;
+ },
+ 10,
+ 2
+ );
+
add_action(
- 'wp_enqueue_scripts',
- function () use ( $asset_loader ) {
- if ( is_account_page() && $this->is_payments_page() ) {
- $asset_loader->enqueue();
- $asset_loader->localize();
+ 'wp',
+ function() use ( $container ) {
+ global $wp;
+
+ if ( isset( $wp->query_vars['delete-payment-method'] ) ) {
+ $token_id = absint( $wp->query_vars['delete-payment-method'] );
+ $token = WC_Payment_Tokens::get( $token_id );
+
+ if (
+ is_null( $token )
+ || ( $token->get_gateway_id() !== PayPalGateway::ID && $token->get_gateway_id() !== CreditCardGateway::ID )
+ ) {
+ return;
+ }
+
+ $wpnonce = wc_clean( wp_unslash( $_REQUEST['_wpnonce'] ?? '' ) );
+ $token_id_string = (string) $token_id;
+ $action = 'delete-payment-method-' . $token_id_string;
+ if (
+ $token->get_user_id() !== get_current_user_id()
+ || ! isset( $wpnonce ) || ! is_string( $wpnonce )
+ || wp_verify_nonce( $wpnonce, $action ) === false
+ ) {
+ wc_add_notice( __( 'Invalid payment method.', 'woocommerce-paypal-payments' ), 'error' );
+ wp_safe_redirect( wc_get_account_endpoint_url( 'payment-methods' ) );
+ exit();
+ }
+
+ try {
+ $payment_token_endpoint = $container->get( 'api.endpoint.payment-token' );
+ $payment_token_endpoint->delete_token_by_id( $token->get_token() );
+ } catch ( RuntimeException $exception ) {
+ wc_add_notice( __( 'Could not delete payment token. ', 'woocommerce-paypal-payments' ) . $exception->getMessage(), 'error' );
+ return;
+ }
}
}
);
add_action(
- 'wc_ajax_' . DeletePaymentTokenEndpoint::ENDPOINT,
- static function () use ( $container ) {
- $endpoint = $container->get( 'vaulting.endpoint.delete' );
- assert( $endpoint instanceof DeletePaymentTokenEndpoint );
+ 'woocommerce_paypal_payments_gateway_migrate_on_update',
+ function () use ( $container ) {
+ $settings = $container->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+ if ( $settings->has( 'vault_enabled' ) && $settings->get( 'vault_enabled' ) && $settings->has( 'vault_enabled_dcc' ) ) {
+ $settings->set( 'vault_enabled_dcc', true );
+ $settings->persist();
+ }
- $endpoint->handle_request();
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+
+ $this->migrate_payment_tokens( $logger );
+ }
+ );
+
+ /**
+ * Allows running migration externally via `do_action('pcp_migrate_payment_tokens')`.
+ */
+ add_action(
+ 'pcp_migrate_payment_tokens',
+ function() use ( $container ) {
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+
+ $this->migrate_payment_tokens( $logger );
}
);
add_action(
- 'woocommerce_paypal_payments_check_saved_payment',
- function ( int $order_id, int $customer_id, string $intent ) use ( $container ) {
- $payment_token_checker = $container->get( 'vaulting.payment-token-checker' );
- $payment_token_checker->check_and_update( $order_id, $customer_id, $intent );
- },
- 10,
- 3
+ 'woocommerce_paypal_payments_payment_tokens_migration',
+ function( int $customer_id ) use ( $container ) {
+ $migration = $container->get( 'vaulting.payment-tokens-migration' );
+ assert( $migration instanceof PaymentTokensMigration );
+
+ $migration->migrate_payment_tokens_for_user( $customer_id );
+ }
);
- $this->filterFailedVaultingEmailsForSubscriptionOrders( $container );
+ add_filter(
+ 'woocommerce_available_payment_gateways',
+ function( array $methods ): array {
+ global $wp;
+ if ( isset( $wp->query_vars['add-payment-method'] ) ) {
+ unset( $methods[ PayPalGateway::ID ] );
+ }
+
+ return $methods;
+ }
+ );
}
/**
- * Filters the emails when vaulting is failed for subscription orders.
+ * Runs the payment tokens migration for users with saved payments.
*
- * @param ContainerInterface $container A services container instance.
- * @throws NotFoundException When service could not be found.
+ * @param LoggerInterface $logger The logger.
+ * @return void
*/
- protected function filterFailedVaultingEmailsForSubscriptionOrders( ContainerInterface $container ):void {
- add_action(
- 'woocommerce_email_before_order_table',
- function( WC_Order $order ) use ( $container ) {
- /**
- * The SubscriptionHelper.
- *
- * @var SubscriptionHelper $subscription_helper
- */
- $subscription_helper = $container->get( 'subscription.helper' );
-
- /**
- * The logger.
- *
- * @var LoggerInterface $logger
- */
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
-
- $vault_failed = $order->get_meta( PaymentTokenChecker::VAULTING_FAILED_META_KEY );
- if ( $subscription_helper->has_subscription( $order->get_id() ) && ! empty( $vault_failed ) ) {
- $logger->info( "Adding vaulting failure info to email for order #{$order->get_id()}." );
-
- if ( $vault_failed === 'void_auth' ) {
- echo wp_kses_post( '' . __( 'The subscription payment failed because the payment method could not be saved. Please try again with a different payment method.', 'woocommerce-paypal-payments' ) . '
' );
- }
-
- if ( $vault_failed === 'capture_auth' ) {
- echo wp_kses_post( '' . __( 'The subscription has been activated, but the payment method could not be saved. Please contact the merchant to save a payment method for automatic subscription renewal payments.', 'woocommerce-paypal-payments' ) . '
' );
- }
- }
- }
+ public function migrate_payment_tokens( LoggerInterface $logger ): void {
+ $initialized = get_option( 'ppcp_payment_tokens_migration_initialized', null );
+ if ( $initialized ) {
+ return;
+ }
+ // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+ // phpcs:disable WordPress.DB.SlowDBQuery.slow_db_query_meta_query
+ $customers = new WP_User_Query(
+ array(
+ 'fields' => 'ID',
+ 'limit' => -1,
+ 'meta_key' => 'ppcp-vault-token',
+ )
);
+ // phpcs:enable
- add_action(
- 'woocommerce_email_after_order_table',
- function( WC_Order $order ) use ( $container ) {
- /**
- * The SubscriptionHelper.
- *
- * @var SubscriptionHelper $subscription_helper
- */
- $subscription_helper = $container->get( 'subscription.helper' );
+ $customers = $customers->get_results();
+ if ( count( $customers ) === 0 ) {
+ $logger->info( 'No customers for payment tokens migration.' );
+ return;
+ }
- /**
- * The logger.
- *
- * @var LoggerInterface $logger
- */
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ $logger->info( 'Identified ' . (string) count( $customers ) . ' users with payment tokens. Initiating token migration.' );
+ update_option( 'ppcp_payment_tokens_migration_initialized', true );
- $vault_failed = $order->get_meta( PaymentTokenChecker::VAULTING_FAILED_META_KEY );
- if ( $subscription_helper->has_subscription( $order->get_id() ) && ! empty( $vault_failed ) ) {
- $logger->info( "Changing subscription auto-renewal status for order #{$order->get_id()}." );
+ $interval_in_seconds = 5;
+ $timestamp = time();
- if ( $vault_failed === 'capture_auth' ) {
- $subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $order->get_id() ) : array();
- foreach ( $subscriptions as $subscription ) {
- $subscription->set_requires_manual_renewal( true );
- $subscription->save();
- }
- }
- }
+ foreach ( $customers as $id ) {
+ $tokens = array_filter( get_user_meta( $id, 'ppcp-vault-token' ) );
+ $skip_empty_key_migration = apply_filters( 'ppcp_skip_payment_tokens_empty_key_migration', true );
+ if ( empty( $tokens ) && $skip_empty_key_migration ) {
+ continue;
}
- );
+
+ /**
+ * Function already exist in WooCommerce
+ *
+ * @psalm-suppress UndefinedFunction
+ */
+ as_schedule_single_action(
+ $timestamp,
+ 'woocommerce_paypal_payments_payment_tokens_migration',
+ array( 'customer_id' => $id )
+ );
+
+ $timestamp += $interval_in_seconds;
+ }
}
/**
* {@inheritDoc}
*/
public function getKey() { }
-
- /**
- * Check if is payments page.
- *
- * @return bool Whethen page is payments or not.
- */
- private function is_payments_page(): bool {
- global $wp;
- $request = explode( '/', wp_parse_url( $wp->request, PHP_URL_PATH ) );
- if ( end( $request ) === 'ppcp-paypal-payment-tokens' ) {
- return true;
- }
-
- return false;
- }
}
diff --git a/modules/ppcp-wc-gateway/extensions.php b/modules/ppcp-wc-gateway/extensions.php
index e56df668a..7b3c1b127 100644
--- a/modules/ppcp-wc-gateway/extensions.php
+++ b/modules/ppcp-wc-gateway/extensions.php
@@ -9,10 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway;
-use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
-use WooCommerce\PayPalCommerce\Session\SessionHandler;
-use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
use WooCommerce\WooCommerce\Logging\Logger\WooCommerceLogger;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@@ -52,43 +49,6 @@ return array(
$settings = $container->get( 'wcgateway.settings' );
return $settings->has( 'prefix' ) ? (string) $settings->get( 'prefix' ) : 'WC-';
},
- 'api.endpoint.order' => static function ( ContainerInterface $container ): OrderEndpoint {
- $order_factory = $container->get( 'api.factory.order' );
- $patch_collection_factory = $container->get( 'api.factory.patch-collection-factory' );
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- /**
- * The session handler.
- *
- * @var SessionHandler $session_handler
- */
- $session_handler = $container->get( 'session.handler' );
- $bn_code = $session_handler->bn_code();
-
- /**
- * The settings.
- *
- * @var Settings $settings
- */
- $settings = $container->get( 'wcgateway.settings' );
- $intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
- $application_context_repository = $container->get( 'api.repository.application-context' );
- $pay_pal_request_id_repository = $container->get( 'api.repository.paypal-request-id' );
- $subscription_helper = $container->get( 'subscription.helper' );
- return new OrderEndpoint(
- $container->get( 'api.host' ),
- $container->get( 'api.bearer' ),
- $order_factory,
- $patch_collection_factory,
- $intent,
- $logger,
- $application_context_repository,
- $pay_pal_request_id_repository,
- $subscription_helper,
- $container->get( 'wcgateway.is-fraudnet-enabled' ),
- $container->get( 'wcgateway.fraudnet' ),
- $bn_code
- );
- },
'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface {
if ( ! function_exists( 'wc_get_logger' ) || ! $container->get( 'wcgateway.logging.is-enabled' ) ) {
return new NullLogger();
diff --git a/modules/ppcp-wc-gateway/package.json b/modules/ppcp-wc-gateway/package.json
index b37863041..f11ae3d5b 100644
--- a/modules/ppcp-wc-gateway/package.json
+++ b/modules/ppcp-wc-gateway/package.json
@@ -22,7 +22,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-wc-gateway/resources/css/common.scss b/modules/ppcp-wc-gateway/resources/css/common.scss
new file mode 100644
index 000000000..299375005
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/css/common.scss
@@ -0,0 +1,11 @@
+
+.ppcp-field-hidden {
+ display: none !important;
+}
+
+.ppcp-field-disabled {
+ cursor: not-allowed;
+ -webkit-filter: grayscale(100%);
+ filter: grayscale(100%);
+ opacity: 0.5;
+}
diff --git a/modules/ppcp-wc-gateway/resources/js/common.js b/modules/ppcp-wc-gateway/resources/js/common.js
new file mode 100644
index 000000000..ba763605b
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common.js
@@ -0,0 +1,28 @@
+import DisplayManager from "./common/display-manager/DisplayManager";
+import moveWrappedElements from "./common/wrapped-elements";
+
+document.addEventListener(
+ 'DOMContentLoaded',
+ () => {
+
+ // Wait for current execution context to end.
+ setTimeout(function () {
+ moveWrappedElements();
+ }, 0);
+
+ // Initialize DisplayManager.
+ const displayManager = new DisplayManager();
+
+ jQuery( '*[data-ppcp-display]' ).each( (index, el) => {
+ const rules = jQuery(el).data('ppcpDisplay');
+
+ // console.log('rules', rules);
+
+ for (const rule of rules) {
+ displayManager.addRule(rule);
+ }
+ });
+
+ displayManager.register();
+ }
+);
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js
new file mode 100644
index 000000000..8531e17e1
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ActionFactory.js
@@ -0,0 +1,14 @@
+import ElementAction from "./action/ElementAction";
+
+class ActionFactory {
+ static make(actionConfig) {
+ switch (actionConfig.type) {
+ case 'element':
+ return new ElementAction(actionConfig);
+ }
+
+ throw new Error('[ActionFactory] Unknown action: ' + actionConfig.type);
+ }
+}
+
+export default ActionFactory;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js
new file mode 100644
index 000000000..ef5461861
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/ConditionFactory.js
@@ -0,0 +1,17 @@
+import ElementCondition from "./condition/ElementCondition";
+import BoolCondition from "./condition/BoolCondition";
+
+class ConditionFactory {
+ static make(conditionConfig, triggerUpdate) {
+ switch (conditionConfig.type) {
+ case 'element':
+ return new ElementCondition(conditionConfig, triggerUpdate);
+ case 'bool':
+ return new BoolCondition(conditionConfig, triggerUpdate);
+ }
+
+ throw new Error('[ConditionFactory] Unknown condition: ' + conditionConfig.type);
+ }
+}
+
+export default ConditionFactory;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js
new file mode 100644
index 000000000..2aede73ee
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/DisplayManager.js
@@ -0,0 +1,32 @@
+import Rule from "./Rule";
+
+class DisplayManager {
+
+ constructor() {
+ this.rules = {};
+ this.ruleStatus = {}; // The current status for each rule. Maybe not necessary, for now just for logging.
+
+ document.ppcpDisplayManagerLog = () => {
+ console.log('DisplayManager', this);
+ }
+ }
+
+ addRule(ruleConfig) {
+ const updateStatus = () => {
+ this.ruleStatus[ruleConfig.key] = this.rules[ruleConfig.key].status;
+ //console.log('ruleStatus', this.ruleStatus);
+ }
+
+ this.rules[ruleConfig.key] = new Rule(ruleConfig, updateStatus.bind(this));
+ //console.log('Rule', this.rules[ruleConfig.key]);
+ }
+
+ register() {
+ this.ruleStatus = {};
+ for (const [key, rule] of Object.entries(this.rules)) {
+ rule.register();
+ }
+ }
+}
+
+export default DisplayManager;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js
new file mode 100644
index 000000000..20581b025
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/Rule.js
@@ -0,0 +1,68 @@
+import ConditionFactory from "./ConditionFactory";
+import ActionFactory from "./ActionFactory";
+
+class Rule {
+
+ constructor(config, triggerUpdate) {
+ this.config = config;
+ this.conditions = {};
+ this.actions = {};
+ this.triggerUpdate = triggerUpdate;
+ this.status = null;
+
+ const updateStatus = this.updateStatus.bind(this);
+ for (const conditionConfig of this.config.conditions) {
+ const condition = ConditionFactory.make(conditionConfig, updateStatus);
+ this.conditions[condition.key] = condition;
+
+ //console.log('Condition', condition);
+ }
+
+ for (const actionConfig of this.config.actions) {
+ const action = ActionFactory.make(actionConfig);
+ this.actions[action.key] = action;
+
+ //console.log('Action', action);
+ }
+ }
+
+ get key() {
+ return this.config.key;
+ }
+
+ updateStatus(forceRunActions = false) {
+ let status = true;
+
+ for (const [key, condition] of Object.entries(this.conditions)) {
+ status &= condition.status;
+ }
+
+ if (status !== this.status) {
+ this.status = status;
+ this.triggerUpdate();
+ this.runActions();
+ } else if (forceRunActions) {
+ this.runActions();
+ }
+ }
+
+ runActions() {
+ for (const [key, action] of Object.entries(this.actions)) {
+ action.run(this.status);
+ }
+ }
+
+ register() {
+ for (const [key, condition] of Object.entries(this.conditions)) {
+ condition.register(this.updateStatus.bind(this));
+ }
+ for (const [key, action] of Object.entries(this.actions)) {
+ action.register();
+ }
+
+ this.updateStatus(true);
+ }
+
+}
+
+export default Rule;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js
new file mode 100644
index 000000000..13b07df4f
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/BaseAction.js
@@ -0,0 +1,21 @@
+
+class BaseAction {
+
+ constructor(config) {
+ this.config = config;
+ }
+
+ get key() {
+ return this.config.key;
+ }
+
+ register() {
+ // To override.
+ }
+
+ run(status) {
+ // To override.
+ }
+}
+
+export default BaseAction;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/ElementAction.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/ElementAction.js
new file mode 100644
index 000000000..f32c036a3
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/action/ElementAction.js
@@ -0,0 +1,35 @@
+import BaseAction from "./BaseAction";
+
+class ElementAction extends BaseAction {
+
+ run(status) {
+
+ if (status) {
+ if (this.config.action === 'visible') {
+ jQuery(this.config.selector).removeClass('ppcp-field-hidden');
+ }
+ if (this.config.action === 'enable') {
+ jQuery(this.config.selector).removeClass('ppcp-field-disabled')
+ .off('mouseup')
+ .find('> *')
+ .css('pointer-events', '');
+ }
+ } else {
+ if (this.config.action === 'visible') {
+ jQuery(this.config.selector).addClass('ppcp-field-hidden');
+ }
+ if (this.config.action === 'enable') {
+ jQuery(this.config.selector).addClass('ppcp-field-disabled')
+ .on('mouseup', function(event) {
+ event.stopImmediatePropagation();
+ })
+ .find('> *')
+ .css('pointer-events', 'none');
+ }
+ }
+
+ }
+
+}
+
+export default ElementAction;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js
new file mode 100644
index 000000000..1d126c128
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BaseCondition.js
@@ -0,0 +1,19 @@
+
+class BaseCondition {
+
+ constructor(config, triggerUpdate) {
+ this.config = config;
+ this.status = false;
+ this.triggerUpdate = triggerUpdate;
+ }
+
+ get key() {
+ return this.config.key;
+ }
+
+ register() {
+ // To override.
+ }
+}
+
+export default BaseCondition;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js
new file mode 100644
index 000000000..27699d2ca
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/BoolCondition.js
@@ -0,0 +1,15 @@
+import BaseCondition from "./BaseCondition";
+
+class BoolCondition extends BaseCondition {
+
+ register() {
+ this.status = this.check();
+ }
+
+ check() {
+ return !! this.config.value;
+ }
+
+}
+
+export default BoolCondition;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js
new file mode 100644
index 000000000..efb0b4613
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/display-manager/condition/ElementCondition.js
@@ -0,0 +1,27 @@
+import BaseCondition from "./BaseCondition";
+import {inputValue} from "../../../helper/form";
+
+class ElementCondition extends BaseCondition {
+
+ register() {
+ jQuery(document).on('change', this.config.selector, () => {
+ const status = this.check();
+ if (status !== this.status) {
+ this.status = status;
+ this.triggerUpdate();
+ }
+ });
+
+ this.status = this.check();
+ }
+
+ check() {
+ let value = inputValue(this.config.selector);
+ value = (value !== null ? value.toString() : value);
+
+ return this.config.value === value;
+ }
+
+}
+
+export default ElementCondition;
diff --git a/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js b/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js
new file mode 100644
index 000000000..827e50586
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/common/wrapped-elements.js
@@ -0,0 +1,14 @@
+
+// This function is needed because WordPress moves our custom notices to the global placeholder.
+function moveWrappedElements() {
+ (($) => {
+ $('*[data-ppcp-wrapper]').each(function() {
+ let $wrapper = $('.' + $(this).data('ppcpWrapper'));
+ if ($wrapper.length) {
+ $wrapper.append(this);
+ }
+ });
+ })(jQuery)
+}
+
+export default moveWrappedElements;
diff --git a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js
index 6548412f8..7886131d9 100644
--- a/modules/ppcp-wc-gateway/resources/js/gateway-settings.js
+++ b/modules/ppcp-wc-gateway/resources/js/gateway-settings.js
@@ -2,9 +2,10 @@ import { loadScript } from "@paypal/paypal-js";
import {debounce} from "./helper/debounce";
import Renderer from '../../../ppcp-button/resources/js/modules/Renderer/Renderer'
import MessageRenderer from "../../../ppcp-button/resources/js/modules/Renderer/MessageRenderer";
-import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"
+import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/modules/Helper/Hiding";
+import widgetBuilder from "../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder";
-;document.addEventListener(
+document.addEventListener(
'DOMContentLoaded',
() => {
function disableAll(nodeList){
@@ -86,6 +87,7 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo
try {
renderer.render({});
+ jQuery(document).trigger('ppcp_paypal_render_preview', settings);
} catch (err) {
console.error(err);
}
@@ -112,12 +114,15 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo
'client-id': PayPalCommerceGatewaySettings.client_id,
'currency': PayPalCommerceGatewaySettings.currency,
'integration-date': PayPalCommerceGatewaySettings.integration_date,
- 'components': ['buttons', 'funding-eligibility', 'messages'],
+ 'components': PayPalCommerceGatewaySettings.components,
'enable-funding': ['venmo', 'paylater'],
- 'buyer-country': PayPalCommerceGatewaySettings.country,
};
- if(payLaterButtonPreview?.length) {
+ if (PayPalCommerceGatewaySettings.environment === 'sandbox') {
+ settings['buyer-country'] = PayPalCommerceGatewaySettings.country;
+ }
+
+ if (payLaterButtonPreview?.length) {
disabledSources = Object.keys(PayPalCommerceGatewaySettings.all_funding_sources);
}
@@ -135,6 +140,8 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo
function loadPaypalScript(settings, onLoaded = () => {}) {
loadScript(JSON.parse(JSON.stringify(settings))) // clone the object to prevent modification
.then(paypal => {
+ widgetBuilder.setPaypal(paypal);
+
document.dispatchEvent(new CustomEvent('ppcp_paypal_script_loaded'));
onLoaded(paypal);
@@ -166,11 +173,16 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo
function createMessagesPreview(settingsCallback) {
const render = (settings) => {
- const wrapper = document.querySelector(settings.wrapper);
+ let wrapper = document.querySelector(settings.wrapper);
if (!wrapper) {
return;
}
- wrapper.innerHTML = '';
+ // looks like .innerHTML = '' is not enough, PayPal somehow renders with old style
+ const parent = wrapper.parentElement;
+ parent.removeChild(wrapper);
+ wrapper = document.createElement('div');
+ wrapper.setAttribute('id', settings.wrapper.replace('#', ''));
+ parent.appendChild(wrapper);
const messageRenderer = new MessageRenderer(settings);
@@ -263,7 +275,7 @@ import {setVisibleByClass, isVisible} from "../../../ppcp-button/resources/js/mo
}, 1000));
loadPaypalScript(oldScriptSettings, () => {
- const payLaterMessagingLocations = ['product', 'cart', 'checkout', 'general'];
+ const payLaterMessagingLocations = ['product', 'cart', 'checkout', 'shop', 'home', 'general'];
const paypalButtonLocations = ['product', 'cart', 'checkout', 'mini-cart', 'general'];
paypalButtonLocations.forEach((location) => {
diff --git a/modules/ppcp-wc-gateway/resources/js/helper/form.js b/modules/ppcp-wc-gateway/resources/js/helper/form.js
new file mode 100644
index 000000000..4f4891f03
--- /dev/null
+++ b/modules/ppcp-wc-gateway/resources/js/helper/form.js
@@ -0,0 +1,13 @@
+export const inputValue = (element) => {
+ const $el = jQuery(element);
+
+ if ($el.is(':checkbox') || $el.is(':radio')) {
+ if ($el.is(':checked')) {
+ return $el.val();
+ } else {
+ return null;
+ }
+ } else {
+ return $el.val();
+ }
+}
diff --git a/modules/ppcp-wc-gateway/services.php b/modules/ppcp-wc-gateway/services.php
index ed7e8cb28..57a58db18 100644
--- a/modules/ppcp-wc-gateway/services.php
+++ b/modules/ppcp-wc-gateway/services.php
@@ -11,20 +11,18 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway;
-use WooCommerce\PayPalCommerce\Session\SessionHandler;
-use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
-use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
-use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Button\Helper\MessagesDisclaimers;
+use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
+use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
use WooCommerce\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
@@ -32,29 +30,33 @@ use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderAuthorizeAction;
use WooCommerce\PayPalCommerce\WcGateway\Assets\FraudNetAssets;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
+use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
-use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOEndpoint;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
-use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSessionId;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNetSourceWebsiteId;
+use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXO;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PaymentSourceFactory;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoice;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\TransactionUrlProvider;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
+use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
@@ -94,7 +96,8 @@ return array(
$environment,
$payment_token_repository,
$logger,
- $api_shop_country
+ $api_shop_country,
+ $container->get( 'api.endpoint.order' )
);
},
'wcgateway.credit-card-gateway' => static function ( ContainerInterface $container ): CreditCardGateway {
@@ -145,7 +148,8 @@ return array(
$session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' );
$settings_status = $container->get( 'wcgateway.settings.status' );
- return new DisableGateways( $session_handler, $settings, $settings_status );
+ $subscription_helper = $container->get( 'subscription.helper' );
+ return new DisableGateways( $session_handler, $settings, $settings_status, $subscription_helper );
},
'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool {
@@ -196,15 +200,33 @@ return array(
return $ppcp_tab ? $ppcp_tab : $section;
},
- 'wcgateway.settings' => static function ( ContainerInterface $container ): Settings {
- $default_button_locations = $container->get( 'wcgateway.button.default-locations' );
- return new Settings( $default_button_locations );
- },
+ 'wcgateway.settings' => SingletonDecorator::make(
+ static function ( ContainerInterface $container ): Settings {
+ return new Settings(
+ $container->get( 'wcgateway.button.default-locations' ),
+ $container->get( 'wcgateway.settings.dcc-gateway-title.default' )
+ );
+ }
+ ),
'wcgateway.notice.connect' => static function ( ContainerInterface $container ): ConnectAdminNotice {
$state = $container->get( 'onboarding.state' );
$settings = $container->get( 'wcgateway.settings' );
return new ConnectAdminNotice( $state, $settings );
},
+ 'wcgateway.notice.currency-unsupported' => static function ( ContainerInterface $container ): UnsupportedCurrencyAdminNotice {
+ $state = $container->get( 'onboarding.state' );
+ $shop_currency = $container->get( 'api.shop.currency' );
+ $supported_currencies = $container->get( 'api.supported-currencies' );
+ $is_wc_gateways_list_page = $container->get( 'wcgateway.is-wc-gateways-list-page' );
+ $is_ppcp_settings_page = $container->get( 'wcgateway.is-ppcp-settings-page' );
+ return new UnsupportedCurrencyAdminNotice(
+ $state,
+ $shop_currency,
+ $supported_currencies,
+ $is_wc_gateways_list_page,
+ $is_ppcp_settings_page
+ );
+ },
'wcgateway.notice.dcc-without-paypal' => static function ( ContainerInterface $container ): GatewayWithoutPayPalAdminNotice {
return new GatewayWithoutPayPalAdminNotice(
CreditCardGateway::ID,
@@ -220,7 +242,8 @@ return array(
$container->get( 'onboarding.state' ),
$container->get( 'wcgateway.settings' ),
$container->get( 'wcgateway.is-wc-payments-page' ),
- $container->get( 'wcgateway.is-ppcp-settings-page' )
+ $container->get( 'wcgateway.is-ppcp-settings-page' ),
+ $container->get( 'wcgateway.settings.status' )
);
},
'wcgateway.notice.authorize-order-action' =>
@@ -230,8 +253,11 @@ return array(
'wcgateway.settings.sections-renderer' => static function ( ContainerInterface $container ): SectionsRenderer {
return new SectionsRenderer(
$container->get( 'wcgateway.current-ppcp-settings-page-id' ),
- $container->get( 'wcgateway.settings.sections' ),
- $container->get( 'onboarding.state' )
+ $container->get( 'onboarding.state' ),
+ $container->get( 'wcgateway.helper.dcc-product-status' ),
+ $container->get( 'api.helpers.dccapplies' ),
+ $container->get( 'button.helper.messages-apply' ),
+ $container->get( 'wcgateway.pay-upon-invoice-product-status' )
);
},
'wcgateway.settings.header-renderer' => static function ( ContainerInterface $container ): HeaderRenderer {
@@ -240,52 +266,6 @@ return array(
$container->get( 'wcgateway.url' )
);
},
- 'wcgateway.settings.sections' => static function ( ContainerInterface $container ): array {
- $sections = array(
- Settings::CONNECTION_TAB_ID => __( 'Connection', 'woocommerce-paypal-payments' ),
- PayPalGateway::ID => __( 'Standard Payments', 'woocommerce-paypal-payments' ),
- Settings::PAY_LATER_TAB_ID => __( 'Pay Later', 'woocommerce-paypal-payments' ),
- CreditCardGateway::ID => __( 'Advanced Card Processing', 'woocommerce-paypal-payments' ),
- CardButtonGateway::ID => __( 'Standard Card Button', 'woocommerce-paypal-payments' ),
- OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ),
- PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
- );
-
- // Remove for all not registered in WC gateways that cannot render anything in this case.
- $gateways = WC()->payment_gateways->payment_gateways();
- foreach ( array_diff(
- array_keys( $sections ),
- array( Settings::CONNECTION_TAB_ID, PayPalGateway::ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID )
- ) as $id ) {
- if ( ! isset( $gateways[ $id ] ) ) {
- unset( $sections[ $id ] );
- }
- }
-
- $dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
- assert( $dcc_product_status instanceof DCCProductStatus );
- $dcc_applies = $container->get( 'api.helpers.dccapplies' );
- assert( $dcc_applies instanceof DccApplies );
- if ( ! $dcc_product_status->dcc_is_active() || ! $dcc_applies->for_country_currency() ) {
- unset( $sections['ppcp-credit-card-gateway'] );
- }
-
- $messages_apply = $container->get( 'button.helper.messages-apply' );
- assert( $messages_apply instanceof MessagesApply );
-
- if ( ! $messages_apply->for_country() ) {
- unset( $sections[ Settings::PAY_LATER_TAB_ID ] );
- }
-
- $pui_product_status = $container->get( 'wcgateway.pay-upon-invoice-product-status' );
- assert( $pui_product_status instanceof PayUponInvoiceProductStatus );
-
- if ( ! $pui_product_status->pui_is_active() ) {
- unset( $sections[ PayUponInvoiceGateway::ID ] );
- }
-
- return $sections;
- },
'wcgateway.settings.status' => static function ( ContainerInterface $container ): SettingsStatus {
$settings = $container->get( 'wcgateway.settings' );
return new SettingsStatus( $settings );
@@ -324,6 +304,7 @@ return array(
$signup_link_ids = $container->get( 'onboarding.signup-link-ids' );
$pui_status_cache = $container->get( 'pui.status-cache' );
$dcc_status_cache = $container->get( 'dcc.status-cache' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
return new SettingsListener(
$settings,
$fields,
@@ -335,7 +316,11 @@ return array(
$signup_link_cache,
$signup_link_ids,
$pui_status_cache,
- $dcc_status_cache
+ $dcc_status_cache,
+ $container->get( 'http.redirector' ),
+ $container->get( 'api.partner_merchant_id-production' ),
+ $container->get( 'api.partner_merchant_id-sandbox' ),
+ $logger
);
},
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
@@ -364,10 +349,11 @@ return array(
);
},
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {
- $order_endpoint = $container->get( 'api.endpoint.order' );
- $payments_endpoint = $container->get( 'api.endpoint.payments' );
- $logger = $container->get( 'woocommerce.logger.woocommerce' );
- return new RefundProcessor( $order_endpoint, $payments_endpoint, $logger );
+ $order_endpoint = $container->get( 'api.endpoint.order' );
+ $payments_endpoint = $container->get( 'api.endpoint.payments' );
+ $refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new RefundProcessor( $order_endpoint, $payments_endpoint, $refund_fees_updater, $logger );
},
'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor {
$order_endpoint = $container->get( 'api.endpoint.order' );
@@ -416,6 +402,28 @@ return array(
return array_key_exists( $current_page_id, $sections );
},
+ 'wcgateway.settings.fields.subscriptions_mode' => static function ( ContainerInterface $container ): array {
+ return array(
+ 'title' => __( 'Subscriptions Mode', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'desc_tip' => true,
+ 'description' => __( 'Utilize PayPal Vaulting for flexible subscription processing with saved payment methods, create “PayPal Subscriptions” to bill customers at regular intervals, or disable PayPal for subscription-type products.', 'woocommerce-paypal-payments' ),
+ 'default' => 'vaulting_api',
+ 'options' => array(
+ 'vaulting_api' => __( 'PayPal Vaulting', 'woocommerce-paypal-payments' ),
+ 'subscriptions_api' => __( 'PayPal Subscriptions', 'woocommerce-paypal-payments' ),
+ 'disable_paypal_subscriptions' => __( 'Disable PayPal for subscriptions', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => 'paypal',
+ );
+ },
+
'wcgateway.settings.fields' => static function ( ContainerInterface $container ): array {
$should_render_settings = $container->get( 'wcgateway.settings.should-render-settings' );
@@ -427,8 +435,6 @@ return array(
$state = $container->get( 'onboarding.state' );
assert( $state instanceof State );
- $messages_disclaimers = $container->get( 'button.helper.messages-disclaimers' );
-
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
assert( $dcc_applies instanceof DccApplies );
@@ -439,7 +445,7 @@ return array(
assert( $subscription_helper instanceof SubscriptionHelper );
$fields = array(
- 'checkout_settings_heading' => array(
+ 'checkout_settings_heading' => array(
'heading' => __( 'Standard Payments Settings', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'screens' => array(
@@ -449,7 +455,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'title' => array(
+ 'title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
@@ -465,7 +471,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'dcc_enabled' => array(
+ 'dcc_enabled' => array(
'title' => __( 'Enable/Disable', 'woocommerce-paypal-payments' ),
'desc_tip' => true,
'description' => __( 'Once enabled, the Credit Card option will show up in the checkout.', 'woocommerce-paypal-payments' ),
@@ -480,14 +486,14 @@ return array(
State::STATE_ONBOARDED,
),
),
- 'dcc_gateway_title' => array(
+ 'dcc_gateway_title' => array(
'title' => __( 'Title', 'woocommerce-paypal-payments' ),
'type' => 'text',
'description' => __(
'This controls the title which the user sees during checkout.',
'woocommerce-paypal-payments'
),
- 'default' => __( 'Credit Cards', 'woocommerce-paypal-payments' ),
+ 'default' => $container->get( 'wcgateway.settings.dcc-gateway-title.default' ),
'desc_tip' => true,
'screens' => array(
State::STATE_ONBOARDED,
@@ -497,7 +503,7 @@ return array(
),
'gateway' => 'dcc',
),
- 'description' => array(
+ 'description' => array(
'title' => __( 'Description', 'woocommerce-paypal-payments' ),
'type' => 'text',
'desc_tip' => true,
@@ -516,7 +522,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'intent' => array(
+ 'intent' => array(
'title' => __( 'Intent', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
@@ -538,7 +544,24 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'capture_for_virtual_only' => array(
+ 'capture_on_status_change' => array(
+ 'title' => __( 'Capture On Status Change', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'default' => false,
+ 'desc_tip' => true,
+ 'description' => __(
+ 'The transaction will be captured automatically when the order status changes to Processing or Completed.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'label' => __( 'Capture On Status Change', 'woocommerce-paypal-payments' ),
+ 'screens' => array(
+ State::STATE_START,
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => 'paypal',
+ ),
+ 'capture_for_virtual_only' => array(
'title' => __( 'Capture Virtual-Only Orders ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@@ -555,7 +578,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'payee_preferred' => array(
+ 'payee_preferred' => array(
'title' => __( 'Instant Payments ', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'default' => false,
@@ -572,7 +595,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'brand_name' => array(
+ 'brand_name' => array(
'title' => __( 'Brand Name', 'woocommerce-paypal-payments' ),
'type' => 'text',
'default' => get_bloginfo( 'name' ),
@@ -588,12 +611,12 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'landing_page' => array(
+ 'landing_page' => array(
'title' => __( 'Landing Page', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
- 'default' => 'gold',
+ 'default' => ApplicationContext::LANDING_PAGE_LOGIN,
'desc_tip' => true,
'description' => __(
'Type of PayPal page to display.',
@@ -610,23 +633,15 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'disable_funding' => array(
- 'title' => __( 'Hide Funding Source(s)', 'woocommerce-paypal-payments' ),
- 'type' => 'ppcp-multiselect',
- 'class' => array(),
- 'input_class' => array( 'wc-enhanced-select' ),
- 'default' => array(),
- 'desc_tip' => false,
+ 'alternative_payment_methods' => array(
+ 'heading' => __( 'Alternative Payment Methods', 'woocommerce-paypal-payments' ),
'description' => sprintf(
- // translators: %1$s and %2$s are the opening and closing of HTML tag.
- __( 'By default, all possible funding sources will be shown. This setting can disable funding sources such as Credit Cards, Pay Later, Venmo, or other %1$sAlternative Payment Methods%2$s.', 'woocommerce-paypal-payments' ),
- ' ',
+ // translators: %1$s, %2$s, %3$s and %4$s are a link tags.
+ __( '%1$sAlternative Payment Methods%2$s allow you to accept payments from customers around the globe who use their credit cards, bank accounts, wallets, and local payment methods. When a buyer pays in a currency different than yours, PayPal handles currency conversion for you and presents conversion information to the buyer during checkout.', 'woocommerce-paypal-payments' ),
+ ' ',
' '
),
- 'options' => $container->get( 'wcgateway.all-funding-sources' ),
+ 'type' => 'ppcp-heading',
'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
@@ -634,42 +649,32 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
- 'vault_enabled' => array(
- 'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
- 'type' => 'checkbox',
- 'desc_tip' => true,
- 'label' => $container->get( 'button.helper.vaulting-label' ),
- 'description' => __( 'Allow registered buyers to save PayPal and Credit Card accounts. Allow Subscription renewals.', 'woocommerce-paypal-payments' ),
- 'default' => false,
+ 'disable_funding' => array(
+ 'title' => __( 'Disable Alternative Payment Methods', 'woocommerce-paypal-payments' ),
+ 'type' => 'ppcp-multiselect',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => array(),
+ 'desc_tip' => false,
+ 'description' => sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Choose to hide specific %1$sAlternative Payment Methods%2$s such as Credit Cards, Venmo, or others.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ ),
+ 'options' => $container->get( 'wcgateway.settings.funding-sources' ),
'screens' => array(
+ State::STATE_START,
State::STATE_ONBOARDED,
),
'requirements' => array(),
- 'gateway' => array( 'paypal', 'dcc' ),
- 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
+ 'gateway' => 'paypal',
),
- 'subscription_behavior_when_vault_fails' => array(
- 'title' => __( 'Subscription capture behavior if Vault fails', 'woocommerce-paypal-payments' ),
- 'type' => 'select',
- 'classes' => $subscription_helper->plugin_is_active() ? array() : array( 'hide' ),
- 'input_class' => array( 'wc-enhanced-select' ),
- 'default' => 'void_auth',
- 'desc_tip' => true,
- 'description' => __( 'By default, subscription payments are captured only when saving the payment method was successful. Without a saved payment method, automatic renewal payments are not possible.', 'woocommerce-paypal-payments' ),
- 'description_with_tip' => __( 'Determines whether authorized payments for subscription orders are captured or voided if there is no saved payment method. This only applies when the intent Capture is used for the subscription order.', 'woocommerce-paypal-payments' ),
- 'options' => array(
- 'void_auth' => __( 'Void authorization & fail the order/subscription', 'woocommerce-paypal-payments' ),
- 'capture_auth' => __( 'Capture authorized payment & set subscription to Manual Renewal', 'woocommerce-paypal-payments' ),
- 'capture_auth_ignore' => __( 'Capture authorized payment & disregard missing payment method', 'woocommerce-paypal-payments' ),
- ),
- 'screens' => array(
- State::STATE_ONBOARDED,
- ),
- 'requirements' => array(),
- 'gateway' => array( 'paypal', 'dcc' ),
- ),
- 'card_billing_data_mode' => array(
- 'title' => __( 'Card billing data handling', 'woocommerce-paypal-payments' ),
+ 'card_billing_data_mode' => array(
+ 'title' => __( 'Send checkout billing data to card fields', 'woocommerce-paypal-payments' ),
'type' => 'select',
'class' => array(),
'input_class' => array( 'wc-enhanced-select' ),
@@ -688,11 +693,11 @@ return array(
'requirements' => array(),
'gateway' => array( 'paypal', CardButtonGateway::ID ),
),
- 'allow_card_button_gateway' => array(
- 'title' => __( 'Separate Card Button from PayPal gateway', 'woocommerce-paypal-payments' ),
+ 'allow_card_button_gateway' => array(
+ 'title' => __( 'Create gateway for Standard Card Button', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
- 'label' => __( 'Enable a separate payment gateway for the branded PayPal Debit or Credit Card button.', 'woocommerce-paypal-payments' ),
+ 'label' => __( 'Moves the Standard Card Button from the PayPal gateway into its own dedicated gateway.', 'woocommerce-paypal-payments' ),
'description' => __( 'By default, the Debit or Credit Card button is displayed in the Standard Payments payment gateway. This setting creates a second gateway for the Card button.', 'woocommerce-paypal-payments' ),
'default' => $container->get( 'wcgateway.settings.allow_card_button_gateway.default' ),
'screens' => array(
@@ -702,8 +707,7 @@ return array(
'requirements' => array(),
'gateway' => 'paypal',
),
-
- 'disable_cards' => array(
+ 'disable_cards' => array(
'title' => __( 'Disable specific credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@@ -731,7 +735,7 @@ return array(
),
'gateway' => 'dcc',
),
- 'card_icons' => array(
+ 'card_icons' => array(
'title' => __( 'Show logo of the following credit cards', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-multiselect',
'class' => array(),
@@ -761,7 +765,26 @@ return array(
),
'gateway' => 'dcc',
),
- '3d_secure_heading' => array(
+ 'vault_enabled_dcc' => array(
+ 'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'desc_tip' => true,
+ 'label' => sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Securely store your customers’ credit cards for a seamless checkout experience and subscription features. Payment methods are saved in the secure %1$sPayPal Vault%2$s.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ ),
+ 'description' => __( 'Allow registered buyers to save Credit Card payments.', 'woocommerce-paypal-payments' ),
+ 'default' => false,
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => 'dcc',
+ 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
+ ),
+ '3d_secure_heading' => array(
'heading' => __( '3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
'description' => wp_kses_post(
@@ -789,7 +812,7 @@ return array(
),
'gateway' => 'dcc',
),
- '3d_secure_contingency' => array(
+ '3d_secure_contingency' => array(
'title' => __( 'Contingency for 3D Secure', 'woocommerce-paypal-payments' ),
'type' => 'select',
'description' => sprintf(
@@ -817,8 +840,52 @@ return array(
),
'gateway' => 'dcc',
),
+ 'paypal_saved_payments' => array(
+ 'heading' => __( 'Saved payments', 'woocommerce-paypal-payments' ),
+ 'description' => sprintf(
+ // translators: %1$s, %2$s, %3$s and %4$s are a link tags.
+ __( 'PayPal can securely store your customers\' payment methods for %1$sfuture payments%2$s and %3$ssubscriptions%4$s, simplifying the checkout process and enabling recurring transactions on your website.', 'woocommerce-paypal-payments' ),
+ '',
+ ' ',
+ '',
+ ' '
+ ),
+ 'type' => 'ppcp-heading',
+ 'screens' => array(
+ State::STATE_START,
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => 'paypal',
+ ),
+ 'subscriptions_mode' => $container->get( 'wcgateway.settings.fields.subscriptions_mode' ),
+ 'vault_enabled' => array(
+ 'title' => __( 'Vaulting', 'woocommerce-paypal-payments' ),
+ 'type' => 'checkbox',
+ 'desc_tip' => true,
+ 'label' => sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Securely store your customers’ PayPal accounts for a seamless checkout experience. Payment methods are saved in the secure %1$sPayPal Vault%2$s.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ ) . $container->get( 'button.helper.vaulting-label' ),
+ 'description' => __( 'Allow registered buyers to save PayPal payments.', 'woocommerce-paypal-payments' ),
+ 'default' => false,
+ 'screens' => array(
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => 'paypal',
+ 'input_class' => $container->get( 'wcgateway.helper.vaulting-scope' ) ? array() : array( 'ppcp-disabled-checkbox' ),
+ ),
);
- if ( ! defined( 'PPCP_FLAG_SUBSCRIPTION' ) || ! PPCP_FLAG_SUBSCRIPTION ) {
+
+ if ( ! $subscription_helper->plugin_is_active() ) {
+ unset( $fields['subscriptions_mode'] );
+ }
+
+ $billing_agreements_endpoint = $container->get( 'api.endpoint.billing-agreements' );
+ if ( ! $billing_agreements_endpoint->reference_transaction_enabled() ) {
unset( $fields['vault_enabled'] );
}
@@ -862,6 +929,32 @@ return array(
'p24' => _x( 'Przelewy24', 'Name of payment method', 'woocommerce-paypal-payments' ),
'sofort' => _x( 'Sofort', 'Name of payment method', 'woocommerce-paypal-payments' ),
'venmo' => _x( 'Venmo', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ 'trustly' => _x( 'Trustly', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ 'paylater' => _x( 'Pay Later', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ );
+ },
+
+ 'wcgateway.extra-funding-sources' => static function( ContainerInterface $container ): array {
+ return array(
+ 'googlepay' => _x( 'Google Pay', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ 'applepay' => _x( 'Apple Pay', 'Name of payment method', 'woocommerce-paypal-payments' ),
+ );
+ },
+
+ /**
+ * The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
+ */
+ 'wcgateway.funding-sources-without-redirect' => static function( ContainerInterface $container ): array {
+ return array( 'paypal', 'paylater', 'venmo', 'card' );
+ },
+ 'wcgateway.settings.funding-sources' => static function( ContainerInterface $container ): array {
+ return array_diff_key(
+ $container->get( 'wcgateway.all-funding-sources' ),
+ array_flip(
+ array(
+ 'paylater',
+ )
+ )
);
},
@@ -889,11 +982,11 @@ return array(
'wcgateway.endpoint.return-url' => static function ( ContainerInterface $container ) : ReturnUrlEndpoint {
$gateway = $container->get( 'wcgateway.paypal-gateway' );
$endpoint = $container->get( 'api.endpoint.order' );
- $prefix = $container->get( 'api.prefix' );
return new ReturnUrlEndpoint(
$gateway,
$endpoint,
- $prefix
+ $container->get( 'session.handler' ),
+ $container->get( 'woocommerce.logger.woocommerce' )
);
},
@@ -905,6 +998,15 @@ return array(
return 'https://www.paypal.com/cgi-bin/webscr?cmd=_view-a-trans&id=%s';
},
+ 'wcgateway.soft-descriptor' => static function ( ContainerInterface $container ): string {
+ $settings = $container->get( 'wcgateway.settings' );
+ assert( $settings instanceof Settings );
+ if ( $settings->has( 'soft_descriptor' ) ) {
+ return $settings->get( 'soft_descriptor' );
+ }
+ return '';
+ },
+
'wcgateway.transaction-url-provider' => static function ( ContainerInterface $container ): TransactionUrlProvider {
$sandbox_url_base = $container->get( 'wcgateway.transaction-url-sandbox' );
$live_url_base = $container->get( 'wcgateway.transaction-url-live' );
@@ -920,10 +1022,18 @@ return array(
$settings,
$partner_endpoint,
$container->get( 'dcc.status-cache' ),
- $container->get( 'api.helpers.dccapplies' )
+ $container->get( 'api.helpers.dccapplies' ),
+ $container->get( 'onboarding.state' ),
+ $container->get( 'api.helper.failure-registry' )
);
},
+ 'wcgateway.helper.refund-fees-updater' => static function ( ContainerInterface $container ): RefundFeesUpdater {
+ $order_endpoint = $container->get( 'api.endpoint.order' );
+ $logger = $container->get( 'woocommerce.logger.woocommerce' );
+ return new RefundFeesUpdater( $order_endpoint, $logger );
+ },
+
'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers {
return new MessagesDisclaimers(
$container->get( 'api.shop.country' )
@@ -932,9 +1042,14 @@ return array(
'wcgateway.funding-source.renderer' => function ( ContainerInterface $container ) : FundingSourceRenderer {
return new FundingSourceRenderer(
- $container->get( 'wcgateway.settings' )
+ $container->get( 'wcgateway.settings' ),
+ array_merge(
+ $container->get( 'wcgateway.all-funding-sources' ),
+ $container->get( 'wcgateway.extra-funding-sources' )
+ )
);
},
+
'wcgateway.checkout-helper' => static function ( ContainerInterface $container ): CheckoutHelper {
return new CheckoutHelper();
},
@@ -988,7 +1103,9 @@ return array(
return new PayUponInvoiceProductStatus(
$container->get( 'wcgateway.settings' ),
$container->get( 'api.endpoint.partners' ),
- $container->get( 'pui.status-cache' )
+ $container->get( 'pui.status-cache' ),
+ $container->get( 'onboarding.state' ),
+ $container->get( 'api.helper.failure-registry' )
);
},
'wcgateway.pay-upon-invoice' => static function ( ContainerInterface $container ): PayUponInvoice {
@@ -1022,15 +1139,6 @@ return array(
$container->get( 'woocommerce.logger.woocommerce' )
);
},
- 'wcgateway.endpoint.oxxo' => static function ( ContainerInterface $container ): OXXOEndpoint {
- return new OXXOEndpoint(
- $container->get( 'button.request-data' ),
- $container->get( 'api.endpoint.order' ),
- $container->get( 'api.factory.purchase-unit' ),
- $container->get( 'api.factory.shipping-preference' ),
- $container->get( 'woocommerce.logger.woocommerce' )
- );
- },
'wcgateway.logging.is-enabled' => function ( ContainerInterface $container ) : bool {
$settings = $container->get( 'wcgateway.settings' );
@@ -1053,13 +1161,7 @@ return array(
},
'button.helper.vaulting-label' => static function ( ContainerInterface $container ): string {
- $vaulting_label = sprintf(
- // translators: %1$s and %2$s are the opening and closing of HTML tag.
- __( 'Enable saved cards, PayPal accounts, and subscription features on your store. Payment methods are saved in the secure %1$sPayPal Vault%2$s.', 'woocommerce-paypal-payments' ),
- ' ',
- ' '
- );
-
+ $vaulting_label = '';
if ( ! $container->get( 'wcgateway.helper.vaulting-scope' ) ) {
$vaulting_label .= sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML tag.
@@ -1092,6 +1194,10 @@ return array(
return $vaulting_label;
},
+ 'wcgateway.settings.dcc-gateway-title.default' => static function ( ContainerInterface $container ): string {
+ return __( 'Debit & Credit Cards', 'woocommerce-paypal-payments' );
+ },
+
'wcgateway.settings.card_billing_data_mode.default' => static function ( ContainerInterface $container ): string {
return $container->get( 'api.shop.is-latin-america' ) ? CardBillingMode::MINIMAL_INPUT : CardBillingMode::USE_WC;
},
@@ -1118,62 +1224,34 @@ return array(
'wcgateway.settings.has_enabled_separate_button_gateways' => static function ( ContainerInterface $container ): bool {
return (bool) $container->get( 'wcgateway.settings.allow_card_button_gateway' );
},
-
- 'order-tracking.is-tracking-available' => static function ( ContainerInterface $container ): bool {
- try {
- $bearer = $container->get( 'api.bearer' );
- assert( $bearer instanceof Bearer );
-
- $token = $bearer->bearer();
- return $token->is_tracking_available();
- } catch ( RuntimeException $exception ) {
- return false;
- }
- },
-
- 'wcgateway.settings.should-disable-tracking-checkbox' => static function ( ContainerInterface $container ): bool {
+ 'wcgateway.settings.should-disable-fraudnet-checkbox' => static function( ContainerInterface $container ): bool {
$pui_helper = $container->get( 'wcgateway.pay-upon-invoice-helper' );
assert( $pui_helper instanceof PayUponInvoiceHelper );
- $is_tracking_available = $container->get( 'order-tracking.is-tracking-available' );
-
- if ( ! $is_tracking_available ) {
- return true;
- }
-
if ( $pui_helper->is_pui_gateway_enabled() ) {
return true;
}
return false;
},
- 'wcgateway.settings.tracking-label' => static function ( ContainerInterface $container ): string {
- $tracking_label = sprintf(
- // translators: %1$s and %2$s are the opening and closing of HTML tag.
- __( 'Enable shipment tracking information to be sent to PayPal for seller protection features. Required when %1$sPay upon Invoice%2$s is used.', 'woocommerce-paypal-payments' ),
- ' ',
+ 'wcgateway.settings.fraudnet-label' => static function ( ContainerInterface $container ): string {
+ $label = sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Manage online risk with %1$sFraudNet%2$s.', 'woocommerce-paypal-payments' ),
+ ' ',
' '
);
- $is_tracking_available = $container->get( 'order-tracking.is-tracking-available' );
- if ( $is_tracking_available ) {
- return $tracking_label;
+ if ( 'DE' === $container->get( 'api.shop.country' ) ) {
+ $label .= ' ' . sprintf(
+ // translators: %1$s and %2$s are the opening and closing of HTML tag.
+ __( 'Required when %1$sPay upon Invoice%2$s is used.', 'woocommerce-paypal-payments' ),
+ ' ',
+ ' '
+ );
}
- $tracking_label .= sprintf(
- // translators: %1$s and %2$s are the opening and closing of HTML tag.
- __(
- ' To use tracking features, you must %1$senable tracking on your account%2$s.',
- 'woocommerce-paypal-payments'
- ),
- ' ',
- ' '
- );
-
- return $tracking_label;
+ return $label;
},
'wcgateway.enable-dcc-url-sandbox' => static function ( ContainerInterface $container ): string {
return 'https://www.sandbox.paypal.com/bizsignup/entry/product/ppcp';
@@ -1188,6 +1266,11 @@ return array(
return 'https://www.paypal.com/bizsignup/entry?country.x=DE&product=payment_methods&capabilities=PAY_UPON_INVOICE';
},
'wcgateway.settings.connection.dcc-status-text' => static function ( ContainerInterface $container ): string {
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() < State::STATE_ONBOARDED ) {
+ return '';
+ }
+
$dcc_product_status = $container->get( 'wcgateway.helper.dcc-product-status' );
assert( $dcc_product_status instanceof DCCProductStatus );
@@ -1196,7 +1279,7 @@ return array(
$dcc_enabled = $dcc_product_status->dcc_is_active();
- $enabled_status_text = esc_html__( 'Status: Enabled', 'woocommerce-paypal-payments' );
+ $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$dcc_button_text = $dcc_enabled
@@ -1221,6 +1304,11 @@ return array(
);
},
'wcgateway.settings.connection.pui-status-text' => static function ( ContainerInterface $container ): string {
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() < State::STATE_ONBOARDED ) {
+ return '';
+ }
+
$pui_product_status = $container->get( 'wcgateway.pay-upon-invoice-product-status' );
assert( $pui_product_status instanceof PayUponInvoiceProductStatus );
@@ -1229,7 +1317,7 @@ return array(
$pui_enabled = $pui_product_status->pui_is_active();
- $enabled_status_text = esc_html__( 'Status: Enabled', 'woocommerce-paypal-payments' );
+ $enabled_status_text = esc_html__( 'Status: Available', 'woocommerce-paypal-payments' );
$disabled_status_text = esc_html__( 'Status: Not yet enabled', 'woocommerce-paypal-payments' );
$enable_pui_url = $environment->current_environment_is( Environment::PRODUCTION )
@@ -1270,7 +1358,13 @@ return array(
'wcgateway.settings.pay-later.messaging-locations' => static function( ContainerInterface $container ): array {
$button_locations = $container->get( 'wcgateway.button.locations' );
unset( $button_locations['mini-cart'] );
- return $button_locations;
+ return array_merge(
+ $button_locations,
+ array(
+ 'shop' => __( 'Shop', 'woocommerce-paypal-payments' ),
+ 'home' => __( 'Home', 'woocommerce-paypal-payments' ),
+ )
+ );
},
'wcgateway.button.default-locations' => static function( ContainerInterface $container ): array {
return array_keys( $container->get( 'wcgateway.settings.pay-later.messaging-locations' ) );
@@ -1280,24 +1374,10 @@ return array(
assert( $settings instanceof Settings );
$button_locations = $container->get( 'wcgateway.button.locations' );
- unset( $button_locations['mini-cart'] );
$smart_button_selected_locations = $settings->has( 'smart_button_locations' ) ? $settings->get( 'smart_button_locations' ) : array();
- $pay_later_button_locations = array();
- if ( empty( $smart_button_selected_locations ) ) {
- return $pay_later_button_locations;
- }
-
- foreach ( $button_locations as $location_key => $location ) {
- if ( ! in_array( $location_key, $smart_button_selected_locations, true ) ) {
- continue;
- }
-
- $pay_later_button_locations[ $location_key ] = $location;
- }
-
- return $pay_later_button_locations;
+ return array_intersect_key( $button_locations, array_flip( $smart_button_selected_locations ) );
},
'wcgateway.ppcp-gateways' => static function ( ContainerInterface $container ): array {
return array(
@@ -1308,52 +1388,10 @@ return array(
OXXOGateway::ID,
);
},
- 'wcgateway.enabled-ppcp-gateways' => static function ( ContainerInterface $container ): array {
- $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
- $ppcp_gateways = $container->get( 'wcgateway.ppcp-gateways' );
- $enabled_ppcp_gateways = array();
-
- foreach ( $ppcp_gateways as $gateway ) {
- if ( ! isset( $available_gateways[ $gateway ] ) ) {
- continue;
- }
- $enabled_ppcp_gateways[] = $gateway;
- }
-
- return $enabled_ppcp_gateways;
- },
- 'wcgateway.is-paypal-continuation' => static function ( ContainerInterface $container ): bool {
- $session_handler = $container->get( 'session.handler' );
- assert( $session_handler instanceof SessionHandler );
-
- $order = $session_handler->order();
- if ( ! $order ) {
- return false;
- }
- $source = $order->payment_source();
- if ( $source && $source->card() ) {
- return false; // Ignore for DCC.
- }
- if ( 'card' === $session_handler->funding_source() ) {
- return false; // Ignore for card buttons.
- }
- return true;
- },
- 'wcgateway.current-context' => static function ( ContainerInterface $container ): string {
- $context = 'mini-cart';
- if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
- $context = 'product';
- }
- if ( is_cart() ) {
- $context = 'cart';
- }
- if ( is_checkout() && ! $container->get( 'wcgateway.is-paypal-continuation' ) ) {
- $context = 'checkout';
- }
- if ( is_checkout_pay_page() ) {
- $context = 'pay-now';
- }
- return $context;
+ 'wcgateway.gateway-repository' => static function ( ContainerInterface $container ): GatewayRepository {
+ return new GatewayRepository(
+ $container->get( 'wcgateway.ppcp-gateways' )
+ );
},
'wcgateway.is-fraudnet-enabled' => static function ( ContainerInterface $container ): bool {
$settings = $container->get( 'wcgateway.settings' );
@@ -1368,11 +1406,22 @@ return array(
$container->get( 'wcgateway.fraudnet' ),
$container->get( 'onboarding.environment' ),
$container->get( 'wcgateway.settings' ),
- $container->get( 'wcgateway.enabled-ppcp-gateways' ),
- $container->get( 'wcgateway.current-context' ),
+ $container->get( 'wcgateway.gateway-repository' ),
+ $container->get( 'session.handler' ),
$container->get( 'wcgateway.is-fraudnet-enabled' )
);
},
+ 'wcgateway.cli.settings.command' => function( ContainerInterface $container ) : SettingsCommand {
+ return new SettingsCommand(
+ $container->get( 'wcgateway.settings' )
+ );
+ },
+ 'wcgateway.display-manager' => SingletonDecorator::make(
+ static function( ContainerInterface $container ): DisplayManager {
+ $settings = $container->get( 'wcgateway.settings' );
+ return new DisplayManager( $settings );
+ }
+ ),
'wcgateway.wp-paypal-locales-map' => static function( ContainerInterface $container ): array {
return array(
'ar' => 'ar_EG',
diff --git a/modules/ppcp-wc-gateway/src/Admin/FeesRenderer.php b/modules/ppcp-wc-gateway/src/Admin/FeesRenderer.php
index f3ef396d8..8c5fac27d 100644
--- a/modules/ppcp-wc-gateway/src/Admin/FeesRenderer.php
+++ b/modules/ppcp-wc-gateway/src/Admin/FeesRenderer.php
@@ -24,11 +24,18 @@ class FeesRenderer {
* @return string
*/
public function render( WC_Order $wc_order ) : string {
- $breakdown = $wc_order->get_meta( PayPalGateway::FEES_META_KEY );
+ $breakdown = $wc_order->get_meta( PayPalGateway::FEES_META_KEY );
+ $refund_breakdown = $wc_order->get_meta( PayPalGateway::REFUND_FEES_META_KEY ) ?: array();
+
if ( ! is_array( $breakdown ) ) {
return '';
}
+ $refund_fee = $refund_breakdown['paypal_fee'] ?? array();
+ $refund_amount = $refund_breakdown['net_amount'] ?? array();
+ $refund_total = ( $refund_fee['value'] ?? 0 ) + ( $refund_amount['value'] ?? 0 );
+ $refund_currency = ( ( $refund_amount['currency_code'] ?? '' ) === ( $refund_fee['currency_code'] ?? '' ) ) ? ( $refund_amount['currency_code'] ?? '' ) : '';
+
$html = '';
$fee = $breakdown['paypal_fee'] ?? null;
@@ -42,6 +49,28 @@ class FeesRenderer {
);
}
+ if ( $refund_fee ) {
+ $html .= $this->render_money_row(
+ __( 'PayPal Refund Fee:', 'woocommerce-paypal-payments' ),
+ __( 'The fee PayPal collects for the refund transactions.', 'woocommerce-paypal-payments' ),
+ $refund_fee['value'],
+ $refund_fee['currency_code'],
+ true,
+ 'refunded-total'
+ );
+ }
+
+ if ( $refund_amount ) {
+ $html .= $this->render_money_row(
+ __( 'PayPal Refunded:', 'woocommerce-paypal-payments' ),
+ __( 'The net amount that was refunded.', 'woocommerce-paypal-payments' ),
+ $refund_amount['value'],
+ $refund_amount['currency_code'],
+ true,
+ 'refunded-total'
+ );
+ }
+
$net = $breakdown['net_amount'] ?? null;
if ( is_array( $net ) ) {
$html .= $this->render_money_row(
@@ -50,6 +79,15 @@ class FeesRenderer {
$net['value'],
$net['currency_code']
);
+
+ if ( ( $refund_total > 0.0 && $refund_currency === $net['currency_code'] ) ) {
+ $html .= $this->render_money_row(
+ __( 'PayPal Net Total:', 'woocommerce-paypal-payments' ),
+ __( 'The net total that will be credited to your PayPal account minus the refunds.', 'woocommerce-paypal-payments' ),
+ $net['value'] - $refund_total,
+ $net['currency_code']
+ );
+ }
}
return $html;
@@ -63,9 +101,10 @@ class FeesRenderer {
* @param string|float $value The money value.
* @param string $currency The currency code.
* @param bool $negative Whether to add the minus sign.
+ * @param string $html_class Html class to add to the elements.
* @return string
*/
- private function render_money_row( string $title, string $tooltip, $value, string $currency, bool $negative = false ): string {
+ private function render_money_row( string $title, string $tooltip, $value, string $currency, bool $negative = false, string $html_class = '' ): string {
/**
* Bad type hint in WC phpdoc.
*
@@ -73,10 +112,10 @@ class FeesRenderer {
*/
return '
- ' . wc_help_tip( $tooltip ) . ' ' . esc_html( $title ) . '
+ ' . wc_help_tip( $tooltip ) . ' ' . esc_html( $title ) . '
-
+
' .
( $negative ? ' - ' : '' ) .
wc_price( $value, array( 'currency' => $currency ) ) . '
diff --git a/modules/ppcp-wc-gateway/src/Assets/FraudNetAssets.php b/modules/ppcp-wc-gateway/src/Assets/FraudNetAssets.php
index 6ab3f7d98..074a57c2e 100644
--- a/modules/ppcp-wc-gateway/src/Assets/FraudNetAssets.php
+++ b/modules/ppcp-wc-gateway/src/Assets/FraudNetAssets.php
@@ -9,8 +9,12 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
+use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
+use WooCommerce\PayPalCommerce\Session\SessionHandler;
+use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
@@ -20,6 +24,8 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
*/
class FraudNetAssets {
+ use ContextTrait;
+
/**
* The URL of this module.
*
@@ -58,16 +64,23 @@ class FraudNetAssets {
/**
* The list of enabled PayPal gateways.
*
- * @var string[]
+ * @var string[]|null
*/
protected $enabled_ppcp_gateways;
/**
- * The current context.
+ * The GatewayRepository.
*
- * @var string
+ * @var GatewayRepository
*/
- protected $context;
+ protected $gateway_repository;
+
+ /**
+ * The session handler
+ *
+ * @var SessionHandler
+ */
+ protected $session_handler;
/**
* True if FraudNet support is enabled in settings, otherwise false.
@@ -79,14 +92,14 @@ class FraudNetAssets {
/**
* Assets constructor.
*
- * @param string $module_url The url of this module.
- * @param string $version The assets version.
- * @param FraudNet $fraud_net The FraudNet entity.
- * @param Environment $environment The environment.
- * @param Settings $settings The Settings.
- * @param string[] $enabled_ppcp_gateways The list of enabled PayPal gateways.
- * @param string $context The current context.
- * @param bool $is_fraudnet_enabled true if FraudNet support is enabled in settings, otherwise false.
+ * @param string $module_url The url of this module.
+ * @param string $version The assets version.
+ * @param FraudNet $fraud_net The FraudNet entity.
+ * @param Environment $environment The environment.
+ * @param Settings $settings The Settings.
+ * @param GatewayRepository $gateway_repository The GatewayRepository.
+ * @param SessionHandler $session_handler The session handler.
+ * @param bool $is_fraudnet_enabled true if FraudNet support is enabled in settings, otherwise false.
*/
public function __construct(
string $module_url,
@@ -94,18 +107,18 @@ class FraudNetAssets {
FraudNet $fraud_net,
Environment $environment,
Settings $settings,
- array $enabled_ppcp_gateways,
- string $context,
+ GatewayRepository $gateway_repository,
+ SessionHandler $session_handler,
bool $is_fraudnet_enabled
) {
- $this->module_url = $module_url;
- $this->version = $version;
- $this->fraud_net = $fraud_net;
- $this->environment = $environment;
- $this->settings = $settings;
- $this->enabled_ppcp_gateways = $enabled_ppcp_gateways;
- $this->context = $context;
- $this->is_fraudnet_enabled = $is_fraudnet_enabled;
+ $this->module_url = $module_url;
+ $this->version = $version;
+ $this->fraud_net = $fraud_net;
+ $this->environment = $environment;
+ $this->settings = $settings;
+ $this->gateway_repository = $gateway_repository;
+ $this->session_handler = $session_handler;
+ $this->is_fraudnet_enabled = $is_fraudnet_enabled;
}
/**
@@ -144,19 +157,18 @@ class FraudNetAssets {
* @return bool true if FraudNet script should be loaded, otherwise false.
*/
protected function should_load_fraudnet_script(): bool {
- if ( empty( $this->enabled_ppcp_gateways ) ) {
+ if ( empty( $this->enabled_ppcp_gateways() ) ) {
return false;
}
- $is_pui_gateway_enabled = in_array( PayUponInvoiceGateway::ID, $this->enabled_ppcp_gateways, true );
- $is_only_standard_gateway_enabled = $this->enabled_ppcp_gateways === array( PayPalGateway::ID );
+ $is_pui_gateway_enabled = in_array( PayUponInvoiceGateway::ID, $this->enabled_ppcp_gateways(), true );
+ $is_only_standard_gateway_enabled = $this->enabled_ppcp_gateways() === array( PayPalGateway::ID );
- if ( $this->context !== 'checkout' || $is_only_standard_gateway_enabled ) {
+ if ( $this->context() !== 'checkout' || $is_only_standard_gateway_enabled ) {
return $this->is_fraudnet_enabled && $this->are_buttons_enabled_for_context();
}
return $is_pui_gateway_enabled ? true : $this->is_fraudnet_enabled;
-
}
/**
@@ -165,22 +177,35 @@ class FraudNetAssets {
* @return bool true if enabled, otherwise false.
*/
protected function are_buttons_enabled_for_context() : bool {
- if ( ! in_array( PayPalGateway::ID, $this->enabled_ppcp_gateways, true ) ) {
+ if ( ! in_array( PayPalGateway::ID, $this->enabled_ppcp_gateways(), true ) ) {
+ return false;
+ }
+ try {
+ $button_locations = $this->settings->get( 'smart_button_locations' );
+ } catch ( NotFoundException $exception ) {
return false;
}
- $location_prefix = $this->context === 'checkout' ? '' : "{$this->context}_";
- $setting_name = "button_{$location_prefix}enabled";
- $buttons_enabled_for_context = $this->settings->has( $setting_name ) && $this->settings->get( $setting_name );
-
- if ( $this->context === 'product' ) {
- return $buttons_enabled_for_context || $this->settings->has( 'mini-cart' ) && $this->settings->get( 'mini-cart' );
- }
-
- if ( $this->context === 'pay-now' ) {
+ if ( $this->context() === 'pay-now' ) {
return true;
}
- return $buttons_enabled_for_context;
+ if ( $this->context() === 'product' ) {
+ return in_array( 'product', $button_locations, true ) || in_array( 'mini-cart', $button_locations, true );
+ }
+
+ return in_array( $this->context(), $button_locations, true );
+ }
+
+ /**
+ * Returns IDs of the currently enabled PPCP gateways.
+ *
+ * @return string[]
+ */
+ protected function enabled_ppcp_gateways(): array {
+ if ( null === $this->enabled_ppcp_gateways ) {
+ $this->enabled_ppcp_gateways = $this->gateway_repository->get_enabled_ppcp_gateway_ids();
+ }
+ return $this->enabled_ppcp_gateways;
}
}
diff --git a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php
index e772d9028..03a9c59b6 100644
--- a/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php
+++ b/modules/ppcp-wc-gateway/src/Assets/SettingsPageAssets.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
+use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
/**
@@ -58,6 +59,13 @@ class SettingsPageAssets {
*/
private $country;
+ /**
+ * The environment object.
+ *
+ * @var Environment
+ */
+ private $environment;
+
/**
* Whether Pay Later button is enabled either for checkout, cart or product page.
*
@@ -79,6 +87,13 @@ class SettingsPageAssets {
*/
protected $all_funding_sources;
+ /**
+ * Whether it's a settings page of this plugin.
+ *
+ * @var bool
+ */
+ private $is_settings_page;
+
/**
* Assets constructor.
*
@@ -88,9 +103,11 @@ class SettingsPageAssets {
* @param string $client_id The PayPal SDK client ID.
* @param string $currency 3-letter currency code of the shop.
* @param string $country 2-letter country code of the shop.
+ * @param Environment $environment The environment object.
* @param bool $is_pay_later_button_enabled Whether Pay Later button is enabled either for checkout, cart or product page.
* @param array $disabled_sources The list of disabled funding sources.
* @param array $all_funding_sources The list of all existing funding sources.
+ * @param bool $is_settings_page Whether it's a settings page of this plugin.
*/
public function __construct(
string $module_url,
@@ -99,9 +116,11 @@ class SettingsPageAssets {
string $client_id,
string $currency,
string $country,
+ Environment $environment,
bool $is_pay_later_button_enabled,
array $disabled_sources,
- array $all_funding_sources
+ array $all_funding_sources,
+ bool $is_settings_page
) {
$this->module_url = $module_url;
$this->version = $version;
@@ -109,15 +128,19 @@ class SettingsPageAssets {
$this->client_id = $client_id;
$this->currency = $currency;
$this->country = $country;
+ $this->environment = $environment;
$this->is_pay_later_button_enabled = $is_pay_later_button_enabled;
$this->disabled_sources = $disabled_sources;
$this->all_funding_sources = $all_funding_sources;
+ $this->is_settings_page = $is_settings_page;
}
/**
* Register assets provided by this module.
+ *
+ * @return void
*/
- public function register_assets() {
+ public function register_assets(): void {
add_action(
'admin_enqueue_scripts',
function() {
@@ -125,11 +148,13 @@ class SettingsPageAssets {
return;
}
- if ( ! $this->is_paypal_payment_method_page() ) {
- return;
+ if ( $this->is_settings_page ) {
+ $this->register_admin_assets();
}
- $this->register_admin_assets();
+ if ( $this->is_paypal_payment_method_page() ) {
+ $this->register_paypal_admin_assets();
+ }
}
);
@@ -160,9 +185,9 @@ class SettingsPageAssets {
}
/**
- * Register assets for admin pages.
+ * Register assets for PayPal admin pages.
*/
- private function register_admin_assets(): void {
+ private function register_paypal_admin_assets(): void {
wp_enqueue_style(
'ppcp-gateway-settings',
trailingslashit( $this->module_url ) . 'assets/css/gateway-settings.css',
@@ -186,16 +211,42 @@ class SettingsPageAssets {
wp_localize_script(
'ppcp-gateway-settings',
'PayPalCommerceGatewaySettings',
- array(
- 'is_subscriptions_plugin_active' => $this->subscription_helper->plugin_is_active(),
- 'client_id' => $this->client_id,
- 'currency' => $this->currency,
- 'country' => $this->country,
- 'integration_date' => PAYPAL_INTEGRATION_DATE,
- 'is_pay_later_button_enabled' => $this->is_pay_later_button_enabled,
- 'disabled_sources' => $this->disabled_sources,
- 'all_funding_sources' => $this->all_funding_sources,
+ apply_filters(
+ 'woocommerce_paypal_payments_admin_gateway_settings',
+ array(
+ 'is_subscriptions_plugin_active' => $this->subscription_helper->plugin_is_active(),
+ 'client_id' => $this->client_id,
+ 'currency' => $this->currency,
+ 'country' => $this->country,
+ 'environment' => $this->environment->current_environment(),
+ 'integration_date' => PAYPAL_INTEGRATION_DATE,
+ 'is_pay_later_button_enabled' => $this->is_pay_later_button_enabled,
+ 'disabled_sources' => $this->disabled_sources,
+ 'all_funding_sources' => $this->all_funding_sources,
+ 'components' => array( 'buttons', 'funding-eligibility', 'messages' ),
+ )
)
);
}
+
+ /**
+ * Register assets for PayPal admin pages.
+ */
+ private function register_admin_assets(): void {
+ wp_enqueue_style(
+ 'ppcp-admin-common',
+ trailingslashit( $this->module_url ) . 'assets/css/common.css',
+ array(),
+ $this->version
+ );
+
+ wp_enqueue_script(
+ 'ppcp-admin-common',
+ trailingslashit( $this->module_url ) . 'assets/js/common.js',
+ array(),
+ $this->version,
+ true
+ );
+ }
+
}
diff --git a/modules/ppcp-wc-gateway/src/Checkout/CheckoutPayPalAddressPreset.php b/modules/ppcp-wc-gateway/src/Checkout/CheckoutPayPalAddressPreset.php
index 77f3e6dc1..5dae650ca 100644
--- a/modules/ppcp-wc-gateway/src/Checkout/CheckoutPayPalAddressPreset.php
+++ b/modules/ppcp-wc-gateway/src/Checkout/CheckoutPayPalAddressPreset.php
@@ -137,7 +137,7 @@ class CheckoutPayPalAddressPreset {
}
$shipping = null;
- foreach ( $this->session_handler->order()->purchase_units() as $unit ) {
+ foreach ( $order->purchase_units() as $unit ) {
$shipping = $unit->shipping();
if ( $shipping ) {
break;
diff --git a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php
index ad60cb9fb..6afa24f31 100644
--- a/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php
+++ b/modules/ppcp-wc-gateway/src/Checkout/DisableGateways.php
@@ -9,7 +9,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Checkout;
+use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
+use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@@ -20,6 +22,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
* Class DisableGateways
*/
class DisableGateways {
+ use ContextTrait;
/**
* The Session Handler.
@@ -42,22 +45,32 @@ class DisableGateways {
*/
protected $settings_status;
+ /**
+ * The subscription helper.
+ *
+ * @var SubscriptionHelper
+ */
+ private $subscription_helper;
+
/**
* DisableGateways constructor.
*
* @param SessionHandler $session_handler The Session Handler.
* @param ContainerInterface $settings The Settings.
* @param SettingsStatus $settings_status The Settings status helper.
+ * @param SubscriptionHelper $subscription_helper The subscription helper.
*/
public function __construct(
SessionHandler $session_handler,
ContainerInterface $settings,
- SettingsStatus $settings_status
+ SettingsStatus $settings_status,
+ SubscriptionHelper $subscription_helper
) {
- $this->session_handler = $session_handler;
- $this->settings = $settings;
- $this->settings_status = $settings_status;
+ $this->session_handler = $session_handler;
+ $this->settings = $settings;
+ $this->settings_status = $settings_status;
+ $this->subscription_helper = $subscription_helper;
}
/**
@@ -82,8 +95,11 @@ class DisableGateways {
unset( $methods[ CreditCardGateway::ID ] );
}
- if ( ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) && ! $this->session_handler->order() && is_checkout() ) {
- unset( $methods[ PayPalGateway::ID ] );
+ if ( ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) ) {
+ unset( $methods[ CardButtonGateway::ID ] );
+ if ( ! $this->session_handler->order() && is_checkout() ) {
+ unset( $methods[ PayPalGateway::ID ] );
+ }
}
if ( ! $this->needs_to_disable_gateways() ) {
@@ -99,6 +115,10 @@ class DisableGateways {
* @return bool
*/
private function disable_all_gateways() : bool {
+ if ( is_null( WC()->payment_gateways ) ) {
+ return false;
+ }
+
foreach ( WC()->payment_gateways->payment_gateways() as $gateway ) {
if ( PayPalGateway::ID === $gateway->id && $gateway->enabled !== 'yes' ) {
return true;
@@ -120,20 +140,6 @@ class DisableGateways {
* @return bool
*/
private function needs_to_disable_gateways(): bool {
- $order = $this->session_handler->order();
- if ( ! $order ) {
- return false;
- }
-
- $source = $order->payment_source();
- if ( $source && $source->card() ) {
- return false; // DCC.
- }
-
- if ( 'card' === $this->session_handler->funding_source() ) {
- return false; // Card buttons.
- }
-
- return true;
+ return $this->is_paypal_continuation();
}
}
diff --git a/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php b/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php
new file mode 100644
index 000000000..f67c7c6f1
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php
@@ -0,0 +1,70 @@
+settings = $settings;
+ }
+
+ /**
+ * Updates the specified settings.
+ *
+ * ## OPTIONS
+ *
+ *
+ * : The setting key.
+ *
+ *
+ * : The setting value.
+ *
+ * ## EXAMPLES
+ *
+ * wp pcp settings update description "Pay via PayPal."
+ * wp pcp settings update vault_enabled true
+ * wp pcp settings update vault_enabled false
+ *
+ * @param array $args Positional args.
+ * @param array $assoc_args Option args.
+ */
+ public function update( array $args, array $assoc_args ): void {
+ $key = (string) $args[0];
+ $value = $args[1];
+
+ if ( 'true' === strtolower( $value ) ) {
+ $value = true;
+ } elseif ( 'false' === strtolower( $value ) ) {
+ $value = false;
+ }
+
+ $this->settings->set( $key, $value );
+ $this->settings->persist();
+
+ WP_CLI::success( "Updated '{$key}' to '{$value}'." );
+ }
+}
diff --git a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php
index 57c29c2a4..3efe16542 100644
--- a/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php
+++ b/modules/ppcp-wc-gateway/src/Endpoint/ReturnUrlEndpoint.php
@@ -9,17 +9,18 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Endpoint;
+use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
+use WooCommerce\PayPalCommerce\Session\SessionHandler;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
-use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
/**
* Class ReturnUrlEndpoint
*/
class ReturnUrlEndpoint {
- use PrefixTrait;
const ENDPOINT = 'ppc-return-url';
/**
@@ -36,17 +37,38 @@ class ReturnUrlEndpoint {
*/
private $order_endpoint;
+ /**
+ * The session handler
+ *
+ * @var SessionHandler
+ */
+ protected $session_handler;
+
+ /**
+ * The logger.
+ *
+ * @var LoggerInterface
+ */
+ protected $logger;
+
/**
* ReturnUrlEndpoint constructor.
*
- * @param PayPalGateway $gateway The PayPal Gateway.
- * @param OrderEndpoint $order_endpoint The Order Endpoint.
- * @param string $prefix The prefix.
+ * @param PayPalGateway $gateway The PayPal Gateway.
+ * @param OrderEndpoint $order_endpoint The Order Endpoint.
+ * @param SessionHandler $session_handler The session handler.
+ * @param LoggerInterface $logger The logger.
*/
- public function __construct( PayPalGateway $gateway, OrderEndpoint $order_endpoint, string $prefix ) {
- $this->gateway = $gateway;
- $this->order_endpoint = $order_endpoint;
- $this->prefix = $prefix;
+ public function __construct(
+ PayPalGateway $gateway,
+ OrderEndpoint $order_endpoint,
+ SessionHandler $session_handler,
+ LoggerInterface $logger
+ ) {
+ $this->gateway = $gateway;
+ $this->order_endpoint = $order_endpoint;
+ $this->session_handler = $session_handler;
+ $this->logger = $logger;
}
/**
@@ -63,13 +85,25 @@ class ReturnUrlEndpoint {
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$order = $this->order_endpoint->order( $token );
- $wc_order_id = $this->sanitize_custom_id( $order->purchase_units()[0]->custom_id() );
+ $wc_order_id = (int) $order->purchase_units()[0]->custom_id();
if ( ! $wc_order_id ) {
+ // We cannot finish processing here without WC order, but at least go into the continuation mode.
+ if ( $order->status()->is( OrderStatus::APPROVED )
+ || $order->status()->is( OrderStatus::COMPLETED )
+ ) {
+ $this->session_handler->replace_order( $order );
+
+ wp_safe_redirect( wc_get_checkout_url() );
+ exit();
+ }
+
+ $this->logger->warning( "Return URL endpoint $token: no WC order ID." );
exit();
}
$wc_order = wc_get_order( $wc_order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
+ $this->logger->warning( "Return URL endpoint $token: WC order $wc_order_id not found." );
exit();
}
diff --git a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
index e91d87574..a465db721 100644
--- a/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
+++ b/modules/ppcp-wc-gateway/src/FundingSource/FundingSourceRenderer.php
@@ -22,13 +22,32 @@ class FundingSourceRenderer {
*/
protected $settings;
+ /**
+ * Map funding source ID -> human-readable name.
+ *
+ * @var array
+ */
+ protected $funding_sources;
+
+ /**
+ * The IDs of the sources belonging to PayPal that do not need to mention "via PayPal".
+ *
+ * @var string[]
+ */
+ protected $own_funding_sources = array( 'venmo', 'paylater' );
+
/**
* FundingSourceRenderer constructor.
*
- * @param ContainerInterface $settings The settings.
+ * @param ContainerInterface $settings The settings.
+ * @param array $funding_sources Map funding source ID -> human-readable name.
*/
- public function __construct( ContainerInterface $settings ) {
- $this->settings = $settings;
+ public function __construct(
+ ContainerInterface $settings,
+ array $funding_sources
+ ) {
+ $this->settings = $settings;
+ $this->funding_sources = $funding_sources;
}
/**
@@ -37,9 +56,17 @@ class FundingSourceRenderer {
* @param string $id The ID of the funding source, such as 'venmo'.
*/
public function render_name( string $id ): string {
- if ( 'venmo' === $id ) {
- return __( 'Venmo', 'woocommerce-paypal-payments' );
+ if ( array_key_exists( $id, $this->funding_sources ) ) {
+ if ( in_array( $id, $this->own_funding_sources, true ) ) {
+ return $this->funding_sources[ $id ];
+ }
+ return sprintf(
+ /* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
+ __( '%s (via PayPal)', 'woocommerce-paypal-payments' ),
+ $this->funding_sources[ $id ]
+ );
}
+
return $this->settings->has( 'title' ) ?
$this->settings->get( 'title' )
: __( 'PayPal', 'woocommerce-paypal-payments' );
@@ -51,9 +78,14 @@ class FundingSourceRenderer {
* @param string $id The ID of the funding source, such as 'venmo'.
*/
public function render_description( string $id ): string {
- if ( 'venmo' === $id ) {
- return __( 'Pay via Venmo.', 'woocommerce-paypal-payments' );
+ if ( array_key_exists( $id, $this->funding_sources ) ) {
+ return sprintf(
+ /* translators: %s - Sofort, BLIK, iDeal, Mercado Pago, etc. */
+ __( 'Pay via %s.', 'woocommerce-paypal-payments' ),
+ $this->funding_sources[ $id ]
+ );
}
+
return $this->settings->has( 'description' ) ?
$this->settings->get( 'description' )
: __( 'Pay via PayPal.', 'woocommerce-paypal-payments' );
diff --git a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
index 3357bd5e4..010df9e9b 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/CardButtonGateway.php
@@ -171,18 +171,17 @@ class CardButtonGateway extends \WC_Payment_Gateway {
$this->payment_token_repository = $payment_token_repository;
$this->logger = $logger;
- if ( $this->onboarded ) {
- $this->supports = array( 'refunds' );
- }
+ $this->supports = array(
+ 'refunds',
+ 'products',
+ );
+
if (
- defined( 'PPCP_FLAG_SUBSCRIPTION' )
- && PPCP_FLAG_SUBSCRIPTION
- && $this->gateways_enabled()
- && $this->vault_setting_enabled()
+ ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) )
+ || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' )
) {
- $this->supports = array(
- 'refunds',
- 'products',
+ array_push(
+ $this->supports,
'subscriptions',
'subscription_cancellation',
'subscription_suspension',
@@ -192,7 +191,7 @@ class CardButtonGateway extends \WC_Payment_Gateway {
'subscription_payment_method_change',
'subscription_payment_method_change_customer',
'subscription_payment_method_change_admin',
- 'multiple_subscriptions',
+ 'multiple_subscriptions'
);
}
@@ -304,9 +303,7 @@ class CardButtonGateway extends \WC_Payment_Gateway {
);
}
- if ( $this->subscription_helper->has_subscription( $order_id ) ) {
- $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
- }
+ do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
diff --git a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
index 5d2cc322f..1b99b9d05 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/CreditCardGateway.php
@@ -174,26 +174,27 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
if ( $state->current_state() === State::STATE_ONBOARDED ) {
$this->supports = array( 'refunds' );
}
- if (
- defined( 'PPCP_FLAG_SUBSCRIPTION' )
- && PPCP_FLAG_SUBSCRIPTION
- && $this->gateways_enabled()
- && $this->vault_setting_enabled()
- ) {
+ if ( $this->config->has( 'dcc_enabled' ) && $this->config->get( 'dcc_enabled' ) ) {
$this->supports = array(
'refunds',
'products',
- 'subscriptions',
- 'subscription_cancellation',
- 'subscription_suspension',
- 'subscription_reactivation',
- 'subscription_amount_changes',
- 'subscription_date_changes',
- 'subscription_payment_method_change',
- 'subscription_payment_method_change_customer',
- 'subscription_payment_method_change_admin',
- 'multiple_subscriptions',
);
+
+ if ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) {
+ array_push(
+ $this->supports,
+ 'subscriptions',
+ 'subscription_cancellation',
+ 'subscription_suspension',
+ 'subscription_reactivation',
+ 'subscription_amount_changes',
+ 'subscription_date_changes',
+ 'subscription_payment_method_change',
+ 'subscription_payment_method_change_customer',
+ 'subscription_payment_method_change_admin',
+ 'multiple_subscriptions'
+ );
+ }
}
$this->method_title = __(
@@ -395,9 +396,7 @@ class CreditCardGateway extends \WC_Payment_Gateway_CC {
);
}
- if ( $this->subscription_helper->has_subscription( $order_id ) ) {
- $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
- }
+ do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
diff --git a/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php b/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php
new file mode 100644
index 000000000..6362fa260
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Gateway/GatewayRepository.php
@@ -0,0 +1,57 @@
+ppcp_gateway_ids = $ppcp_gateway_ids;
+ }
+
+ /**
+ * Returns IDs of the currently enabled PPCP gateways.
+ *
+ * @return array
+ */
+ public function get_enabled_ppcp_gateway_ids(): array {
+ $available_gateways = WC()->payment_gateways->get_available_payment_gateways();
+
+ return array_filter(
+ $this->ppcp_gateway_ids,
+ function ( string $id ) use ( $available_gateways ): bool {
+ return isset( $available_gateways[ $id ] );
+ }
+ );
+ }
+
+ /**
+ * Indicates if a given gateway ID is registered.
+ *
+ * @param string $gateway_id The gateway ID.
+ * @return bool
+ */
+ public function exists( string $gateway_id ): bool {
+ return in_array( $gateway_id, $this->ppcp_gateway_ids, true );
+ }
+}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php
index 2c0034ef4..ff3eb9f78 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXO.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO;
+use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use WC_Order;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
@@ -63,7 +64,15 @@ class OXXO {
add_filter(
'woocommerce_available_payment_gateways',
- function ( array $methods ): array {
+ /**
+ * Param types removed to avoid third-party issues.
+ *
+ * @psalm-suppress MissingClosureParamType
+ */
+ function ( $methods ) {
+ if ( ! is_array( $methods ) ) {
+ return $methods;
+ }
if ( ! $this->checkout_allowed_for_oxxo() ) {
unset( $methods[ OXXOGateway::ID ] );
@@ -146,9 +155,19 @@ class OXXO {
add_action(
'add_meta_boxes',
function( string $post_type ) {
- if ( $post_type === 'shop_order' ) {
+ /**
+ * Class and function exist in WooCommerce.
+ *
+ * @psalm-suppress UndefinedClass
+ * @psalm-suppress UndefinedFunction
+ */
+ $screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
+ ? wc_get_page_screen_id( 'shop-order' )
+ : 'shop_order';
+
+ if ( $post_type === $screen ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
+ $post_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) );
$order = wc_get_order( $post_id );
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) {
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' );
@@ -159,7 +178,7 @@ class OXXO {
function() use ( $payer_action ) {
echo '' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '
';
},
- $post_type,
+ $screen,
'side',
'high'
);
@@ -220,24 +239,5 @@ class OXXO {
true
);
}
-
- wp_localize_script(
- 'ppcp-oxxo',
- 'OXXOConfig',
- array(
- 'oxxo_endpoint' => \WC_AJAX::get_endpoint( 'ppc-oxxo' ),
- 'oxxo_nonce' => wp_create_nonce( 'ppc-oxxo' ),
- 'error' => array(
- 'generic' => __(
- 'Something went wrong. Please try again or choose another payment source.',
- 'woocommerce-paypal-payments'
- ),
- 'js_validation' => __(
- 'Required form fields are not filled or invalid.',
- 'woocommerce-paypal-payments'
- ),
- ),
- )
- );
}
}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php b/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php
deleted file mode 100644
index 64d8e713f..000000000
--- a/modules/ppcp-wc-gateway/src/Gateway/OXXO/OXXOEndpoint.php
+++ /dev/null
@@ -1,156 +0,0 @@
-request_data = $request_data;
- $this->purchase_unit_factory = $purchase_unit_factory;
- $this->shipping_preference_factory = $shipping_preference_factory;
- $this->order_endpoint = $order_endpoint;
- $this->logger = $logger;
- }
-
- /**
- * The nonce
- *
- * @return string
- */
- public static function nonce(): string {
- return 'ppc-oxxo';
- }
-
- /**
- * Handles the request.
- *
- * @return bool
- */
- public function handle_request(): bool {
- $purchase_unit = $this->purchase_unit_factory->from_wc_cart();
- $payer_action = '';
-
- try {
- $shipping_preference = $this->shipping_preference_factory->from_state(
- $purchase_unit,
- 'checkout'
- );
-
- $order = $this->order_endpoint->create( array( $purchase_unit ), $shipping_preference );
-
- $payment_source = array(
- 'oxxo' => array(
- 'name' => 'John Doe',
- 'email' => 'foo@bar.com',
- 'country_code' => 'MX',
- ),
- );
-
- $payment_method = $this->order_endpoint->confirm_payment_source( $order->id(), $payment_source );
-
- foreach ( $payment_method->links as $link ) {
- if ( $link->rel === 'payer-action' ) {
- $payer_action = $link->href;
- }
- }
- } catch ( RuntimeException $exception ) {
- $error = $exception->getMessage();
-
- if ( is_a( $exception, PayPalApiException::class ) && is_array( $exception->details() ) ) {
- $details = '';
- foreach ( $exception->details() as $detail ) {
- $issue = $detail->issue ?? '';
- $field = $detail->field ?? '';
- $description = $detail->description ?? '';
- $details .= $issue . ' ' . $field . ' ' . $description . ' ';
- }
-
- $error = $details;
- }
-
- $this->logger->error( $error );
- wc_add_notice( $error, 'error' );
-
- wp_send_json_error( 'Could not get OXXO payer action.' );
- return false;
- }
-
- WC()->session->set( 'ppcp_payer_action', $payer_action );
-
- wp_send_json_success(
- array( 'payer_action' => $payer_action )
- );
-
- return true;
- }
-}
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
index 944d7b67b..b17e48a7a 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayPalGateway.php
@@ -12,6 +12,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Exception;
use Psr\Log\LoggerInterface;
use WC_Order;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
@@ -24,8 +26,11 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\PaymentsStatusHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
@@ -35,7 +40,7 @@ use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
*/
class PayPalGateway extends \WC_Payment_Gateway {
- use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait;
+ use ProcessPaymentTrait, FreeTrialHandlerTrait, GatewaySettingsRendererTrait, OrderMetaTrait, TransactionIdHandlingTrait, PaymentsStatusHandlingTrait;
const ID = 'ppcp-gateway';
const INTENT_META_KEY = '_ppcp_paypal_intent';
@@ -43,6 +48,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
const FEES_META_KEY = '_ppcp_paypal_fees';
+ const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
const REFUNDS_META_KEY = '_ppcp_refunds';
/**
@@ -150,6 +156,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
*/
protected $api_shop_country;
+ /**
+ * The order endpoint.
+ *
+ * @var OrderEndpoint
+ */
+ private $order_endpoint;
+
/**
* PayPalGateway constructor.
*
@@ -165,8 +178,9 @@ class PayPalGateway extends \WC_Payment_Gateway {
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
* @param Environment $environment The environment.
* @param PaymentTokenRepository $payment_token_repository The payment token repository.
- * @param LoggerInterface $logger The logger.
+ * @param LoggerInterface $logger The logger.
* @param string $api_shop_country The api shop country.
+ * @param OrderEndpoint $order_endpoint The order endpoint.
*/
public function __construct(
SettingsRenderer $settings_renderer,
@@ -182,7 +196,8 @@ class PayPalGateway extends \WC_Payment_Gateway {
Environment $environment,
PaymentTokenRepository $payment_token_repository,
LoggerInterface $logger,
- string $api_shop_country
+ string $api_shop_country,
+ OrderEndpoint $order_endpoint
) {
$this->id = self::ID;
$this->settings_renderer = $settings_renderer;
@@ -202,28 +217,35 @@ class PayPalGateway extends \WC_Payment_Gateway {
$this->api_shop_country = $api_shop_country;
if ( $this->onboarded ) {
- $this->supports = array( 'refunds' );
+ $this->supports = array( 'refunds', 'tokenization' );
}
- if (
- defined( 'PPCP_FLAG_SUBSCRIPTION' )
- && PPCP_FLAG_SUBSCRIPTION
- && $this->gateways_enabled()
- && $this->vault_setting_enabled()
- ) {
+ if ( $this->config->has( 'enabled' ) && $this->config->get( 'enabled' ) ) {
$this->supports = array(
'refunds',
'products',
- 'subscriptions',
- 'subscription_cancellation',
- 'subscription_suspension',
- 'subscription_reactivation',
- 'subscription_amount_changes',
- 'subscription_date_changes',
- 'subscription_payment_method_change',
- 'subscription_payment_method_change_customer',
- 'subscription_payment_method_change_admin',
- 'multiple_subscriptions',
);
+
+ if (
+ ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) )
+ || ( $this->config->has( 'subscriptions_mode' ) && $this->config->get( 'subscriptions_mode' ) === 'subscriptions_api' )
+ ) {
+ array_push(
+ $this->supports,
+ 'tokenization',
+ 'subscriptions',
+ 'subscription_cancellation',
+ 'subscription_suspension',
+ 'subscription_reactivation',
+ 'subscription_amount_changes',
+ 'subscription_date_changes',
+ 'subscription_payment_method_change',
+ 'subscription_payment_method_change_customer',
+ 'subscription_payment_method_change_admin',
+ 'multiple_subscriptions'
+ );
+ } elseif ( $this->config->has( 'vault_enabled_dcc' ) && $this->config->get( 'vault_enabled_dcc' ) ) {
+ $this->supports[] = 'tokenization';
+ }
}
$this->method_title = $this->define_method_title();
@@ -235,8 +257,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
$funding_source = $this->session_handler->funding_source();
if ( $funding_source ) {
- $this->title = $this->funding_source_renderer->render_name( $funding_source );
- $this->description = $this->funding_source_renderer->render_description( $funding_source );
+ $order = $this->session_handler->order();
+ if ( $order &&
+ ( $order->status()->is( OrderStatus::APPROVED ) || $order->status()->is( OrderStatus::COMPLETED ) )
+ ) {
+ $this->title = $this->funding_source_renderer->render_name( $funding_source );
+ $this->description = $this->funding_source_renderer->render_description( $funding_source );
+ }
}
$this->init_form_fields();
@@ -249,6 +276,31 @@ class PayPalGateway extends \WC_Payment_Gateway {
'process_admin_options',
)
);
+
+ $this->order_endpoint = $order_endpoint;
+ }
+
+ /**
+ * Return the gateway's title.
+ *
+ * @return string
+ */
+ public function get_title() {
+ if ( is_admin() ) {
+ // $theorder and other things for retrieving the order or post info are not available
+ // in the constructor, so must do it here.
+ global $theorder;
+ if ( $theorder instanceof WC_Order ) {
+ if ( $theorder->get_payment_method() === self::ID ) {
+ $payment_method_title = $theorder->get_payment_method_title();
+ if ( $payment_method_title ) {
+ $this->title = $payment_method_title;
+ }
+ }
+ }
+ }
+
+ return parent::get_title();
}
/**
@@ -424,7 +476,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
}
// phpcs:ignore WordPress.Security.NonceVerification.Missing
- $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) );
+ $funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? ( $_POST['funding_source'] ?? '' ) ) );
+
+ if ( $funding_source ) {
+ $wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
+ $wc_order->save();
+ }
+
if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
$user_id = (int) $wc_order->get_customer_id();
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
@@ -466,6 +524,29 @@ class PayPalGateway extends \WC_Payment_Gateway {
//phpcs:enable WordPress.Security.NonceVerification.Recommended
try {
+ $paypal_subscription_id = WC()->session->get( 'ppcp_subscription_id' ) ?? '';
+ if ( $paypal_subscription_id ) {
+ $order = $this->session_handler->order();
+ $this->add_paypal_meta( $wc_order, $order, $this->environment );
+
+ $subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $order_id ) : array();
+ foreach ( $subscriptions as $subscription ) {
+ $subscription->update_meta_data( 'ppcp_subscription', $paypal_subscription_id );
+ $subscription->save();
+
+ $subscription->add_order_note( "PayPal subscription {$paypal_subscription_id} added." );
+ }
+
+ $transaction_id = $this->get_paypal_order_transaction_id( $order );
+ if ( $transaction_id ) {
+ $this->update_transaction_id( $transaction_id, $wc_order );
+ }
+
+ $wc_order->payment_complete();
+
+ return $this->handle_payment_success( $wc_order );
+ }
+
if ( ! $this->order_processor->process( $wc_order ) ) {
return $this->handle_payment_failure(
$wc_order,
@@ -475,9 +556,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
);
}
- if ( $this->subscription_helper->has_subscription( $order_id ) ) {
- $this->schedule_saved_payment_check( $order_id, $wc_order->get_customer_id() );
- }
+ do_action( 'woocommerce_paypal_payments_before_handle_payment_success', $wc_order );
return $this->handle_payment_success( $wc_order );
} catch ( PayPalApiException $error ) {
@@ -485,12 +564,15 @@ class PayPalGateway extends \WC_Payment_Gateway {
'INSTRUMENT_DECLINED' => __( 'Instrument declined.', 'woocommerce-paypal-payments' ),
'PAYER_ACTION_REQUIRED' => __( 'Payer action required, possibly overcharge.', 'woocommerce-paypal-payments' ),
);
- $retry_errors = array_filter(
- array_keys( $retry_keys_messages ),
- function ( string $key ) use ( $error ): bool {
- return $error->has_detail( $key );
- }
+ $retry_errors = array_values(
+ array_filter(
+ array_keys( $retry_keys_messages ),
+ function ( string $key ) use ( $error ): bool {
+ return $error->has_detail( $key );
+ }
+ )
);
+
if ( $retry_errors ) {
$retry_error_key = $retry_errors[0];
diff --git a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php
index f60e81b75..9da44d069 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/PayUponInvoice/PayUponInvoice.php
@@ -9,15 +9,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice;
+use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
use Psr\Log\LoggerInterface;
use WC_Email;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
-use WooCommerce\PayPalCommerce\Onboarding\Environment;
-use WooCommerce\PayPalCommerce\Session\SessionHandler;
-use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
@@ -139,6 +137,25 @@ class PayUponInvoice {
* @throws NotFoundException When setting is not found.
*/
public function init(): void {
+ if ( $this->pui_helper->is_pui_gateway_enabled() ) {
+ $this->settings->set( 'fraudnet_enabled', true );
+ $this->settings->persist();
+ }
+
+ add_filter(
+ 'ppcp_partner_referrals_option',
+ function ( array $option ): array {
+ if ( $option['valid'] ) {
+ return $option;
+ }
+ if ( $option['field'] === 'ppcp-onboarding-pui' ) {
+ $option['valid'] = true;
+ $option['value'] = ( $option['value'] ? '1' : '' );
+ }
+ return $option;
+ }
+ );
+
add_filter(
'ppcp_partner_referrals_data',
function ( array $data ): array {
@@ -417,16 +434,27 @@ class PayUponInvoice {
*
* @psalm-suppress MissingClosureParamType
*/
- function ( $methods ): array {
- if ( ! is_array( $methods ) || State::STATE_ONBOARDED !== $this->state->current_state() ) {
+ function ( $methods ) {
+ if (
+ ! is_array( $methods )
+ || State::STATE_ONBOARDED !== $this->state->current_state()
+ // phpcs:ignore WordPress.Security.NonceVerification.Recommended
+ || ! ( is_checkout() || isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true' )
+ ) {
return $methods;
}
if (
! $this->pui_product_status->pui_is_active()
|| ! $this->pui_helper->is_checkout_ready_for_pui()
- // phpcs:ignore WordPress.Security.NonceVerification.Recommended
- || ( isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true' && ! $this->pui_helper->is_pay_for_order_ready_for_pui() )
+ ) {
+ unset( $methods[ PayUponInvoiceGateway::ID ] );
+ }
+
+ if (
+ // phpcs:ignore WordPress.Security.NonceVerification
+ isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true'
+ && ! $this->pui_helper->is_pay_for_order_ready_for_pui()
) {
unset( $methods[ PayUponInvoiceGateway::ID ] );
}
@@ -435,31 +463,6 @@ class PayUponInvoice {
}
);
- add_action(
- 'woocommerce_settings_checkout',
- function () {
- if (
- PayUponInvoiceGateway::ID === $this->current_ppcp_settings_page_id
- && ! $this->pui_product_status->pui_is_active()
- ) {
- $gateway_settings = get_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings' );
- $gateway_enabled = $gateway_settings['enabled'] ?? '';
- if ( 'yes' === $gateway_enabled ) {
- $gateway_settings['enabled'] = 'no';
- update_option( 'woocommerce_ppcp-pay-upon-invoice-gateway_settings', $gateway_settings );
- $redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-pay-upon-invoice-gateway' );
- wp_safe_redirect( $redirect_url );
- exit;
- }
-
- printf(
- '',
- esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay upon Invoice is selected to try again.', 'woocommerce-paypal-payments' )
- );
- }
- }
- );
-
add_action(
'woocommerce_update_options_checkout_ppcp-pay-upon-invoice-gateway',
function () {
@@ -504,6 +507,19 @@ class PayUponInvoice {
current_ppcp_settings_page_id ) {
+ $pui_gateway = WC()->payment_gateways->payment_gateways()[ PayUponInvoiceGateway::ID ];
+ if ( 'yes' === $pui_gateway->get_option( 'enabled' ) ) {
+ $pui_gateway->update_option( 'enabled', 'no' );
+ $redirect_url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-pay-upon-invoice-gateway' );
+ wp_safe_redirect( $redirect_url );
+ exit;
+ }
+
+ printf(
+ '',
+ esc_html__( 'Could not enable gateway because the connected PayPal account is not activated for Pay upon Invoice. Reconnect your account while Onboard with Pay upon Invoice is selected to try again.', 'woocommerce-paypal-payments' )
+ );
}
}
);
@@ -511,9 +527,19 @@ class PayUponInvoice {
add_action(
'add_meta_boxes',
function( string $post_type ) {
- if ( $post_type === 'shop_order' ) {
+ /**
+ * Class and function exist in WooCommerce.
+ *
+ * @psalm-suppress UndefinedClass
+ * @psalm-suppress UndefinedFunction
+ */
+ $screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
+ ? wc_get_page_screen_id( 'shop-order' )
+ : 'shop_order';
+
+ if ( $post_type === $screen ) {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
- $post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
+ $post_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) );
$order = wc_get_order( $post_id );
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) {
$instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' );
@@ -536,7 +562,7 @@ class PayUponInvoice {
echo wp_kses_post( "Verwendungszweck: {$payment_reference} " );
echo '';
},
- $post_type,
+ $screen,
'side',
'high'
);
diff --git a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
index da17f822b..3cf401b29 100644
--- a/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
+++ b/modules/ppcp-wc-gateway/src/Gateway/ProcessPaymentTrait.php
@@ -18,60 +18,6 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\GatewayGenericException;
* Trait ProcessPaymentTrait
*/
trait ProcessPaymentTrait {
- /**
- * Checks if PayPal or Credit Card gateways are enabled.
- *
- * @return bool Whether any of the gateways is enabled.
- */
- protected function gateways_enabled(): bool {
- if ( $this->config->has( 'enabled' ) && $this->config->get( 'enabled' ) ) {
- return true;
- }
- if ( $this->config->has( 'dcc_enabled' ) && $this->config->get( 'dcc_enabled' ) ) {
- return true;
- }
- return false;
- }
-
- /**
- * Checks if vault setting is enabled.
- *
- * @return bool Whether vault settings are enabled or not.
- * @throws \WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting hasn't been found.
- */
- protected function vault_setting_enabled(): bool {
- if ( $this->config->has( 'vault_enabled' ) && $this->config->get( 'vault_enabled' ) ) {
- return true;
- }
- return false;
- }
-
- /**
- * Scheduled the vaulted payment check.
- *
- * @param int $wc_order_id The WC order ID.
- * @param int $customer_id The customer ID.
- */
- protected function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void {
- $timestamp = 1 * MINUTE_IN_SECONDS;
- if (
- $this->config->has( 'subscription_behavior_when_vault_fails' )
- && $this->config->get( 'subscription_behavior_when_vault_fails' ) === 'capture_auth'
- ) {
- $timestamp = 0;
- }
-
- as_schedule_single_action(
- time() + $timestamp,
- 'woocommerce_paypal_payments_check_saved_payment',
- array(
- 'order_id' => $wc_order_id,
- 'customer_id' => $customer_id,
- 'intent' => $this->config->has( 'intent' ) ? $this->config->get( 'intent' ) : '',
- )
- );
- }
-
/**
* Handles the payment failure.
*
@@ -90,12 +36,14 @@ trait ProcessPaymentTrait {
}
$this->session_handler->destroy_session_data();
+ WC()->session->set( 'ppcp_subscription_id', '' );
wc_add_notice( $error->getMessage(), 'error' );
return array(
- 'result' => 'failure',
- 'redirect' => wc_get_checkout_url(),
+ 'result' => 'failure',
+ 'redirect' => wc_get_checkout_url(),
+ 'errorMessage' => $error->getMessage(),
);
}
@@ -112,6 +60,7 @@ trait ProcessPaymentTrait {
}
$this->session_handler->destroy_session_data();
+ WC()->session->set( 'ppcp_subscription_id', '' );
return array(
'result' => 'success',
diff --git a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php
index 3c60fea55..772a76e3a 100644
--- a/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php
+++ b/modules/ppcp-wc-gateway/src/Helper/DCCProductStatus.php
@@ -14,6 +14,8 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
+use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@@ -36,6 +38,14 @@ class DCCProductStatus {
* @var bool|null
*/
private $current_status_cache;
+
+ /**
+ * If there was a request failure.
+ *
+ * @var bool
+ */
+ private $has_request_failure = false;
+
/**
* The settings.
*
@@ -57,6 +67,20 @@ class DCCProductStatus {
*/
protected $dcc_applies;
+ /**
+ * The onboarding state.
+ *
+ * @var State
+ */
+ private $onboarding_state;
+
+ /**
+ * The API failure registry
+ *
+ * @var FailureRegistry
+ */
+ private $api_failure_registry;
+
/**
* DccProductStatus constructor.
*
@@ -64,17 +88,23 @@ class DCCProductStatus {
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
* @param DccApplies $dcc_applies The dcc applies helper.
+ * @param State $onboarding_state The onboarding state.
+ * @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
Cache $cache,
- DccApplies $dcc_applies
+ DccApplies $dcc_applies,
+ State $onboarding_state,
+ FailureRegistry $api_failure_registry
) {
- $this->settings = $settings;
- $this->partners_endpoint = $partners_endpoint;
- $this->cache = $cache;
- $this->dcc_applies = $dcc_applies;
+ $this->settings = $settings;
+ $this->partners_endpoint = $partners_endpoint;
+ $this->cache = $cache;
+ $this->dcc_applies = $dcc_applies;
+ $this->onboarding_state = $onboarding_state;
+ $this->api_failure_registry = $api_failure_registry;
}
/**
@@ -83,22 +113,34 @@ class DCCProductStatus {
* @return bool
*/
public function dcc_is_active() : bool {
- if ( $this->cache->has( self::DCC_STATUS_CACHE_KEY ) ) {
- return (bool) $this->cache->get( self::DCC_STATUS_CACHE_KEY );
+ if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
+ return false;
}
- if ( is_bool( $this->current_status_cache ) ) {
+ if ( $this->cache->has( self::DCC_STATUS_CACHE_KEY ) ) {
+ return $this->cache->get( self::DCC_STATUS_CACHE_KEY ) === 'true';
+ }
+
+ if ( $this->current_status_cache === true ) {
return $this->current_status_cache;
}
- if ( $this->settings->has( 'products_dcc_enabled' ) && $this->settings->get( 'products_dcc_enabled' ) ) {
+ if ( $this->settings->has( 'products_dcc_enabled' ) && $this->settings->get( 'products_dcc_enabled' ) === true ) {
$this->current_status_cache = true;
return true;
}
+ // Check API failure registry to prevent multiple failed API requests.
+ if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) {
+ $this->has_request_failure = true;
+ $this->current_status_cache = false;
+ return $this->current_status_cache;
+ }
+
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
+ $this->has_request_failure = true;
$this->current_status_cache = false;
return false;
}
@@ -120,18 +162,28 @@ class DCCProductStatus {
$this->settings->set( 'products_dcc_enabled', true );
$this->settings->persist();
$this->current_status_cache = true;
- $this->cache->set( self::DCC_STATUS_CACHE_KEY, true, 3 * MONTH_IN_SECONDS );
+ $this->cache->set( self::DCC_STATUS_CACHE_KEY, 'true', MONTH_IN_SECONDS );
return true;
}
}
- $expiration = 3 * MONTH_IN_SECONDS;
+ $expiration = MONTH_IN_SECONDS;
if ( $this->dcc_applies->for_country_currency() ) {
$expiration = 3 * HOUR_IN_SECONDS;
}
- $this->cache->set( self::DCC_STATUS_CACHE_KEY, false, $expiration );
+ $this->cache->set( self::DCC_STATUS_CACHE_KEY, 'false', $expiration );
$this->current_status_cache = false;
return false;
}
+
+ /**
+ * Returns if there was a request failure.
+ *
+ * @return bool
+ */
+ public function has_request_failure(): bool {
+ return $this->has_request_failure;
+ }
+
}
diff --git a/modules/ppcp-wc-gateway/src/Helper/DisplayManager.php b/modules/ppcp-wc-gateway/src/Helper/DisplayManager.php
new file mode 100644
index 000000000..8114c6848
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Helper/DisplayManager.php
@@ -0,0 +1,60 @@
+settings = $settings;
+ }
+
+ /**
+ * Creates and returns a rule.
+ *
+ * @param string|null $key The rule key.
+ * @return DisplayRule
+ */
+ public function rule( string $key = null ): DisplayRule {
+ if ( null === $key ) {
+ $key = '_rule_' . ( (string) count( $this->rules ) );
+ }
+
+ $rule = new DisplayRule( $key, $this->settings );
+
+ $this->rules[ $key ] = $rule;
+ return $rule;
+ }
+
+}
diff --git a/modules/ppcp-wc-gateway/src/Helper/DisplayRule.php b/modules/ppcp-wc-gateway/src/Helper/DisplayRule.php
new file mode 100644
index 000000000..807ab4b36
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Helper/DisplayRule.php
@@ -0,0 +1,262 @@
+key = $key;
+ $this->settings = $settings;
+ }
+
+ /**
+ * Adds a condition related to an HTML element.
+ *
+ * @param string $selector The condition selector.
+ * @param mixed $value The value to compare against.
+ * @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
+ * @return self
+ */
+ public function condition_element( string $selector, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
+ $this->add_condition(
+ array(
+ 'type' => self::CONDITION_TYPE_ELEMENT,
+ 'selector' => $selector,
+ 'operation' => $operation,
+ 'value' => $value,
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Adds a condition related to a bool check.
+ *
+ * @param bool $value The value to enable / disable the condition.
+ * @return self
+ */
+ public function condition_is_true( bool $value ): self {
+ $this->add_condition(
+ array(
+ 'type' => self::CONDITION_TYPE_BOOL,
+ 'value' => $value,
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Adds a condition related to the settings.
+ *
+ * @param string $settings_key The settings key.
+ * @param mixed $value The value to compare against.
+ * @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
+ * @return self
+ */
+ public function condition_is_settings( string $settings_key, $value, string $operation = self::CONDITION_OPERATION_EQUALS ): self {
+ $settings_value = null;
+
+ if ( $this->settings->has( $settings_key ) ) {
+ $settings_value = $this->settings->get( $settings_key );
+ }
+
+ $this->condition_is_true( $this->resolve_operation( $settings_value, $value, $operation ) );
+ return $this;
+ }
+
+ /**
+ * Adds a condition to show/hide the element.
+ *
+ * @param string $selector The condition selector.
+ */
+ public function action_visible( string $selector ): self {
+ $this->add_action(
+ array(
+ 'type' => self::ACTION_TYPE_ELEMENT,
+ 'selector' => $selector,
+ 'action' => self::ACTION_VISIBLE,
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Adds a condition to enable/disable the element.
+ *
+ * @param string $selector The condition selector.
+ */
+ public function action_enable( string $selector ): self {
+ $this->add_action(
+ array(
+ 'type' => self::ACTION_TYPE_ELEMENT,
+ 'selector' => $selector,
+ 'action' => self::ACTION_ENABLE,
+ )
+ );
+ return $this;
+ }
+
+ /**
+ * Adds a condition to the rule.
+ *
+ * @param array $options The condition options.
+ * @return void
+ */
+ private function add_condition( array $options ): void {
+ if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
+ $options['selector'] = '#ppcp-' . $options['selector']; // Refers to the input.
+ }
+
+ if ( ! isset( $options['key'] ) ) {
+ $options['key'] = '_condition_' . ( (string) count( $this->conditions ) );
+ }
+
+ $this->conditions[] = $options;
+ }
+
+ /**
+ * Adds an action to do.
+ *
+ * @param array $options The action options.
+ * @return void
+ */
+ private function add_action( array $options ): void {
+ if ( $this->add_selector_prefixes && isset( $options['selector'] ) ) {
+ $options['selector'] = '#field-' . $options['selector']; // Refers to the whole field.
+ }
+
+ if ( ! isset( $options['key'] ) ) {
+ $options['key'] = '_action_' . ( (string) count( $this->actions ) );
+ }
+
+ $this->actions[] = $options;
+ }
+
+ /**
+ * Set if selector prefixes like, "#ppcp-" or "#field-" should be added to condition or action selectors.
+ *
+ * @param bool $add_selector_prefixes If should add prefixes.
+ * @return self
+ */
+ public function should_add_selector_prefixes( bool $add_selector_prefixes = true ): self {
+ $this->add_selector_prefixes = $add_selector_prefixes;
+ return $this;
+ }
+
+ /**
+ * Adds a condition related to the settings.
+ *
+ * @param mixed $value_1 The value 1.
+ * @param mixed $value_2 The value 2.
+ * @param string $operation The condition operation (ex: equals, differs, in, not_empty, empty).
+ * @return bool
+ */
+ private function resolve_operation( $value_1, $value_2, string $operation ): bool {
+ switch ( $operation ) {
+ case self::CONDITION_OPERATION_EQUALS:
+ return $value_1 === $value_2;
+ case self::CONDITION_OPERATION_NOT_EQUALS:
+ return $value_1 !== $value_2;
+ case self::CONDITION_OPERATION_IN:
+ return in_array( $value_1, $value_2, true );
+ case self::CONDITION_OPERATION_NOT_IN:
+ return ! in_array( $value_1, $value_2, true );
+ case self::CONDITION_OPERATION_EMPTY:
+ return empty( $value_1 );
+ case self::CONDITION_OPERATION_NOT_EMPTY:
+ return ! empty( $value_1 );
+ }
+ return false;
+ }
+
+ /**
+ * Returns array representation.
+ *
+ * @return array
+ */
+ public function to_array(): array {
+ return array(
+ 'key' => $this->key,
+ 'conditions' => $this->conditions,
+ 'actions' => $this->actions,
+ );
+ }
+
+ /**
+ * Returns JSON representation.
+ *
+ * @return string
+ */
+ public function json(): string {
+ return wp_json_encode( $this->to_array() ) ?: '';
+ }
+
+}
diff --git a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php
index b8953a4dc..f1a3221eb 100644
--- a/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php
+++ b/modules/ppcp-wc-gateway/src/Helper/PayUponInvoiceProductStatus.php
@@ -13,6 +13,8 @@ use Throwable;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnersEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerStatusProduct;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
+use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@@ -35,6 +37,14 @@ class PayUponInvoiceProductStatus {
* @var bool|null
*/
private $current_status_cache;
+
+ /**
+ * If there was a request failure.
+ *
+ * @var bool
+ */
+ private $has_request_failure = false;
+
/**
* The settings.
*
@@ -49,21 +59,41 @@ class PayUponInvoiceProductStatus {
*/
private $partners_endpoint;
+ /**
+ * The onboarding status
+ *
+ * @var State
+ */
+ private $onboarding_state;
+
+ /**
+ * The API failure registry
+ *
+ * @var FailureRegistry
+ */
+ private $api_failure_registry;
+
/**
* PayUponInvoiceProductStatus constructor.
*
* @param Settings $settings The Settings.
* @param PartnersEndpoint $partners_endpoint The Partner Endpoint.
* @param Cache $cache The cache.
+ * @param State $onboarding_state The onboarding state.
+ * @param FailureRegistry $api_failure_registry The API failure registry.
*/
public function __construct(
Settings $settings,
PartnersEndpoint $partners_endpoint,
- Cache $cache
+ Cache $cache,
+ State $onboarding_state,
+ FailureRegistry $api_failure_registry
) {
- $this->settings = $settings;
- $this->partners_endpoint = $partners_endpoint;
- $this->cache = $cache;
+ $this->settings = $settings;
+ $this->partners_endpoint = $partners_endpoint;
+ $this->cache = $cache;
+ $this->onboarding_state = $onboarding_state;
+ $this->api_failure_registry = $api_failure_registry;
}
/**
@@ -72,21 +102,33 @@ class PayUponInvoiceProductStatus {
* @return bool
*/
public function pui_is_active() : bool {
- if ( $this->cache->has( self::PUI_STATUS_CACHE_KEY ) ) {
- return (bool) $this->cache->get( self::PUI_STATUS_CACHE_KEY );
+ if ( $this->onboarding_state->current_state() < State::STATE_ONBOARDED ) {
+ return false;
}
- if ( is_bool( $this->current_status_cache ) ) {
+ if ( $this->cache->has( self::PUI_STATUS_CACHE_KEY ) ) {
+ return $this->cache->get( self::PUI_STATUS_CACHE_KEY ) === 'true';
+ }
+
+ if ( $this->current_status_cache === true ) {
return $this->current_status_cache;
}
- if ( $this->settings->has( 'products_pui_enabled' ) && $this->settings->get( 'products_pui_enabled' ) ) {
+ if ( $this->settings->has( 'products_pui_enabled' ) && $this->settings->get( 'products_pui_enabled' ) === true ) {
$this->current_status_cache = true;
return true;
}
+ // Check API failure registry to prevent multiple failed API requests.
+ if ( $this->api_failure_registry->has_failure_in_timeframe( FailureRegistry::SELLER_STATUS_KEY, HOUR_IN_SECONDS ) ) {
+ $this->has_request_failure = true;
+ $this->current_status_cache = false;
+ return $this->current_status_cache;
+ }
+
try {
$seller_status = $this->partners_endpoint->seller_status();
} catch ( Throwable $error ) {
+ $this->has_request_failure = true;
$this->current_status_cache = false;
return false;
}
@@ -112,13 +154,23 @@ class PayUponInvoiceProductStatus {
$this->settings->set( 'products_pui_enabled', true );
$this->settings->persist();
$this->current_status_cache = true;
- $this->cache->set( self::PUI_STATUS_CACHE_KEY, true, 3 * MONTH_IN_SECONDS );
+ $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'true', MONTH_IN_SECONDS );
return true;
}
}
- $this->cache->set( self::PUI_STATUS_CACHE_KEY, false, 3 * MONTH_IN_SECONDS );
+ $this->cache->set( self::PUI_STATUS_CACHE_KEY, 'false', MONTH_IN_SECONDS );
$this->current_status_cache = false;
return false;
}
+
+ /**
+ * Returns if there was a request failure.
+ *
+ * @return bool
+ */
+ public function has_request_failure(): bool {
+ return $this->has_request_failure;
+ }
+
}
diff --git a/modules/ppcp-wc-gateway/src/Helper/RefundFeesUpdater.php b/modules/ppcp-wc-gateway/src/Helper/RefundFeesUpdater.php
new file mode 100644
index 000000000..b1fcab376
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Helper/RefundFeesUpdater.php
@@ -0,0 +1,195 @@
+order_endpoint = $order_endpoint;
+ $this->logger = $logger;
+ }
+
+ /**
+ * Updates the fees meta for a given order.
+ *
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @return void
+ */
+ public function update( WC_Order $wc_order ): void {
+ $paypal_order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
+
+ if ( ! $paypal_order_id ) {
+ $this->logger->error(
+ sprintf( 'Failed to update order paypal refund fees. No PayPal order_id. [wc_order: %s]', $wc_order->get_id() )
+ );
+ return;
+ }
+
+ $this->logger->debug(
+ sprintf( 'Updating order paypal refund fees. [wc_order: %s, paypal_order: %s]', $wc_order->get_id(), $paypal_order_id )
+ );
+
+ $paypal_order = $this->order_endpoint->order( $paypal_order_id );
+ $purchase_units = $paypal_order->purchase_units();
+
+ $gross_amount_total = 0.0;
+ $fee_total = 0.0;
+ $net_amount_total = 0.0;
+ $currency_codes = array();
+ $refunds_ids = array();
+
+ foreach ( $purchase_units as $purchase_unit ) {
+ $payments = $purchase_unit->payments();
+
+ if ( ! $payments ) {
+ continue;
+ }
+
+ $refunds = $payments->refunds();
+
+ foreach ( $refunds as $refund ) {
+ $breakdown = $refund->seller_payable_breakdown();
+ $refunds_ids[] = $refund->id();
+
+ if ( ! $breakdown ) {
+ continue;
+ }
+
+ $gross_amount = $breakdown->gross_amount();
+ if ( $gross_amount ) {
+ $gross_amount_total += $gross_amount->value();
+ $currency_codes[] = $gross_amount->currency_code();
+ }
+
+ $paypal_fee = $breakdown->paypal_fee();
+ if ( $paypal_fee ) {
+ $fee_total += $paypal_fee->value();
+ $currency_codes[] = $paypal_fee->currency_code();
+ }
+
+ $net_amount = $breakdown->net_amount();
+ if ( $net_amount ) {
+ $net_amount_total += $net_amount->value();
+ $currency_codes[] = $net_amount->currency_code();
+ }
+ }
+ }
+
+ $currency_codes = array_unique( $currency_codes );
+
+ if ( count( $currency_codes ) > 1 ) {
+ // There are multiple different currencies codes in the refunds.
+
+ $this->logger->warning(
+ sprintf(
+ 'Updating order paypal refund fees. Multiple currencies detected. [wc_order: %s, paypal_order: %s, currencies: %s]',
+ $wc_order->get_id(),
+ $paypal_order_id,
+ implode( ',', $currency_codes )
+ )
+ );
+
+ $wc_order->update_meta_data( PayPalGateway::REFUND_FEES_META_KEY, array() );
+ return;
+ }
+
+ $currency_code = current( $currency_codes ) ?: '';
+
+ $meta_data = array(
+ 'gross_amount' => ( new Money( $gross_amount_total, $currency_code ) )->to_array(),
+ 'paypal_fee' => ( new Money( $fee_total, $currency_code ) )->to_array(),
+ 'net_amount' => ( new Money( $net_amount_total, $currency_code ) )->to_array(),
+ );
+
+ $wc_order->update_meta_data( PayPalGateway::REFUND_FEES_META_KEY, $meta_data );
+ $wc_order->save();
+
+ $order_notes = $this->get_order_notes( $wc_order );
+
+ foreach ( $refunds_ids as $refund_id ) {
+ $has_note = false;
+ foreach ( $order_notes as $order_note ) {
+ if ( strpos( $order_note->comment_content, $refund_id ) !== false ) {
+ $has_note = true;
+ }
+ }
+ if ( ! $has_note ) {
+ $wc_order->add_order_note( sprintf( 'PayPal refund ID: %s', $refund_id ) );
+ }
+ }
+
+ $this->logger->debug(
+ sprintf( 'Updated order paypal refund fees. [wc_order: %s, paypal_order: %s]', $wc_order->get_id(), $paypal_order_id )
+ );
+ }
+
+ /**
+ * Returns all order notes
+ * Based on WC_Order::get_customer_order_notes
+ *
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @return WP_Comment[]
+ */
+ private function get_order_notes( WC_Order $wc_order ): array {
+ $notes = array();
+ $args = array(
+ 'post_id' => $wc_order->get_id(),
+ );
+
+ // By default, WooCommerce excludes comments of the comment_type order_note.
+ // We need to remove this filter to get the order notes.
+ remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
+
+ $comments = get_comments( $args );
+
+ if ( is_array( $comments ) ) {
+ foreach ( $comments as $comment ) {
+ if ( $comment instanceof WP_Comment ) {
+ $notes[] = $comment;
+ }
+ }
+ }
+
+ add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
+
+ return $notes;
+ }
+
+}
diff --git a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php
index b6a4cb640..d1f848d19 100644
--- a/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php
+++ b/modules/ppcp-wc-gateway/src/Helper/SettingsStatus.php
@@ -9,7 +9,6 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
-use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
/**
@@ -37,7 +36,6 @@ class SettingsStatus {
* Check whether Pay Later message is enabled either for checkout, cart or product page.
*
* @return bool true if is enabled, otherwise false.
- * @throws NotFoundException When a setting was not found.
*/
public function is_pay_later_messaging_enabled(): bool {
$messaging_enabled = $this->settings->has( 'pay_later_messaging_enabled' ) && $this->settings->get( 'pay_later_messaging_enabled' );
@@ -51,27 +49,15 @@ class SettingsStatus {
*
* @param string $location The location setting name.
* @return bool true if is enabled, otherwise false.
- * @throws NotFoundException When a setting was not found.
*/
public function is_pay_later_messaging_enabled_for_location( string $location ): bool {
- if ( ! $this->is_pay_later_messaging_enabled() ) {
- return false;
- }
-
- $selected_locations = $this->settings->has( 'pay_later_messaging_locations' ) ? $this->settings->get( 'pay_later_messaging_locations' ) : array();
-
- if ( empty( $selected_locations ) ) {
- return false;
- }
-
- return in_array( $location, $selected_locations, true );
+ return $this->is_pay_later_messaging_enabled() && $this->is_enabled_for_location( 'pay_later_messaging_locations', $location );
}
/**
* Check whether Pay Later button is enabled either for checkout, cart or product page.
*
* @return bool true if is enabled, otherwise false.
- * @throws NotFoundException When a setting was not found.
*/
public function is_pay_later_button_enabled(): bool {
$pay_later_button_enabled = $this->settings->has( 'pay_later_button_enabled' ) && $this->settings->get( 'pay_later_button_enabled' );
@@ -85,45 +71,11 @@ class SettingsStatus {
*
* @param string $location The location.
* @return bool true if is enabled, otherwise false.
- * @throws NotFoundException When a setting was not found.
*/
public function is_pay_later_button_enabled_for_location( string $location ): bool {
- if ( ! $this->is_pay_later_button_enabled() ) {
- return false;
- }
-
- $selected_locations = $this->settings->has( 'pay_later_button_locations' ) ? $this->settings->get( 'pay_later_button_locations' ) : array();
-
- if ( empty( $selected_locations ) ) {
- return false;
- }
-
- return in_array( $location, $selected_locations, true );
- }
-
- /**
- * Check whether Pay Later button is enabled for a given context.
- *
- * @param string $context The context.
- * @return bool true if is enabled, otherwise false.
- * @throws NotFoundException When a setting was not found.
- */
- public function is_pay_later_button_enabled_for_context( string $context ): bool {
- if ( ! $this->is_pay_later_button_enabled() ) {
- return false;
- }
-
- $selected_locations = $this->settings->has( 'pay_later_button_locations' ) ? $this->settings->get( 'pay_later_button_locations' ) : array();
-
- if ( empty( $selected_locations ) ) {
- return false;
- }
-
- $enabled_for_current_location = $this->is_pay_later_button_enabled_for_location( $context );
- $enabled_for_product = $this->is_pay_later_button_enabled_for_location( 'product' );
- $enabled_for_mini_cart = $this->is_pay_later_button_enabled_for_location( 'mini-cart' );
-
- return $context === 'product' ? $enabled_for_product || $enabled_for_mini_cart : $enabled_for_current_location;
+ return $this->is_pay_later_button_enabled() &&
+ ( $this->is_enabled_for_location( 'pay_later_button_locations', $location ) ||
+ ( 'product' === $location && $this->is_enabled_for_location( 'pay_later_button_locations', 'mini-cart' ) ) );
}
/**
@@ -133,7 +85,36 @@ class SettingsStatus {
* @return bool true if is enabled, otherwise false.
*/
public function is_smart_button_enabled_for_location( string $location ): bool {
- $selected_locations = $this->settings->has( 'smart_button_locations' ) ? $this->settings->get( 'smart_button_locations' ) : array();
+ return $this->is_enabled_for_location( 'smart_button_locations', $location );
+ }
+
+ /**
+ * Adapts the context value to match the location settings.
+ *
+ * @param string $location The location/context.
+ * @return string
+ */
+ protected function normalize_location( string $location ): string {
+ if ( 'pay-now' === $location ) {
+ $location = 'checkout';
+ }
+ if ( 'checkout-block' === $location ) {
+ $location = 'checkout-block-express';
+ }
+ return $location;
+ }
+
+ /**
+ * Checks whether the locations field in the settings includes the given location.
+ *
+ * @param string $setting_name The name of the enabled locations field in the settings.
+ * @param string $location The location.
+ * @return bool
+ */
+ protected function is_enabled_for_location( string $setting_name, string $location ): bool {
+ $location = $this->normalize_location( $location );
+
+ $selected_locations = $this->settings->has( $setting_name ) ? $this->settings->get( $setting_name ) : array();
if ( empty( $selected_locations ) ) {
return false;
diff --git a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php
index 08a20651b..81b057423 100644
--- a/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php
+++ b/modules/ppcp-wc-gateway/src/Notice/GatewayWithoutPayPalAdminNotice.php
@@ -13,11 +13,16 @@ use WC_Payment_Gateway;
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
+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';
+
/**
* The gateway ID.
*
@@ -53,27 +58,37 @@ class GatewayWithoutPayPalAdminNotice {
*/
private $is_ppcp_settings_page;
+ /**
+ * The Settings status helper.
+ *
+ * @var SettingsStatus|null
+ */
+ protected $settings_status;
+
/**
* ConnectAdminNotice constructor.
*
- * @param string $id The gateway ID.
- * @param State $state The state.
- * @param ContainerInterface $settings The settings.
- * @param bool $is_payments_page Whether the current page is the WC payment page.
- * @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
+ * @param string $id The gateway ID.
+ * @param State $state The state.
+ * @param ContainerInterface $settings The settings.
+ * @param bool $is_payments_page Whether the current page is the WC payment page.
+ * @param bool $is_ppcp_settings_page Whether the current page is the PPCP settings page.
+ * @param SettingsStatus|null $settings_status The Settings status helper.
*/
public function __construct(
string $id,
State $state,
ContainerInterface $settings,
bool $is_payments_page,
- bool $is_ppcp_settings_page
+ bool $is_ppcp_settings_page,
+ ?SettingsStatus $settings_status = null
) {
$this->id = $id;
$this->state = $state;
$this->settings = $settings;
$this->is_payments_page = $is_payments_page;
$this->is_ppcp_settings_page = $is_ppcp_settings_page;
+ $this->settings_status = $settings_status;
}
/**
@@ -82,8 +97,25 @@ class GatewayWithoutPayPalAdminNotice {
* @return Message|null
*/
public function message(): ?Message {
- if ( ! $this->should_display() ) {
- return null;
+ $notice_type = $this->check();
+
+ switch ( $notice_type ) {
+ case self::NOTICE_DISABLED_GATEWAY:
+ /* translators: %1$s the gateway name, %2$s URL. */
+ $text = __(
+ '%1$s cannot be used without the PayPal gateway. Enable the PayPal gateway .',
+ 'woocommerce-paypal-payments'
+ );
+ break;
+ case self::NOTICE_DISABLED_LOCATION:
+ /* translators: %1$s the gateway name, %2$s URL. */
+ $text = __(
+ '%1$s cannot be used without enabling the Checkout location for the PayPal gateway. Enable the Checkout location .',
+ 'woocommerce-paypal-payments'
+ );
+ break;
+ default:
+ return null;
}
$gateway = $this->get_gateway();
@@ -94,11 +126,7 @@ class GatewayWithoutPayPalAdminNotice {
$name = $gateway->get_method_title();
$message = sprintf(
- /* translators: %1$s the gateway name, %2$s URL. */
- __(
- '%1$s cannot be used without the PayPal gateway. Enable the PayPal gateway .',
- 'woocommerce-paypal-payments'
- ),
+ $text,
$name,
admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway' )
);
@@ -106,22 +134,33 @@ class GatewayWithoutPayPalAdminNotice {
}
/**
- * Whether the message should be displayed.
+ * Checks whether one of the messages should be displayed.
*
- * @return bool
+ * @return string One of the NOTICE_* constants.
*/
- protected function should_display(): bool {
+ protected function check(): string {
if ( State::STATE_ONBOARDED !== $this->state->current_state() ||
( ! $this->is_payments_page && ! $this->is_ppcp_settings_page ) ) {
- return false;
- }
- if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
- return false;
+ return self::NOTICE_OK;
}
- $gateway = $this->get_gateway();
+ $gateway = $this->get_gateway();
+ $gateway_enabled = $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
- return $gateway && wc_string_to_bool( $gateway->get_option( 'enabled' ) );
+ if ( ! $gateway_enabled ) {
+ return self::NOTICE_OK;
+ }
+
+ $paypal_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
+ if ( ! $paypal_enabled ) {
+ return self::NOTICE_DISABLED_GATEWAY;
+ }
+
+ if ( $this->settings_status && ! $this->settings_status->is_smart_button_enabled_for_location( 'checkout' ) ) {
+ return self::NOTICE_DISABLED_LOCATION;
+ }
+
+ return self::NOTICE_OK;
}
/**
diff --git a/modules/ppcp-wc-gateway/src/Notice/UnsupportedCurrencyAdminNotice.php b/modules/ppcp-wc-gateway/src/Notice/UnsupportedCurrencyAdminNotice.php
new file mode 100644
index 000000000..88f02cffe
--- /dev/null
+++ b/modules/ppcp-wc-gateway/src/Notice/UnsupportedCurrencyAdminNotice.php
@@ -0,0 +1,127 @@
+state = $state;
+ $this->shop_currency = $shop_currency;
+ $this->supported_currencies = $supported_currencies;
+ $this->is_wc_gateways_list_page = $is_wc_gateways_list_page;
+ $this->is_ppcp_settings_page = $is_ppcp_settings_page;
+
+ }
+
+ /**
+ * Returns the message.
+ *
+ * @return Message|null
+ */
+ public function unsupported_currency_message() {
+ if ( ! $this->should_display() ) {
+ return null;
+ }
+
+ $paypal_currency_support_url = 'https://developer.paypal.com/api/rest/reference/currency-codes/';
+
+ $message = sprintf(
+ /* translators: %1$s the shop currency, %2$s the PayPal currency support page link opening HTML tag, %3$s the link ending HTML tag. */
+ __(
+ 'Attention: Your current WooCommerce store currency (%1$s) is not supported by PayPal. Please update your store currency to one that is supported by PayPal to ensure smooth transactions. Visit the %2$sPayPal currency support page%3$s for more information on supported currencies.',
+ 'woocommerce-paypal-payments'
+ ),
+ $this->shop_currency,
+ '',
+ ' '
+ );
+ return new Message( $message, 'warning', true, 'ppcp-notice-wrapper' );
+ }
+
+ /**
+ * Whether the message should display.
+ *
+ * @return bool
+ */
+ protected function should_display(): bool {
+ return $this->state->current_state() === State::STATE_ONBOARDED
+ && ! $this->currency_supported()
+ && ( $this->is_wc_gateways_list_page || $this->is_ppcp_settings_page );
+ }
+
+ /**
+ * Whether the currency is supported by PayPal.
+ *
+ * @return bool
+ */
+ private function currency_supported(): bool {
+ $currency = $this->shop_currency;
+ $supported_currencies = $this->supported_currencies;
+ return in_array( $currency, $supported_currencies, true );
+ }
+}
diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
index 815f8d73e..47348eca3 100644
--- a/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
+++ b/modules/ppcp-wc-gateway/src/Processor/OrderMetaTrait.php
@@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
@@ -22,14 +23,16 @@ trait OrderMetaTrait {
/**
* Adds common metadata to the order.
*
- * @param WC_Order $wc_order The WC order to which metadata will be added.
- * @param Order $order The PayPal order.
- * @param Environment $environment The environment.
+ * @param WC_Order $wc_order The WC order to which metadata will be added.
+ * @param Order $order The PayPal order.
+ * @param Environment $environment The environment.
+ * @param OrderTransient|null $order_transient The order transient helper.
*/
protected function add_paypal_meta(
WC_Order $wc_order,
Order $order,
- Environment $environment
+ Environment $environment,
+ OrderTransient $order_transient = null
): void {
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
@@ -43,6 +46,8 @@ trait OrderMetaTrait {
}
$wc_order->save();
+
+ do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
}
/**
diff --git a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php
index 7b9b80c55..1fb10ed8c 100644
--- a/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php
+++ b/modules/ppcp-wc-gateway/src/Processor/OrderProcessor.php
@@ -10,9 +10,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Psr\Log\LoggerInterface;
+use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderHelper;
use WooCommerce\PayPalCommerce\Button\Helper\ThreeDSecure;
@@ -114,6 +116,13 @@ class OrderProcessor {
*/
private $order_helper;
+ /**
+ * Array to store temporary order data changes to restore after processing.
+ *
+ * @var array
+ */
+ private $restore_order_data = array();
+
/**
* OrderProcessor constructor.
*
@@ -161,11 +170,31 @@ class OrderProcessor {
* @return bool
*/
public function process( \WC_Order $wc_order ): bool {
- $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
- $order = $this->session_handler->order() ?? $this->order_endpoint->order( $order_id );
+ // phpcs:ignore WordPress.Security.NonceVerification
+ $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ?: wc_clean( wp_unslash( $_POST['paypal_order_id'] ?? '' ) );
+ $order = $this->session_handler->order();
+ if ( ! $order && is_string( $order_id ) && $order_id ) {
+ $order = $this->order_endpoint->order( $order_id );
+ }
if ( ! $order ) {
- $this->last_error = __( 'No PayPal order found in the current WooCommerce session.', 'woocommerce-paypal-payments' );
- return false;
+ $order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
+ if ( ! $order_id ) {
+ $this->logger->warning(
+ sprintf(
+ 'No PayPal order ID found in order #%d meta.',
+ $wc_order->get_id()
+ )
+ );
+ $this->last_error = __( 'Could not retrieve order. Maybe it was already completed or this browser is not supported. Please check your email or try again with a different browser.', 'woocommerce-paypal-payments' );
+ return false;
+ }
+
+ try {
+ $order = $this->order_endpoint->order( $order_id );
+ } catch ( RuntimeException $exception ) {
+ $this->last_error = __( 'Could not retrieve PayPal order.', 'woocommerce-paypal-payments' );
+ return false;
+ }
}
$this->add_paypal_meta( $wc_order, $order, $this->environment );
@@ -270,8 +299,12 @@ class OrderProcessor {
* @return Order
*/
public function patch_order( \WC_Order $wc_order, Order $order ): Order {
+ $this->apply_outbound_order_filters( $wc_order );
$updated_order = $this->order_factory->from_wc_order( $wc_order, $order );
- $order = $this->order_endpoint->patch_order_with( $order, $updated_order );
+ $this->restore_order_from_filters( $wc_order );
+
+ $order = $this->order_endpoint->patch_order_with( $order, $updated_order );
+
return $order;
}
@@ -301,4 +334,48 @@ class OrderProcessor {
true
);
}
+
+ /**
+ * Applies filters to the WC_Order, so they are reflected only on PayPal Order.
+ *
+ * @param WC_Order $wc_order The WoocOmmerce Order.
+ * @return void
+ */
+ private function apply_outbound_order_filters( WC_Order $wc_order ): void {
+ $items = $wc_order->get_items();
+
+ $this->restore_order_data['names'] = array();
+
+ foreach ( $items as $item ) {
+ if ( ! $item instanceof \WC_Order_Item ) {
+ continue;
+ }
+
+ $original_name = $item->get_name();
+ $new_name = apply_filters( 'woocommerce_paypal_payments_order_line_item_name', $original_name, $item->get_id(), $wc_order->get_id() );
+
+ if ( $new_name !== $original_name ) {
+ $this->restore_order_data['names'][ $item->get_id() ] = $original_name;
+ $item->set_name( $new_name );
+ }
+ }
+ }
+
+ /**
+ * Restores the WC_Order to it's state before filters.
+ *
+ * @param WC_Order $wc_order The WooCommerce Order.
+ * @return void
+ */
+ private function restore_order_from_filters( WC_Order $wc_order ): void {
+ if ( is_array( $this->restore_order_data['names'] ?? null ) ) {
+ foreach ( $this->restore_order_data['names'] as $wc_item_id => $original_name ) {
+ $wc_item = $wc_order->get_item( $wc_item_id, false );
+
+ if ( $wc_item ) {
+ $wc_item->set_name( $original_name );
+ }
+ }
+ }
+ }
}
diff --git a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php
index 16f9b4698..a8f6dfa31 100644
--- a/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php
+++ b/modules/ppcp-wc-gateway/src/Processor/RefundProcessor.php
@@ -11,16 +11,19 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Processor;
use Exception;
use Psr\Log\LoggerInterface;
+use WC_Order;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundCapture;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
/**
* Class RefundProcessor
@@ -53,24 +56,33 @@ class RefundProcessor {
*/
private $logger;
+ /**
+ * The refund fees updater.
+ *
+ * @var RefundFeesUpdater
+ */
+ private $refund_fees_updater;
+
/**
* RefundProcessor constructor.
*
- * @param OrderEndpoint $order_endpoint The order endpoint.
- * @param PaymentsEndpoint $payments_endpoint The payments endpoint.
- * @param LoggerInterface $logger The logger.
+ * @param OrderEndpoint $order_endpoint The order endpoint.
+ * @param PaymentsEndpoint $payments_endpoint The payments endpoint.
+ * @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
+ * @param LoggerInterface $logger The logger.
*/
- public function __construct( OrderEndpoint $order_endpoint, PaymentsEndpoint $payments_endpoint, LoggerInterface $logger ) {
+ public function __construct( OrderEndpoint $order_endpoint, PaymentsEndpoint $payments_endpoint, RefundFeesUpdater $refund_fees_updater, LoggerInterface $logger ) {
- $this->order_endpoint = $order_endpoint;
- $this->payments_endpoint = $payments_endpoint;
- $this->logger = $logger;
+ $this->order_endpoint = $order_endpoint;
+ $this->payments_endpoint = $payments_endpoint;
+ $this->refund_fees_updater = $refund_fees_updater;
+ $this->logger = $logger;
}
/**
* Processes a refund.
*
- * @param \WC_Order $wc_order The WooCommerce order.
+ * @param WC_Order $wc_order The WooCommerce order.
* @param float|null $amount The refund amount.
* @param string $reason The reason for the refund.
*
@@ -78,7 +90,7 @@ class RefundProcessor {
*
* @phpcs:ignore Squiz.Commenting.FunctionCommentThrowTag.Missing
*/
- public function process( \WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
+ public function process( WC_Order $wc_order, float $amount = null, string $reason = '' ) : bool {
try {
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
if ( ! $order_id ) {
@@ -87,15 +99,7 @@ class RefundProcessor {
$order = $this->order_endpoint->order( $order_id );
- $purchase_units = $order->purchase_units();
- if ( ! $purchase_units ) {
- throw new RuntimeException( 'No purchase units.' );
- }
-
- $payments = $purchase_units[0]->payments();
- if ( ! $payments ) {
- throw new RuntimeException( 'No payments.' );
- }
+ $payments = $this->get_payments( $order );
$this->logger->debug(
sprintf(
@@ -109,39 +113,14 @@ class RefundProcessor {
switch ( $mode ) {
case self::REFUND_MODE_REFUND:
- $captures = $payments->captures();
- if ( ! $captures ) {
- throw new RuntimeException( 'No capture.' );
- }
-
- $capture = $captures[0];
- $refund = new Refund(
- $capture,
- $capture->invoice_id(),
- $reason,
- new Amount(
- new Money( $amount, $wc_order->get_currency() )
- )
- );
- $refund_id = $this->payments_endpoint->refund( $refund );
+ $refund_id = $this->refund( $order, $wc_order, $amount, $reason );
$this->add_refund_to_meta( $wc_order, $refund_id );
+ $this->refund_fees_updater->update( $wc_order );
break;
case self::REFUND_MODE_VOID:
- $voidable_authorizations = array_filter(
- $payments->authorizations(),
- function ( Authorization $authorization ): bool {
- return $authorization->is_voidable();
- }
- );
- if ( ! $voidable_authorizations ) {
- throw new RuntimeException( 'No voidable authorizations.' );
- }
-
- foreach ( $voidable_authorizations as $authorization ) {
- $this->payments_endpoint->void( $authorization );
- }
+ $this->void( $order );
$wc_order->set_status( 'refunded' );
$wc_order->save();
@@ -158,6 +137,67 @@ class RefundProcessor {
}
}
+ /**
+ * Adds a refund to the PayPal order.
+ *
+ * @param Order $order The PayPal order.
+ * @param WC_Order $wc_order The WooCommerce order.
+ * @param float $amount The refund amount.
+ * @param string $reason The reason for the refund.
+ *
+ * @throws RuntimeException When operation fails.
+ * @return string The PayPal refund ID.
+ */
+ public function refund(
+ Order $order,
+ WC_Order $wc_order,
+ float $amount,
+ string $reason = ''
+ ): string {
+ $payments = $this->get_payments( $order );
+
+ $captures = $payments->captures();
+ if ( ! $captures ) {
+ throw new RuntimeException( 'No capture.' );
+ }
+
+ $capture = $captures[0];
+ $refund = new RefundCapture(
+ $capture,
+ $capture->invoice_id(),
+ $reason,
+ new Amount(
+ new Money( $amount, $wc_order->get_currency() )
+ )
+ );
+
+ return $this->payments_endpoint->refund( $refund );
+ }
+
+ /**
+ * Voids the authorization.
+ *
+ * @param Order $order The PayPal order.
+ * @throws RuntimeException When operation fails.
+ */
+ public function void( Order $order ): void {
+ $payments = $this->get_payments( $order );
+
+ $voidable_authorizations = array_filter(
+ $payments->authorizations(),
+ function ( Authorization $authorization ): bool {
+ return $authorization->is_voidable();
+ }
+ );
+ if ( ! $voidable_authorizations ) {
+ throw new RuntimeException( 'No voidable authorizations.' );
+ }
+
+ foreach ( $voidable_authorizations as $authorization ) {
+ $this->payments_endpoint->void( $authorization );
+ }
+ }
+
/**
* Determines the refunding mode.
*
@@ -181,4 +221,24 @@ class RefundProcessor {
return self::REFUND_MODE_UNKNOWN;
}
+
+ /**
+ * Returns the payments object or throws.
+ *
+ * @param Order $order The order.
+ * @throws RuntimeException When payment not available.
+ */
+ protected function get_payments( Order $order ): Payments {
+ $purchase_units = $order->purchase_units();
+ if ( ! $purchase_units ) {
+ throw new RuntimeException( 'No purchase units.' );
+ }
+
+ $payments = $purchase_units[0]->payments();
+ if ( ! $payments ) {
+ throw new RuntimeException( 'No payments.' );
+ }
+
+ return $payments;
+ }
}
diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
index 4a16aca77..067acf434 100644
--- a/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
+++ b/modules/ppcp-wc-gateway/src/Settings/Fields/connection-tab-fields.php
@@ -11,11 +11,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
use WooCommerce\PayPalCommerce\Onboarding\State;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
return function ( ContainerInterface $container, array $fields ): array {
@@ -38,6 +40,9 @@ return function ( ContainerInterface $container, array $fields ): array {
$module_url = $container->get( 'wcgateway.url' );
+ $display_manager = $container->get( 'wcgateway.display-manager' );
+ assert( $display_manager instanceof DisplayManager );
+
$connection_fields = array(
'ppcp_onboarading_header' => array(
'type' => 'ppcp-text',
@@ -197,6 +202,11 @@ return function ( ContainerInterface $container, array $fields ): array {
'toggle_manual_input' => array(
'type' => 'ppcp-text',
'text' => '' . __( 'Toggle to manual credential input', 'woocommerce-paypal-payments' ) . ' ',
+ 'description' => sprintf(
+ '%1$s %2$s ',
+ esc_html__( 'Further information on manual credential input:', 'woocommerce-paypal-payments' ),
+ esc_html__( 'documentation', 'woocommerce-paypal-payments' )
+ ),
'classes' => array( 'ppcp-onboarding-element' ),
'screens' => array(
State::STATE_START,
@@ -232,7 +242,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'merchant_email_production' => array(
'title' => __( 'Live Email address', 'woocommerce-paypal-payments' ),
'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'text',
+ 'type' => 'email',
'required' => true,
'desc_tip' => true,
'description' => __( 'The email address of your PayPal account.', 'woocommerce-paypal-payments' ),
@@ -245,32 +255,40 @@ return function ( ContainerInterface $container, array $fields ): array {
'gateway' => Settings::CONNECTION_TAB_ID,
),
'merchant_id_production' => array(
- 'title' => __( 'Live Merchant Id', 'woocommerce-paypal-payments' ),
- 'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'ppcp-text-input',
- 'desc_tip' => true,
- 'description' => __( 'The merchant id of your account ', 'woocommerce-paypal-payments' ),
- 'default' => false,
- 'screens' => array(
+ 'title' => __( 'Live Merchant Id', 'woocommerce-paypal-payments' ),
+ 'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The merchant id of your account. Should be exactly 13 alphanumeric uppercase letters.', 'woocommerce-paypal-payments' ),
+ 'maxlength' => 13,
+ 'custom_attributes' => array(
+ 'pattern' => '[A-Z0-9]{13}',
+ 'autocomplete' => 'off',
+ ),
+ 'default' => false,
+ 'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
- 'requirements' => array(),
- 'gateway' => Settings::CONNECTION_TAB_ID,
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
),
'client_id_production' => array(
- 'title' => __( 'Live Client Id', 'woocommerce-paypal-payments' ),
- 'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'ppcp-text-input',
- 'desc_tip' => true,
- 'description' => __( 'The client id of your api ', 'woocommerce-paypal-payments' ),
- 'default' => false,
- 'screens' => array(
+ 'title' => __( 'Live Client Id', 'woocommerce-paypal-payments' ),
+ 'classes' => array( State::STATE_ONBOARDED === $state->production_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The client id of your api ', 'woocommerce-paypal-payments' ),
+ 'custom_attributes' => array(
+ 'autocomplete' => 'off',
+ ),
+ 'default' => false,
+ 'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
- 'requirements' => array(),
- 'gateway' => Settings::CONNECTION_TAB_ID,
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
),
'client_secret_production' => array(
'title' => __( 'Live Secret Key', 'woocommerce-paypal-payments' ),
@@ -290,7 +308,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'merchant_email_sandbox' => array(
'title' => __( 'Sandbox Email address', 'woocommerce-paypal-payments' ),
'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'text',
+ 'type' => 'email',
'required' => true,
'desc_tip' => true,
'description' => __( 'The email address of your PayPal account.', 'woocommerce-paypal-payments' ),
@@ -303,32 +321,40 @@ return function ( ContainerInterface $container, array $fields ): array {
'gateway' => Settings::CONNECTION_TAB_ID,
),
'merchant_id_sandbox' => array(
- 'title' => __( 'Sandbox Merchant Id', 'woocommerce-paypal-payments' ),
- 'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'ppcp-text-input',
- 'desc_tip' => true,
- 'description' => __( 'The merchant id of your account ', 'woocommerce-paypal-payments' ),
- 'default' => false,
- 'screens' => array(
+ 'title' => __( 'Sandbox Merchant Id', 'woocommerce-paypal-payments' ),
+ 'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The merchant id of your account. Should be exactly 13 alphanumeric uppercase letters.', 'woocommerce-paypal-payments' ),
+ 'maxlength' => 13,
+ 'custom_attributes' => array(
+ 'pattern' => '[A-Z0-9]{13}',
+ 'autocomplete' => 'off',
+ ),
+ 'default' => false,
+ 'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
- 'requirements' => array(),
- 'gateway' => Settings::CONNECTION_TAB_ID,
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
),
'client_id_sandbox' => array(
- 'title' => __( 'Sandbox Client Id', 'woocommerce-paypal-payments' ),
- 'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
- 'type' => 'ppcp-text-input',
- 'desc_tip' => true,
- 'description' => __( 'The client id of your api ', 'woocommerce-paypal-payments' ),
- 'default' => false,
- 'screens' => array(
+ 'title' => __( 'Sandbox Client Id', 'woocommerce-paypal-payments' ),
+ 'classes' => array( State::STATE_ONBOARDED === $state->sandbox_state() ? 'onboarded' : '', 'ppcp-always-shown-element' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The client id of your api ', 'woocommerce-paypal-payments' ),
+ 'custom_attributes' => array(
+ 'autocomplete' => 'off',
+ ),
+ 'default' => false,
+ 'screens' => array(
State::STATE_START,
State::STATE_ONBOARDED,
),
- 'requirements' => array(),
- 'gateway' => Settings::CONNECTION_TAB_ID,
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
),
'client_secret_sandbox' => array(
'title' => __( 'Sandbox Secret Key', 'woocommerce-paypal-payments' ),
@@ -380,30 +406,11 @@ return function ( ContainerInterface $container, array $fields ): array {
'requirements' => array( 'pui_ready' ),
'gateway' => Settings::CONNECTION_TAB_ID,
),
- 'tracking_enabled' => array(
- 'title' => __( 'Shipment Tracking', 'woocommerce-paypal-payments' ),
- 'type' => 'checkbox',
- 'desc_tip' => true,
- 'label' => $container->get( 'wcgateway.settings.tracking-label' ),
- 'description' => __( 'Allows to send shipment tracking numbers to PayPal for PayPal transactions.', 'woocommerce-paypal-payments' ),
- 'default' => false,
- 'screens' => array(
- State::STATE_ONBOARDED,
- ),
- 'requirements' => array(),
- 'gateway' => Settings::CONNECTION_TAB_ID,
- 'input_class' => $container->get( 'wcgateway.settings.should-disable-tracking-checkbox' ) ? array( 'ppcp-disabled-checkbox' ) : array(),
- ),
'fraudnet_enabled' => array(
'title' => __( 'FraudNet', 'woocommerce-paypal-payments' ),
'type' => 'checkbox',
'desc_tip' => true,
- 'label' => sprintf(
- // translators: %1$s and %2$s are the opening and closing of HTML tag.
- __( 'Manage online risk with %1$sFraudNet%2$s.', 'woocommerce-paypal-payments' ),
- ' ',
- ' '
- ),
+ 'label' => $container->get( 'wcgateway.settings.fraudnet-label' ),
'description' => __( 'FraudNet is a JavaScript library developed by PayPal and embedded into a merchant’s web page to collect browser-based data to help reduce fraud.', 'woocommerce-paypal-payments' ),
'default' => false,
'screens' => array(
@@ -411,8 +418,8 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'requirements' => array(),
'gateway' => Settings::CONNECTION_TAB_ID,
+ 'input_class' => $container->get( 'wcgateway.settings.should-disable-fraudnet-checkbox' ) ? array( 'ppcp-disabled-checkbox' ) : array(),
),
-
'credentials_integration_configuration_heading' => array(
'heading' => __( 'General integration configuration', 'woocommerce-paypal-payments' ),
'type' => 'ppcp-heading',
@@ -428,6 +435,20 @@ return function ( ContainerInterface $container, array $fields ): array {
''
),
),
+ 'soft_descriptor' => array(
+ 'title' => __( 'Soft Descriptor', 'woocommerce-paypal-payments' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The soft descriptor is the dynamic text used to construct the statement descriptor that appears on a payer\'s card statement. Text field, max value of 22 characters.', 'woocommerce-paypal-payments' ),
+ 'maxlength' => 22,
+ 'default' => '',
+ 'screens' => array(
+ State::STATE_START,
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
+ ),
'prefix' => array(
'title' => __( 'Invoice prefix', 'woocommerce-paypal-payments' ),
'type' => 'text',
@@ -435,7 +456,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'description' => __( 'If you use your PayPal account with more than one installation, please use a distinct prefix to separate those installations. Please use only English letters and "-", "_" characters.', 'woocommerce-paypal-payments' ),
'maxlength' => 15,
'custom_attributes' => array(
- 'pattern' => '[a-zA-Z_-]+',
+ 'pattern' => '[a-zA-Z_\\-]+',
),
'default' => ( static function (): string {
$site_url = get_site_url( get_current_blog_id() );
@@ -466,6 +487,53 @@ return function ( ContainerInterface $container, array $fields ): array {
'requirements' => array(),
'gateway' => Settings::CONNECTION_TAB_ID,
),
+ 'subtotal_mismatch_behavior' => array(
+ 'title' => __( 'Subtotal mismatch behavior', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'vertical',
+ 'desc_tip' => true,
+ 'description' => __(
+ 'Differences between WooCommerce and PayPal roundings may cause mismatch in order items subtotal calculations. If not handled, these mismatches will cause the PayPal transaction to fail.',
+ 'woocommerce-paypal-payments'
+ ),
+ 'options' => array(
+ PurchaseUnitSanitizer::MODE_DITCH => __( 'Do not send line items to PayPal', 'woocommerce-paypal-payments' ),
+ PurchaseUnitSanitizer::MODE_EXTRA_LINE => __( 'Add another line item', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array(
+ State::STATE_START,
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'gateway' => Settings::CONNECTION_TAB_ID,
+ 'custom_attributes' => array(
+ 'data-ppcp-display' => wp_json_encode(
+ array(
+ $display_manager
+ ->rule()
+ ->condition_element( 'subtotal_mismatch_behavior', PurchaseUnitSanitizer::MODE_EXTRA_LINE )
+ ->action_visible( 'subtotal_mismatch_line_name' )
+ ->to_array(),
+ )
+ ),
+ ),
+ ),
+ 'subtotal_mismatch_line_name' => array(
+ 'title' => __( 'Subtotal mismatch line name', 'woocommerce-paypal-payments' ),
+ 'type' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The name of the extra line that will be sent to PayPal to correct the subtotal mismatch.', 'woocommerce-paypal-payments' ),
+ 'maxlength' => 22,
+ 'default' => '',
+ 'screens' => array(
+ State::STATE_START,
+ State::STATE_ONBOARDED,
+ ),
+ 'requirements' => array(),
+ 'placeholder' => PurchaseUnitSanitizer::EXTRA_LINE_NAME,
+ 'gateway' => Settings::CONNECTION_TAB_ID,
+ ),
);
return array_merge( $fields, $connection_fields );
diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php
index fab61ca83..99a3fd3f0 100644
--- a/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php
+++ b/modules/ppcp-wc-gateway/src/Settings/Fields/pay-later-tab-fields.php
@@ -33,11 +33,12 @@ return function ( ContainerInterface $container, array $fields ): array {
$selected_country = $container->get( 'api.shop.country' );
$default_messaging_flex_color = $selected_country === 'US' ? 'white-no-border' : 'white';
-
- $render_preview_element = function ( string $id, string $type ): string {
+ $button_message = __( 'Pay Later Button Preview', 'woocommerce-paypal-payments' );
+ $messaging_message = __( 'Pay Later Messaging Preview', 'woocommerce-paypal-payments' );
+ $render_preview_element = function ( string $id, string $type, string $message ): string {
return '
-
' . __( 'Preview', 'woocommerce-paypal-payments' ) . '
+
' . $message . '
';
};
@@ -82,7 +83,7 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'pay_later_button_preview' => array(
'type' => 'ppcp-text',
- 'text' => $render_preview_element( 'ppcpPayLaterButtonPreview', 'button' ),
+ 'text' => $render_preview_element( 'ppcpPayLaterButtonPreview', 'button', $button_message ),
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array( 'messages' ),
'gateway' => Settings::PAY_LATER_TAB_ID,
@@ -245,7 +246,7 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'pay_later_general_message_preview' => array(
'type' => 'ppcp-text',
- 'text' => $render_preview_element( 'ppcpGeneralMessagePreview', 'message' ),
+ 'text' => $render_preview_element( 'ppcpGeneralMessagePreview', 'message', $messaging_message ),
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array( 'messages' ),
'gateway' => Settings::PAY_LATER_TAB_ID,
@@ -369,7 +370,7 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'pay_later_product_message_preview' => array(
'type' => 'ppcp-text',
- 'text' => $render_preview_element( 'ppcpProductMessagePreview', 'message' ),
+ 'text' => $render_preview_element( 'ppcpProductMessagePreview', 'message', $messaging_message ),
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array( 'messages' ),
'gateway' => Settings::PAY_LATER_TAB_ID,
@@ -493,7 +494,7 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'pay_later_cart_message_preview' => array(
'type' => 'ppcp-text',
- 'text' => $render_preview_element( 'ppcpCartMessagePreview', 'message' ),
+ 'text' => $render_preview_element( 'ppcpCartMessagePreview', 'message', $messaging_message ),
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array( 'messages' ),
'gateway' => Settings::PAY_LATER_TAB_ID,
@@ -617,7 +618,255 @@ return function ( ContainerInterface $container, array $fields ): array {
),
'pay_later_checkout_message_preview' => array(
'type' => 'ppcp-text',
- 'text' => $render_preview_element( 'ppcpCheckoutMessagePreview', 'message' ),
+ 'text' => $render_preview_element( 'ppcpCheckoutMessagePreview', 'message', $messaging_message ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+
+ // Shop.
+ 'pay_later_shop_messaging_heading' => array(
+ 'heading' => __( 'Pay Later Messaging on the Shop page', 'woocommerce-paypal-payments' ),
+ 'type' => 'ppcp-heading',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array(),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_layout' => array(
+ 'title' => __( 'Shop Messaging Layout', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The layout of the message.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'text' => __( 'Text', 'woocommerce-paypal-payments' ),
+ 'flex' => __( 'Banner', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_logo' => array(
+ 'title' => __( 'Shop Messaging Logo', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'inline',
+ 'desc_tip' => true,
+ 'description' => __( 'What logo the text message contains. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'primary' => __( 'Primary', 'woocommerce-paypal-payments' ),
+ 'alternative' => __( 'Alternative', 'woocommerce-paypal-payments' ),
+ 'inline' => __( 'Inline', 'woocommerce-paypal-payments' ),
+ 'none' => __( 'None', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_position' => array(
+ 'title' => __( 'Shop Messaging Logo Position', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'left',
+ 'desc_tip' => true,
+ 'description' => __( 'The position of the logo. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'left' => __( 'Left', 'woocommerce-paypal-payments' ),
+ 'right' => __( 'Right', 'woocommerce-paypal-payments' ),
+ 'top' => __( 'Top', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_color' => array(
+ 'title' => __( 'Shop Messaging Text Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'black',
+ 'desc_tip' => true,
+ 'description' => __( 'The color of the text. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
+ 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ),
+ 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_flex_color' => array(
+ 'title' => __( 'Shop Messaging Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => $default_messaging_flex_color,
+ 'desc_tip' => true,
+ 'description' => __( 'The color of the text. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
+ 'white-no-border' => __( 'White no border', 'woocommerce-paypal-payments' ),
+ 'gray' => __( 'Gray', 'woocommerce-paypal-payments' ),
+ 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ),
+ 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_flex_ratio' => array(
+ 'title' => __( 'Shop Messaging Ratio', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => '8x1',
+ 'desc_tip' => true,
+ 'description' => __( 'The width/height ratio of the banner. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ '1x1' => __( '1x1', 'woocommerce-paypal-payments' ),
+ '1x4' => __( '1x4', 'woocommerce-paypal-payments' ),
+ '8x1' => __( '8x1', 'woocommerce-paypal-payments' ),
+ '20x1' => __( '20x1', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_shop_message_preview' => array(
+ 'type' => 'ppcp-text',
+ 'text' => $render_preview_element( 'ppcpShopMessagePreview', 'message', $messaging_message ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+
+ // Home.
+ 'pay_later_home_messaging_heading' => array(
+ 'heading' => __( 'Pay Later Messaging on the Home page', 'woocommerce-paypal-payments' ),
+ 'type' => 'ppcp-heading',
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array(),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_layout' => array(
+ 'title' => __( 'Home Messaging Layout', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'text',
+ 'desc_tip' => true,
+ 'description' => __( 'The layout of the message.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'text' => __( 'Text', 'woocommerce-paypal-payments' ),
+ 'flex' => __( 'Banner', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_logo' => array(
+ 'title' => __( 'Home Messaging Logo', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'inline',
+ 'desc_tip' => true,
+ 'description' => __( 'What logo the text message contains. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'primary' => __( 'Primary', 'woocommerce-paypal-payments' ),
+ 'alternative' => __( 'Alternative', 'woocommerce-paypal-payments' ),
+ 'inline' => __( 'Inline', 'woocommerce-paypal-payments' ),
+ 'none' => __( 'None', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_position' => array(
+ 'title' => __( 'Home Messaging Logo Position', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'left',
+ 'desc_tip' => true,
+ 'description' => __( 'The position of the logo. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'left' => __( 'Left', 'woocommerce-paypal-payments' ),
+ 'right' => __( 'Right', 'woocommerce-paypal-payments' ),
+ 'top' => __( 'Top', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_color' => array(
+ 'title' => __( 'Home Messaging Text Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => 'black',
+ 'desc_tip' => true,
+ 'description' => __( 'The color of the text. Only applicable, when the layout style Text is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
+ 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ),
+ 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_flex_color' => array(
+ 'title' => __( 'Home Messaging Color', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => $default_messaging_flex_color,
+ 'desc_tip' => true,
+ 'description' => __( 'The color of the text. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ 'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
+ 'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
+ 'white-no-border' => __( 'White no border', 'woocommerce-paypal-payments' ),
+ 'gray' => __( 'Gray', 'woocommerce-paypal-payments' ),
+ 'monochrome' => __( 'Monochrome', 'woocommerce-paypal-payments' ),
+ 'grayscale' => __( 'Grayscale', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_flex_ratio' => array(
+ 'title' => __( 'Home Messaging Ratio', 'woocommerce-paypal-payments' ),
+ 'type' => 'select',
+ 'class' => array(),
+ 'input_class' => array( 'wc-enhanced-select' ),
+ 'default' => '8x1',
+ 'desc_tip' => true,
+ 'description' => __( 'The width/height ratio of the banner. Only applicable, when the layout style Banner is used.', 'woocommerce-paypal-payments' ),
+ 'options' => array(
+ '1x1' => __( '1x1', 'woocommerce-paypal-payments' ),
+ '1x4' => __( '1x4', 'woocommerce-paypal-payments' ),
+ '8x1' => __( '8x1', 'woocommerce-paypal-payments' ),
+ '20x1' => __( '20x1', 'woocommerce-paypal-payments' ),
+ ),
+ 'screens' => array( State::STATE_ONBOARDED ),
+ 'requirements' => array( 'messages' ),
+ 'gateway' => Settings::PAY_LATER_TAB_ID,
+ ),
+ 'pay_later_home_message_preview' => array(
+ 'type' => 'ppcp-text',
+ 'text' => $render_preview_element( 'ppcpHomeMessagePreview', 'message', $messaging_message ),
'screens' => array( State::STATE_ONBOARDED ),
'requirements' => array( 'messages' ),
'gateway' => Settings::PAY_LATER_TAB_ID,
diff --git a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php
index 36388b493..0eb00c82e 100644
--- a/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php
+++ b/modules/ppcp-wc-gateway/src/Settings/Fields/paypal-smart-button-fields.php
@@ -31,7 +31,7 @@ return function ( ContainerInterface $container, array $fields ): array {
$render_preview_element = function ( string $id ): string {
return '
';
};
@@ -49,7 +49,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'description' => sprintf(
// translators: %1$s and %2$s are the opening and closing of HTML tag.
__(
- 'Customize the appearance of the PayPal smart buttons on the
+ 'Customize the appearance of the PayPal smart buttons on the
%1$sCheckout page%5$s, %2$sSingle Product Page%5$s, %3$sCart page%5$s or on %4$sMini Cart%5$s.',
'woocommerce-paypal-payments'
),
@@ -174,6 +174,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
'silver' => __( 'Silver', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@@ -319,6 +320,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
'silver' => __( 'Silver', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@@ -458,6 +460,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
'silver' => __( 'Silver', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@@ -597,6 +600,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
'silver' => __( 'Silver', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
@@ -736,6 +740,7 @@ return function ( ContainerInterface $container, array $fields ): array {
'blue' => __( 'Blue', 'woocommerce-paypal-payments' ),
'silver' => __( 'Silver', 'woocommerce-paypal-payments' ),
'black' => __( 'Black', 'woocommerce-paypal-payments' ),
+ 'white' => __( 'White', 'woocommerce-paypal-payments' ),
),
'screens' => array(
State::STATE_START,
diff --git a/modules/ppcp-wc-gateway/src/Settings/HeaderRenderer.php b/modules/ppcp-wc-gateway/src/Settings/HeaderRenderer.php
index a4302bc37..73bd843d8 100644
--- a/modules/ppcp-wc-gateway/src/Settings/HeaderRenderer.php
+++ b/modules/ppcp-wc-gateway/src/Settings/HeaderRenderer.php
@@ -60,7 +60,7 @@ class HeaderRenderer {
return '
+
+
';
}
}
diff --git a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php
index 753caef1a..bc61c64a9 100644
--- a/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php
+++ b/modules/ppcp-wc-gateway/src/Settings/SectionsRenderer.php
@@ -9,8 +9,16 @@ declare( strict_types=1 );
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
+use WooCommerce\PayPalCommerce\Button\Helper\MessagesApply;
use WooCommerce\PayPalCommerce\Onboarding\State;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
/**
* Class SectionsRenderer
@@ -26,13 +34,6 @@ class SectionsRenderer {
*/
protected $page_id;
- /**
- * Key - page/gateway ID, value - displayed text.
- *
- * @var array
- */
- protected $sections;
-
/**
* The onboarding state.
*
@@ -40,17 +41,65 @@ class SectionsRenderer {
*/
private $state;
+ /**
+ * The DCC product status
+ *
+ * @var DCCProductStatus
+ */
+ private $dcc_product_status;
+
+ /**
+ * The DCC applies
+ *
+ * @var DccApplies
+ */
+ private $dcc_applies;
+
+ /**
+ * The messages apply.
+ *
+ * @var MessagesApply
+ */
+ private $messages_apply;
+
+ /**
+ * The PUI product status.
+ *
+ * @var PayUponInvoiceProductStatus
+ */
+ private $pui_product_status;
+
/**
* SectionsRenderer constructor.
*
* @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
- * @param array $sections Key - page/gateway ID, value - displayed text.
* @param State $state The onboarding state.
*/
- public function __construct( string $page_id, array $sections, State $state ) {
- $this->page_id = $page_id;
- $this->sections = $sections;
- $this->state = $state;
+
+ /**
+ * SectionsRenderer constructor.
+ *
+ * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
+ * @param State $state The onboarding state.
+ * @param DCCProductStatus $dcc_product_status The DCC product status.
+ * @param DccApplies $dcc_applies The DCC applies.
+ * @param MessagesApply $messages_apply The Messages apply.
+ * @param PayUponInvoiceProductStatus $pui_product_status The PUI product status.
+ */
+ public function __construct(
+ string $page_id,
+ State $state,
+ DCCProductStatus $dcc_product_status,
+ DccApplies $dcc_applies,
+ MessagesApply $messages_apply,
+ PayUponInvoiceProductStatus $pui_product_status
+ ) {
+ $this->page_id = $page_id;
+ $this->state = $state;
+ $this->dcc_product_status = $dcc_product_status;
+ $this->dcc_applies = $dcc_applies;
+ $this->messages_apply = $messages_apply;
+ $this->pui_product_status = $pui_product_status;
}
/**
@@ -66,6 +115,8 @@ class SectionsRenderer {
/**
* Renders the Sections tab.
+ *
+ * @return string
*/
public function render(): string {
if ( ! $this->should_render() ) {
@@ -74,8 +125,8 @@ class SectionsRenderer {
$html = '';
- foreach ( $this->sections as $id => $label ) {
- $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . $id );
+ foreach ( $this->sections() as $id => $label ) {
+ $url = admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=' . (string) $id );
if ( in_array( $id, array( Settings::CONNECTION_TAB_ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID ), true ) ) {
// We need section=ppcp-gateway for the webhooks page because it is not a gateway,
// and for DCC because otherwise it will not render the page if gateway is not available (country/currency).
@@ -89,4 +140,46 @@ class SectionsRenderer {
return $html;
}
+
+ /**
+ * Returns sections as Key - page/gateway ID, value - displayed text.
+ *
+ * @return array
+ */
+ private function sections(): array {
+ $sections = array(
+ Settings::CONNECTION_TAB_ID => __( 'Connection', 'woocommerce-paypal-payments' ),
+ PayPalGateway::ID => __( 'Standard Payments', 'woocommerce-paypal-payments' ),
+ Settings::PAY_LATER_TAB_ID => __( 'Pay Later', 'woocommerce-paypal-payments' ),
+ CreditCardGateway::ID => __( 'Advanced Card Processing', 'woocommerce-paypal-payments' ),
+ CardButtonGateway::ID => __( 'Standard Card Button', 'woocommerce-paypal-payments' ),
+ OXXOGateway::ID => __( 'OXXO', 'woocommerce-paypal-payments' ),
+ PayUponInvoiceGateway::ID => __( 'Pay upon Invoice', 'woocommerce-paypal-payments' ),
+ );
+
+ // Remove for all not registered in WC gateways that cannot render anything in this case.
+ $gateways = WC()->payment_gateways->payment_gateways();
+ foreach ( array_diff(
+ array_keys( $sections ),
+ array( Settings::CONNECTION_TAB_ID, PayPalGateway::ID, CreditCardGateway::ID, Settings::PAY_LATER_TAB_ID )
+ ) as $id ) {
+ if ( ! isset( $gateways[ $id ] ) ) {
+ unset( $sections[ $id ] );
+ }
+ }
+
+ if ( ! $this->dcc_product_status->dcc_is_active() || ! $this->dcc_applies->for_country_currency() ) {
+ unset( $sections['ppcp-credit-card-gateway'] );
+ }
+
+ if ( ! $this->messages_apply->for_country() ) {
+ unset( $sections[ Settings::PAY_LATER_TAB_ID ] );
+ }
+
+ if ( ! $this->pui_product_status->pui_is_active() ) {
+ unset( $sections[ PayUponInvoiceGateway::ID ] );
+ }
+
+ return $sections;
+ }
}
diff --git a/modules/ppcp-wc-gateway/src/Settings/Settings.php b/modules/ppcp-wc-gateway/src/Settings/Settings.php
index aa0981a03..9abca804d 100644
--- a/modules/ppcp-wc-gateway/src/Settings/Settings.php
+++ b/modules/ppcp-wc-gateway/src/Settings/Settings.php
@@ -35,13 +35,22 @@ class Settings implements ContainerInterface {
*/
protected $default_button_locations;
+ /**
+ * The default ACDC gateway title.
+ *
+ * @var string
+ */
+ protected $default_dcc_gateway_title;
+
/**
* Settings constructor.
*
* @param string[] $default_button_locations The list of selected default button locations.
+ * @param string $default_dcc_gateway_title The default ACDC gateway title.
*/
- public function __construct( array $default_button_locations ) {
- $this->default_button_locations = $default_button_locations;
+ public function __construct( array $default_button_locations, string $default_dcc_gateway_title ) {
+ $this->default_button_locations = $default_button_locations;
+ $this->default_dcc_gateway_title = $default_dcc_gateway_title;
}
/**
@@ -116,7 +125,7 @@ class Settings implements ContainerInterface {
'pay_later_button_locations' => $this->default_button_locations,
'pay_later_messaging_locations' => $this->default_button_locations,
'brand_name' => get_bloginfo( 'name' ),
- 'dcc_gateway_title' => __( 'Credit Cards', 'woocommerce-paypal-payments' ),
+ 'dcc_gateway_title' => $this->default_dcc_gateway_title,
'dcc_gateway_description' => __(
'Pay with your credit card.',
'woocommerce-paypal-payments'
diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php
index 16a2d34d9..7c690c942 100644
--- a/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php
+++ b/modules/ppcp-wc-gateway/src/Settings/SettingsListener.php
@@ -9,16 +9,22 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
+use Psr\Log\LoggerInterface;
+use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
+use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
+use WooCommerce\PayPalCommerce\Http\RedirectorInterface;
use WooCommerce\PayPalCommerce\Onboarding\State;
+use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
+use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
/**
* Class SettingsListener
@@ -111,20 +117,66 @@ class SettingsListener {
*/
protected $dcc_status_cache;
+ /**
+ * The HTTP redirector.
+ *
+ * @var RedirectorInterface
+ */
+ protected $redirector;
+
+ /**
+ * Max onboarding URL retries.
+ *
+ * @var int
+ */
+ private $onboarding_max_retries = 5;
+
+ /**
+ * Delay between onboarding URL retries.
+ *
+ * @var int
+ */
+ private $onboarding_retry_delay = 2;
+
+ /**
+ * Partner merchant ID production.
+ *
+ * @var string
+ */
+ private $partner_merchant_id_production;
+
+ /**
+ * Partner merchant ID sandbox.
+ *
+ * @var string
+ */
+ private $partner_merchant_id_sandbox;
+
+ /**
+ * The logger.
+ *
+ * @var LoggerInterface
+ */
+ private $logger;
+
/**
* SettingsListener constructor.
*
- * @param Settings $settings The settings.
- * @param array $setting_fields The setting fields.
- * @param WebhookRegistrar $webhook_registrar The Webhook Registrar.
- * @param Cache $cache The Cache.
- * @param State $state The state.
- * @param Bearer $bearer The bearer.
- * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
- * @param Cache $signup_link_cache The signup link cache.
- * @param array $signup_link_ids Signup link ids.
- * @param Cache $pui_status_cache The PUI status cache.
- * @param Cache $dcc_status_cache The DCC status cache.
+ * @param Settings $settings The settings.
+ * @param array $setting_fields The setting fields.
+ * @param WebhookRegistrar $webhook_registrar The Webhook Registrar.
+ * @param Cache $cache The Cache.
+ * @param State $state The state.
+ * @param Bearer $bearer The bearer.
+ * @param string $page_id ID of the current PPCP gateway settings page, or empty if it is not such page.
+ * @param Cache $signup_link_cache The signup link cache.
+ * @param array $signup_link_ids Signup link ids.
+ * @param Cache $pui_status_cache The PUI status cache.
+ * @param Cache $dcc_status_cache The DCC status cache.
+ * @param RedirectorInterface $redirector The HTTP redirector.
+ * @param string $partner_merchant_id_production Partner merchant ID production.
+ * @param string $partner_merchant_id_sandbox Partner merchant ID sandbox.
+ * @param ?LoggerInterface $logger The logger.
*/
public function __construct(
Settings $settings,
@@ -137,27 +189,34 @@ class SettingsListener {
Cache $signup_link_cache,
array $signup_link_ids,
Cache $pui_status_cache,
- Cache $dcc_status_cache
+ Cache $dcc_status_cache,
+ RedirectorInterface $redirector,
+ string $partner_merchant_id_production,
+ string $partner_merchant_id_sandbox,
+ LoggerInterface $logger = null
) {
- $this->settings = $settings;
- $this->setting_fields = $setting_fields;
- $this->webhook_registrar = $webhook_registrar;
- $this->cache = $cache;
- $this->state = $state;
- $this->bearer = $bearer;
- $this->page_id = $page_id;
- $this->signup_link_cache = $signup_link_cache;
- $this->signup_link_ids = $signup_link_ids;
- $this->pui_status_cache = $pui_status_cache;
- $this->dcc_status_cache = $dcc_status_cache;
+ $this->settings = $settings;
+ $this->setting_fields = $setting_fields;
+ $this->webhook_registrar = $webhook_registrar;
+ $this->cache = $cache;
+ $this->state = $state;
+ $this->bearer = $bearer;
+ $this->page_id = $page_id;
+ $this->signup_link_cache = $signup_link_cache;
+ $this->signup_link_ids = $signup_link_ids;
+ $this->pui_status_cache = $pui_status_cache;
+ $this->dcc_status_cache = $dcc_status_cache;
+ $this->redirector = $redirector;
+ $this->partner_merchant_id_production = $partner_merchant_id_production;
+ $this->partner_merchant_id_sandbox = $partner_merchant_id_sandbox;
+ $this->logger = $logger ?: new NullLogger();
}
/**
* Listens if the merchant ID should be updated.
*/
- public function listen_for_merchant_id() {
-
+ public function listen_for_merchant_id(): void {
if ( ! $this->is_valid_site_request() ) {
return;
}
@@ -167,17 +226,59 @@ class SettingsListener {
* phpcs:disable WordPress.Security.NonceVerification.Missing
* phpcs:disable WordPress.Security.NonceVerification.Recommended
*/
- if ( ! isset( $_GET['merchantIdInPayPal'] ) || ! isset( $_GET['merchantId'] ) ) {
+ if ( ! isset( $_GET['merchantIdInPayPal'] ) || ! isset( $_GET['merchantId'] ) || ! isset( $_GET['ppcpToken'] ) ) {
return;
}
- $merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) );
- $merchant_email = sanitize_text_field( wp_unslash( $_GET['merchantId'] ) );
+
+ $merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) );
+ if ( $merchant_id === $this->partner_merchant_id_production || $merchant_id === $this->partner_merchant_id_sandbox ) {
+ return;
+ }
+
+ $merchant_email = $this->sanitize_onboarding_email( sanitize_text_field( wp_unslash( $_GET['merchantId'] ) ) );
+ $onboarding_token = sanitize_text_field( wp_unslash( $_GET['ppcpToken'] ) );
+ $retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0;
// phpcs:enable WordPress.Security.NonceVerification.Missing
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$this->settings->set( 'merchant_id', $merchant_id );
$this->settings->set( 'merchant_email', $merchant_email );
+ // If no client_id is present we will try to wait for PayPal to invoke LoginSellerEndpoint.
+ if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
+
+ // Try at most {onboarding_max_retries} times ({onboarding_retry_delay} seconds delay). Then give up and just fill the merchant fields like before.
+ if ( $retry_count < $this->onboarding_max_retries ) {
+
+ if ( $this->onboarding_retry_delay > 0 ) {
+ sleep( $this->onboarding_retry_delay );
+ }
+
+ $retry_count++;
+ $this->logger->info( 'Retrying onboarding return URL, retry nr: ' . ( (string) $retry_count ) );
+ $redirect_url = add_query_arg( 'ppcpRetry', $retry_count );
+ $this->redirector->redirect( $redirect_url );
+ }
+ }
+
+ // Process token validation.
+ $onboarding_token_sample = ( (string) substr( $onboarding_token, 0, 2 ) ) . '...' . ( (string) substr( $onboarding_token, -6 ) );
+ $this->logger->debug( 'Validating onboarding ppcpToken: ' . $onboarding_token_sample );
+
+ if ( ! OnboardingUrl::validate_token_and_delete( $this->signup_link_cache, $onboarding_token, get_current_user_id() ) ) {
+ if ( OnboardingUrl::validate_previous_token( $this->signup_link_cache, $onboarding_token, get_current_user_id() ) ) {
+ // It's a valid token used previously, don't do anything but silently redirect.
+ $this->logger->info( 'Validated previous token, silently redirecting: ' . $onboarding_token_sample );
+ $this->onboarding_redirect();
+ } else {
+ $this->logger->error( 'Failed to validate onboarding ppcpToken: ' . $onboarding_token_sample );
+ $this->onboarding_redirect( false );
+ }
+ }
+
+ $this->logger->info( 'Validated onboarding ppcpToken: ' . $onboarding_token_sample );
+
+ // Save the merchant data.
$is_sandbox = $this->settings->has( 'sandbox_on' ) && $this->settings->get( 'sandbox_on' );
if ( $is_sandbox ) {
$this->settings->set( 'merchant_id_sandbox', $merchant_id );
@@ -193,20 +294,48 @@ class SettingsListener {
*/
do_action( 'woocommerce_paypal_payments_onboarding_before_redirect' );
- /**
- * The URL opened at the end of onboarding after saving the merchant ID/email.
- */
- $redirect_url = apply_filters( 'woocommerce_paypal_payments_onboarding_redirect_url', admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=' . Settings::CONNECTION_TAB_ID ) );
+ // If after all the retry redirects there still isn't a valid client_id then just send an error.
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
- $redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
+ $this->onboarding_redirect( false );
}
- wp_safe_redirect( $redirect_url, 302 );
- exit;
+ $this->onboarding_redirect();
+ }
+
+ /**
+ * Sanitizes the onboarding email.
+ *
+ * @param string $email The onboarding email.
+ * @return string
+ */
+ private function sanitize_onboarding_email( string $email ): string {
+ return str_replace( ' ', '+', $email );
+ }
+
+ /**
+ * Redirect to the onboarding URL.
+ *
+ * @param bool $success Should redirect to the success or error URL.
+ * @return void
+ */
+ private function onboarding_redirect( bool $success = true ): void {
+ $redirect_url = $this->get_onboarding_redirect_url();
+
+ if ( ! $success ) {
+ $redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
+ $this->logger->info( 'Redirect ERROR: ' . $redirect_url );
+ } else {
+ $redirect_url = remove_query_arg( 'ppcp-onboarding-error', $redirect_url );
+ $this->logger->info( 'Redirect OK: ' . $redirect_url );
+ }
+
+ $this->redirector->redirect( $redirect_url );
}
/**
* Prevent enabling both Pay Later messaging and PayPal vaulting
+ *
+ * @throws RuntimeException When API request fails.
*/
public function listen_for_vaulting_enabled() {
if ( ! $this->is_valid_site_request() || State::STATE_ONBOARDED !== $this->state->current_state() ) {
@@ -217,23 +346,16 @@ class SettingsListener {
$token = $this->bearer->bearer();
if ( ! $token->vaulting_available() ) {
$this->settings->set( 'vault_enabled', false );
+ $this->settings->set( 'vault_enabled_dcc', false );
$this->settings->persist();
return;
}
} catch ( RuntimeException $exception ) {
$this->settings->set( 'vault_enabled', false );
+ $this->settings->set( 'vault_enabled_dcc', false );
$this->settings->persist();
- add_action(
- 'admin_notices',
- function () use ( $exception ) {
- printf(
- '',
- esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
- wp_kses_post( __( 'Please verify your API Credentials and try again to connect your PayPal business account. Visit the plugin documentation for more information about the setup.', 'woocommerce-paypal-payments' ) )
- );
- }
- );
+ throw $exception;
}
/**
@@ -242,7 +364,20 @@ class SettingsListener {
* phpcs:disable WordPress.Security.NonceVerification.Missing
* phpcs:disable WordPress.Security.NonceVerification.Recommended
*/
- if ( ! isset( $_POST['ppcp']['vault_enabled'] ) ) {
+ $vault_enabled = wc_clean( wp_unslash( $_POST['ppcp']['vault_enabled'] ?? '' ) );
+ $subscription_mode = wc_clean( wp_unslash( $_POST['ppcp']['subscriptions_mode'] ?? '' ) );
+
+ if ( $subscription_mode === 'vaulting_api' && $vault_enabled !== '1' ) {
+ $this->settings->set( 'vault_enabled', true );
+ $this->settings->persist();
+ }
+
+ if ( $subscription_mode === 'disable_paypal_subscriptions' && $vault_enabled === '1' ) {
+ $this->settings->set( 'vault_enabled', false );
+ $this->settings->persist();
+ }
+
+ if ( $vault_enabled !== '1' ) {
return;
}
@@ -303,6 +438,7 @@ class SettingsListener {
if ( self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) {
$this->settings->set( 'products_dcc_enabled', null );
$this->settings->set( 'products_pui_enabled', null );
+ do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $this->settings );
}
if ( in_array(
@@ -313,9 +449,7 @@ class SettingsListener {
$this->webhook_registrar->unregister();
foreach ( $this->signup_link_ids as $key ) {
- if ( $this->signup_link_cache->has( $key ) ) {
- $this->signup_link_cache->delete( $key );
- }
+ ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete();
}
}
}
@@ -325,16 +459,6 @@ class SettingsListener {
}
$this->settings->persist();
- if ( $credentials_change_status ) {
- if ( in_array(
- $credentials_change_status,
- array( self::CREDENTIALS_ADDED, self::CREDENTIALS_CHANGED ),
- true
- ) ) {
- $this->webhook_registrar->register();
- }
- }
-
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY );
}
@@ -347,16 +471,45 @@ class SettingsListener {
$this->dcc_status_cache->delete( DCCProductStatus::DCC_STATUS_CACHE_KEY );
}
+ /**
+ * The hook fired during listening the request so a module can remove also the cache or other logic.
+ */
+ do_action( 'woocommerce_paypal_payments_on_listening_request' );
+
+ $ppcp_reference_transaction_enabled = get_transient( 'ppcp_reference_transaction_enabled' ) ?? '';
+ if ( $ppcp_reference_transaction_enabled ) {
+ delete_transient( 'ppcp_reference_transaction_enabled' );
+ }
+
+ $redirect_url = false;
+ if ( $credentials_change_status && self::CREDENTIALS_UNCHANGED !== $credentials_change_status ) {
+ $redirect_url = $this->get_onboarding_redirect_url();
+ }
+
if ( isset( $_GET['ppcp-onboarding-error'] ) ) {
- $url = remove_query_arg( 'ppcp-onboarding-error' );
- wp_safe_redirect( $url, 302 );
- exit;
+ $redirect_url = remove_query_arg( 'ppcp-onboarding-error', $redirect_url );
+ }
+
+ if ( $redirect_url ) {
+ $this->redirector->redirect( $redirect_url );
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
+ /**
+ * Returns the URL opened at the end of onboarding.
+ *
+ * @return string
+ */
+ private function get_onboarding_redirect_url(): string {
+ /**
+ * The URL opened at the end of onboarding after saving the merchant ID/email.
+ */
+ return apply_filters( 'woocommerce_paypal_payments_onboarding_redirect_url', admin_url( 'admin.php?page=wc-settings&tab=checkout§ion=ppcp-gateway&ppcp-tab=' . Settings::CONNECTION_TAB_ID ) );
+ }
+
/**
* The actual used client credentials are stored in 'client_secret', 'client_id', 'merchant_id' and 'merchant_email'.
* This method populates those fields depending on the sandbox status.
@@ -369,10 +522,18 @@ class SettingsListener {
if ( ! isset( $settings['client_id_sandbox'] ) && ! isset( $settings['client_id_production'] ) ) {
return $settings;
}
- $is_sandbox = isset( $settings['sandbox_on'] ) && $settings['sandbox_on'];
- $settings['client_id'] = $is_sandbox ? $settings['client_id_sandbox'] : $settings['client_id_production'];
- $settings['client_secret'] = $is_sandbox ? $settings['client_secret_sandbox'] : $settings['client_secret_production'];
- $settings['merchant_id'] = $is_sandbox ? $settings['merchant_id_sandbox'] : $settings['merchant_id_production'];
+ $is_sandbox = isset( $settings['sandbox_on'] ) && $settings['sandbox_on'];
+ $settings['client_id'] = $is_sandbox ? $settings['client_id_sandbox'] : $settings['client_id_production'];
+ $settings['client_secret'] = $is_sandbox ? $settings['client_secret_sandbox'] : $settings['client_secret_production'];
+
+ if ( $settings['merchant_id_sandbox'] === $this->partner_merchant_id_sandbox || $settings['merchant_id_sandbox'] === $this->partner_merchant_id_production ) {
+ $settings['merchant_id_sandbox'] = '';
+ }
+ if ( $settings['merchant_id_production'] === $this->partner_merchant_id_sandbox || $settings['merchant_id_sandbox'] === $this->partner_merchant_id_production ) {
+ $settings['merchant_id_production'] = '';
+ }
+ $settings['merchant_id'] = $is_sandbox ? $settings['merchant_id_sandbox'] : $settings['merchant_id_production'];
+
$settings['merchant_email'] = $is_sandbox ? $settings['merchant_email_sandbox'] : $settings['merchant_email_production'];
return $settings;
}
@@ -439,7 +600,7 @@ class SettingsListener {
break;
case 'text':
case 'number':
- case 'ppcp-text-input':
+ case 'email':
$settings[ $key ] = isset( $raw_data[ $key ] ) ? wp_kses_post( $raw_data[ $key ] ) : '';
break;
case 'ppcp-password':
@@ -515,6 +676,8 @@ class SettingsListener {
/**
* Prevent enabling tracking if it is not enabled for merchant account.
+ *
+ * @throws RuntimeException When API request fails.
*/
public function listen_for_tracking_enabled(): void {
if ( State::STATE_ONBOARDED !== $this->state->current_state() ) {
@@ -532,16 +695,18 @@ class SettingsListener {
$this->settings->set( 'tracking_enabled', false );
$this->settings->persist();
- add_action(
- 'admin_notices',
- function () use ( $exception ) {
- printf(
- '',
- esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
- wp_kses_post( __( 'Please verify your API Credentials and try again to connect your PayPal business account. Visit the plugin documentation for more information about the setup.', 'woocommerce-paypal-payments' ) )
- );
- }
- );
+ throw $exception;
}
}
+
+ /**
+ * Handles onboarding URLs deletion
+ */
+ public function listen_for_uninstall(): void {
+ // Clear onboarding links from cache.
+ foreach ( $this->signup_link_ids as $key ) {
+ ( new OnboardingUrl( $this->signup_link_cache, $key, get_current_user_id() ) )->delete();
+ }
+ }
+
}
diff --git a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php
index 18372ef34..95046579b 100644
--- a/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php
+++ b/modules/ppcp-wc-gateway/src/Settings/SettingsRenderer.php
@@ -243,39 +243,6 @@ class SettingsRenderer {
return $html;
}
-
- /**
- * Renders the text input field.
- *
- * @param string $field The current field HTML.
- * @param string $key The current key.
- * @param array $config The configuration array.
- * @param string $value The current value.
- *
- * @return string
- */
- public function render_text_input( $field, $key, $config, $value ): string {
-
- if ( 'ppcp-text-input' !== $config['type'] ) {
- return $field;
- }
-
- $html = sprintf(
- ' ',
- esc_attr( implode( ' ', $config['class'] ) ),
- esc_attr( $key ),
- esc_attr( $value )
- );
-
- return $html;
- }
-
/**
* Renders the heading field.
*
diff --git a/modules/ppcp-wc-gateway/src/WCGatewayModule.php b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
index 3de5529bb..4796c2d5b 100644
--- a/modules/ppcp-wc-gateway/src/WCGatewayModule.php
+++ b/modules/ppcp-wc-gateway/src/WCGatewayModule.php
@@ -9,6 +9,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway;
+use Psr\Log\LoggerInterface;
+use Throwable;
+use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
+use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use WC_Order;
@@ -16,7 +21,6 @@ use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\ApiClient\Helper\DccApplies;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\WcGateway\Admin\FeesRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
@@ -29,12 +33,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\GatewayRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Notice\GatewayWithoutPayPalAdminNotice;
+use WooCommerce\PayPalCommerce\WcGateway\Notice\UnsupportedCurrencyAdminNotice;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\HeaderRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Settings\SectionsRenderer;
@@ -149,6 +155,12 @@ class WCGatewayModule implements ModuleInterface {
if ( ! $wc_order instanceof WC_Order ) {
return;
}
+ /**
+ * The filter can be used to remove the rows with PayPal fees in WC orders.
+ */
+ if ( ! apply_filters( 'woocommerce_paypal_payments_show_fees_on_order_admin_page', true, $wc_order ) ) {
+ return;
+ }
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $fees_renderer->render( $wc_order );
@@ -169,9 +181,11 @@ class WCGatewayModule implements ModuleInterface {
$c->get( 'button.client_id_for_admin' ),
$c->get( 'api.shop.currency' ),
$c->get( 'api.shop.country' ),
+ $c->get( 'onboarding.environment' ),
$settings_status->is_pay_later_button_enabled(),
$settings->has( 'disable_funding' ) ? $settings->get( 'disable_funding' ) : array(),
- $c->get( 'wcgateway.all-funding-sources' )
+ $c->get( 'wcgateway.settings.funding-sources' ),
+ $c->get( 'wcgateway.is-ppcp-settings-page' )
);
$assets->register_assets();
}
@@ -186,6 +200,13 @@ class WCGatewayModule implements ModuleInterface {
$notices[] = $connect_message;
}
+ $notice = $c->get( 'wcgateway.notice.currency-unsupported' );
+ assert( $notice instanceof UnsupportedCurrencyAdminNotice );
+ $unsupported_currency_message = $notice->unsupported_currency_message();
+ if ( $unsupported_currency_message ) {
+ $notices[] = $unsupported_currency_message;
+ }
+
foreach ( array(
$c->get( 'wcgateway.notice.dcc-without-paypal' ),
$c->get( 'wcgateway.notice.card-button-without-paypal' ),
@@ -215,7 +236,6 @@ class WCGatewayModule implements ModuleInterface {
'woocommerce_paypal_commerce_gateway_deactivate',
static function () use ( $c ) {
delete_option( Settings::KEY );
- delete_option( PayPalRequestIdRepository::KEY );
delete_option( 'woocommerce_' . PayPalGateway::ID . '_settings' );
delete_option( 'woocommerce_' . CreditCardGateway::ID . '_settings' );
}
@@ -237,6 +257,8 @@ class WCGatewayModule implements ModuleInterface {
add_action(
'woocommerce_paypal_payments_gateway_migrate',
static function () use ( $c ) {
+ delete_option( 'ppcp-request-ids' );
+
$settings = $c->get( 'wcgateway.settings' );
assert( $settings instanceof Settings );
@@ -252,7 +274,35 @@ class WCGatewayModule implements ModuleInterface {
);
add_action(
- 'init',
+ 'woocommerce_paypal_payments_gateway_migrate_on_update',
+ static function() use ( $c ) {
+ $dcc_status_cache = $c->get( 'dcc.status-cache' );
+ assert( $dcc_status_cache instanceof Cache );
+ $pui_status_cache = $c->get( 'pui.status-cache' );
+ assert( $pui_status_cache instanceof Cache );
+
+ $dcc_status_cache->delete( DCCProductStatus::DCC_STATUS_CACHE_KEY );
+ $pui_status_cache->delete( PayUponInvoiceProductStatus::PUI_STATUS_CACHE_KEY );
+
+ $settings = $c->get( 'wcgateway.settings' );
+ $settings->set( 'products_dcc_enabled', false );
+ $settings->set( 'products_pui_enabled', false );
+ $settings->persist();
+ do_action( 'woocommerce_paypal_payments_clear_apm_product_status', $settings );
+
+ // Update caches.
+ $dcc_status = $c->get( 'wcgateway.helper.dcc-product-status' );
+ assert( $dcc_status instanceof DCCProductStatus );
+ $dcc_status->dcc_is_active();
+
+ $pui_status = $c->get( 'wcgateway.pay-upon-invoice-product-status' );
+ assert( $pui_status instanceof PayUponInvoiceProductStatus );
+ $pui_status->pui_is_active();
+ }
+ );
+
+ add_action(
+ 'wp_loaded',
function () use ( $c ) {
if ( 'DE' === $c->get( 'api.shop.country' ) ) {
( $c->get( 'wcgateway.pay-upon-invoice' ) )->init();
@@ -294,12 +344,80 @@ class WCGatewayModule implements ModuleInterface {
);
add_action(
- 'wc_ajax_ppc-oxxo',
+ 'woocommerce_order_status_changed',
+ static function ( int $order_id, string $from, string $to ) use ( $c ) {
+ $wc_order = wc_get_order( $order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return;
+ }
+
+ $settings = $c->get( 'wcgateway.settings' );
+ assert( $settings instanceof ContainerInterface );
+
+ if ( ! $settings->has( 'capture_on_status_change' ) || ! $settings->get( 'capture_on_status_change' ) ) {
+ return;
+ }
+
+ $gateway_repository = $c->get( 'wcgateway.gateway-repository' );
+ assert( $gateway_repository instanceof GatewayRepository );
+
+ // Only allow to proceed if the payment method is one of our Gateways.
+ if ( ! $gateway_repository->exists( $wc_order->get_payment_method() ) ) {
+ return;
+ }
+
+ $intent = strtoupper( (string) $wc_order->get_meta( PayPalGateway::INTENT_META_KEY ) );
+ $captured = wc_string_to_bool( $wc_order->get_meta( AuthorizedPaymentsProcessor::CAPTURED_META_KEY ) );
+ if ( $intent !== 'AUTHORIZE' || $captured ) {
+ return;
+ }
+
+ /**
+ * The filter returning the WC order statuses which trigger capturing of payment authorization.
+ */
+ $capture_statuses = apply_filters( 'woocommerce_paypal_payments_auto_capture_statuses', array( 'processing', 'completed' ), $wc_order );
+ if ( ! in_array( $to, $capture_statuses, true ) ) {
+ return;
+ }
+
+ $authorized_payment_processor = $c->get( 'wcgateway.processor.authorized-payments' );
+ assert( $authorized_payment_processor instanceof AuthorizedPaymentsProcessor );
+
+ try {
+ if ( $authorized_payment_processor->capture_authorized_payment( $wc_order ) ) {
+ return;
+ }
+ } catch ( Throwable $error ) {
+ $logger = $c->get( 'woocommerce.logger.woocommerce' );
+ assert( $logger instanceof LoggerInterface );
+ $logger->error( "Capture failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
+ }
+
+ $wc_order->update_status(
+ 'failed',
+ __( 'Could not capture the payment.', 'woocommerce-paypal-payments' )
+ );
+ },
+ 10,
+ 3
+ );
+
+ add_action(
+ 'woocommerce_paypal_payments_uninstall',
static function () use ( $c ) {
- $endpoint = $c->get( 'wcgateway.endpoint.oxxo' );
- $endpoint->handle_request();
+ $listener = $c->get( 'wcgateway.settings.listener' );
+ assert( $listener instanceof SettingsListener );
+
+ $listener->listen_for_uninstall();
}
);
+
+ if ( defined( 'WP_CLI' ) && WP_CLI ) {
+ \WP_CLI::add_command(
+ 'pcp settings',
+ $c->get( 'wcgateway.cli.settings.command' )
+ );
+ }
}
/**
@@ -393,14 +511,29 @@ class WCGatewayModule implements ModuleInterface {
'admin_init',
static function () use ( $container ) {
$listener = $container->get( 'wcgateway.settings.listener' );
- /**
- * The settings listener.
- *
- * @var SettingsListener $listener
- */
+ assert( $listener instanceof SettingsListener );
+
$listener->listen_for_merchant_id();
- $listener->listen_for_vaulting_enabled();
- $listener->listen_for_tracking_enabled();
+
+ try {
+ $listener->listen_for_vaulting_enabled();
+ } catch ( RuntimeException $exception ) {
+ add_action(
+ 'admin_notices',
+ function () use ( $exception ) {
+ printf(
+ '',
+ esc_html__( 'Authentication with PayPal failed: ', 'woocommerce-paypal-payments' ) . esc_attr( $exception->getMessage() ),
+ wp_kses_post(
+ __(
+ 'Please verify your API Credentials and try again to connect your PayPal business account. Visit the plugin documentation for more information about the setup.',
+ 'woocommerce-paypal-payments'
+ )
+ )
+ );
+ }
+ );
+ }
}
);
@@ -415,7 +548,6 @@ class WCGatewayModule implements ModuleInterface {
*/
$field = $renderer->render_multiselect( $field, $key, $args, $value );
$field = $renderer->render_password( $field, $key, $args, $value );
- $field = $renderer->render_text_input( $field, $key, $args, $value );
$field = $renderer->render_heading( $field, $key, $args, $value );
$field = $renderer->render_table( $field, $key, $args, $value );
return $field;
@@ -519,7 +651,7 @@ class WCGatewayModule implements ModuleInterface {
* @var OrderTablePaymentStatusColumn $payment_status_column
*/
$payment_status_column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
- $payment_status_column->render( $column, intval( $wc_order_id ) );
+ $payment_status_column->render( (string) $column, intval( $wc_order_id ) );
},
10,
2
diff --git a/modules/ppcp-wc-gateway/webpack.config.js b/modules/ppcp-wc-gateway/webpack.config.js
index bd4a22fdc..394e549fe 100644
--- a/modules/ppcp-wc-gateway/webpack.config.js
+++ b/modules/ppcp-wc-gateway/webpack.config.js
@@ -6,10 +6,12 @@ module.exports = {
mode: isProduction ? 'production' : 'development',
target: 'web',
entry: {
+ 'common': path.resolve('./resources/js/common.js'),
'gateway-settings': path.resolve('./resources/js/gateway-settings.js'),
'fraudnet': path.resolve('./resources/js/fraudnet.js'),
'oxxo': path.resolve('./resources/js/oxxo.js'),
'gateway-settings-style': path.resolve('./resources/css/gateway-settings.scss'),
+ 'common-style': path.resolve('./resources/css/common.scss'),
},
output: {
path: path.resolve(__dirname, 'assets/'),
diff --git a/modules/ppcp-wc-gateway/yarn.lock b/modules/ppcp-wc-gateway/yarn.lock
index 9c070466e..1a21a94c3 100644
--- a/modules/ppcp-wc-gateway/yarn.lock
+++ b/modules/ppcp-wc-gateway/yarn.lock
@@ -1028,12 +1028,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
-"@types/estree@*":
- version "0.0.50"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
- integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
-
-"@types/estree@^0.0.51":
+"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@@ -1201,12 +1196,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
-acorn@^8.5.0:
- version "8.7.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
- integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
-
-acorn@^8.7.1:
+acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -1299,18 +1289,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.14.5:
- version "4.17.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
- integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
- dependencies:
- caniuse-lite "^1.0.30001259"
- electron-to-chromium "^1.3.846"
- escalade "^3.1.1"
- nanocolors "^0.1.5"
- node-releases "^1.1.76"
-
-browserslist@^4.20.2, browserslist@^4.21.3:
+browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
@@ -1333,11 +1312,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
-caniuse-lite@^1.0.30001259:
- version "1.0.30001261"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
- integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
-
caniuse-lite@^1.0.30001370:
version "1.0.30001393"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
@@ -1462,11 +1436,6 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
-electron-to-chromium@^1.3.846:
- version "1.3.853"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
- integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
-
electron-to-chromium@^1.4.202:
version "1.4.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
@@ -1628,12 +1597,7 @@ globals@^11.1.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.4:
- version "4.2.8"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
- integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
-
-graceful-fs@^4.2.9:
+graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@@ -1760,17 +1724,10 @@ json-schema-traverse@^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:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
- integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
- dependencies:
- minimist "^1.2.5"
-
-json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+json5@^2.1.2, json5@^2.2.1:
+ 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"
@@ -1788,9 +1745,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
- integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
+ 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"
@@ -1832,31 +1789,16 @@ mime-types@^2.1.27:
dependencies:
mime-db "1.49.0"
-minimist@^1.2.5:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
- integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
-
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==
-nanocolors@^0.1.5:
- version "0.1.12"
- resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
- integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
-
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@^1.1.76:
- version "1.1.76"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
- integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
-
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -2294,10 +2236,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/modules/ppcp-webhooks/package.json b/modules/ppcp-webhooks/package.json
index f5035b116..a1e060e90 100644
--- a/modules/ppcp-webhooks/package.json
+++ b/modules/ppcp-webhooks/package.json
@@ -21,7 +21,7 @@
"file-loader": "^6.2.0",
"sass": "^1.42.1",
"sass-loader": "^12.1.0",
- "webpack": "^5.74",
+ "webpack": "^5.76",
"webpack-cli": "^4.10"
},
"scripts": {
diff --git a/modules/ppcp-webhooks/resources/js/status-page.js b/modules/ppcp-webhooks/resources/js/status-page.js
index f477ec403..8f5334f51 100644
--- a/modules/ppcp-webhooks/resources/js/status-page.js
+++ b/modules/ppcp-webhooks/resources/js/status-page.js
@@ -1,3 +1,5 @@
+import {setVisibleByClass} from "../../../ppcp-button/resources/js/modules/Helper/Hiding"
+
document.addEventListener(
'DOMContentLoaded',
() => {
@@ -147,5 +149,25 @@ document.addEventListener(
simulateBtn.prop('disabled', false);
}
});
+
+ const sandboxCheckbox = document.querySelector('#ppcp-sandbox_on');
+ if (sandboxCheckbox) {
+ const setWebhooksVisibility = (show) => {
+ [
+ '#field-webhook_status_heading',
+ '#field-webhooks_list',
+ '#field-webhooks_resubscribe',
+ '#field-webhooks_simulate',
+ ].forEach(selector => {
+ setVisibleByClass(selector, show, 'hide');
+ });
+ };
+
+ const serverSandboxState = PayPalCommerceGatewayWebhooksStatus.environment === 'sandbox';
+ setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked);
+ sandboxCheckbox.addEventListener('click', () => {
+ setWebhooksVisibility(serverSandboxState === sandboxCheckbox.checked);
+ });
+ }
}
);
diff --git a/modules/ppcp-webhooks/services.php b/modules/ppcp-webhooks/services.php
index d7ed8013c..4d585206a 100644
--- a/modules/ppcp-webhooks/services.php
+++ b/modules/ppcp-webhooks/services.php
@@ -14,20 +14,26 @@ use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\WebhookEndpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Webhook;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookFactory;
+use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\BillingPlanPricingChangeActivated;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\BillingPlanUpdated;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\BillingSubscriptionCancelled;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\CatalogProductUpdated;
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderApproved;
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutOrderCompleted;
use WooCommerce\PayPalCommerce\Webhooks\Handler\CheckoutPaymentApprovalReversed;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureCompleted;
-use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureDenied;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCapturePending;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureRefunded;
use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentCaptureReversed;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
-use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultCreditCardCreated;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentSaleCompleted;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\PaymentSaleRefunded;
use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultPaymentTokenCreated;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\VaultPaymentTokenDeleted;
use WooCommerce\PayPalCommerce\Webhooks\Status\Assets\WebhooksStatusPageAssets;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
@@ -73,16 +79,32 @@ return array(
$prefix = $container->get( 'api.prefix' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
+ $payment_token_factory = $container->get( 'vaulting.payment-token-factory' );
+ $payment_token_helper = $container->get( 'vaulting.payment-token-helper' );
+ $refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
+
return array(
- new CheckoutOrderApproved( $logger, $prefix, $order_endpoint ),
- new CheckoutOrderCompleted( $logger, $prefix ),
+ new CheckoutOrderApproved(
+ $logger,
+ $order_endpoint,
+ $container->get( 'session.handler' ),
+ $container->get( 'wcgateway.funding-source.renderer' ),
+ $container->get( 'wcgateway.order-processor' )
+ ),
+ new CheckoutOrderCompleted( $logger ),
new CheckoutPaymentApprovalReversed( $logger ),
- new PaymentCaptureRefunded( $logger, $prefix ),
- new PaymentCaptureReversed( $logger, $prefix ),
- new PaymentCaptureCompleted( $logger, $prefix, $order_endpoint ),
- new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor ),
- new VaultCreditCardCreated( $logger, $prefix ),
+ new PaymentCaptureRefunded( $logger, $refund_fees_updater ),
+ new PaymentCaptureReversed( $logger ),
+ new PaymentCaptureCompleted( $logger, $order_endpoint ),
+ new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory, $payment_token_helper ),
+ new VaultPaymentTokenDeleted( $logger ),
new PaymentCapturePending( $logger ),
+ new PaymentSaleCompleted( $logger ),
+ new PaymentSaleRefunded( $logger, $refund_fees_updater ),
+ new BillingSubscriptionCancelled( $logger ),
+ new BillingPlanPricingChangeActivated( $logger ),
+ new CatalogProductUpdated( $logger ),
+ new BillingPlanUpdated( $logger ),
);
},
@@ -113,7 +135,12 @@ return array(
$endpoint = $container->get( 'api.endpoint.webhook' );
assert( $endpoint instanceof WebhookEndpoint );
- return $endpoint->list();
+ $state = $container->get( 'onboarding.state' );
+ if ( $state->current_state() >= State::STATE_ONBOARDED ) {
+ return $endpoint->list();
+ }
+
+ return array();
},
'webhook.status.registered-webhooks-data' => function( ContainerInterface $container ) : array {
@@ -167,7 +194,8 @@ return array(
'webhook.status.assets' => function( ContainerInterface $container ) : WebhooksStatusPageAssets {
return new WebhooksStatusPageAssets(
$container->get( 'webhook.module-url' ),
- $container->get( 'ppcp.asset-version' )
+ $container->get( 'ppcp.asset-version' ),
+ $container->get( 'onboarding.environment' )
);
},
@@ -198,8 +226,8 @@ return array(
);
},
- 'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookInfoStorage {
- return new WebhookInfoStorage( $container->get( 'webhook.last-webhook-storage.key' ) );
+ 'webhook.last-webhook-storage' => static function ( ContainerInterface $container ): WebhookEventStorage {
+ return new WebhookEventStorage( $container->get( 'webhook.last-webhook-storage.key' ) );
},
'webhook.last-webhook-storage.key' => static function ( ContainerInterface $container ): string {
return 'ppcp-last-webhook';
diff --git a/modules/ppcp-webhooks/src/CustomIds.php b/modules/ppcp-webhooks/src/CustomIds.php
new file mode 100644
index 000000000..97a78e43d
--- /dev/null
+++ b/modules/ppcp-webhooks/src/CustomIds.php
@@ -0,0 +1,18 @@
+request_data->read_request( $this->nonce() );
- $this->registrar->unregister();
-
if ( ! $this->registrar->register() ) {
wp_send_json_error( 'Webhook subscription failed.', 500 );
return false;
diff --git a/modules/ppcp-webhooks/src/Endpoint/SimulateEndpoint.php b/modules/ppcp-webhooks/src/Endpoint/SimulateEndpoint.php
index f6b2cf17e..869c8cbdc 100644
--- a/modules/ppcp-webhooks/src/Endpoint/SimulateEndpoint.php
+++ b/modules/ppcp-webhooks/src/Endpoint/SimulateEndpoint.php
@@ -61,6 +61,11 @@ class SimulateEndpoint {
* Handles the incoming request.
*/
public function handle_request() {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return false;
+ }
+
try {
// Validate nonce.
$this->request_data->read_request( $this->nonce() );
diff --git a/modules/ppcp-webhooks/src/Endpoint/SimulationStateEndpoint.php b/modules/ppcp-webhooks/src/Endpoint/SimulationStateEndpoint.php
index 089722ebc..17a4d5cbf 100644
--- a/modules/ppcp-webhooks/src/Endpoint/SimulationStateEndpoint.php
+++ b/modules/ppcp-webhooks/src/Endpoint/SimulationStateEndpoint.php
@@ -51,6 +51,11 @@ class SimulationStateEndpoint {
* Handles the incoming request.
*/
public function handle_request() {
+ if ( ! current_user_can( 'manage_woocommerce' ) ) {
+ wp_send_json_error( 'Not admin.', 403 );
+ return false;
+ }
+
try {
$state = $this->simulation->get_state();
diff --git a/modules/ppcp-webhooks/src/Handler/BillingPlanPricingChangeActivated.php b/modules/ppcp-webhooks/src/Handler/BillingPlanPricingChangeActivated.php
new file mode 100644
index 000000000..7a1461caf
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/BillingPlanPricingChangeActivated.php
@@ -0,0 +1,97 @@
+logger = $logger;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array(
+ 'BILLING.PLAN.PRICING-CHANGE.ACTIVATED',
+ );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ $plan_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
+ if ( $plan_id && ! empty( $request['resource']['billing_cycles'] ) ) {
+ $this->logger->info( 'Starting stuff...' );
+ $args = array(
+ // phpcs:ignore WordPress.DB.SlowDBQuery
+ 'meta_key' => 'ppcp_subscription_plan',
+ );
+
+ $products = wc_get_products( $args );
+ if ( is_array( $products ) ) {
+ foreach ( $products as $product ) {
+ if ( $product->get_meta( 'ppcp_subscription_plan' )['id'] === $plan_id ) {
+ foreach ( $request['resource']['billing_cycles'] as $cycle ) {
+ if ( $cycle['tenure_type'] === 'REGULAR' ) {
+ $product->update_meta_data( '_subscription_price', $cycle['pricing_scheme']['fixed_price']['value'] );
+ $product->save();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/BillingPlanUpdated.php b/modules/ppcp-webhooks/src/Handler/BillingPlanUpdated.php
new file mode 100644
index 000000000..b341a80c2
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/BillingPlanUpdated.php
@@ -0,0 +1,114 @@
+logger = $logger;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array(
+ 'BILLING.PLAN.UPDATED',
+ );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ $plan_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
+ if ( $plan_id ) {
+ $products = wc_get_products(
+ array(
+ // phpcs:ignore WordPress.DB.SlowDBQuery
+ 'meta_key' => 'ppcp_subscription_product',
+ )
+ );
+
+ if ( is_array( $products ) ) {
+ foreach ( $products as $product ) {
+ if ( $product->meta_exists( 'ppcp_subscription_plan' ) ) {
+ $plan_name = wc_clean( wp_unslash( $request['resource']['name'] ?? '' ) );
+ if ( $plan_name !== $product->get_meta( '_ppcp_subscription_plan_name' ) ) {
+ $product->update_meta_data( '_ppcp_subscription_plan_name', $plan_name );
+ $product->save();
+ }
+
+ $billing_cycles = wc_clean( wp_unslash( $request['resource']['billing_cycles'] ?? array() ) );
+ if ( $billing_cycles ) {
+ $price = $billing_cycles[0]['pricing_scheme']['fixed_price']['value'] ?? '';
+ if ( $price && round( $price, 2 ) !== round( $product->get_meta( '_subscription_price' ), 2 ) ) {
+ $product->update_meta_data( '_subscription_price', $price );
+ $product->save();
+ }
+ }
+
+ $payment_preferences = wc_clean( wp_unslash( $request['resource']['payment_preferences'] ?? array() ) );
+ if ( $payment_preferences ) {
+ $setup_fee = $payment_preferences['setup_fee']['value'] ?? '';
+ if ( $setup_fee && round( $setup_fee, 2 ) !== round( $product->get_meta( '_subscription_sign_up_fee' ), 2 ) ) {
+ $product->update_meta_data( '_subscription_sign_up_fee', $setup_fee );
+ $product->save();
+ }
+ }
+ }
+ }
+ }
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/BillingSubscriptionCancelled.php b/modules/ppcp-webhooks/src/Handler/BillingSubscriptionCancelled.php
new file mode 100644
index 000000000..6b9df1250
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/BillingSubscriptionCancelled.php
@@ -0,0 +1,92 @@
+logger = $logger;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array(
+ 'BILLING.SUBSCRIPTION.CANCELLED',
+ );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ $subscription_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
+ if ( $subscription_id ) {
+ $args = array(
+ // phpcs:ignore WordPress.DB.SlowDBQuery
+ 'meta_query' => array(
+ array(
+ 'key' => 'ppcp_subscription',
+ 'value' => $subscription_id,
+ 'compare' => '=',
+ ),
+ ),
+ );
+ $subscriptions = function_exists( 'wcs_get_subscriptions' ) ? wcs_get_subscriptions( $args ) : array();
+ foreach ( $subscriptions as $subscription ) {
+ $subscription->update_status( 'cancelled' );
+ }
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/CatalogProductUpdated.php b/modules/ppcp-webhooks/src/Handler/CatalogProductUpdated.php
new file mode 100644
index 000000000..dea42333d
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/CatalogProductUpdated.php
@@ -0,0 +1,109 @@
+logger = $logger;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array(
+ 'CATALOG.PRODUCT.UPDATED',
+ );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ $product_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
+ $name = wc_clean( wp_unslash( $request['resource']['name'] ?? '' ) );
+ if ( $product_id && $name ) {
+ $args = array(
+ // phpcs:ignore WordPress.DB.SlowDBQuery
+ 'meta_key' => 'ppcp_subscription_product',
+ );
+ $products = wc_get_products( $args );
+
+ if ( is_array( $products ) ) {
+ foreach ( $products as $product ) {
+ if (
+ $product->meta_exists( 'ppcp_subscription_product' )
+ && isset( $product->get_meta( 'ppcp_subscription_product' )['id'] )
+ && $product->get_meta( 'ppcp_subscription_product' )['id'] === $product_id
+ && $product->get_title() !== $name
+ ) {
+ /**
+ * Suppress ArgumentTypeCoercion
+ *
+ * @psalm-suppress ArgumentTypeCoercion
+ */
+ wp_update_post(
+ array(
+ 'ID' => $product->get_id(),
+ 'post_title' => $name,
+ )
+ );
+
+ break;
+ }
+ }
+ }
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php b/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php
index 350d0e168..177bda0ad 100644
--- a/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php
+++ b/modules/ppcp-webhooks/src/Handler/CheckoutOrderApproved.php
@@ -9,18 +9,25 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
+use WC_Checkout;
+use WC_Order;
+use WC_Session_Handler;
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
-use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Psr\Log\LoggerInterface;
+use WooCommerce\PayPalCommerce\Session\MemoryWcSession;
+use WooCommerce\PayPalCommerce\Session\SessionHandler;
+use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\OXXO\OXXOGateway;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice\PayUponInvoiceGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
/**
* Class CheckoutOrderApproved
*/
class CheckoutOrderApproved implements RequestHandler {
- use PrefixTrait;
+ use RequestHandlerTrait;
/**
* The logger.
@@ -36,17 +43,48 @@ class CheckoutOrderApproved implements RequestHandler {
*/
private $order_endpoint;
+ /**
+ * The Session handler.
+ *
+ * @var SessionHandler
+ */
+ private $session_handler;
+
+ /**
+ * The funding source renderer.
+ *
+ * @var FundingSourceRenderer
+ */
+ protected $funding_source_renderer;
+
+ /**
+ * The processor for orders.
+ *
+ * @var OrderProcessor
+ */
+ protected $order_processor;
+
/**
* CheckoutOrderApproved constructor.
*
- * @param LoggerInterface $logger The logger.
- * @param string $prefix The prefix.
- * @param OrderEndpoint $order_endpoint The order endpoint.
+ * @param LoggerInterface $logger The logger.
+ * @param OrderEndpoint $order_endpoint The order endpoint.
+ * @param SessionHandler $session_handler The session handler.
+ * @param FundingSourceRenderer $funding_source_renderer The funding source renderer.
+ * @param OrderProcessor $order_processor The Order Processor.
*/
- public function __construct( LoggerInterface $logger, string $prefix, OrderEndpoint $order_endpoint ) {
- $this->logger = $logger;
- $this->prefix = $prefix;
- $this->order_endpoint = $order_endpoint;
+ public function __construct(
+ LoggerInterface $logger,
+ OrderEndpoint $order_endpoint,
+ SessionHandler $session_handler,
+ FundingSourceRenderer $funding_source_renderer,
+ OrderProcessor $order_processor
+ ) {
+ $this->logger = $logger;
+ $this->order_endpoint = $order_endpoint;
+ $this->session_handler = $session_handler;
+ $this->funding_source_renderer = $funding_source_renderer;
+ $this->order_processor = $order_processor;
}
/**
@@ -79,114 +117,106 @@ class CheckoutOrderApproved implements RequestHandler {
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
- $response = array( 'success' => false );
- $custom_ids = array_filter(
- array_map(
- static function ( array $purchase_unit ): string {
- return isset( $purchase_unit['custom_id'] ) ?
- (string) $purchase_unit['custom_id'] : '';
- },
- isset( $request['resource']['purchase_units'] ) ?
- (array) $request['resource']['purchase_units'] : array()
- ),
- static function ( string $order_id ): bool {
- return ! empty( $order_id );
- }
- );
-
- if ( empty( $custom_ids ) ) {
- $message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'No order for webhook event %s was found.',
- 'woocommerce-paypal-payments'
- ),
- isset( $request['id'] ) ? $request['id'] : ''
- );
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
+ $order_id = isset( $request['resource']['id'] ) ? $request['resource']['id'] : null;
+ if ( ! $order_id ) {
+ return $this->failure_response(
+ sprintf(
+ 'No order ID in webhook event %s.',
+ $request['id'] ?: ''
)
);
- $response['message'] = $message;
- return rest_ensure_response( $response );
}
- try {
- $order = isset( $request['resource']['id'] ) ?
- $this->order_endpoint->order( $request['resource']['id'] ) : null;
- if ( ! $order ) {
- $message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'No paypal payment for webhook event %s was found.',
- 'woocommerce-paypal-payments'
- ),
- isset( $request['id'] ) ? $request['id'] : ''
- );
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
+ $order = $this->order_endpoint->order( $order_id );
+
+ $wc_orders = array();
+
+ $wc_order_ids = $this->get_wc_order_ids_from_request( $request );
+ if ( empty( $wc_order_ids ) ) {
+ $customer_ids = $this->get_wc_customer_ids_from_request( $request );
+ if ( empty( $customer_ids ) ) {
+ return $this->no_custom_ids_response( $request );
+ }
+
+ $customer_id = $customer_ids[0];
+
+ if ( $order->status()->is( OrderStatus::COMPLETED ) ) {
+ $this->logger->info( "Order {$order->id()} already completed." );
+ return $this->success_response();
+ }
+
+ if ( ! (bool) apply_filters( 'woocommerce_paypal_payments_order_approved_webhook_can_create_wc_order', false ) ) {
+ return $this->success_response();
+ }
+
+ $wc_session = new WC_Session_Handler();
+
+ $session_data = $wc_session->get_session( $customer_id );
+ if ( ! is_array( $session_data ) ) {
+ return $this->failure_response( "Failed to get session data {$customer_id}" );
+ }
+
+ MemoryWcSession::replace_session_handler( $session_data, $customer_id );
+
+ wc_load_cart();
+ WC()->cart->get_cart_from_session();
+ WC()->cart->calculate_shipping();
+
+ $form = $this->session_handler->checkout_form();
+ if ( ! $form ) {
+ return $this->failure_response(
+ sprintf(
+ 'Failed to create WC order in webhook event %s, checkout data not found.',
+ $request['id'] ?: ''
)
);
- $response['message'] = $message;
- return rest_ensure_response( $response );
}
- if ( $order->intent() === 'CAPTURE' ) {
- $order = $this->order_endpoint->capture( $order );
+ $checkout = new WC_Checkout();
+ $wc_order_id = $checkout->create_order( $form );
+ $wc_order = wc_get_order( $wc_order_id );
+ if ( ! $wc_order instanceof WC_Order ) {
+ return $this->failure_response(
+ sprintf(
+ 'Failed to create WC order in webhook event %s.',
+ $request['id'] ?: ''
+ )
+ );
}
- } catch ( RuntimeException $error ) {
- $message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'Could not capture payment for webhook event %s.',
- 'woocommerce-paypal-payments'
- ),
- isset( $request['id'] ) ? $request['id'] : ''
- );
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return rest_ensure_response( $response );
- }
- $wc_order_ids = array_map(
- array(
- $this,
- 'sanitize_custom_id',
- ),
- $custom_ids
- );
- $args = array(
- 'post__in' => $wc_order_ids,
- 'limit' => -1,
- );
- $wc_orders = wc_get_orders( $args );
- if ( ! $wc_orders ) {
- $message = sprintf(
- // translators: %s is the PayPal order Id.
- __( 'Order for PayPal order %s not found.', 'woocommerce-paypal-payments' ),
- isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
+ $funding_source = $this->session_handler->funding_source();
+ if ( $funding_source ) {
+ $wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
+ }
+
+ if ( is_numeric( $customer_id ) ) {
+ $wc_order->set_customer_id( (int) $customer_id );
+ }
+
+ $wc_order->save();
+
+ $wc_orders[] = $wc_order;
+
+ add_action(
+ 'shutdown',
+ function () use ( $customer_id ): void {
+ $session = WC()->session;
+ assert( $session instanceof WC_Session_Handler );
+
+ /**
+ * Wrong type-hint.
+ *
+ * @psalm-suppress InvalidScalarArgument
+ */
+ $session->delete_session( $customer_id );
+ $session->forget_session();
+ }
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return rest_ensure_response( $response );
+ } else {
+ $wc_orders = $this->get_wc_orders_from_custom_ids( $wc_order_ids );
+ if ( ! $wc_orders ) {
+ return $this->no_wc_orders_response( $request );
+ }
}
foreach ( $wc_orders as $wc_order ) {
@@ -197,31 +227,24 @@ class CheckoutOrderApproved implements RequestHandler {
if ( ! in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
continue;
}
- if ( $order->intent() === 'CAPTURE' ) {
- $wc_order->payment_complete();
- } else {
- $wc_order->update_status(
- 'on-hold',
- __( 'Payment can be captured.', 'woocommerce-paypal-payments' )
+
+ if ( ! $this->order_processor->process( $wc_order ) ) {
+ return $this->failure_response(
+ sprintf(
+ 'Failed to process WC order %s: %s.',
+ (string) $wc_order->get_id(),
+ $this->order_processor->last_error()
+ )
);
}
- $this->logger->log(
- 'info',
+
+ $this->logger->info(
sprintf(
- // translators: %s is the order ID.
- __(
- 'Order %s has been updated through PayPal',
- 'woocommerce-paypal-payments'
- ),
+ 'WC order %s has been processed after approval in PayPal.',
(string) $wc_order->get_id()
- ),
- array(
- 'request' => $request,
- 'order' => $wc_order,
)
);
}
- $response['success'] = true;
- return rest_ensure_response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/CheckoutOrderCompleted.php b/modules/ppcp-webhooks/src/Handler/CheckoutOrderCompleted.php
index 85bbd376d..6bf897720 100644
--- a/modules/ppcp-webhooks/src/Handler/CheckoutOrderCompleted.php
+++ b/modules/ppcp-webhooks/src/Handler/CheckoutOrderCompleted.php
@@ -19,7 +19,7 @@ use WP_REST_Response;
*/
class CheckoutOrderCompleted implements RequestHandler {
- use PrefixTrait, RequestHandlerTrait;
+ use RequestHandlerTrait;
/**
* The logger.
@@ -32,11 +32,9 @@ class CheckoutOrderCompleted implements RequestHandler {
* CheckoutOrderCompleted constructor.
*
* @param LoggerInterface $logger The logger.
- * @param string $prefix The prefix.
*/
- public function __construct( LoggerInterface $logger, string $prefix ) {
+ public function __construct( LoggerInterface $logger ) {
$this->logger = $logger;
- $this->prefix = $prefix;
}
/**
@@ -69,16 +67,14 @@ class CheckoutOrderCompleted implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
-
- $custom_ids = $this->get_custom_ids_from_request( $request );
+ $custom_ids = $this->get_wc_order_ids_from_request( $request );
if ( empty( $custom_ids ) ) {
- return $this->no_custom_ids_from_request( $request, $response );
+ return $this->no_custom_ids_response( $request );
}
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
if ( ! $wc_orders ) {
- return $this->no_wc_orders_from_custom_ids( $request, $response );
+ return $this->no_wc_orders_response( $request );
}
foreach ( $wc_orders as $wc_order ) {
@@ -93,17 +89,12 @@ class CheckoutOrderCompleted implements RequestHandler {
$this->logger->info(
sprintf(
- // translators: %s is the order ID.
- __(
- 'Order %s has been updated through PayPal',
- 'woocommerce-paypal-payments'
- ),
+ 'Order %s has been updated through PayPal',
(string) $wc_order->get_id()
)
);
}
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/CheckoutPaymentApprovalReversed.php b/modules/ppcp-webhooks/src/Handler/CheckoutPaymentApprovalReversed.php
index 7377ec3bd..168d76382 100644
--- a/modules/ppcp-webhooks/src/Handler/CheckoutPaymentApprovalReversed.php
+++ b/modules/ppcp-webhooks/src/Handler/CheckoutPaymentApprovalReversed.php
@@ -18,7 +18,7 @@ use WP_REST_Response;
*/
class CheckoutPaymentApprovalReversed implements RequestHandler {
- use RequestHandlerTrait, PrefixTrait;
+ use RequestHandlerTrait;
/**
* The logger.
@@ -66,26 +66,20 @@ class CheckoutPaymentApprovalReversed implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
-
- $custom_ids = $this->get_custom_ids_from_request( $request );
+ $custom_ids = $this->get_wc_order_ids_from_request( $request );
if ( empty( $custom_ids ) ) {
- return $this->no_custom_ids_from_request( $request, $response );
+ return $this->no_custom_ids_response( $request );
}
$wc_orders = $this->get_wc_orders_from_custom_ids( $custom_ids );
if ( ! $wc_orders ) {
- return $this->no_wc_orders_from_custom_ids( $request, $response );
+ return $this->no_wc_orders_response( $request );
}
foreach ( $wc_orders as $wc_order ) {
if ( in_array( $wc_order->get_status(), array( 'pending', 'on-hold' ), true ) ) {
$error_message = sprintf(
- // translators: %1$s is the order id.
- __(
- 'Failed to capture order %1$s through PayPal.',
- 'woocommerce-paypal-payments'
- ),
+ 'Failed to capture order %1$s through PayPal.',
(string) $wc_order->get_id()
);
@@ -95,7 +89,6 @@ class CheckoutPaymentApprovalReversed implements RequestHandler {
}
}
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureCompleted.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureCompleted.php
index eda546118..d8f927b42 100644
--- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureCompleted.php
+++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureCompleted.php
@@ -22,7 +22,7 @@ use WP_REST_Response;
*/
class PaymentCaptureCompleted implements RequestHandler {
- use PrefixTrait, TransactionIdHandlingTrait;
+ use TransactionIdHandlingTrait, RequestHandlerTrait;
/**
* The logger.
@@ -42,16 +42,13 @@ class PaymentCaptureCompleted implements RequestHandler {
* PaymentCaptureCompleted constructor.
*
* @param LoggerInterface $logger The logger.
- * @param string $prefix The prefix.
* @param OrderEndpoint $order_endpoint The order endpoint.
*/
public function __construct(
LoggerInterface $logger,
- string $prefix,
OrderEndpoint $order_endpoint
) {
$this->logger = $logger;
- $this->prefix = $prefix;
$this->order_endpoint = $order_endpoint;
}
@@ -83,33 +80,24 @@ class PaymentCaptureCompleted implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
-
$webhook_id = (string) ( $request['id'] ?? '' );
$resource = $request['resource'];
if ( ! is_array( $resource ) ) {
$message = 'Resource data not found in webhook request.';
- $this->logger->warning( $message, array( 'request' => $request ) );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
- $wc_order_id = isset( $resource['custom_id'] ) ?
- $this->sanitize_custom_id( (string) $resource['custom_id'] ) : 0;
+ $wc_order_id = isset( $resource['custom_id'] ) ? (string) $resource['custom_id'] : 0;
if ( ! $wc_order_id ) {
$message = sprintf( 'No order for webhook event %s was found.', $webhook_id );
- $this->logger->warning( $message, array( 'request' => $request ) );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$wc_order = wc_get_order( $wc_order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf( 'No order for webhook event %s was found.', $webhook_id );
- $this->logger->warning( $message, array( 'request' => $request ) );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$order_id = $resource['supplementary_data']['related_ids']['order_id'] ?? null;
@@ -120,8 +108,7 @@ class PaymentCaptureCompleted implements RequestHandler {
do_action( 'ppcp_payment_capture_completed_webhook_handler', $wc_order, $order_id );
if ( $wc_order->get_status() !== 'on-hold' ) {
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
$wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-payments' )
@@ -130,19 +117,10 @@ class PaymentCaptureCompleted implements RequestHandler {
$wc_order->payment_complete();
$wc_order->update_meta_data( AuthorizedPaymentsProcessor::CAPTURED_META_KEY, 'true' );
$wc_order->save();
- $this->logger->log(
- 'info',
+ $this->logger->info(
sprintf(
- // translators: %s is the order ID.
- __(
- 'Order %s has been updated through PayPal',
- 'woocommerce-paypal-payments'
- ),
+ 'Order %s has been updated through PayPal',
(string) $wc_order->get_id()
- ),
- array(
- 'request' => $request,
- 'order' => $wc_order,
)
);
@@ -159,7 +137,6 @@ class PaymentCaptureCompleted implements RequestHandler {
}
}
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php
index 225e70121..bbbc41dd8 100644
--- a/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php
+++ b/modules/ppcp-webhooks/src/Handler/PaymentCapturePending.php
@@ -18,7 +18,7 @@ use WP_REST_Response;
*/
class PaymentCapturePending implements RequestHandler {
- use PrefixTrait;
+ use RequestHandlerTrait;
/**
* The logger.
@@ -66,36 +66,21 @@ class PaymentCapturePending implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
$order_id = $request['resource'] !== null && isset( $request['resource']['custom_id'] )
- ? $this->sanitize_custom_id( $request['resource']['custom_id'] )
+ ? $request['resource']['custom_id']
: 0;
if ( ! $order_id ) {
$message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'No order for webhook event %s was found.',
- 'woocommerce-paypal-payments'
- ),
+ 'No order for webhook event %s was found.',
$request['id'] !== null && isset( $request['id'] ) ? $request['id'] : ''
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$resource = $request['resource'];
if ( ! is_array( $resource ) ) {
$message = 'Resource data not found in webhook request.';
- $this->logger->warning( $message, array( 'request' => $request ) );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$wc_order = wc_get_order( $order_id );
@@ -105,10 +90,7 @@ class PaymentCapturePending implements RequestHandler {
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
);
- $this->logger->warning( $message );
-
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
if ( $wc_order->get_status() === 'pending' ) {
@@ -116,7 +98,6 @@ class PaymentCapturePending implements RequestHandler {
}
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php
index d879a2345..eab1defc0 100644
--- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php
+++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureRefunded.php
@@ -10,8 +10,11 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
+use WC_Order;
+use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait;
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
+use WP_Error;
use WP_REST_Request;
use WP_REST_Response;
@@ -20,7 +23,7 @@ use WP_REST_Response;
*/
class PaymentCaptureRefunded implements RequestHandler {
- use PrefixTrait, TransactionIdHandlingTrait, RefundMetaTrait;
+ use TransactionIdHandlingTrait, RefundMetaTrait, RequestHandlerTrait;
/**
* The logger.
@@ -29,15 +32,22 @@ class PaymentCaptureRefunded implements RequestHandler {
*/
private $logger;
+ /**
+ * The refund fees updater.
+ *
+ * @var RefundFeesUpdater
+ */
+ private $refund_fees_updater;
+
/**
* PaymentCaptureRefunded constructor.
*
- * @param LoggerInterface $logger The logger.
- * @param string $prefix The prefix.
+ * @param LoggerInterface $logger The logger.
+ * @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
*/
- public function __construct( LoggerInterface $logger, string $prefix ) {
- $this->logger = $logger;
- $this->prefix = $prefix;
+ public function __construct( LoggerInterface $logger, RefundFeesUpdater $refund_fees_updater ) {
+ $this->logger = $logger;
+ $this->refund_fees_updater = $refund_fees_updater;
}
/**
@@ -68,106 +78,64 @@ class PaymentCaptureRefunded implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
- $order_id = isset( $request['resource']['custom_id'] ) ?
- $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
- $refund_id = (string) ( $request['resource']['id'] ?? '' );
+ $resource = ( $request['resource'] ?? array() ) ?: array();
+
+ $order_id = $resource['custom_id'] ?? 0;
+ $refund_id = (string) ( $resource['id'] ?? '' );
+
if ( ! $order_id ) {
$message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'No order for webhook event %s was found.',
- 'woocommerce-paypal-payments'
- ),
+ 'No order for webhook event %s was found.',
isset( $request['id'] ) ? $request['id'] : ''
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$wc_order = wc_get_order( $order_id );
- if ( ! is_a( $wc_order, \WC_Order::class ) ) {
+ if ( ! is_a( $wc_order, WC_Order::class ) ) {
$message = sprintf(
- // translators: %s is the PayPal refund Id.
- __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ),
+ 'Order for PayPal refund %s not found.',
$refund_id
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
$already_added_refunds = $this->get_refunds_meta( $wc_order );
if ( in_array( $refund_id, $already_added_refunds, true ) ) {
$this->logger->info( "Refund {$refund_id} is already handled." );
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
- /**
- * The WooCommerce order.
- *
- * @var \WC_Order $wc_order
- */
$refund = wc_create_refund(
array(
'order_id' => $wc_order->get_id(),
'amount' => $request['resource']['amount']['value'],
)
);
- if ( is_wp_error( $refund ) ) {
- $this->logger->log(
- 'warning',
- sprintf(
- // translators: %s is the order id.
- __( 'Order %s could not be refunded', 'woocommerce-paypal-payments' ),
- (string) $wc_order->get_id()
- ),
- array(
- 'request' => $request,
- 'error' => $refund,
- )
+ if ( $refund instanceof WP_Error ) {
+ $message = sprintf(
+ 'Order %1$s could not be refunded. %2$s',
+ (string) $wc_order->get_id(),
+ $refund->get_error_message()
);
- $response['message'] = $refund->get_error_message();
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
}
- $this->logger->log(
- 'info',
+ $this->logger->info(
sprintf(
- // translators: %1$s is the order id %2$s is the amount which has been refunded.
- __(
- 'Order %1$s has been refunded with %2$s through PayPal',
- 'woocommerce-paypal-payments'
- ),
+ 'Order %1$s has been refunded with %2$s through PayPal',
(string) $wc_order->get_id(),
(string) $refund->get_amount()
- ),
- array(
- 'request' => $request,
- 'order' => $wc_order,
)
);
if ( $refund_id ) {
$this->update_transaction_id( $refund_id, $wc_order, $this->logger );
$this->add_refund_to_meta( $wc_order, $refund_id );
+ $this->refund_fees_updater->update( $wc_order );
}
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php b/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php
index 5354e555c..5369a9ca3 100644
--- a/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php
+++ b/modules/ppcp-webhooks/src/Handler/PaymentCaptureReversed.php
@@ -19,7 +19,7 @@ use Psr\Log\LoggerInterface;
*/
class PaymentCaptureReversed implements RequestHandler {
- use PrefixTrait;
+ use RequestHandlerTrait;
/**
* The logger.
@@ -32,11 +32,9 @@ class PaymentCaptureReversed implements RequestHandler {
* PaymentCaptureReversed constructor.
*
* @param LoggerInterface $logger The logger.
- * @param string $prefix The prefix.
*/
- public function __construct( LoggerInterface $logger, string $prefix ) {
+ public function __construct( LoggerInterface $logger ) {
$this->logger = $logger;
- $this->prefix = $prefix;
}
/**
@@ -71,45 +69,23 @@ class PaymentCaptureReversed implements RequestHandler {
* @return \WP_REST_Response
*/
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
- $response = array( 'success' => false );
$order_id = isset( $request['resource']['custom_id'] ) ?
- $this->sanitize_custom_id( $request['resource']['custom_id'] ) : 0;
+ $request['resource']['custom_id'] : 0;
if ( ! $order_id ) {
$message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __(
- 'No order for webhook event %s was found.',
- 'woocommerce-paypal-payments'
- ),
+ 'No order for webhook event %s was found.',
isset( $request['id'] ) ? $request['id'] : ''
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return rest_ensure_response( $response );
+ return $this->failure_response( $message );
}
$wc_order = wc_get_order( $order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) ) {
$message = sprintf(
- // translators: %s is the PayPal refund Id.
- __( 'Order for PayPal refund %s not found.', 'woocommerce-paypal-payments' ),
+ 'Order for PayPal refund %s not found.',
isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response['message'] = $message;
- return rest_ensure_response( $response );
+ return $this->failure_response( $message );
}
/**
@@ -117,33 +93,20 @@ class PaymentCaptureReversed implements RequestHandler {
*/
$note = apply_filters( 'ppcp_payment_capture_reversed_webhook_update_status_note', '', $wc_order, $request['event_type'] );
- /**
- * The WooCommerce order.
- *
- * @var \WC_Order $wc_order
- */
- $response['success'] = (bool) $wc_order->update_status( 'cancelled', $note );
+ $is_success = $wc_order->update_status( 'cancelled', $note );
+ if ( ! $is_success ) {
+ $message = sprintf(
+ 'Failed to cancel order %1$s cancelled through PayPal',
+ (string) $wc_order->get_id()
+ );
+ return $this->failure_response( $message );
+ }
- $message = $response['success'] ? sprintf(
- // translators: %1$s is the order id.
- __(
- 'Order %1$s has been cancelled through PayPal',
- 'woocommerce-paypal-payments'
- ),
- (string) $wc_order->get_id()
- ) : sprintf(
- // translators: %1$s is the order id.
- __( 'Failed to cancel order %1$s through PayPal', 'woocommerce-paypal-payments' ),
+ $message = sprintf(
+ 'Order %1$s has been cancelled through PayPal',
(string) $wc_order->get_id()
);
- $this->logger->log(
- $response['success'] ? 'info' : 'warning',
- $message,
- array(
- 'request' => $request,
- 'order' => $wc_order,
- )
- );
- return rest_ensure_response( $response );
+ $this->logger->info( $message );
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php b/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php
new file mode 100644
index 000000000..1679c46db
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/PaymentSaleCompleted.php
@@ -0,0 +1,121 @@
+logger = $logger;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array( 'PAYMENT.SALE.COMPLETED' );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
+ return $this->failure_response( 'WooCommerce Subscriptions plugin is not active.' );
+ }
+
+ $billing_agreement_id = wc_clean( wp_unslash( $request['resource']['billing_agreement_id'] ?? '' ) );
+ if ( ! $billing_agreement_id ) {
+ return $this->failure_response( 'Could not retrieve billing agreement id for subscription.' );
+ }
+
+ $transaction_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
+ if ( ! $transaction_id || ! is_string( $transaction_id ) ) {
+ return $this->failure_response( 'Could not retrieve transaction id for subscription.' );
+ }
+
+ $args = array(
+ // phpcs:ignore WordPress.DB.SlowDBQuery
+ 'meta_query' => array(
+ array(
+ 'key' => 'ppcp_subscription',
+ 'value' => $billing_agreement_id,
+ 'compare' => '=',
+ ),
+ ),
+ );
+ $subscriptions = wcs_get_subscriptions( $args );
+ foreach ( $subscriptions as $subscription ) {
+ $is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
+ if ( $is_renewal ) {
+ $renewal_order = wcs_create_renewal_order( $subscription );
+ if ( is_a( $renewal_order, WC_Order::class ) ) {
+ $renewal_order->set_payment_method( $subscription->get_payment_method() );
+ $renewal_order->payment_complete();
+ $this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
+ break;
+ }
+ }
+
+ $parent_order = wc_get_order( $subscription->get_parent() );
+ if ( is_a( $parent_order, WC_Order::class ) ) {
+ $subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
+ $subscription->save_meta_data();
+ $this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
+ }
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/PaymentSaleRefunded.php b/modules/ppcp-webhooks/src/Handler/PaymentSaleRefunded.php
new file mode 100644
index 000000000..fd05bc922
--- /dev/null
+++ b/modules/ppcp-webhooks/src/Handler/PaymentSaleRefunded.php
@@ -0,0 +1,138 @@
+logger = $logger;
+ $this->refund_fees_updater = $refund_fees_updater;
+ }
+
+ /**
+ * The event types a handler handles.
+ *
+ * @return string[]
+ */
+ public function event_types(): array {
+ return array( 'PAYMENT.SALE.REFUNDED' );
+ }
+
+ /**
+ * Whether a handler is responsible for a given request or not.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return bool
+ */
+ public function responsible_for_request( WP_REST_Request $request ): bool {
+ return in_array( $request['event_type'], $this->event_types(), true );
+
+ }
+
+ /**
+ * Responsible for handling the request.
+ *
+ * @param WP_REST_Request $request The request.
+ *
+ * @return WP_REST_Response
+ */
+ public function handle_request( WP_REST_Request $request ): WP_REST_Response {
+ if ( is_null( $request['resource'] ) ) {
+ return $this->failure_response();
+ }
+
+ $refund_id = (string) ( $request['resource']['id'] ?? '' );
+ $transaction_id = $request['resource']['sale_id'] ?? '';
+ $total_refunded_amount = $request['resource']['total_refunded_amount']['value'] ?? '';
+ if ( ! $refund_id || ! $transaction_id || ! $total_refunded_amount ) {
+ return $this->failure_response();
+ }
+
+ $args = array(
+ // phpcs:disable WordPress.DB.SlowDBQuery
+ 'meta_key' => '_transaction_id',
+ 'meta_value' => $transaction_id,
+ 'meta_compare' => '=',
+ // phpcs:enable
+ );
+ $wc_orders = wc_get_orders( $args );
+
+ if ( ! is_array( $wc_orders ) ) {
+ return $this->failure_response();
+ }
+
+ foreach ( $wc_orders as $wc_order ) {
+ $refund = wc_create_refund(
+ array(
+ 'order_id' => $wc_order->get_id(),
+ 'amount' => $total_refunded_amount,
+ )
+ );
+
+ if ( $refund instanceof WP_Error ) {
+ $message = sprintf(
+ 'Order %s could not be refunded. %s',
+ (string) $wc_order->get_id(),
+ $refund->get_error_message()
+ );
+
+ return $this->failure_response( $message );
+ }
+
+ $order_refunded_message = sprintf(
+ 'Order %1$s has been refunded with %2$s through PayPal.',
+ (string) $wc_order->get_id(),
+ (string) $total_refunded_amount
+ );
+ $this->logger->info( $order_refunded_message );
+ $wc_order->add_order_note( $order_refunded_message );
+
+ $this->update_transaction_id( $refund_id, $wc_order, $this->logger );
+ $this->add_refund_to_meta( $wc_order, $refund_id );
+ $this->refund_fees_updater->update( $wc_order );
+ }
+
+ return $this->success_response();
+ }
+}
diff --git a/modules/ppcp-webhooks/src/Handler/PrefixTrait.php b/modules/ppcp-webhooks/src/Handler/PrefixTrait.php
deleted file mode 100644
index bc92d9e36..000000000
--- a/modules/ppcp-webhooks/src/Handler/PrefixTrait.php
+++ /dev/null
@@ -1,40 +0,0 @@
-prefix ) > 0 && 0 === strpos( $id, $this->prefix ) ) {
- $id = substr( $id, strlen( $this->prefix ) );
- }
- return (int) $id;
- }
-}
diff --git a/modules/ppcp-webhooks/src/Handler/RequestHandlerTrait.php b/modules/ppcp-webhooks/src/Handler/RequestHandlerTrait.php
index be155c0ee..933e202e4 100644
--- a/modules/ppcp-webhooks/src/Handler/RequestHandlerTrait.php
+++ b/modules/ppcp-webhooks/src/Handler/RequestHandlerTrait.php
@@ -9,8 +9,8 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
-use stdClass;
use WC_Order;
+use WooCommerce\PayPalCommerce\Webhooks\CustomIds;
use WP_REST_Request;
use WP_REST_Response;
@@ -19,22 +19,78 @@ trait RequestHandlerTrait {
/**
* Get available custom ids from the given request
*
- * @param \WP_REST_Request $request The request.
- * @return array
+ * @param WP_REST_Request $request The request.
+ * @return string[]
*/
protected function get_custom_ids_from_request( WP_REST_Request $request ): array {
- return array_filter(
- array_map(
+ $resource = $request['resource'];
+ if ( ! is_array( $resource ) ) {
+ return array();
+ }
+
+ $ids = array();
+ if ( isset( $resource['custom_id'] ) && ! empty( $resource['custom_id'] ) ) {
+ $ids[] = $resource['custom_id'];
+ } elseif ( isset( $resource['purchase_units'] ) ) {
+ $ids = array_map(
static function ( array $purchase_unit ): string {
- return isset( $purchase_unit['custom_id'] ) ?
- (string) $purchase_unit['custom_id'] : '';
+ return $purchase_unit['custom_id'] ?? '';
},
- $request['resource'] !== null && isset( $request['resource']['purchase_units'] ) ?
- (array) $request['resource']['purchase_units'] : array()
- ),
- static function ( string $order_id ): bool {
- return ! empty( $order_id );
- }
+ (array) $resource['purchase_units']
+ );
+ }
+
+ return array_values(
+ array_filter(
+ $ids,
+ function ( string $id ): bool {
+ return ! empty( $id );
+ }
+ )
+ );
+ }
+
+ /**
+ * Get available WC order ids from the given request.
+ *
+ * @param WP_REST_Request $request The request.
+ * @return string[]
+ */
+ protected function get_wc_order_ids_from_request( WP_REST_Request $request ): array {
+ $ids = $this->get_custom_ids_from_request( $request );
+
+ return array_values(
+ array_filter(
+ $ids,
+ function ( string $id ): bool {
+ return strpos( $id, CustomIds::CUSTOMER_ID_PREFIX ) === false;
+ }
+ )
+ );
+ }
+
+ /**
+ * Get available WC customer ids from the given request.
+ *
+ * @param WP_REST_Request $request The request.
+ * @return string[]
+ */
+ protected function get_wc_customer_ids_from_request( WP_REST_Request $request ): array {
+ $ids = $this->get_custom_ids_from_request( $request );
+
+ $customer_ids = array_values(
+ array_filter(
+ $ids,
+ function ( string $id ): bool {
+ return strpos( $id, CustomIds::CUSTOMER_ID_PREFIX ) === 0;
+ }
+ )
+ );
+ return array_map(
+ function ( string $str ): string {
+ return (string) substr( $str, strlen( CustomIds::CUSTOMER_ID_PREFIX ) );
+ },
+ $customer_ids
);
}
@@ -45,13 +101,7 @@ trait RequestHandlerTrait {
* @return WC_Order[]
*/
protected function get_wc_orders_from_custom_ids( array $custom_ids ): array {
- $order_ids = array_map(
- array(
- $this,
- 'sanitize_custom_id',
- ),
- $custom_ids
- );
+ $order_ids = $custom_ids;
$args = array(
'post__in' => $order_ids,
'limit' => -1,
@@ -62,49 +112,62 @@ trait RequestHandlerTrait {
}
/**
- * Return and log response for no custom ids found in request.
+ * Logs and returns response for no custom ids found in request.
*
* @param WP_REST_Request $request The request.
- * @param array $response The response.
* @return WP_REST_Response
*/
- protected function no_custom_ids_from_request( WP_REST_Request $request, array $response ): WP_REST_Response {
+ protected function no_custom_ids_response( WP_REST_Request $request ): WP_REST_Response {
$message = sprintf(
- // translators: %s is the PayPal webhook Id.
- __( 'No order for webhook event %s was found.', 'woocommerce-paypal-payments' ),
- $request['id'] !== null && isset( $request['id'] ) ? $request['id'] : ''
- );
-
- return $this->log_and_return_response( $message, $response );
- }
-
- /**
- * Return and log response for no WC orders found in response.
- *
- * @param WP_REST_Request $request The request.
- * @param array $response The response.
- * @return WP_REST_Response
- */
- protected function no_wc_orders_from_custom_ids( WP_REST_Request $request, array $response ): WP_REST_Response {
- $message = sprintf(
- // translators: %s is the PayPal order Id.
- __( 'WC order for PayPal order %s not found.', 'woocommerce-paypal-payments' ),
+ 'WC order ID was not found in webhook event %s for PayPal order %s.',
+ (string) ( $request['id'] ?? '' ),
+ // Psalm 4.x does not seem to understand ?? with ArrayAccess correctly.
$request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
);
- return $this->log_and_return_response( $message, $response );
+ return $this->failure_response( $message );
}
/**
- * Return and log response with the given message.
+ * Logs and returns response for no WC orders found via custom ids.
*
- * @param string $message The message.
- * @param array $response The response.
+ * @param WP_REST_Request $request The request.
* @return WP_REST_Response
*/
- private function log_and_return_response( string $message, array $response ): WP_REST_Response {
- $this->logger->warning( $message );
- $response['message'] = $message;
+ protected function no_wc_orders_response( WP_REST_Request $request ): WP_REST_Response {
+ $message = sprintf(
+ 'WC order %s not found in webhook event %s for PayPal order %s.',
+ implode( ', ', $this->get_custom_ids_from_request( $request ) ),
+ (string) ( $request['id'] ?? '' ),
+ $request['resource'] !== null && isset( $request['resource']['id'] ) ? $request['resource']['id'] : ''
+ );
+
+ return $this->failure_response( $message );
+ }
+
+ /**
+ * Returns success response.
+ *
+ * @return WP_REST_Response
+ */
+ protected function success_response(): WP_REST_Response {
+ return new WP_REST_Response( array( 'success' => true ) );
+ }
+
+ /**
+ * Logs and returns failure response with the given message.
+ *
+ * @param string $message The message.
+ * @return WP_REST_Response
+ */
+ private function failure_response( string $message = '' ): WP_REST_Response {
+ $response = array(
+ 'success' => false,
+ );
+ if ( $message ) {
+ $this->logger->warning( $message );
+ $response['message'] = $message;
+ }
return new WP_REST_Response( $response );
}
diff --git a/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php
index 0d2c1e6b4..893305c75 100644
--- a/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php
+++ b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenCreated.php
@@ -10,6 +10,13 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
use Psr\Log\LoggerInterface;
+use WC_Payment_Token_CC;
+use WC_Payment_Tokens;
+use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenFactory;
+use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenHelper;
+use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenPayPal;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
+use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WP_REST_Request;
use WP_REST_Response;
@@ -18,6 +25,7 @@ use WP_REST_Response;
* Class VaultPaymentTokenCreated
*/
class VaultPaymentTokenCreated implements RequestHandler {
+ use RequestHandlerTrait;
/**
* The logger.
@@ -40,17 +48,41 @@ class VaultPaymentTokenCreated implements RequestHandler {
*/
protected $authorized_payments_processor;
+ /**
+ * The payment token factory.
+ *
+ * @var PaymentTokenFactory
+ */
+ protected $payment_token_factory;
+
+ /**
+ * The payment token helper.
+ *
+ * @var PaymentTokenHelper
+ */
+ private $payment_token_helper;
+
/**
* VaultPaymentTokenCreated constructor.
*
* @param LoggerInterface $logger The logger.
* @param string $prefix The prefix.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payment processor.
+ * @param PaymentTokenFactory $payment_token_factory The payment token factory.
+ * @param PaymentTokenHelper $payment_token_helper The payment token helper.
*/
- public function __construct( LoggerInterface $logger, string $prefix, AuthorizedPaymentsProcessor $authorized_payments_processor ) {
+ public function __construct(
+ LoggerInterface $logger,
+ string $prefix,
+ AuthorizedPaymentsProcessor $authorized_payments_processor,
+ PaymentTokenFactory $payment_token_factory,
+ PaymentTokenHelper $payment_token_helper
+ ) {
$this->logger = $logger;
$this->prefix = $prefix;
$this->authorized_payments_processor = $authorized_payments_processor;
+ $this->payment_token_factory = $payment_token_factory;
+ $this->payment_token_helper = $payment_token_helper;
}
/**
@@ -83,22 +115,86 @@ class VaultPaymentTokenCreated implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- $response = array( 'success' => false );
-
$customer_id = null !== $request['resource'] && isset( $request['resource']['customer_id'] )
? $request['resource']['customer_id']
: '';
+
if ( ! $customer_id ) {
$message = 'No customer id was found.';
- $this->logger->warning( $message, array( 'request' => $request ) );
- $response['message'] = $message;
- return new WP_REST_Response( $response );
+ return $this->failure_response( $message );
+ }
+
+ $wc_customer_id = $this->wc_customer_id_from( $customer_id );
+ if ( ! $wc_customer_id ) {
+ $message = "No WC customer id was found from PayPal customer id {$customer_id}";
+ return $this->failure_response( $message );
}
- $wc_customer_id = (int) str_replace( $this->prefix, '', $customer_id );
$this->authorized_payments_processor->capture_authorized_payments_for_customer( $wc_customer_id );
- $response['success'] = true;
- return new WP_REST_Response( $response );
+ if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
+ if ( ! is_null( $request['resource']['source'] ) && isset( $request['resource']['source']['card'] ) ) {
+ $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_customer_id, CreditCardGateway::ID );
+ if ( ! $this->payment_token_helper->token_exist( $wc_tokens, $request['resource']['id'] ) ) {
+ $token = new WC_Payment_Token_CC();
+ $token->set_token( $request['resource']['id'] );
+ $token->set_user_id( $wc_customer_id );
+ $token->set_gateway_id( CreditCardGateway::ID );
+
+ $token->set_last4( $request['resource']['source']['card']['last_digits'] ?? '' );
+ $expiry = explode( '-', $request['resource']['source']['card']['expiry'] ?? '' );
+ $token->set_expiry_year( $expiry[0] ?? '' );
+ $token->set_expiry_month( $expiry[1] ?? '' );
+ $token->set_card_type( $request['resource']['source']['card']['brand'] ?? '' );
+ $token->save();
+ WC_Payment_Tokens::set_users_default( $wc_customer_id, $token->get_id() );
+ }
+ } elseif ( isset( $request['resource']['source']['paypal'] ) ) {
+ $wc_tokens = WC_Payment_Tokens::get_customer_tokens( $wc_customer_id, PayPalGateway::ID );
+ if ( ! $this->payment_token_helper->token_exist( $wc_tokens, $request['resource']['id'] ) ) {
+ $payment_token_paypal = $this->payment_token_factory->create( 'paypal' );
+ assert( $payment_token_paypal instanceof PaymentTokenPayPal );
+
+ $payment_token_paypal->set_token( $request['resource']['id'] );
+ $payment_token_paypal->set_user_id( $wc_customer_id );
+ $payment_token_paypal->set_gateway_id( PayPalGateway::ID );
+
+ $email = $request['resource']['source']['paypal']['payer']['email_address'] ?? '';
+ if ( $email && is_email( $email ) ) {
+ $payment_token_paypal->set_email( $email );
+ }
+
+ $payment_token_paypal->save();
+ WC_Payment_Tokens::set_users_default( $wc_customer_id, $payment_token_paypal->get_id() );
+ }
+ }
+ }
+
+ return $this->success_response();
+ }
+
+ /**
+ * Returns WC customer id from PayPal customer id.
+ *
+ * @param string $customer_id The customer ID from PayPal.
+ * @return int
+ */
+ private function wc_customer_id_from( string $customer_id ): int {
+ $customers = get_users(
+ array(
+ 'meta_key' => 'ppcp_guest_customer_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
+ 'meta_value' => $customer_id, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
+ 'fields' => 'ids',
+ 'number' => 1,
+ )
+ );
+
+ $wc_customer_id = $customers[0] ?? '';
+ if ( $wc_customer_id ) {
+ return (int) $wc_customer_id;
+ }
+
+ $id = str_replace( $this->prefix, '', $customer_id );
+ return is_numeric( $id ) ? (int) $id : 0;
}
}
diff --git a/modules/ppcp-webhooks/src/Handler/VaultCreditCardCreated.php b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenDeleted.php
similarity index 53%
rename from modules/ppcp-webhooks/src/Handler/VaultCreditCardCreated.php
rename to modules/ppcp-webhooks/src/Handler/VaultPaymentTokenDeleted.php
index cdc74c5d6..92dad5ed9 100644
--- a/modules/ppcp-webhooks/src/Handler/VaultCreditCardCreated.php
+++ b/modules/ppcp-webhooks/src/Handler/VaultPaymentTokenDeleted.php
@@ -1,6 +1,6 @@
logger = $logger;
- $this->prefix = $prefix;
}
/**
@@ -50,7 +43,7 @@ class VaultCreditCardCreated implements RequestHandler {
*/
public function event_types(): array {
return array(
- 'VAULT.CREDIT-CARD.CREATED',
+ 'VAULT.PAYMENT-TOKEN.DELETED',
);
}
@@ -73,15 +66,28 @@ class VaultCreditCardCreated implements RequestHandler {
* @return WP_REST_Response
*/
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
- // TODO currently this webhook is not triggered from PayPal, implement it once is available.
+ if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
+ $token_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
- $message = 'VAULT.CREDIT-CARD.CREATED received.';
- $this->logger->log( 'info', $message );
- $response = array(
- 'success' => true,
- 'message' => $message,
- );
+ /**
+ * Needed for database query.
+ *
+ * @psalm-suppress InvalidGlobal
+ */
+ global $wpdb;
- return new WP_REST_Response( $response );
+ $token = $wpdb->get_row(
+ $wpdb->prepare(
+ "SELECT * FROM {$wpdb->prefix}woocommerce_payment_tokens WHERE token=%s",
+ $token_id
+ )
+ );
+
+ if ( isset( $token->token_id ) ) {
+ WC_Payment_Tokens::delete( $token->token_id );
+ }
+ }
+
+ return $this->success_response();
}
}
diff --git a/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php b/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php
index 9f8bbcf7a..6b61d9c1a 100644
--- a/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php
+++ b/modules/ppcp-webhooks/src/IncomingWebhookEndpoint.php
@@ -17,12 +17,14 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
use WooCommerce\PayPalCommerce\ApiClient\Factory\WebhookEventFactory;
use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandler;
use Psr\Log\LoggerInterface;
+use WooCommerce\PayPalCommerce\Webhooks\Handler\RequestHandlerTrait;
use WooCommerce\PayPalCommerce\Webhooks\Status\WebhookSimulation;
/**
* Class IncomingWebhookEndpoint
*/
class IncomingWebhookEndpoint {
+ use RequestHandlerTrait;
const NAMESPACE = 'paypal/v1';
const ROUTE = 'incoming';
@@ -77,11 +79,19 @@ class IncomingWebhookEndpoint {
private $simulation;
/**
- * The last webhook info storage.
+ * The last webhook event storage.
*
- * @var WebhookInfoStorage
+ * @var WebhookEventStorage
*/
- private $last_webhook_storage;
+ private $last_webhook_event_storage;
+
+ /**
+ * Cached webhook verification results
+ * to avoid repeating requests when permission_callback is called multiple times.
+ *
+ * @var array
+ */
+ private $verification_results = array();
/**
* IncomingWebhookEndpoint constructor.
@@ -92,7 +102,7 @@ class IncomingWebhookEndpoint {
* @param bool $verify_request Whether requests need to be verified or not.
* @param WebhookEventFactory $webhook_event_factory The webhook event factory.
* @param WebhookSimulation $simulation The simulation handler.
- * @param WebhookInfoStorage $last_webhook_storage The last webhook info storage.
+ * @param WebhookEventStorage $last_webhook_event_storage The last webhook event storage.
* @param RequestHandler ...$handlers The handlers, which process a request in the end.
*/
public function __construct(
@@ -102,18 +112,18 @@ class IncomingWebhookEndpoint {
bool $verify_request,
WebhookEventFactory $webhook_event_factory,
WebhookSimulation $simulation,
- WebhookInfoStorage $last_webhook_storage,
+ WebhookEventStorage $last_webhook_event_storage,
RequestHandler ...$handlers
) {
- $this->webhook_endpoint = $webhook_endpoint;
- $this->webhook = $webhook;
- $this->handlers = $handlers;
- $this->logger = $logger;
- $this->verify_request = $verify_request;
- $this->webhook_event_factory = $webhook_event_factory;
- $this->last_webhook_storage = $last_webhook_storage;
- $this->simulation = $simulation;
+ $this->webhook_endpoint = $webhook_endpoint;
+ $this->webhook = $webhook;
+ $this->handlers = $handlers;
+ $this->logger = $logger;
+ $this->verify_request = $verify_request;
+ $this->webhook_event_factory = $webhook_event_factory;
+ $this->last_webhook_event_storage = $last_webhook_event_storage;
+ $this->simulation = $simulation;
}
/**
@@ -160,7 +170,17 @@ class IncomingWebhookEndpoint {
try {
$event = $this->event_from_request( $request );
+ } catch ( RuntimeException $exception ) {
+ $this->logger->error( 'Webhook parsing failed: ' . $exception->getMessage() );
+ return false;
+ }
+ $cache_key = $event->id();
+ if ( isset( $this->verification_results[ $cache_key ] ) ) {
+ return $this->verification_results[ $cache_key ];
+ }
+
+ try {
if ( $this->simulation->is_simulation_event( $event ) ) {
return true;
}
@@ -169,9 +189,11 @@ class IncomingWebhookEndpoint {
if ( ! $result ) {
$this->logger->error( 'Webhook verification failed.' );
}
+ $this->verification_results[ $cache_key ] = $result;
return $result;
} catch ( RuntimeException $exception ) {
$this->logger->error( 'Webhook verification failed: ' . $exception->getMessage() );
+ $this->verification_results[ $cache_key ] = false;
return false;
}
}
@@ -186,31 +208,40 @@ class IncomingWebhookEndpoint {
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
$event = $this->event_from_request( $request );
- $this->last_webhook_storage->save( $event );
+ $this->logger->debug(
+ sprintf(
+ 'Webhook %s received of type %s and by resource "%s"',
+ $event->id(),
+ $event->event_type(),
+ $event->resource_type()
+ )
+ );
+
+ $this->last_webhook_event_storage->save( $event );
if ( $this->simulation->is_simulation_event( $event ) ) {
$this->logger->info( 'Received simulated webhook.' );
$this->simulation->receive( $event );
- return rest_ensure_response(
- array(
- 'success' => true,
- )
- );
+ return $this->success_response();
}
foreach ( $this->handlers as $handler ) {
if ( $handler->responsible_for_request( $request ) ) {
- $response = $handler->handle_request( $request );
- $this->logger->log(
- 'info',
+ $event_type = ( $handler->event_types() ? current( $handler->event_types() ) : '' ) ?: '';
+
+ $this->logger->debug(
sprintf(
- // translators: %s is the event type.
- __( 'Webhook has been handled by %s', 'woocommerce-paypal-payments' ),
- ( $handler->event_types() ) ? current( $handler->event_types() ) : ''
- ),
- array(
- 'request' => $request,
- 'response' => $response,
+ 'Webhook is going to be handled by %s on %s',
+ $event_type,
+ get_class( $handler )
+ )
+ );
+ $response = $handler->handle_request( $request );
+ $this->logger->info(
+ sprintf(
+ 'Webhook has been handled by %s on %s',
+ $event_type,
+ get_class( $handler )
)
);
return $response;
@@ -218,22 +249,10 @@ class IncomingWebhookEndpoint {
}
$message = sprintf(
- // translators: %s is the request type.
- __( 'Could not find handler for request type %s', 'woocommerce-paypal-payments' ),
- $request['event_type']
+ 'Could not find handler for request type %s',
+ $request['event_type'] ?: ''
);
- $this->logger->log(
- 'warning',
- $message,
- array(
- 'request' => $request,
- )
- );
- $response = array(
- 'success' => false,
- 'message' => $message,
- );
- return rest_ensure_response( $response );
+ return $this->failure_response( $message );
}
/**
diff --git a/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php b/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php
index 28a9ddd84..475c2a85e 100644
--- a/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php
+++ b/modules/ppcp-webhooks/src/Status/Assets/WebhooksStatusPageAssets.php
@@ -9,6 +9,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Webhooks\Status\Assets;
+use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\ResubscribeEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulateEndpoint;
use WooCommerce\PayPalCommerce\Webhooks\Endpoint\SimulationStateEndpoint;
@@ -33,18 +34,28 @@ class WebhooksStatusPageAssets {
*/
private $version;
+ /**
+ * The environment object.
+ *
+ * @var Environment
+ */
+ private $environment;
+
/**
* WebhooksStatusPageAssets constructor.
*
- * @param string $module_url The URL to the module.
- * @param string $version The assets version.
+ * @param string $module_url The URL to the module.
+ * @param string $version The assets version.
+ * @param Environment $environment The environment object.
*/
public function __construct(
string $module_url,
- string $version
+ string $version,
+ Environment $environment
) {
- $this->module_url = untrailingslashit( $module_url );
- $this->version = $version;
+ $this->module_url = untrailingslashit( $module_url );
+ $this->version = $version;
+ $this->environment = $environment;
}
/**
@@ -103,6 +114,7 @@ class WebhooksStatusPageAssets {
'tooLongDelayMessage' => __( 'Looks like the webhook cannot be received. Check that your website is accessible from the internet.', 'woocommerce-paypal-payments' ),
),
),
+ 'environment' => $this->environment->current_environment(),
);
}
diff --git a/modules/ppcp-webhooks/src/WebhookInfoStorage.php b/modules/ppcp-webhooks/src/WebhookEventStorage.php
similarity index 95%
rename from modules/ppcp-webhooks/src/WebhookInfoStorage.php
rename to modules/ppcp-webhooks/src/WebhookEventStorage.php
index 9fb9865f6..2c2370c0e 100644
--- a/modules/ppcp-webhooks/src/WebhookInfoStorage.php
+++ b/modules/ppcp-webhooks/src/WebhookEventStorage.php
@@ -12,9 +12,9 @@ namespace WooCommerce\PayPalCommerce\Webhooks;
use WooCommerce\PayPalCommerce\ApiClient\Entity\WebhookEvent;
/**
- * Class WebhookInfoStorage
+ * Class WebhookEventStorage
*/
-class WebhookInfoStorage {
+class WebhookEventStorage {
/**
* The WP option key.
diff --git a/modules/ppcp-webhooks/src/WebhookModule.php b/modules/ppcp-webhooks/src/WebhookModule.php
index 6f8669a8b..2458e5b4d 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 WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
use Exception;
@@ -126,8 +127,8 @@ class WebhookModule implements ModuleInterface {
try {
$webhooks = $container->get( 'webhook.status.registered-webhooks' );
-
- if ( empty( $webhooks ) ) {
+ $state = $container->get( 'onboarding.state' );
+ if ( empty( $webhooks ) && $state->current_state() >= State::STATE_ONBOARDED ) {
$registrar = $container->get( 'webhook.registrar' );
assert( $registrar instanceof WebhookRegistrar );
@@ -152,7 +153,6 @@ class WebhookModule implements ModuleInterface {
add_action(
'init',
function () use ( $registrar ) {
- $registrar->unregister();
$registrar->register();
}
);
diff --git a/modules/ppcp-webhooks/src/WebhookRegistrar.php b/modules/ppcp-webhooks/src/WebhookRegistrar.php
index c16aa9acd..8f8919ec2 100644
--- a/modules/ppcp-webhooks/src/WebhookRegistrar.php
+++ b/modules/ppcp-webhooks/src/WebhookRegistrar.php
@@ -45,11 +45,11 @@ class WebhookRegistrar {
private $rest_endpoint;
/**
- * The last webhook info storage.
+ * The last webhook event storage.
*
- * @var WebhookInfoStorage
+ * @var WebhookEventStorage
*/
- private $last_webhook_storage;
+ private $last_webhook_event_storage;
/**
* The logger.
@@ -64,22 +64,22 @@ class WebhookRegistrar {
* @param WebhookFactory $webhook_factory The Webhook factory.
* @param WebhookEndpoint $endpoint The Webhook endpoint.
* @param IncomingWebhookEndpoint $rest_endpoint The WordPress Rest API endpoint.
- * @param WebhookInfoStorage $last_webhook_storage The last webhook info storage.
+ * @param WebhookEventStorage $last_webhook_event_storage The last webhook event storage.
* @param LoggerInterface $logger The logger.
*/
public function __construct(
WebhookFactory $webhook_factory,
WebhookEndpoint $endpoint,
IncomingWebhookEndpoint $rest_endpoint,
- WebhookInfoStorage $last_webhook_storage,
+ WebhookEventStorage $last_webhook_event_storage,
LoggerInterface $logger
) {
- $this->webhook_factory = $webhook_factory;
- $this->endpoint = $endpoint;
- $this->rest_endpoint = $rest_endpoint;
- $this->last_webhook_storage = $last_webhook_storage;
- $this->logger = $logger;
+ $this->webhook_factory = $webhook_factory;
+ $this->endpoint = $endpoint;
+ $this->rest_endpoint = $rest_endpoint;
+ $this->last_webhook_event_storage = $last_webhook_event_storage;
+ $this->logger = $logger;
}
/**
@@ -88,6 +88,8 @@ class WebhookRegistrar {
* @return bool
*/
public function register(): bool {
+ $this->unregister();
+
$webhook = $this->webhook_factory->for_url_and_events(
$this->rest_endpoint->url(),
$this->rest_endpoint->handled_event_types()
@@ -102,7 +104,7 @@ class WebhookRegistrar {
self::KEY,
$created->to_array()
);
- $this->last_webhook_storage->clear();
+ $this->last_webhook_event_storage->clear();
$this->logger->info( 'Webhooks subscribed.' );
return true;
} catch ( RuntimeException $error ) {
@@ -113,27 +115,23 @@ class WebhookRegistrar {
/**
* Unregister webhooks with PayPal.
- *
- * @return bool
*/
- public function unregister(): bool {
- $data = (array) get_option( self::KEY, array() );
- if ( ! $data ) {
- return false;
- }
+ public function unregister(): void {
try {
- $webhook = $this->webhook_factory->from_array( $data );
- $success = $this->endpoint->delete( $webhook );
+ $webhooks = $this->endpoint->list();
+ foreach ( $webhooks as $webhook ) {
+ try {
+ $this->endpoint->delete( $webhook );
+ } catch ( RuntimeException $deletion_error ) {
+ $this->logger->error( "Failed to delete webhook {$webhook->id()}: {$deletion_error->getMessage()}" );
+ }
+ }
} catch ( RuntimeException $error ) {
$this->logger->error( 'Failed to delete webhooks: ' . $error->getMessage() );
- return false;
}
- if ( $success ) {
- delete_option( self::KEY );
- $this->last_webhook_storage->clear();
- $this->logger->info( 'Webhooks deleted.' );
- }
- return $success;
+ delete_option( self::KEY );
+ $this->last_webhook_event_storage->clear();
+ $this->logger->info( 'Webhooks deleted.' );
}
}
diff --git a/modules/ppcp-webhooks/yarn.lock b/modules/ppcp-webhooks/yarn.lock
index c25ba6dd3..296eeecf2 100644
--- a/modules/ppcp-webhooks/yarn.lock
+++ b/modules/ppcp-webhooks/yarn.lock
@@ -1021,12 +1021,7 @@
"@types/estree" "*"
"@types/json-schema" "*"
-"@types/estree@*":
- version "0.0.50"
- resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.50.tgz#1e0caa9364d3fccd2931c3ed96fdbeaa5d4cca83"
- integrity sha512-C6N5s2ZFtuZRj54k2/zyRhNDjJwwcViAM3Nbm8zjBpbqAdZ00mr0CFxvSKeO8Y/e03WVFLpQMdHYVfUd6SB+Hw==
-
-"@types/estree@^0.0.51":
+"@types/estree@*", "@types/estree@^0.0.51":
version "0.0.51"
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==
@@ -1194,12 +1189,7 @@ acorn-import-assertions@^1.7.6:
resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.7.6.tgz#580e3ffcae6770eebeec76c3b9723201e9d01f78"
integrity sha512-FlVvVFA1TX6l3lp8VjDnYYq7R1nyW6x3svAt4nDgrWQ9SBaSh9CnbwgSUTasgfNfOG5HlM1ehugCvM+hjo56LA==
-acorn@^8.5.0:
- version "8.7.1"
- resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
- integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
-
-acorn@^8.7.1:
+acorn@^8.5.0, acorn@^8.7.1:
version "8.8.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8"
integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w==
@@ -1292,18 +1282,7 @@ braces@~3.0.2:
dependencies:
fill-range "^7.0.1"
-browserslist@^4.14.5:
- version "4.17.1"
- resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.17.1.tgz#a98d104f54af441290b7d592626dd541fa642eb9"
- integrity sha512-aLD0ZMDSnF4lUt4ZDNgqi5BUn9BZ7YdQdI/cYlILrhdSSZJLU9aNZoD5/NBmM4SK34APB2e83MOsRt1EnkuyaQ==
- dependencies:
- caniuse-lite "^1.0.30001259"
- electron-to-chromium "^1.3.846"
- escalade "^3.1.1"
- nanocolors "^0.1.5"
- node-releases "^1.1.76"
-
-browserslist@^4.20.2, browserslist@^4.21.3:
+browserslist@^4.14.5, browserslist@^4.20.2, browserslist@^4.21.3:
version "4.21.3"
resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.3.tgz#5df277694eb3c48bc5c4b05af3e8b7e09c5a6d1a"
integrity sha512-898rgRXLAyRkM1GryrrBHGkqA5hlpkV5MhtZwg9QXeiyLUYs2k00Un05aX5l2/yJIOObYKOpS2JNo8nJDE7fWQ==
@@ -1326,11 +1305,6 @@ call-bind@^1.0.0:
function-bind "^1.1.1"
get-intrinsic "^1.0.2"
-caniuse-lite@^1.0.30001259:
- version "1.0.30001261"
- resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001261.tgz#96d89813c076ea061209a4e040d8dcf0c66a1d01"
- integrity sha512-vM8D9Uvp7bHIN0fZ2KQ4wnmYFpJo/Etb4Vwsuc+ka0tfGDHvOPrFm6S/7CCNLSOkAUjenT2HnUPESdOIL91FaA==
-
caniuse-lite@^1.0.30001370:
version "1.0.30001393"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001393.tgz#1aa161e24fe6af2e2ccda000fc2b94be0b0db356"
@@ -1455,11 +1429,6 @@ define-properties@^1.1.3:
dependencies:
object-keys "^1.0.12"
-electron-to-chromium@^1.3.846:
- version "1.3.853"
- resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.853.tgz#f3ed1d31f092cb3a17af188bca6c6a3ec91c3e82"
- integrity sha512-W4U8n+U8I5/SUaFcqZgbKRmYZwcyEIQVBDf+j5QQK6xChjXnQD+wj248eGR9X4u+dDmDR//8vIfbu4PrdBBIoQ==
-
electron-to-chromium@^1.4.202:
version "1.4.247"
resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.247.tgz#cc93859bc5fc521f611656e65ce17eae26a0fd3d"
@@ -1621,12 +1590,7 @@ globals@^11.1.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.4:
- version "4.2.8"
- resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
- integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
-
-graceful-fs@^4.2.9:
+graceful-fs@^4.1.2, graceful-fs@^4.2.4, graceful-fs@^4.2.9:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
@@ -1748,17 +1712,10 @@ json-schema-traverse@^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:
- version "2.2.0"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3"
- integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==
- dependencies:
- minimist "^1.2.5"
-
-json5@^2.2.1:
- version "2.2.1"
- resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c"
- integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==
+json5@^2.1.2, json5@^2.2.1:
+ 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"
@@ -1776,9 +1733,9 @@ loader-runner@^4.2.0:
integrity sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==
loader-utils@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-2.0.0.tgz#e4cace5b816d425a166b5f097e10cd12b36064b0"
- integrity sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==
+ 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"
@@ -1820,31 +1777,16 @@ mime-types@^2.1.27:
dependencies:
mime-db "1.49.0"
-minimist@^1.2.5:
- version "1.2.6"
- resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
- integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
-
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==
-nanocolors@^0.1.5:
- version "0.1.12"
- resolved "https://registry.yarnpkg.com/nanocolors/-/nanocolors-0.1.12.tgz#8577482c58cbd7b5bb1681db4cf48f11a87fd5f6"
- integrity sha512-2nMHqg1x5PU+unxX7PGY7AuYxl2qDx7PSrTRjizr8sxdd3l/3hBuWWaki62qmtYm2U5i4Z5E7GbjlyDFhs9/EQ==
-
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@^1.1.76:
- version "1.1.76"
- resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.76.tgz#df245b062b0cafbd5282ab6792f7dccc2d97f36e"
- integrity sha512-9/IECtNr8dXNmPWmFXepT0/7o5eolGesHUa3mtr0KlgnCvnZxwh2qensKL42JJY2vQKC3nIBXetFAqR+PW1CmA==
-
node-releases@^2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.6.tgz#8a7088c63a55e493845683ebf3c828d8c51c5503"
@@ -2270,10 +2212,10 @@ webpack-sources@^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.74:
- version "5.74.0"
- resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
- integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
+webpack@^5.76:
+ version "5.76.0"
+ resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
+ integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
dependencies:
"@types/eslint-scope" "^3.7.3"
"@types/estree" "^0.0.51"
diff --git a/package.json b/package.json
index 25fcfc920..929473958 100644
--- a/package.json
+++ b/package.json
@@ -1,42 +1,47 @@
{
"name": "woocommerce-paypal-payments",
- "version": "2.0.1",
+ "version": "2.4.0",
"description": "WooCommerce PayPal Payments",
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
"license": "GPL-2.0",
"author": "WooCommerce",
"scripts": {
"postinstall": "run-s install:modules:* && run-s build:modules",
-
+ "install:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn install",
+ "install:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn install",
"install:modules:ppcp-button": "cd modules/ppcp-button && yarn install",
+ "install:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn install",
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install",
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install",
- "install:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn install",
"install:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn install",
+ "install:modules:ppcp-subscription": "cd modules/ppcp-subscription && yarn install",
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && 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",
+ "build:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run build",
"build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build",
+ "build:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn run build",
"build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build",
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build",
- "build:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn run build",
"build:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run build",
+ "build:modules:ppcp-subscription": "cd modules/ppcp-subscription && yarn run build",
"build:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && 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:*",
-
+ "watch:modules:ppcp-applepay": "cd modules/ppcp-applepay && yarn run watch",
+ "watch:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run watch",
"watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch",
+ "watch:modules:ppcp-googlepay": "cd modules/ppcp-googlepay && yarn run watch",
"watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch",
"watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch",
- "watch:modules:ppcp-vaulting": "cd modules/ppcp-vaulting && yarn run watch",
"watch:modules:ppcp-order-tracking": "cd modules/ppcp-order-tracking && yarn run watch",
+ "watch:modules:ppcp-subscription": "cd modules/ppcp-subscription && yarn run watch",
"watch:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && 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:*",
-
"ddev:setup": "ddev start && ddev orchestrate",
"ddev:start": "ddev start",
"ddev:stop": "ddev stop",
@@ -46,8 +51,10 @@
"ddev:watch-js": "ddev yarn watch:modules",
"ddev:composer-update": "ddev composer update && ddev composer update --lock",
"ddev:unit-tests": "ddev exec phpunit",
- "ddev:e2e-tests": "cp -n .env.e2e.example .env.e2e && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist",
- "ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests",
+ "ddev:e2e-tests": "(cp -n .env.e2e.example .env.e2e || true) && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist",
+ "ddev:pw-install": "ddev yarn playwright install --with-deps",
+ "ddev:pw-tests": "ddev yarn playwright test",
+ "ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests && yarn run ddev:pw-tests",
"ddev:lint": "yarn ddev:phpcs && yarn ddev:psalm",
"ddev:phpcs": "ddev exec phpcs --parallel=8 -s",
"ddev:psalm": "ddev exec psalm --show-info=false --threads=8 --diff",
@@ -55,19 +62,21 @@
"ddev:xdebug-on": "ddev xdebug",
"ddev:xdebug-off": "ddev xdebug",
"ddev:build-package": "ddev yarn build",
-
+ "pw-install": "yarn playwright install --with-deps",
+ "pw-tests": "yarn playwright test",
+ "pw-tests-headed": "yarn playwright test --headed",
"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/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store README.md .gitattributes .gitignore .travis.yml composer.json composer.lock package.json package-lock.json patchwork.json yarn.lock phpunit.xml.dist .phpunit.result.cache phpcs.xml.dist modules/ppcp-button/.babelrc modules/ppcp-button/package.json modules/ppcp-button/webpack.config.js modules/ppcp-button/yarn.lock vendor/\\*/.idea/\\* vendor/\\*/.gitignore vendor/\\*/.gitattributes vendor/\\*/.travis.yml"
+ "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\\"
},
"config": {
"wp_org_slug": "woocommerce-paypal-payments"
},
"dependencies": {
+ "dotenv": "^16.0.3",
"npm-run-all": "^4.1.5"
- },
- "devDependencies": {}
+ }
}
diff --git a/phpcs.xml.dist b/phpcs.xml.dist
index d12770d98..8e32114f5 100644
--- a/phpcs.xml.dist
+++ b/phpcs.xml.dist
@@ -42,6 +42,7 @@
+ api
src
modules
woocommerce-paypal-payments.php
@@ -52,4 +53,5 @@
*/vendor/*
./tests/*
*/resources/*
+ *.asset.php
diff --git a/psalm-baseline.xml b/psalm-baseline.xml
index bddf0058e..e96206781 100644
--- a/psalm-baseline.xml
+++ b/psalm-baseline.xml
@@ -259,6 +259,16 @@
DAY_IN_SECONDS
+
+
+ DAY_IN_SECONDS
+
+
+
+
+ DAY_IN_SECONDS
+
+
realpath( __FILE__ )
@@ -685,9 +695,11 @@
listen_for_merchant_id
listen_for_vaulting_enabled
-
+
wp_unslash( $_GET['merchantId'] )
wp_unslash( $_GET['merchantIdInPayPal'] )
+ wp_unslash( $_GET['ppcpToken'] )
+ wp_unslash( $_GET['ppcpRetry'] )
wp_unslash( $_POST['ppcp-nonce'] )
diff --git a/psalm.xml.dist b/psalm.xml.dist
index fffe2fa98..092361998 100644
--- a/psalm.xml.dist
+++ b/psalm.xml.dist
@@ -23,14 +23,19 @@
errorBaseline="psalm-baseline.xml"
>
+
+
+
+
+
diff --git a/readme.txt b/readme.txt
index 9142ca0d6..7df738d23 100644
--- a/readme.txt
+++ b/readme.txt
@@ -1,55 +1,146 @@
=== WooCommerce PayPal Payments ===
Contributors: woocommerce, automattic, inpsyde
-Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout
+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.1
+Tested up to: 6.3
Requires PHP: 7.2
-Stable tag: 2.0.1
+Stable tag: 2.4.0
License: GPLv2
License URI: http://www.gnu.org/licenses/gpl-2.0.html
-PayPal's latest payments processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets and bank accounts.
+PayPal's latest payment processing solution. Accept PayPal, Pay Later, credit/debit cards, alternative digital wallets and bank accounts.
== Description ==
-PayPal's latest, most complete payment processing solution. Accept PayPal exclusives, credit/debit cards and local payment methods. Turn on only PayPal options or process a full suite of payment methods. Enable global transactions with extensive currency and country coverage.
-Built and supported by [WooCommerce](https://woocommerce.com) and [PayPal](https://paypal.com).
+One checkout solution. Many ways to pay. PayPal’s all-in-one solution allows you to offer PayPal, Venmo (US), Pay Later at no additional cost, credit and debit cards, and country-specific payment options.
+= The all-in-one checkout solution* =
+**WooCommerce PayPal Payments** lets you offer PayPal, Venmo (US only), Pay Later options, credit cards and more — all designed to help you maximize conversion.
-= Give your customers their preferred ways to pay with one checkout solution =
+= PayPal =
+Help increase conversion by automatically offering PayPal buttons on product pages and at checkout. **Consumers are nearly three times more likely to purchase when you offer PayPal.¹**
-Streamline your business with one simple, powerful solution.
+= PayPal Pay Later =
+Let customers pay over time while you get paid up front — at no additional cost. **PayPal Pay Later options are boosting merchant conversion rates and increasing cart sizes by 39%.²**
-With the latest PayPal extension, your customers can pay with PayPal, Pay Later options, credit & debit cards, and country-specific, local payment methods on any device — all with one seamless checkout experience.
+= Help increase conversions with Pay Later messaging =
+PayPal Pay Later allows customers to pay over time while you get paid up front — at no additional cost to your business. And since 81% of younger consumers who use buy now, pay later decide which payment method to use before they get to checkout⁴, showing Pay Later messaging on your site lets customers know they have flexible options to pay over time while they are browsing — helping convert your browsers into buyers.
-= Reach more customers in 200+ markets worldwide =
+[Pay Later features are available in these countries.](https://developer.paypal.com/docs/checkout/pay-later/us/) Customers automatically see the most relevant Pay Later offering.
-Expand your business by connecting with over 370+ million active PayPal accounts around the globe. With PayPal, you can:
+= Venmo (US only) =
+Reach Venmo shoppers while allowing customers to share purchases with friends. **Venmo users spend 2.2 times more annually on online purchases compared to other online buyers.³**
-- Sell in 200+ markets and accept 100+ currencies
-- Identify customer locations and offer country-specific, local payment methods
+= Country-specific payment methods =
+Offer locally recognized payment methods to help build trust and reach international customers, including Payment Upon Invoice (PUI), a local payment option available only for purchases completed in Germany.
-= Offer subscription payments to help drive repeat business =
+= Add credit and debit card processing capabilities =
+**PayPal Payments** provides two different card processing options:
+
+- **Standard Card Processing**: Card transactions are managed by PayPal in a prebuilt user experience, which simplifies your compliance requirements. Suitable for all business and personal seller accounts.
+
+- **Advanced Card Processing**: Get everything that comes with PayPal’s standard integration, along with the ability to customize the look, feel, and placement of your debit and credit card payment fields. You can also use fraud protection tools to set up your own risk tolerance filters.
+
+= Offer Subscription payments to help drive repeat business =
Create stable, predictable income by offering subscription plans.
+WooCommerce PayPal Payments is compatible with [WooCommerce Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/).
-With the vaulting feature on PayPal, you can offer flexible plans with fixed or quantity pricing, set billing cycles for the time period you want, and offer subscriptions with discounted trial periods.
+[PayPal Subscriptions](https://www.paypal.com/webapps/mpp/subscription-payments) allow business and casual sellers to accept reliable recurring payments on a fixed billing schedule (buyers may require a PayPal account).
-It’s easy for shoppers, simple for you, and great for your business–with no monthly or setup fees.
+With the [Vaulting](https://www.paypal.com/us/brc/article/securely-store-payments) feature ([business account approval required](https://woocommerce.com/document/woocommerce-paypal-payments/#get-vaulting-approval)), you can offer flexible plans with fixed or quantity pricing, set billing cycles for the time period you want, and offer all kinds of subscriptions with prorated payments or discounted trial periods.
-PayPal is also compatible with [WooCommerce Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/).
+It’s easy for shoppers, simple for you, and great for your business – with no monthly or setup fees.
+
+= Activate PayPal =
+Are you new to PayPal? [Learn how to add it to your store.](https://woocommerce.com/document/woocommerce-paypal-payments/)
+Need to update your existing PayPal integration? [Learn how to upgrade your integration.](https://woocommerce.com/document/woocommerce-paypal-payments/paypal-payments-upgrade-guide/)
+
+[Watch the video on how to activate PayPal for your WooCommerce store](https://woocommerce.com/document/woocommerce-paypal-payments/#complete-setup-video)
+
+= More reasons it pays to PayPal =
+- **Instant access to funds**: Access card payments, send money or make a payment from your bank account. You’re credited immediately while the payment is processing. Funds settle instantly into your PayPal business account.
+
+- **Fraud Detection**: Save time and money by letting PayPal help you handle the risk of fraudulent transactions with our fraud, chargeback, and Seller Protection capabilities (on eligible transactions†). Our AI technology works hard to monitor all of your transactions — so you can focus on what matters most. †Available on eligible purchases. Limits apply.
+
+- **Global compliance**: PayPal payment solutions help you meet your global compliance standards, such as PCI and PSD2, bringing international markets within reach for your business. Our platform has built-in compliance with two-factor authentication provided by cards (3D Secure). We include automatic updates as security protocols change.
+
+---
+
+**Legal Disclosures:**
+
+\*For Australian users, the PayPal service is provided by PayPal Australia Pty Limited AFSL 304962. Any information provided is general only and does not take into account your objectives, financial situation or needs. Please read and consider the CFSGPDS (paypal.com.au) before acquiring or using the service. See website for TMD.
+
+1. An online study commissioned by PayPal and conducted by Netfluential in November 2020, involving 1,000 US online shoppers ages 18-39.
+2. PayPal Q2 Earnings-2021.
+3. Edison Trends commissioned by PayPal, April 2020 to March 2021. Edison Trends conducted a behavioural panel of email receipts from 306,939 US consumers and 3.4+ M purchases at a vertical level between Pay with Venmo and Non-Venmo users during a 12-month period.
+4. TRC online survey commissioned by PayPal in April 2021 involving 5,000 consumers ages 18+ across US, UK, DE, FR, AU (among Millennial & Gen Z BNPL users (ages 18-40), US (n=222), UK (n=269), DE (n=275), AU (n=344), FR (n=150).
+5. Morning Consult – The 15 Most Trusted Brands Globally. March 2021. Morning Consult surveyed over 330,000 consumers across 10 international markets to provide a global view on the current state of consumer trust across brands.
+
+== Frequently Asked Questions ==
+
+= Where can I find the WooCommerce PayPal Payments documentation and setup guide? =
+
+For help setting up and configuring WooCommerce PayPal Payments, please refer to the documentation. [Learn how to add PayPal to your store.](https://woocommerce.com/document/woocommerce-paypal-payments/)
+Need to update your existing PayPal integration? [Learn how to upgrade your integration.](https://woocommerce.com/document/woocommerce-paypal-payments/paypal-payments-upgrade-guide/)
+
+= Where can I get help for WooCommerce PayPal Payments? =
+
+For questions regarding the plugin setup, we recommend reviewing our [documentation](https://woocommerce.com/document/woocommerce-paypal-payments/) and the [troubleshooting guide](https://woocommerce.com/document/woocommerce-paypal-payments/#troubleshooting) if you encounter any issues.
+If the question or problem persists after reviewing the documentation, kindly create a new thread in the [support forums](https://wordpress.org/support/plugin/woocommerce-paypal-payments/#new-topic-0) or open a support ticket via [our helpdesk](https://woocommerce.com/document/woocommerce-paypal-payments/#get-help).
+
+= WooCommerce PayPal Payments suddenly no longer works – what do I do? =
+
+Start by diagnosing the issue using our helpful [troubleshooting guide](https://woocommerce.com/document/woocommerce-paypal-payments/#troubleshooting).
+If you notice an error or any other issue after updating a plugin or the theme, there could be a potential compatibility issue between it and WooCommerce PayPal Payments.
+In such cases, we recommend running a [conflict test](https://github.com/woocommerce/woocommerce-paypal-payments/wiki/Advanced-Troubleshooting) using [Health Check](https://github.com/woocommerce/woocommerce-paypal-payments/wiki/Advanced-Troubleshooting#test-on-staging) (which allows you to disable themes and plugins without affecting your visitors) or troubleshooting the issue using a [staging site](https://github.com/woocommerce/woocommerce-paypal-payments/wiki/Advanced-Troubleshooting#test-on-staging).
+
+If you can't solve the problem yourself, don't worry! Please open a support ticket via [our helpdesk](https://woocommerce.com/document/woocommerce-paypal-payments/#get-help).
+
+= Where can I report bugs? =
+
+Please report confirmed bugs on the [WooCommerce PayPal Payments GitHub repository](https://github.com/woocommerce/woocommerce-paypal-payments/issues). You can also notify us via our [support forum](https://wordpress.org/support/plugin/woocommerce-paypal-payments/) if you are not sure if the problem you encounter is the result of a bug in the plugin. When reporting a bug as a [GitHub issue](https://github.com/woocommerce/woocommerce-paypal-payments/issues) directly, be sure to search the repository to confirm that the bug has not already been reported.
+
+= Where can I request new features or compatibility with other extensions? =
+
+Request new and vote on existing suggestions for features and extensions on our official [feature request board](https://woocommerce.com/feature-requests/woocommerce-paypal-payments/). Our product teams regularly review requests and consider them valuable for product planning. Our support team is also always interested in learning about your ideas to improve the extension.
+
+= Does WooCommerce PayPal Payments support subscriptions? =
+
+Yes, WooCommerce PayPal Payments can be used with the official [WooCommerce Subscriptions](https://woocommerce.com/products/woocommerce-subscriptions/) plugin.
+Accept recurring payments on a fixed billing schedule with [PayPal Subscriptions](https://www.paypal.com/webapps/mpp/subscription-payments) or save your buyers' payment methods for future payments using the Vaulting feature ([business account approval required](https://woocommerce.com/document/woocommerce-paypal-payments/#get-vaulting-approval)).
+View the [Subscriptions FAQ](https://woocommerce.com/document/woocommerce-paypal-payments/#subscriptions-faq) for more details.
+
+= Does WooCommerce PayPal Payments support WooCommerce Blocks? =
+
+Yes, WooCommerce PayPal Payments offers experimental support for the new WooCommerce Cart & Checkout Blocks. The team continues to work on providing a seamless integration with the new WooCommerce Blocks.
+View the [Blocks FAQ](https://woocommerce.com/document/woocommerce-paypal-payments/#faq-does-paypal-payments-support-blocks) for more details.
+
+= Are there any other new features in development? =
+
+Yes, the WooCommerce PayPal Payments development team is constantly expanding the functionality of the WooCommerce PayPal Payments plugin with the latest features that PayPal offers.
+View the [Testing features previews FAQ](https://github.com/woocommerce/woocommerce-paypal-payments/wiki/Testing-features-previews) for more details.
+
+= WooCommerce PayPal Payments is awesome! Can I contribute? =
+
+Yes, you can! Contributions are always appreciated. Join in on our [GitHub repository](https://github.com/woocommerce/woocommerce-paypal-payments/issues) to stay up-to-date with the latest developments.
+If you like the plugin, please also consider leaving a review [here](https://wordpress.org/support/plugin/woocommerce-paypal-payments/reviews/#new-post) or [here](https://woocommerce.com/de-de/products/woocommerce-paypal-payments/#reviews) to share your valuable feedback with other users as it is much appreciated.
+
+= My question is not listed here. Where can I find more answers? =
+
+Check out the [Frequently Asked Questions](https://woocommerce.com/document/woocommerce-paypal-payments/#faqs) for more or contact the support team.
== Installation ==
= Requirements =
-To install WooCommerce PayPal Payments, you need:
+To install and configure WooCommerce PayPal Payments, you will need:
* WordPress Version 5.3 or newer (installed)
* WooCommerce Version 3.9 or newer (installed and activated)
* PHP Version 7.2 or newer
-* PayPal business account
+* PayPal business **or** personal account
-= Instructions =
+= Installation instructions =
1. Log in to WordPress admin.
2. Go to **Plugins > Add New**.
@@ -63,12 +154,19 @@ Follow the steps below to connect the plugin to your PayPal account:
1. After you have activated the WooCommerce PayPal Payments plugin, go to **WooCommerce > Settings**.
2. Click the **Payments** tab.
-3. The Payment methods list will include two PayPal options. Click on **PayPal** (not PayPal Standard).
-4. Click the **PayPal Checkout** tab.
-5. Click on the **Connect to PayPal** button.
-6. Sign in to your PayPal account. If you do not have a PayPal account yet, sign up for a new PayPal business account.
-7. After you have successfully connected your PayPal account, click on the **Enable the PayPal Gateway** checkbox to enable PayPal.
-8. Click **Save changes**.
+3. The Payment methods list may include two PayPal options. Click on **PayPal** (not PayPal Standard).
+4. Click on the **Activate PayPal** button.
+5. Sign in to your PayPal account. If you do not have a PayPal account yet, sign up for a new PayPal business or personal account.
+6. After you have successfully connected your PayPal account, click on the **Standard Payments** tab and check the **Enable Paypal features for your store** checkbox to enable PayPal.
+7. Click **Save changes**.
+
+Complete onboarding instructions can be found in the [documentation here](https://woocommerce.com/document/woocommerce-paypal-payments/#connect-paypal-account).
+
+= Updating =
+
+Automatic updates should work generally smoothly, but we still recommend you back up your site.
+
+If you encounter issues with the PayPal buttons not appearing after an update, purge your website cache. [Caching](https://woocommerce.com/document/configuring-caching-plugins/) or [JavaScript minification](https://woocommerce.com/document/configuring-caching-plugins/#w3-total-cache-minify-settings) can impact PayPal Payments and should be [disabled for the PayPal scripts](https://woocommerce.com/document/woocommerce-paypal-payments/#faq-paypal-smart-buttons-not-loading) or payment-related pages.
== Screenshots ==
@@ -79,9 +177,197 @@ Follow the steps below to connect the plugin to your PayPal account:
5. Click "Connect to PayPal" to link your site to your PayPal account.
6. Main settings screen.
+
== Changelog ==
-= 2.0.1 =
+= 2.4.0 - 2023-10-31 =
+* Fix - Mini-Cart Bug cause of wrong DOM-Structure in v2.3.1 #1735
+* Fix - ACDC disappearing after plugin updates #1751
+* Fix - Subscription module hooks #1748
+* Fix - Ensure PayPal Subscriptions API products description is 1-127 characters #1738
+* Fix - Add validation on the Plan Name field to not accept a blank value #1754
+* Enhancement - Improve Pay Later messages and add Shop, Home locations #1770
+* Enhancement - Use api-m PayPal API URLs #1740
+* Enhancement - Google Pay Settings improvements #1719
+* Enhancement - Apple Pay transaction improvements #1767
+* Enhancement - Change default ACDC title #1750
+* Enhancement - Cart simulation improvements #1753
+* Enhancement - Billing schedule fields not greyed out when PayPal Subscriptions product is connected #1755
+* Enhancement - Check validation errors when submitting in block #1528
+* Enhancement - Improve handling of server error when submitting block #1785
+* Enhancement - Extend Apple Pay country eligibility #1781
+* Enhancement - Apple Pay validation notice improvements #1783
+* Enhancement - Apple Pay payment process issues #1789
+* Enhancement - Disable the tracking if payment is not captured #1780
+* Enhancement - Place order button remains - Could not retrieve order #1786
+* Enhancement - Google Pay for variable product greyed out but clickable #1788
+* Enhancement - Merchant credential validation & remove PAYEE object #1795
+
+= 2.3.1 - 2023-09-26 =
+* Fix - Fatal error when saving product while WooCommerce Subscriptions plugin is not active #1731
+* Fix - Validate tracking data only for add/update Package Tracking #1729
+* Fix - Disable Package Tracking for order if transaction ID doesn't exist #1727
+
+= 2.3.0 - 2023-09-26 =
+* Fix - Plus sign in PayPal account email address gets converted to space #771
+* Fix - Payment method dropdown option label on edit order screen for ppcp-gateway option displaying wrong name #1639
+* Fix - WooCommerce Bookings products don't remain in Cart as a guest when PayPal button active on single product #1645
+* Fix - Since version > 2.2.0 the PayPal Checkout button on single product pages does not redirect anymore #1664
+* Fix - PayPal fee and PayPal Payout do not change on order if we do partial refund #1578
+* Fix - Order does not contain intent error when using ACDC payment token while buyer is not present #1506
+* Fix - Error when product description linked with a PayPal subscription exceeds 127 characters #1700
+* Fix - $_POST uses the wrong key to hold the shipping method #1652
+* Fix - WC Payment Token created multiple times when webhook is received #1663
+* Fix - Subtotal mismatch line name shows on Account settings page when merchant is disconnected #1702
+* Fix - Warning prevents payments on Pay for Order page when debugging is enabled #1703
+* Fix - paypal-overlay-uid_ blocks page after closing PayPal popup on Pay for Order page | Terms checkbox validation fails on Pay for Order page #1704
+* Enhancement - Add support for HPOS for tracking module #1676
+* Enhancement - Billing agreements endpoint called too frequently for Reference Transactions check #1646
+* Enhancement - Do not declare subscription support for PayPal when only ACDC vaulting #1669
+* Enhancement - Apply Capture On Status Change only when order contains PayPal payment method #1595
+* Enhancement - Do not use transient expiration longer than one month to support memcached #1448
+* Enhancement - By disconnecting or disabling the plugin the connection should clear the Onboarding links from cache #1668
+* Enhancement - Upgrade tracking integration #1562
+* Enhancement - Include url & image_url in create order call #1649
+* Enhancement - Add compat layer for Yith tracking #1656
+* Enhancement - Improve invalid currency backend notice (1926) #1588
+* Enhancement - Hide ACDC footer frame via CSS to avoid empty space #1613
+* Enhancement - Compatibility with WooCommerce Product Add-Ons plugin #1586
+* Enhancement - Remove "no shipment" message after adding tracking #1674
+* Enhancement - Improve error & success validation messages #1675
+* Enhancement - Compatibility with third-party "Product Add-Ons" plugins #1601
+* Enhancement - PayPal logo flashes when switching between tabs #1345
+* Enhancement - Include url & image_url in create order call #1649
+* Enhancement - Include item_url & image_url to tracking call #1712
+* Enhancement - Update strings for tracking metabox #1714
+* Enhancement - Validate email address API credentials field #1691
+* Enhancement - Set payment method title for order edit page only if our gateway #1661
+* Enhancement - Fix missing Pay Later messages in cart + refactoring #1683
+* Enhancement - Product page PP button keep loading popup - "wc_add_to_cart_params is not defined" error in WooCommerce #1655
+* Enhancement - Remove PayPal Subscriptions API feature flag #1690
+* Enhancement - Don't send image_url when it is empty #1678
+* Enhancement - Subscription support depending on Vaulting setting instead of subscription mode setting #1697
+* Enhancement - Wrong PayPal subscription id on vaulted subscriptions #1699
+* Enhancement - Remove payment vaulted checker functionality (2030) #1711
+* Feature preview - Apple Pay integration #1514
+* Feature preview - Google Pay integration #1654
+
+= 2.2.2 - 2023-08-29 =
+* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529
+* Enhancement - HPOS compatibility issues #1594
+* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607
+
+= 2.2.1 - 2023-08-24 =
+* Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536
+* Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565
+* Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533
+* Fix - Send payee_preferred correctly for instant payments #1489
+* Fix - Auto-disabled ACDC vaulting after updating to 2.1.0 #1490
+* Fix - PayPal Payments serializing formData of array inputs #1501
+* Fix - Buttons not working on single product page for WooCommerce Bookings product #1478
+* Enhancement - PayPal Later message price amount doesn't update dynamically #1585
+* Enhancement - Improve WC order creation in webhook #1530
+* Enhancement - Refactor hosted fields for early card detection #1554
+* Enhancement - Pay Later button and message get hidden when product/cart/checkout value is outside of range #1511
+* Enhancement - Add link to manual credential docs #1430
+* Enhancement - Validate Merchant ID field format when saving settings #1509
+* Enhancement - Include soft descriptor for card's activity #1427
+* Enhancement - Update Pay Later amount on the cart page and checkout when total changes #1441
+* Enhancement - Log Subscription Mode configuration in system report #1507
+* Enhancement - HPOS compatibility issues #1555
+* Feature preview - PayPal Subscriptions API fixes and improvements #1443
+
+= 2.2.0 - 2023-07-17 =
+* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
+* Fix - Use order currency instead of shop currency on order-pay page #1363
+* Fix - Do not show broken card button gateway when no checkout location #1358
+* Fix - Smart buttons not greyed out/removed on single product when deselecting product variation #1469
+* Fix - Type error with advanced columns pro #1367
+* Fix - Undefined array key 0 when checking $retry_errors in process_payment method #1375
+* Fix - Advanced Card Processing gateway becomes invisible post-plugin update unless admin pages are accessed once #1432
+* Fix - Incompatibility with WooCommerce One Page Checkout (or similar use cases) in Version 2.1.0 #1473
+* Fix - Prevent Repetitive Token Migration and Database Overload After 2.1.0 Update #1461
+* Fix - Onboarding from connection page with CSRF parameter manipulates email and merchant id fields #1502
+* Fix - Do not complete non-checkout button orders via webhooks #1513
+* Enhancement - Remove feature flag requirement for express cart/checkout block integration #1483
+* Enhancement - Add notice when shop currency is unsupported #1433
+* Enhancement - Improve ACDC error message when empty fields #1360
+* Enhancement - Do not exclude free items #1362
+* Enhancement - Trigger WC checkout_error event #1384
+* Enhancement - Update wording in buttons previews #1408
+* Enhancement - Filter to conditionally block the PayPal buttons #1485
+* Enhancement - Display funding source on the admin order page #1450
+* Enhancement - Update system report plugin status for Vaulting #1471
+* Enhancement - Revert Elementor Pro Checkout hook compatibility #1482
+
+= 2.1.0 - 2023-06-13 =
+* Fix - Performance issue #1182
+* Fix - Webhooks not registered when onboarding with manual credentials #1223
+* Fix - Boolean false type sent as empty value when setting cache #1313
+* Fix - Ajax vulnerabilities #1411
+* Enhancement - Save and display vaulted payment methods in WC Payment Token API #1059
+* Enhancement - Cache webhook verification results #1379
+* Enhancement - Refresh checkout totals after validation if needed #1294
+* Enhancement - Improve Divi and Elementor Pro compatibility #1254
+* Enhancement - Add MX and JP to ACDC #1415
+* Enhancement - Add fraudnet script to SGO filter #1366
+* Feature preview - Add express cart/checkout block #1346
+* Feature preview - Integrate PayPal Subscriptions API #1217
+
+= 2.0.5 - 2023-05-31 =
+* Fix - Potential invalidation of merchant credentials #1339
+
+= 2.0.4 - 2023-04-03 =
+* Fix - Allow Pay Later in mini-cart #1221
+* Fix - Duplicated auth error when credentials become wrong #1229
+* Fix - Webhook issues when switching sandbox, and delete all webhooks when unsubscribing #1239
+* Fix - High volume of traffic from merchant-integrations endpoint #1273
+* Fix - Add content type json to all fetch ajax endpoints #1275
+* Enhancement - Remove shortcodes from description #1226
+* Enhancement - Handle price suffix with price for product button check #1234
+* Enhancement - Show funding source as payment method #1220
+* Enhancement - Change "Enabled" to "Available" in status text #1237
+* Enhancement - Programmatically capturing/voiding authorized payments #590
+
+= 2.0.3 - 2023-03-14 =
+* Fix - `DEVICE_DATA_NOT_AVAILABLE` error message when FraudNet is enabled #1177
+* Fix - Redirect to connection tab after manual credentials input #1201
+* Fix - Asking for address fields in checkout when not using them #1089
+* Fix - Validate before free trial #1170
+* Fix - Validate new user creation #1131
+* Fix - After Updating to 2.0.2, Site Health reports REST API error #1195
+* Fix - Do not send buyer-country for previews in live mode to avoid error #1186
+* Fix - PPEC compatibility layer does not take over subscriptions #1193
+* Fix - Checkout conflict with "All products for subscriptions" plugin #629
+* Fix - Pay Later on order pay page #1214
+* Fix - High volume of traffic from merchant-integrations endpoint #1241
+* Enhancement - Save checkout form before free trial redirect #1135
+* Enhancement - Add filter for controlling the ditching of items/breakdown #1146
+* Enhancement - Add patch order data filter #1147
+* Enhancement - Add filter for disabling fees on wc order admin pages #1153
+* Enhancement - Use wp_loaded for fraudnet loading to avoid warnings #1172
+* Enhancement - reCaptcha for WooCommerce support #1093
+* Enhancement - Make it possible to hide missing funding resource Trustly #1155
+* Enhancement - Add white color option #1167
+* Enhancement - Checkout validation for other fields #861
+* Enhancement - Mention PUI only for German shops and add line breaks #1169
+* Enhancement - Add filter to fallback tracking_data['carrier'] #1188
+* Enhancement - Error notices in checkout do not update / or are shown twice #1168
+* Enhancement - capture authorized payment by changing order status (or programmatically) #587
+
+= 2.0.2 - 2023-01-31 =
+* Fix - Do not call PayPal get order by ID if it does not exist #1029
+* Fix - Type check error conflict with German Market #1056
+* Fix - Backend Storage for the PayPalRequestIdRepository does not scale #983
+* Fix - Ensure WC()->payment_gateways is not null #1128
+* Enhancement - Remove plugin data after uninstalling #1075
+* Enhancement - Add FraudNet to all payments #1040
+* Enhancement - Update "Standard Payments" tab settings #1065
+* Enhancement - Update PHP 7.2 requirement in all relevant files #1084
+* Enhancement - When PUI is enabled FraudNet should be also enabled #1129
+* Enhancement - Add PayPal-Request-Id if payment source exist #1132
+
+= 2.0.1 - 2022-12-13 =
* Fix - Error while syncing tracking data to PayPal -> Sync GZD Tracking #1020
* Fix - Fix product price retrieval for variable product buttons #1000
* Fix - All tabs hidden on OXXO tab visit #1048
@@ -94,12 +380,12 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Execute WC validation only for smart buttons in checkout #1074
* Enhancement - Param types removed in closure to avoid third-party issues #1046
-= 2.0.0 =
-* Add - Option to separate JSSDK APM payment buttons into individual WooCommerce gateways #671
-* Add - OXXO APM (Alternative Payment Method) #684
+= 2.0.0 - 2022-11-21 =
+* Add - Option to separate JSSDK APM payment buttons into individual WooCommerce gateways #671
+* Add - OXXO APM (Alternative Payment Method) #684
* Add - Pay Later tab #961
* Add - Button preview in settings #929
-* Fix - Prevent Enter key submit for our non-standard button gateways #981
+* Fix - Prevent Enter key submit for our non-standard button gateways #981
* Fix - Pay Upon Invoice - Stock correction on failed orders #964
* Fix - Check that WC session exists before using it #846
* Fix - Compatibility with One Page Checkout Extension #356
@@ -108,11 +394,11 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - PHP 8.1 warning: Constant FILTER_SANITIZE_STRING is deprecated #867
* Enhancement - Execute server-side WC validation when clicking button #942
* Enhancement - Update order with order note if payment failed after billing agreement canceled at PayPal #886
-* Enhancement - Missing PUI refund functionality from WC order #937
+* Enhancement - Missing PUI refund functionality from WC order #937
* Enhancement - Hide Pay upon Invoice tab if not available for merchant #978
* Enhancement - Handle synced sub without upfront payment like free trial #936
* Enhancement - Isolate container and modularity deps #972
- **NOTE**: if you were extending/modifying the plugin using the modularity system,
+ **NOTE**: if you were extending/modifying the plugin using the modularity system,
you will need to add the `WooCommerce\PayPalCommerce\Vendor\` prefix for the container/modularity namespaces in your code,
that is `Psr\Container\ContainerInterface` becomes `WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface`,
and `Dhii\Modular\Module\ModuleInterface` becomes `WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface`.
@@ -120,7 +406,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - When Brand Name field is left empty, PUI purchase fails #916
* Enhancement - Improve styling when using separate buttons #996
-= 1.9.5 =
+= 1.9.5 - 2022-11-01 =
* Fix - Invalid tracking number in logs when adding tracking #903
* Fix - Tracking on Connection tab always enabled #900
* Fix - PUI payment instructions printed in the refund email #873
@@ -136,7 +422,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Onboard with PUI Checkbox automatically set when shop is set to Germany #876
* Enhancement - Update all plugin strings #946
-= 1.9.4 =
+= 1.9.4 - 2022-10-11 =
* Add - Create new connection tab #801
* Add - Functionality to choose subscription failure behavior #728
* Fix - Virtual-only orders always move order status to completed #868
@@ -148,7 +434,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - PUI-relevant webhook not subscribed to #842
* Enhancement - Remove WC logo during onboarding #881
-= 1.9.3 =
+= 1.9.3 - 2022-08-31 =
* Add - Tracking API #792
* Fix - Improve compatibility with Siteground Optimizer plugin #797
* Fix - Transaction ID in order not updated when manually capturing authorized payment from WC #766
@@ -163,7 +449,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - PUI add option for a phone number field next to the Birth Date field #742
* Enhancement - PUI gateway availability on pay for order page with unsupported currency #744
-= 1.9.2 =
+= 1.9.2 - 2022-08-09 =
* Fix - Do not allow birth date older than 100 years for PUI. #743
* Fix - Store the customer id for vaulted payment method in usermeta to not lose vaulted methods after the invoice prefix change. #698
* Fix - Capture Virtual-Only Orders setting did not auto-capture subscription renewal payments. #626
@@ -176,28 +462,28 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Improve Checkout Field Validation Message. #739
* Enhancement - Handle PAYER_ACTION_REQUIRED error. #759
-= 1.9.1 =
+= 1.9.1 - 2022-07-25 =
* Fix - ITEM_TOTAL_MISMATCH error when checking out with multiple products #721
* Fix - Unable to purchase a product with Credit card button in pay for order page #718
* Fix - Pay Later messaging only displayed when smart button is active on the same page #283
* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
* Fix - Placeholders and card type detection not working for PayPal Card Processing (260) #685
* Fix - PUI gateway is displayed with unsupported store currency #711
-* Fix - Wrong PUI locale sent causing error PAYMENT_SOURCE_CANNOT_BE_USED #741
+* Fix - Wrong PUI locale sent causing error PAYMENT_SOURCE_CANNOT_BE_USED #741
* Enhancement - Missing PayPal fee in WC order details for PUI purchase #714
* Enhancement - Skip loading of PUI js file on all pages where PUI gateway is not displayed #723
-* Enhancement - PUI feature capitalization not consistent #724
+* Enhancement - PUI feature capitalization not consistent #724
-= 1.9.0 =
+= 1.9.0 - 2022-07-04 =
* Add - New Feature - Pay Upon Invoice (Germany only) #608
* Fix - Order not approved: payment via vaulted PayPal account fails #677
* Fix - Cant' refund : "ERROR Refund failed: No country given for address." #639
-* Fix - Something went wrong error in Virtual products when using vaulted payment #673
+* Fix - Something went wrong error in Virtual products when using vaulted payment #673
* Fix - PayPal smart buttons are not displayed for product variations when parent product is set to out of stock #669
-* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
-* Fix - "Capture Virtual-Only Orders" intent sets virtual+downloadable product orders to "Processing" instead of "Completed" #665
-* Fix - Free trial period causing incorrerct disable-funding parameters with DCC disabled #661
-* Fix - Smart button not visible on single product page when product price is below 1 and decimal is "," #654
+* Fix - Pay Later messaging displayed for out of stock variable products or with no variation selected #667
+* Fix - "Capture Virtual-Only Orders" intent sets virtual+downloadable product orders to "Processing" instead of "Completed" #665
+* Fix - Free trial period causing incorrerct disable-funding parameters with DCC disabled #661
+* Fix - Smart button not visible on single product page when product price is below 1 and decimal is "," #654
* Fix - Checkout using an email address containing a + symbol results in a "[INVALID_REQUEST]" error #523
* Fix - Order details are sometimes empty in PayPal dashboard #689
* Fix - Incorrect TAX details on PayPal order overview #541
@@ -205,7 +491,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - DCC causes checkout continuation state after checkout validation error #695
* Enhancement - Improve checkout validation & order creation #513
-= 1.8.1 =
+= 1.8.1 - 2022-05-31 =
* Fix - Manual orders return an error for guest users when paying with PayPal Card Processing #530
* Fix - "No PayPal order found in the current WooCommerce session" error for guests on Pay for Order page #605
* Fix - Error on order discount by third-party plugins #548
@@ -221,7 +507,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Add Fraud Processor Response as an order note #616
* Enhancement - Add the Paypal Fee to the Meta Custom Field for export purposes #591
-= 1.8.0 =
+= 1.8.0 - 2022-05-03 =
* Add - Allow free trial subscriptions #580
* Fix - The Card Processing does not appear as an available payment method when manually creating an order #562
* Fix - Express buttons & Pay Later visible on variable Subscription products /w disabled vaulting #281
@@ -232,7 +518,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Onboarding errors improvements #558
* Enhancement - "Place order" button visible during gateway load time when DCC gateway is selected as the default #560
-= 1.7.1 =
+= 1.7.1 - 2022-04-06 =
* Fix - Hide smart buttons for free products and zero-sum carts #499
* Fix - Unprocessable Entity when paying with AMEX card #516
* Fix - Multisite path doubled in ajax URLs #528
@@ -245,7 +531,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Improve payment token checking for subscriptions #525
* Enhancement - Add Spain and Italy to messaging #497
-= 1.7.0 =
+= 1.7.0 - 2022-02-28 =
* Fix - DCC orders randomly failing #503
* Fix - Multi-currency broke #481
* Fix - Address information from PayPal shortcut flow not loaded #451
@@ -265,7 +551,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - PayPal Payments doesn't set transaction fee metadata #467
* Enhancement - Show PayPal fee information in order #489
-= 1.6.5 =
+= 1.6.5 - 2022-01-31 =
* Fix - Allow guest users to purchase subscription products from checkout page #422
* Fix - Transaction ID missing for renewal order #424
* Fix - Save your credit card checkbox should be removed in pay for order for subscriptions #420
@@ -279,7 +565,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Improve DCC error notice when not available #435
* Enhancement - Add View Logs link #416
-= 1.6.4 =
+= 1.6.4 - 2021-12-27 =
* Fix - Non admin user cannot save changes to the plugin settings #278
* Fix - Empty space in invoice prefix causes smart buttons to not load #390
* Fix - woocommerce_payment_complete action not triggered for payments completed via webhook #399
@@ -288,7 +574,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Enhancement - Allow formatted text for the Description field #407
* Enhancement - Remove filter to prevent On-Hold emails #411
-= 1.6.3 =
+= 1.6.3 - 2021-12-14 =
* Fix - Payments fail when using custom order numbers #354
* Fix - Do not display saved payments on PayPal buttons if vault option is disabled #358
* Fix - Double "Place Order" button #362
@@ -299,7 +585,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Error messages are not cleared even when checkout is re-attempted (DCC) #366
* Add - New additions for system report status #377
-= 1.6.2 =
+= 1.6.2 - 2021-11-22 =
* Fix - Order of WooCommerce checkout actions causing incompatibility with AvaTax address validation #335
* Fix - Can't checkout to certain countries with optional postcode #330
* Fix - Prevent subscription from being purchased when saving payment fails #308
@@ -316,14 +602,14 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - When paying for a subscription and vaulting fails, cart is cleared #367
* Fix - Fatal error when activating PayPal Checkout plugin #363
-= 1.6.1 =
+= 1.6.1 - 2021-10-12 =
* Fix - Handle authorization capture failures #312
* Fix - Handle denied payment authorization #302
* Fix - Handle failed authorizations when capturing order #303
* Fix - Transactions cannot be voided #293
* Fix - Fatal error: get_3ds_contingency() #310
-= 1.6.0 =
+= 1.6.0 - 2021-09-29 =
* Add - Webhook status. #246 #273
* Add - Show CC gateway in admin payments list. #236
* Add - Add 3d secure contingency settings. #230
@@ -337,14 +623,14 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - REFUND_CAPTURE_CURRENCY_MISMATCH on multicurrency sites. #225
* Fix - Can't checkout to certain countries with optional postcode. #224
-= 1.5.1 =
+= 1.5.1 - 2021-08-19 =
* Fix - Set 3DS contingencies to "SCA_WHEN_REQUIRED". #178
* Fix - Plugin conflict blocking line item details. #221
* Fix - WooCommerce orders left in "Pending Payment" after a decline. #222
* Fix - Do not send decimals when currency does not support them. #202
* Fix - Gateway can be activated without a connected PayPal account. #205
-= 1.5.0 =
+= 1.5.0 - 2021-08-09 =
* Add - Filter to modify plugin modules list. #203
* Add - Filters to move PayPal buttons and Pay Later messages. #203
* Fix - Remove redirection when enabling payment gateway with setup already done. #206
@@ -353,7 +639,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Hide mini cart height field when mini cart is disabled. #213
* Fix - Address possible error on frontend pages due to an empty gateway description. #214
-= 1.4.0 =
+= 1.4.0 - 2021-07-27 =
* Add - Venmo update #169
* Add - Pay Later Button –Global Expansion #182
* Add - Add Canada to advanced credit and debit card #180
@@ -369,7 +655,7 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Remove merchant-id query parameter in JSSDK #179
* Fix - Error on Plugin activation with Zettle POS Integration for WooCommerce #195
-= 1.3.2 =
+= 1.3.2 - 2021-06-08 =
* Fix - Improve Subscription plugin support. #161
* Fix - Disable vault setting if vaulting feature is not available. #150
* Fix - Cast item get_quantity into int. #168
@@ -380,10 +666,10 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Fix pay later messaging options. #141
* Fix - UI/UX for vaulting settings. #166
-= 1.3.1 =
+= 1.3.1 - 2021-04-30 =
* Fix - Fix Credit Card fields for non logged-in users. #152
-= 1.3.0 =
+= 1.3.0 - 2021-04-28 =
* Add - Client-side vaulting and allow WooCommerce Subscriptions product renewals through payment tokens. #134
* Add - Send transaction ids to woocommerce. #125
* Fix - Validate checkout form before sending request to PayPal #137
@@ -393,31 +679,31 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - Remove disabling credit for UK. #127
* Fix - Show WC message on account creating error. #136
-= 1.2.1 =
+= 1.2.1 - 2021-03-08 =
* Fix - Address compatibility issue with Jetpack.
-= 1.2.0 =
+= 1.2.0 - 2021-03-08 =
* Add - Rework onboarding code and add REST controller for integration with the OBW. #121
* Fix - Remove spinner on click, on cancel and on error. #124
-= 1.1.0 =
+= 1.1.0 - 2021-02-01 =
* Add - Buy Now Pay Later for UK. #104
* Add - DE now has 12 month installments. #106
* Fix - Check phone for empty string. #102
-= 1.0.4 =
+= 1.0.4 - 2021-01-18 =
* Fix - Check if WooCommerce is active before initialize. #99
* Fix - Payment buttons only visible on order-pay site when Mini Cart is enabled; payment fails. #96
* Fix - High volume of failed calls to /v1/notifications/webhooks #93
* Fix - GB country has ACDC blocked. #91
-= 1.0.3 =
+= 1.0.3 - 2020-11-30 =
* Fix - Order with Payment received when Hosted Fields transaction is declined. #88
-= 1.0.2 =
+= 1.0.2 - 2020-11-09 =
* Fix - Purchases over 1.000 USD fail. #84
-= 1.0.1 =
+= 1.0.1 - 2020-11-05 =
* Fix - PayPal Smart buttons don't load when using a production/live account and `WP_Debug` is turned on/true. #66
* Fix - [Card Processing] SCA/Visa Verification form loads underneath the Checkout blockUI element. #63
* Fix - Attempting to checkout without country selected results in unexpected error message. #67
@@ -426,5 +712,5 @@ Follow the steps below to connect the plugin to your PayPal account:
* Fix - "The value of a field does not conform to the expected format" error when using certain e-mail addresses. #56
* Fix - HTML tags in Product description. #79
-= 1.0.0 =
+= 1.0.0 - 2020-10-15 =
* Initial release.
diff --git a/src/FilePathPluginFactory.php b/src/FilePathPluginFactory.php
index da688ad6e..69d118fbb 100644
--- a/src/FilePathPluginFactory.php
+++ b/src/FilePathPluginFactory.php
@@ -58,10 +58,9 @@ class FilePathPluginFactory implements FilePathPluginFactoryInterface {
if ( ! function_exists( 'get_plugin_data' ) ) {
/**
- * Skip check for WP files and constants.
+ * Skip check for WP files.
*
- * @psalm-suppress UnresolvableInclude
- * @psalm-suppress UndefinedConstant
+ * @psalm-suppress MissingFile
*/
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
diff --git a/src/Http/RedirectorInterface.php b/src/Http/RedirectorInterface.php
new file mode 100644
index 000000000..83403e496
--- /dev/null
+++ b/src/Http/RedirectorInterface.php
@@ -0,0 +1,22 @@
+getVersion();
},
+
+ 'http.redirector' => function( ContainerInterface $container ) : RedirectorInterface {
+ return new WpRedirector();
+ },
);
diff --git a/tests/PHPUnit/Api/GetOrderTest.php b/tests/PHPUnit/Api/GetOrderTest.php
new file mode 100644
index 000000000..362276249
--- /dev/null
+++ b/tests/PHPUnit/Api/GetOrderTest.php
@@ -0,0 +1,83 @@
+orderEndpoint = Mockery::mock(OrderEndpoint::class);
+
+ $this->bootstrapModule([
+ 'api.endpoint.order' => function () {
+ return $this->orderEndpoint;
+ },
+ ]);
+ }
+
+ public function testSuccess(): void {
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ ppcp_get_paypal_order('123abc');
+ }
+
+ public function testSuccessWithOrder(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn('123abc');
+
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ ppcp_get_paypal_order($wcOrder);
+ }
+
+ public function testOrderWithoutId(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn(false);
+
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_get_paypal_order($wcOrder);
+ }
+
+ public function testFailure(): void {
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andThrow(new RuntimeException())
+ ->once();
+
+ $this->expectException(RuntimeException::class);
+
+ ppcp_get_paypal_order('123abc');
+ }
+
+ public function testInvalidId(): void {
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_get_paypal_order(123);
+ }
+}
diff --git a/tests/PHPUnit/Api/OrderCaptureTest.php b/tests/PHPUnit/Api/OrderCaptureTest.php
new file mode 100644
index 000000000..4e9dd82ce
--- /dev/null
+++ b/tests/PHPUnit/Api/OrderCaptureTest.php
@@ -0,0 +1,92 @@
+authorizedPaymentProcessor = Mockery::mock(AuthorizedPaymentsProcessor::class);
+
+ $this->bootstrapModule([
+ 'wcgateway.processor.authorized-payments' => function () {
+ return $this->authorizedPaymentProcessor;
+ },
+ ]);
+ }
+
+ public function testSuccess(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::INTENT_META_KEY)
+ ->andReturn('AUTHORIZE');
+ $wcOrder->expects('get_meta')
+ ->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
+ ->andReturn(false);
+
+ $this->authorizedPaymentProcessor
+ ->expects('capture_authorized_payment')
+ ->andReturnTrue()
+ ->once();
+
+ ppcp_capture_order($wcOrder);
+ }
+
+ public function testFailure(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::INTENT_META_KEY)
+ ->andReturn('AUTHORIZE');
+ $wcOrder->expects('get_meta')
+ ->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
+ ->andReturn(false);
+
+ $this->authorizedPaymentProcessor
+ ->expects('capture_authorized_payment')
+ ->andReturnFalse()
+ ->once();
+
+ $this->expectException(RuntimeException::class);
+
+ ppcp_capture_order($wcOrder);
+ }
+
+ public function testNotAuthorize(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->shouldReceive('get_meta')
+ ->with(PayPalGateway::INTENT_META_KEY)
+ ->andReturn('CAPTURE');
+ $wcOrder->shouldReceive('get_meta')
+ ->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
+ ->andReturn(false);
+
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_capture_order($wcOrder);
+ }
+
+ public function testAlreadyCaptured(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->shouldReceive('get_meta')
+ ->with(PayPalGateway::INTENT_META_KEY)
+ ->andReturn('CAPTURE');
+ $wcOrder->shouldReceive('get_meta')
+ ->with(AuthorizedPaymentsProcessor::CAPTURED_META_KEY)
+ ->andReturn(true);
+
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_capture_order($wcOrder);
+ }
+}
diff --git a/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php b/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
new file mode 100644
index 000000000..c9ccedc82
--- /dev/null
+++ b/tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
@@ -0,0 +1,130 @@
+order_endpoint = $this->createMock(OrderEndpoint::class);
+ $this->logger = $this->createMock(LoggerInterface::class);
+ $this->refundFeesUpdater = new RefundFeesUpdater($this->order_endpoint, $this->logger);
+ }
+
+ public function testUpdateWithoutPaypalOrderId(): void
+ {
+ $wc_order_id = 123;
+
+ $wc_order = Mockery::mock(WC_Order::class);
+ $wc_order->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn(null);
+
+ $wc_order->expects('get_id')->andReturn($wc_order_id);
+
+ $this->logger->expects($this->once())
+ ->method('error');
+
+ $this->refundFeesUpdater->update($wc_order);
+ }
+
+ public function testUpdateWithValidData(): void
+ {
+ $wc_order_id = 123;
+ $paypal_order_id = 'test_order_id';
+ $refund_id = 'XYZ123';
+ $meta_data = [
+ 'gross_amount' => ['value' => 10.0, 'currency_code' => 'USD'],
+ 'paypal_fee' => ['value' => 7.0, 'currency_code' => 'USD'],
+ 'net_amount' => ['value' => 3.0, 'currency_code' => 'USD'],
+ ];
+
+ when('get_comments')->justReturn([]);
+
+ $wc_order = Mockery::mock(WC_Order::class);
+ $wc_order->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn($paypal_order_id);
+
+ $wc_order->expects('get_id')
+ ->times(3)
+ ->andReturn($wc_order_id);
+
+ $wc_order->expects('update_meta_data')
+ ->once()
+ ->with('_ppcp_paypal_refund_fees', $meta_data);
+
+ $wc_order->expects('add_order_note')
+ ->once()
+ ->withArgs(function ($arg) use ($refund_id) {
+ return strpos($arg, $refund_id) !== false;
+ });
+
+ $wc_order->expects('save')->once();
+
+ $moneyGross = Mockery::mock(Money::class);
+ $moneyGross->expects('value')->once()->andReturn($meta_data['gross_amount']['value']);
+ $moneyGross->expects('currency_code')->once()->andReturn($meta_data['gross_amount']['currency_code']);
+
+ $moneyFee = Mockery::mock(Money::class);
+ $moneyFee->expects('value')->once()->andReturn($meta_data['paypal_fee']['value']);
+ $moneyFee->expects('currency_code')->once()->andReturn($meta_data['paypal_fee']['currency_code']);
+
+ $moneyNet = Mockery::mock(Money::class);
+ $moneyNet->expects('value')->once()->andReturn($meta_data['net_amount']['value']);
+ $moneyNet->expects('currency_code')->once()->andReturn($meta_data['net_amount']['currency_code']);
+
+ $breakdown = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['gross_amount', 'paypal_fee', 'net_amount'])
+ ->getMock();
+ $breakdown->method('gross_amount')->willReturn($moneyGross);
+ $breakdown->method('paypal_fee')->willReturn($moneyFee);
+ $breakdown->method('net_amount')->willReturn($moneyNet);
+
+ $refund = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['id', 'seller_payable_breakdown'])
+ ->getMock();
+ $refund->method('id')->willReturn($refund_id);
+ $refund->method('seller_payable_breakdown')->willReturn($breakdown);
+
+ $payments = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['refunds'])
+ ->getMock();
+ $payments->method('refunds')->willReturn([$refund]);
+
+ $purchase_unit = $this->getMockBuilder(\stdClass::class)
+ ->addMethods(['payments'])
+ ->getMock();
+ $purchase_unit->method('payments')->willReturn($payments);
+
+ $paypal_order = Mockery::mock(Order::class);
+ $paypal_order->expects('purchase_units')->andReturn([$purchase_unit]);
+
+ $this->order_endpoint->method('order')->with($paypal_order_id)->willReturn($paypal_order);
+
+ $this->logger->expects($this->exactly(2))
+ ->method('debug')
+ ->withConsecutive(
+ [$this->stringContains('Updating order paypal refund fees.')],
+ [$this->stringContains('Updated order paypal refund fees.')]
+ );
+
+ $this->refundFeesUpdater->update($wc_order);
+ }
+}
diff --git a/tests/PHPUnit/Api/OrderRefundTest.php b/tests/PHPUnit/Api/OrderRefundTest.php
new file mode 100644
index 000000000..aa3d4fc4e
--- /dev/null
+++ b/tests/PHPUnit/Api/OrderRefundTest.php
@@ -0,0 +1,90 @@
+refundProcessor = Mockery::mock(RefundProcessor::class);
+ $this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
+
+ $this->bootstrapModule([
+ 'wcgateway.processor.refunds' => function () {
+ return $this->refundProcessor;
+ },
+ 'api.endpoint.order' => function () {
+ return $this->orderEndpoint;
+ },
+ ]);
+ }
+
+ public function testSuccess(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn('123abc');
+
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ $this->refundProcessor
+ ->expects('refund')
+ ->andReturn('456qwe')
+ ->once();
+
+ $refund_id = ppcp_refund_order($wcOrder, 42.0, 'reason');
+ $this->assertEquals('456qwe', $refund_id);
+ }
+
+ public function testOrderWithoutId(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn(false);
+
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_refund_order($wcOrder, 42.0, 'reason');
+ }
+
+ public function testFailure(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn('123abc');
+
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ $this->refundProcessor
+ ->expects('refund')
+ ->andThrow(new RuntimeException())
+ ->once();
+
+ $this->expectException(RuntimeException::class);
+
+ ppcp_refund_order($wcOrder, 42.0, 'reason');
+ }
+}
diff --git a/tests/PHPUnit/Api/OrderVoidTest.php b/tests/PHPUnit/Api/OrderVoidTest.php
new file mode 100644
index 000000000..5bbae93a6
--- /dev/null
+++ b/tests/PHPUnit/Api/OrderVoidTest.php
@@ -0,0 +1,88 @@
+refundProcessor = Mockery::mock(RefundProcessor::class);
+ $this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
+
+ $this->bootstrapModule([
+ 'wcgateway.processor.refunds' => function () {
+ return $this->refundProcessor;
+ },
+ 'api.endpoint.order' => function () {
+ return $this->orderEndpoint;
+ },
+ ]);
+ }
+
+ public function testSuccess(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn('123abc');
+
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ $this->refundProcessor
+ ->expects('void')
+ ->once();
+
+ ppcp_void_order($wcOrder);
+ }
+
+ public function testOrderWithoutId(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn(false);
+
+ $this->expectException(InvalidArgumentException::class);
+
+ ppcp_void_order($wcOrder);
+ }
+
+ public function testFailure(): void {
+ $wcOrder = Mockery::mock(WC_Order::class);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::ORDER_ID_META_KEY)
+ ->andReturn('123abc');
+
+ $this->orderEndpoint
+ ->expects('order')
+ ->with('123abc')
+ ->andReturn(Mockery::mock(Order::class))
+ ->once();
+
+ $this->refundProcessor
+ ->expects('void')
+ ->andThrow(new RuntimeException())
+ ->once();
+
+ $this->expectException(RuntimeException::class);
+
+ ppcp_void_order($wcOrder);
+ }
+}
diff --git a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php
index 6b0b2ef14..fe66ab43f 100644
--- a/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php
+++ b/tests/PHPUnit/ApiClient/Endpoint/IdentityTokenTest.php
@@ -47,7 +47,7 @@ class IdentityTokenTest extends TestCase
public function testGenerateForCustomerReturnsToken()
{
$id = 1;
- define( 'PPCP_FLAG_SUBSCRIPTION', true );
+ !defined('PPCP_FLAG_SUBSCRIPTION') && define('PPCP_FLAG_SUBSCRIPTION', true);
$token = Mockery::mock(Token::class);
$token
->expects('token')->andReturn('bearer');
diff --git a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php
index 3969115d6..5041bf16a 100644
--- a/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php
+++ b/tests/PHPUnit/ApiClient/Endpoint/OrderEndpointTest.php
@@ -24,7 +24,6 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\OrderFactory;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PatchCollectionFactory;
use WooCommerce\PayPalCommerce\ApiClient\Helper\ErrorResponse;
use WooCommerce\PayPalCommerce\ApiClient\Repository\ApplicationContextRepository;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Mockery;
use Psr\Log\LoggerInterface;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
@@ -68,9 +67,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order_id')->with($orderId)->andReturn('uniqueRequestId');
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
@@ -85,7 +81,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -130,12 +125,9 @@ class OrderEndpointTest extends TestCase
$patchCollectionFactory = Mockery::mock(PatchCollectionFactory::class);
$intent = 'CAPTURE';
$logger = Mockery::mock(LoggerInterface::class);
- $logger->shouldReceive('log');
+ $logger->shouldReceive('warning');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order_id')->with($orderId)->andReturn('uniqueRequestId');
$headers = Mockery::mock(Requests_Utility_CaseInsensitiveDictionary::class);
$headers->shouldReceive('getAll');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
@@ -150,7 +142,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -191,9 +182,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order_id')->with($orderId)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -206,7 +194,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -261,9 +248,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToCapture)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -276,7 +260,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -334,7 +317,6 @@ class OrderEndpointTest extends TestCase
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -347,7 +329,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -381,9 +362,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToCapture)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -396,7 +374,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -437,9 +414,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToCapture)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -452,7 +426,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -495,9 +468,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToCapture)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -512,7 +482,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -579,9 +548,6 @@ class OrderEndpointTest extends TestCase
$logger->shouldNotReceive('log');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToUpdate)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -596,7 +562,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -625,9 +590,6 @@ class OrderEndpointTest extends TestCase
if ($args['headers']['Prefer'] !== 'return=representation') {
return false;
}
- if ($args['headers']['PayPal-Request-Id'] !== 'uniqueRequestId') {
- return false;
- }
$body = json_decode($args['body']);
if (! is_array($body) || $body[0] !== 'patch-1' || $body[1] !== 'patch-2') {
return false;
@@ -685,12 +647,9 @@ class OrderEndpointTest extends TestCase
$intent = 'CAPTURE';
$logger = Mockery::mock(LoggerInterface::class);
- $logger->shouldReceive('log');
+ $logger->shouldReceive('warning');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToUpdate)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -703,7 +662,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -727,9 +685,6 @@ class OrderEndpointTest extends TestCase
if ($args['headers']['Prefer'] !== 'return=representation') {
return false;
}
- if ($args['headers']['PayPal-Request-Id'] !== 'uniqueRequestId') {
- return false;
- }
$body = json_decode($args['body']);
if (! is_array($body) || $body[0] !== 'patch-1' || $body[1] !== 'patch-2') {
return false;
@@ -787,13 +742,10 @@ class OrderEndpointTest extends TestCase
$intent = 'CAPTURE';
$logger = Mockery::mock(LoggerInterface::class);
- $logger->shouldReceive('log');
+ $logger->shouldReceive('warning');
$logger->shouldReceive('debug');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('get_for_order')->with($orderToUpdate)->andReturn('uniqueRequestId');
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -808,7 +760,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -833,9 +784,6 @@ class OrderEndpointTest extends TestCase
if ($args['headers']['Prefer'] !== 'return=representation') {
return false;
}
- if ($args['headers']['PayPal-Request-Id'] !== 'uniqueRequestId') {
- return false;
- }
$body = json_decode($args['body']);
if (! is_array($body) || $body[0] !== 'patch-1' || $body[1] !== 'patch-2') {
return false;
@@ -873,7 +821,6 @@ class OrderEndpointTest extends TestCase
$logger = Mockery::mock(LoggerInterface::class);
$logger->shouldNotReceive('log');
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$fraudnet = Mockery::mock(FraudNet::class);
@@ -886,7 +833,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -936,17 +882,8 @@ class OrderEndpointTest extends TestCase
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationContextRepository
->expects('current_context')
- ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING))
+ ->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE)
->andReturn($applicationContext);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('set_for_order')->andReturnUsing(function ($order, $id) use ($expectedOrder) : bool {
- if ($order !== $expectedOrder) {
- return false;
- }
-
- return strpos($id, 'ppcp') !== false;
- });
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
@@ -960,7 +897,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -1049,17 +985,8 @@ class OrderEndpointTest extends TestCase
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationContextRepository
->expects('current_context')
- ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE))
+ ->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE)
->andReturn($applicationContext);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
- $paypalRequestIdRepository
- ->expects('set_for_order')->andReturnUsing(function ($order, $id) use ($expectedOrder) : bool {
- if ($order !== $expectedOrder) {
- return false;
- }
-
- return strpos($id, 'ppcp') !== false;
- });
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
@@ -1073,7 +1000,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -1139,9 +1065,8 @@ class OrderEndpointTest extends TestCase
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationContextRepository
->expects('current_context')
- ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING))
+ ->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE)
->andReturn($applicationContext);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
@@ -1155,7 +1080,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -1232,9 +1156,8 @@ class OrderEndpointTest extends TestCase
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
$applicationContextRepository
->expects('current_context')
- ->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE))
+ ->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE)
->andReturn($applicationContext);
- $paypalRequestIdRepository = Mockery::mock(PayPalRequestIdRepository::class);
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
@@ -1248,7 +1171,6 @@ class OrderEndpointTest extends TestCase
$intent,
$logger,
$applicationContextRepository,
- $paypalRequestIdRepository,
$subscription_helper,
false,
$fraudnet
@@ -1294,3 +1216,4 @@ class OrderEndpointTest extends TestCase
$testee->create([$purchaseUnit], ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, $payer);
}
}
+
diff --git a/tests/PHPUnit/ApiClient/Entity/ItemTest.php b/tests/PHPUnit/ApiClient/Entity/ItemTest.php
index 2028000e3..bbee83f1d 100644
--- a/tests/PHPUnit/ApiClient/Entity/ItemTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/ItemTest.php
@@ -59,6 +59,9 @@ class ItemTest extends TestCase
$tax
->expects('to_array')
->andReturn([2]);
+
+ $image_url = 'https://example.com/wp-content/uploads/2023/06/beanie-2.jpg';
+
$testee = new Item(
'name',
$unitAmount,
@@ -66,7 +69,9 @@ class ItemTest extends TestCase
'description',
$tax,
'sku',
- 'PHYSICAL_GOODS'
+ 'PHYSICAL_GOODS',
+ 'url',
+ $image_url
);
$expected = [
@@ -76,6 +81,8 @@ class ItemTest extends TestCase
'description' => 'description',
'sku' => 'sku',
'category' => 'PHYSICAL_GOODS',
+ 'url' => 'url',
+ 'image_url' => $image_url,
'tax' => [2],
];
diff --git a/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php b/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
index 240735f73..2793039aa 100644
--- a/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/PaymentsTest.php
@@ -43,10 +43,19 @@ class PaymentsTest extends TestCase
'status' => 'CREATED',
]
);
- $captures = [$capture];
- $authorizations = [$authorization];
+ $refund = \Mockery::mock(Refund::class);
+ $refund->shouldReceive('to_array')->andReturn(
+ [
+ 'id' => 'refund',
+ 'status' => 'CREATED',
+ ]
+ );
- $testee = new Payments($authorizations, $captures);
+ $authorizations = [$authorization];
+ $captures = [$capture];
+ $refunds = [$refund];
+
+ $testee = new Payments($authorizations, $captures, $refunds);
$this->assertEquals(
[
@@ -62,6 +71,12 @@ class PaymentsTest extends TestCase
'status' => 'CREATED',
],
],
+ 'refunds' => [
+ [
+ 'id' => 'refund',
+ 'status' => 'CREATED',
+ ],
+ ],
],
$testee->to_array()
);
diff --git a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
index dbb18c4de..da3d988cb 100644
--- a/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
+++ b/tests/PHPUnit/ApiClient/Entity/PurchaseUnitTest.php
@@ -3,6 +3,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
+use WooCommerce\PayPalCommerce\ApiClient\Helper\PurchaseUnitSanitizer;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@@ -44,7 +45,6 @@ class PurchaseUnitTest extends TestCase
$shipping,
'referenceId',
'description',
- null,
'customId',
'invoiceId',
'softDescriptor'
@@ -53,7 +53,6 @@ class PurchaseUnitTest extends TestCase
$this->assertEquals($amount, $testee->amount());
$this->assertEquals('referenceId', $testee->reference_id());
$this->assertEquals('description', $testee->description());
- $this->assertNull($testee->payee());
$this->assertEquals('customId', $testee->custom_id());
$this->assertEquals('invoiceId', $testee->invoice_id());
$this->assertEquals('softDescriptor', $testee->soft_descriptor());
@@ -75,24 +74,43 @@ class PurchaseUnitTest extends TestCase
$this->assertEquals($expected, $testee->to_array());
}
- /**
- * @dataProvider dataForDitchTests
- * @param array $items
- * @param Amount $amount
- * @param bool $doDitch
- */
- public function testDitchMethod(array $items, Amount $amount, bool $doDitch, string $message)
+ /**
+ * @dataProvider dataForDitchTests
+ * @param array $items
+ * @param Amount $amount
+ * @param bool|array $doDitch
+ * @param string $message
+ */
+ public function testDitchMethod(array $items, Amount $amount, $doDitch, string $message)
{
+ if (is_array($doDitch)) {
+ $doDitchItems = $doDitch['items'];
+ $doDitchBreakdown = $doDitch['breakdown'];
+ $doDitchTax = $doDitch['tax'];
+ } else {
+ $doDitchItems = $doDitch;
+ $doDitchBreakdown = $doDitch;
+ $doDitchTax = $doDitch;
+ }
+
$testee = new PurchaseUnit(
$amount,
$items
);
+ $testee->set_sanitizer(new PurchaseUnitSanitizer(PurchaseUnitSanitizer::MODE_DITCH));
+
$array = $testee->to_array();
- $resultItems = $doDitch === ! array_key_exists('items', $array);
- $resultBreakdown = $doDitch === ! array_key_exists('breakdown', $array['amount']);
+ $resultItems = $doDitchItems === ! array_key_exists('items', $array);
+
+ $resultBreakdown = $doDitchBreakdown === ! array_key_exists('breakdown', $array['amount']);
$this->assertTrue($resultItems, $message);
$this->assertTrue($resultBreakdown, $message);
+
+ foreach ($array['items'] ?? [] as $item) {
+ $resultTax = $doDitchTax === ! array_key_exists('tax', $item);
+ $this->assertTrue($resultTax, $message);
+ }
}
public function dataForDitchTests() : array
@@ -406,6 +424,58 @@ class PurchaseUnitTest extends TestCase
'insurance' => null,
],
],
+ 'ditch_items_total_but_not_breakdown' => [
+ 'message' => 'Items should be ditched because the item total does not add up. But not breakdown because it adds up.',
+ 'ditch' => [
+ 'items' => true,
+ 'breakdown' => false,
+ 'tax' => true,
+ ],
+ 'items' => [
+ [
+ 'value' => 11,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26,
+ 'breakdown' => [
+ 'item_total' => 20,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'ditch_items_tax_with_incorrect_tax_total' => [
+ 'message' => 'Ditch tax from items. Items should not be ditched because the mismatch is on the tax.',
+ 'ditch' => [
+ 'items' => false,
+ 'breakdown' => false,
+ 'tax' => true,
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 4,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26,
+ 'breakdown' => [
+ 'item_total' => 20,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
];
$values = [];
@@ -421,10 +491,16 @@ class PurchaseUnitTest extends TestCase
'tax' => $tax,
'quantity'=> $item['quantity'],
'category' => $item['category'],
- 'to_array' => [],
+ 'to_array' => [
+ 'unit_amount' => $unitAmount->to_array(),
+ 'tax' => $tax->to_array(),
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ],
]
);
}
+
$breakdown = null;
if ($test['breakdown']) {
$breakdown = Mockery::mock(AmountBreakdown::class);
@@ -438,10 +514,29 @@ class PurchaseUnitTest extends TestCase
return $money;
});
}
+
+ $breakdown
+ ->shouldReceive('to_array')
+ ->andReturn(
+ array_map(
+ function ($value) {
+ return $value ? (new Money($value, 'EUR'))->to_array() : null;
+ },
+ $test['breakdown']
+ )
+ );
}
+
+ $amountMoney = new Money($test['amount'], 'EUR');
$amount = Mockery::mock(Amount::class);
- $amount->shouldReceive('to_array')->andReturn(['value' => number_format( $test['amount'], 2, '.', '' ), 'breakdown' => []]);
- $amount->shouldReceive('value_str')->andReturn(number_format( $test['amount'], 2, '.', '' ));
+ $amount
+ ->shouldReceive('to_array')
+ ->andReturn([
+ 'value' => $amountMoney->value_str(),
+ 'currency_code' => $amountMoney->currency_code(),
+ 'breakdown' => $breakdown ? $breakdown->to_array() : [],
+ ]);
+ $amount->shouldReceive('value_str')->andReturn($amountMoney->value_str());
$amount->shouldReceive('currency_code')->andReturn('EUR');
$amount->shouldReceive('breakdown')->andReturn($breakdown);
@@ -456,45 +551,259 @@ class PurchaseUnitTest extends TestCase
return $values;
}
- public function testPayee()
- {
- $amount = Mockery::mock(Amount::class);
- $amount->shouldReceive('breakdown')->andReturnNull();
- $amount->shouldReceive('to_array')->andReturn(['amount']);
- $item1 = Mockery::mock(Item::class);
- $item1->shouldReceive('to_array')->andReturn(['item1']);
- $item2 = Mockery::mock(Item::class);
- $item2->shouldReceive('to_array')->andReturn(['item2']);
- $shipping = Mockery::mock(Shipping::class);
- $shipping->shouldReceive('to_array')->andReturn(['shipping']);
- $payee = Mockery::mock(Payee::class);
- $payee->shouldReceive('to_array')->andReturn(['payee']);
- $testee = new PurchaseUnit(
- $amount,
- [],
- $shipping,
- 'referenceId',
- 'description',
- $payee,
- 'customId',
- 'invoiceId',
- 'softDescriptor'
- );
+ /**
+ * @dataProvider dataForExtraLineTests
+ * @param array $items
+ * @param Amount $amount
+ * @param array $expected
+ * @param string $message
+ */
+ public function testExtraLineMethod(array $items, Amount $amount, array $expected, string $message)
+ {
+ $testee = new PurchaseUnit(
+ $amount,
+ $items
+ );
- $this->assertEquals($payee, $testee->payee());
+ $testee->set_sanitizer(new PurchaseUnitSanitizer(PurchaseUnitSanitizer::MODE_EXTRA_LINE, $expected['extra_line_name'] ?? null));
- $expected = [
- 'reference_id' => 'referenceId',
- 'amount' => ['amount'],
- 'description' => 'description',
- 'items' => [],
- 'shipping' => ['shipping'],
- 'custom_id' => 'customId',
- 'invoice_id' => 'invoiceId',
- 'soft_descriptor' => 'softDescriptor',
- 'payee' => ['payee'],
- ];
+ $countItemsBefore = count($items);
+ $array = $testee->to_array();
+ $countItemsAfter = count($array['items']);
+ $extraItem = array_pop($array['items']);
- $this->assertEquals($expected, $testee->to_array());
- }
+ $this->assertEquals($countItemsBefore + 1, $countItemsAfter, $message);
+ $this->assertEquals($expected['extra_line_value'], $extraItem['unit_amount']['value'], $message);
+ $this->assertEquals($expected['extra_line_name'] ?? PurchaseUnitSanitizer::EXTRA_LINE_NAME, $extraItem['name'], $message);
+
+ foreach ($array['items'] as $i => $item) {
+ $this->assertEquals($expected['item_value'][$i], $item['unit_amount']['value'], $message);
+ }
+ }
+
+ public function dataForExtraLineTests() : array
+ {
+ $data = [
+ 'default' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.',
+ 'expected' => [
+ 'item_value' => [10],
+ 'extra_line_value' => 0.01,
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_custom_name' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.',
+ 'expected' => [
+ 'item_value' => [10],
+ 'extra_line_value' => 0.01,
+ 'extra_line_name' => 'My custom line name',
+ ],
+ 'items' => [
+ [
+ 'value' => 10,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_rounding_down' => [
+ 'message' => 'Extra line should be added with price 0.01 and line amount 10.00.',
+ 'expected' => [
+ 'item_value' => [10.00],
+ 'extra_line_value' => 0.01
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 26.01,
+ 'breakdown' => [
+ 'item_total' => 20.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_two_rounding_down' => [
+ 'message' => 'Extra line should be added with price 0.03 and lines amount 10.00 and 4.99.',
+ 'expected' => [
+ 'item_value' => [10.00, 4.99],
+ 'extra_line_value' => 0.03
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 5,
+ 'quantity' => 2,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 36.01,
+ 'breakdown' => [
+ 'item_total' => 30.01,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ],
+ 'with_many_roundings_down' => [
+ 'message' => 'Extra line should be added with price 0.01 and lines amount 10.00, 5.00 and 6.66.',
+ 'expected' => [
+ 'item_value' => [10.00, 4.99, 6.66],
+ 'extra_line_value' => 0.02
+ ],
+ 'items' => [
+ [
+ 'value' => 10.005,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 5.001,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ [
+ 'value' => 6.666,
+ 'quantity' => 1,
+ 'tax' => 3,
+ 'category' => Item::PHYSICAL_GOODS,
+ ],
+ ],
+ 'amount' => 27.67,
+ 'breakdown' => [
+ 'item_total' => 21.67,
+ 'tax_total' => 6,
+ 'shipping' => null,
+ 'discount' => null,
+ 'shipping_discount' => null,
+ 'handling' => null,
+ 'insurance' => null,
+ ],
+ ]
+ ];
+
+ $values = [];
+ foreach ($data as $testKey => $test) {
+ $items = [];
+ foreach ($test['items'] as $key => $item) {
+ $unitAmount = new Money($item['value'], 'EUR');
+ $tax = new Money($item['tax'], 'EUR');
+ $items[$key] = Mockery::mock(
+ Item::class,
+ [
+ 'unit_amount' => $unitAmount,
+ 'tax' => $tax,
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ]
+ );
+
+ $items[$key]->shouldReceive('to_array')->andReturnUsing(function (bool $roundToFloor = false) use ($unitAmount, $tax, $item) {
+ return [
+ 'unit_amount' => $unitAmount->to_array($roundToFloor),
+ 'tax' => $tax->to_array(),
+ 'quantity'=> $item['quantity'],
+ 'category' => $item['category'],
+ ];
+ });
+
+ }
+
+ $breakdown = null;
+ if ($test['breakdown']) {
+ $breakdown = Mockery::mock(AmountBreakdown::class);
+ foreach ($test['breakdown'] as $method => $value) {
+ $breakdown->shouldReceive($method)->andReturnUsing(function () use ($value) {
+ if (! is_numeric($value)) {
+ return null;
+ }
+
+ $money = new Money($value, 'EUR');
+ return $money;
+ });
+ }
+
+ $breakdown
+ ->shouldReceive('to_array')
+ ->andReturn(
+ array_map(
+ function ($value) {
+ return $value ? (new Money($value, 'EUR'))->to_array() : null;
+ },
+ $test['breakdown']
+ )
+ );
+ }
+
+ $amountMoney = new Money($test['amount'], 'EUR');
+ $amount = Mockery::mock(Amount::class);
+ $amount
+ ->shouldReceive('to_array')
+ ->andReturn([
+ 'value' => $amountMoney->value_str(),
+ 'currency_code' => $amountMoney->currency_code(),
+ 'breakdown' => $breakdown ? $breakdown->to_array() : [],
+ ]);
+ $amount->shouldReceive('value_str')->andReturn($amountMoney->value_str());
+ $amount->shouldReceive('currency_code')->andReturn('EUR');
+ $amount->shouldReceive('breakdown')->andReturn($breakdown);
+
+ $values[$testKey] = [
+ $items,
+ $amount,
+ $test['expected'],
+ $test['message'],
+ ];
+ }
+
+ return $values;
+ }
}
diff --git a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php
index 640dffcdf..f8eedb3bb 100644
--- a/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php
+++ b/tests/PHPUnit/ApiClient/Factory/ItemFactoryTest.php
@@ -43,9 +43,8 @@ class ItemFactoryTest extends TestCase
->expects('get_cart_contents')
->andReturn($items);
- expect('wp_strip_all_tags')
- ->with('description')
- ->andReturn('description');
+ expect('wp_strip_all_tags')->andReturnFirstArg();
+ expect('strip_shortcodes')->andReturnFirstArg();
$woocommerce = Mockery::mock(\WooCommerce::class);
$session = Mockery::mock(\WC_Session::class);
@@ -53,6 +52,14 @@ class ItemFactoryTest extends TestCase
$woocommerce->session = $session;
$session->shouldReceive('get')->andReturn([]);
+ when('wp_get_attachment_image_src')->justReturn('image_url');
+ $product
+ ->expects('get_image_id')
+ ->andReturn(1);
+ $product
+ ->expects('get_permalink')
+ ->andReturn('url');
+
$result = $testee->from_wc_cart($cart);
$this->assertCount(1, $result);
@@ -99,9 +106,8 @@ class ItemFactoryTest extends TestCase
->expects('get_cart_contents')
->andReturn($items);
- expect('wp_strip_all_tags')
- ->with('description')
- ->andReturn('description');
+ expect('wp_strip_all_tags')->andReturnFirstArg();
+ expect('strip_shortcodes')->andReturnFirstArg();
$woocommerce = Mockery::mock(\WooCommerce::class);
$session = Mockery::mock(\WC_Session::class);
@@ -109,6 +115,13 @@ class ItemFactoryTest extends TestCase
$woocommerce->session = $session;
$session->shouldReceive('get')->andReturn([]);
+ when('wp_get_attachment_image_src')->justReturn('image_url');
+ $product
+ ->expects('get_image_id')
+ ->andReturn(1);
+ $product
+ ->expects('get_permalink')
+ ->andReturn('url');
$result = $testee->from_wc_cart($cart);
@@ -130,9 +143,17 @@ class ItemFactoryTest extends TestCase
$product
->expects('is_virtual')
->andReturn(false);
- expect('wp_strip_all_tags')
- ->with('description')
- ->andReturn('description');
+
+ expect('wp_strip_all_tags')->andReturnFirstArg();
+ expect('strip_shortcodes')->andReturnFirstArg();
+
+ when('wp_get_attachment_image_src')->justReturn('image_url');
+ $product
+ ->expects('get_image_id')
+ ->andReturn(1);
+ $product
+ ->expects('get_permalink')
+ ->andReturn('url');
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
@@ -190,9 +211,8 @@ class ItemFactoryTest extends TestCase
->expects('is_virtual')
->andReturn(true);
- expect('wp_strip_all_tags')
- ->with('description')
- ->andReturn('description');
+ expect('wp_strip_all_tags')->andReturnFirstArg();
+ expect('strip_shortcodes')->andReturnFirstArg();
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
@@ -220,6 +240,14 @@ class ItemFactoryTest extends TestCase
->expects('get_fees')
->andReturn([]);
+ when('wp_get_attachment_image_src')->justReturn('image_url');
+ $product
+ ->expects('get_image_id')
+ ->andReturn(1);
+ $product
+ ->expects('get_permalink')
+ ->andReturn('url');
+
$result = $testee->from_wc_order($order);
$item = current($result);
/**
@@ -245,9 +273,8 @@ class ItemFactoryTest extends TestCase
->expects('is_virtual')
->andReturn(true);
- expect('wp_strip_all_tags')
- ->with($description)
- ->andReturn(mb_substr( $description, 0, 127 ));
+ expect('wp_strip_all_tags')->andReturnFirstArg();
+ expect('strip_shortcodes')->andReturnFirstArg();
$item = Mockery::mock(\WC_Order_Item_Product::class);
$item
@@ -275,6 +302,14 @@ class ItemFactoryTest extends TestCase
->expects('get_fees')
->andReturn([]);
+ when('wp_get_attachment_image_src')->justReturn('image_url');
+ $product
+ ->expects('get_image_id')
+ ->andReturn(1);
+ $product
+ ->expects('get_permalink')
+ ->andReturn('url');
+
$result = $testee->from_wc_order($order);
$item = current($result);
/**
diff --git a/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
index 28e14b379..0260be7b0 100644
--- a/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
+++ b/tests/PHPUnit/ApiClient/Factory/PaymentsFactoryTest.php
@@ -7,6 +7,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@@ -18,11 +19,15 @@ class PaymentsFactoryTest extends TestCase
$authorization->shouldReceive('to_array')->andReturn(['id' => 'foo', 'status' => 'CREATED']);
$capture = Mockery::mock(Capture::class);
$capture->shouldReceive('to_array')->andReturn(['id' => 'capture', 'status' => 'CREATED']);
+ $refund = Mockery::mock(Refund::class);
+ $refund->shouldReceive('to_array')->andReturn(['id' => 'refund', 'status' => 'CREATED']);
$authorizationsFactory = Mockery::mock(AuthorizationFactory::class);
$authorizationsFactory->shouldReceive('from_paypal_response')->andReturn($authorization);
$captureFactory = Mockery::mock(CaptureFactory::class);
$captureFactory->shouldReceive('from_paypal_response')->andReturn($capture);
+ $refundFactory = Mockery::mock(RefundFactory::class);
+ $refundFactory->shouldReceive('from_paypal_response')->andReturn($refund);
$response = (object)[
'authorizations' => [
(object)['id' => 'foo', 'status' => 'CREATED'],
@@ -30,9 +35,12 @@ class PaymentsFactoryTest extends TestCase
'captures' => [
(object)['id' => 'capture', 'status' => 'CREATED'],
],
+ 'refunds' => [
+ (object)['id' => 'refund', 'status' => 'CREATED'],
+ ],
];
- $testee = new PaymentsFactory($authorizationsFactory, $captureFactory);
+ $testee = new PaymentsFactory($authorizationsFactory, $captureFactory, $refundFactory);
$result = $testee->from_paypal_response($response);
$this->assertInstanceOf(Payments::class, $result);
@@ -44,6 +52,9 @@ class PaymentsFactoryTest extends TestCase
'captures' => [
['id' => 'capture', 'status' => 'CREATED'],
],
+ 'refunds' => [
+ ['id' => 'refund', 'status' => 'CREATED'],
+ ],
];
$this->assertEquals($expectedToArray, $result->to_array());
}
diff --git a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php
index 72c664c91..73d948c46 100644
--- a/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php
+++ b/tests/PHPUnit/ApiClient/Factory/PurchaseUnitFactoryTest.php
@@ -7,11 +7,9 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\Address;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Amount;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Item;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
-use WooCommerce\PayPalCommerce\ApiClient\Entity\Payee;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
-use WooCommerce\PayPalCommerce\ApiClient\Repository\PayeeRepository;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
@@ -45,11 +43,6 @@ class PurchaseUnitFactoryTest extends TestCase
->shouldReceive('from_wc_order')
->with($wcOrder)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->shouldReceive('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->shouldReceive('from_wc_order')
@@ -75,8 +68,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -84,7 +75,6 @@ class PurchaseUnitFactoryTest extends TestCase
$unit = $testee->from_wc_order($wcOrder);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
- $this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals($this->wcOrderId, $unit->custom_id());
@@ -106,11 +96,6 @@ class PurchaseUnitFactoryTest extends TestCase
->shouldReceive('from_wc_order')
->with($wcOrder)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->shouldReceive('payee')->andReturn($payee);
$fee = Mockery::mock(Item::class, [
'category' => Item::DIGITAL_GOODS,
@@ -139,8 +124,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -162,11 +145,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_order')
->with($wcOrder)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_order')
@@ -194,8 +172,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -216,11 +192,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_order')
->with($wcOrder)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_order')
@@ -243,8 +214,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -258,7 +227,7 @@ class PurchaseUnitFactoryTest extends TestCase
{
$wcCustomer = Mockery::mock(\WC_Customer::class);
expect('WC')
- ->andReturn((object) ['customer' => $wcCustomer]);
+ ->andReturn((object) ['customer' => $wcCustomer, 'session' => null]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
@@ -267,11 +236,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
@@ -294,13 +258,11 @@ class PurchaseUnitFactoryTest extends TestCase
$shippingFactory = Mockery::mock(ShippingFactory::class);
$shippingFactory
->expects('from_wc_customer')
- ->with($wcCustomer)
+ ->with($wcCustomer, false)
->andReturn($shipping);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -308,7 +270,6 @@ class PurchaseUnitFactoryTest extends TestCase
$unit = $testee->from_wc_cart($wcCart);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
- $this->assertEquals($payee, $unit->payee());
$this->assertEquals('', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals('', $unit->custom_id());
@@ -322,7 +283,7 @@ class PurchaseUnitFactoryTest extends TestCase
public function testWcCartShippingGetsDroppendWhenNoCustomer()
{
expect('WC')
- ->andReturn((object) ['customer' => null]);
+ ->andReturn((object) ['customer' => null, 'session' => null]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
@@ -331,11 +292,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
@@ -346,8 +302,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -360,7 +314,7 @@ class PurchaseUnitFactoryTest extends TestCase
public function testWcCartShippingGetsDroppendWhenNoCountryCode()
{
expect('WC')
- ->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class)]);
+ ->andReturn((object) ['customer' => Mockery::mock(\WC_Customer::class), 'session' => null]);
$wcCart = Mockery::mock(\WC_Cart::class);
$amount = Mockery::mock(Amount::class);
@@ -369,11 +323,6 @@ class PurchaseUnitFactoryTest extends TestCase
->expects('from_wc_cart')
->with($wcCart)
->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $payee = Mockery::mock(Payee::class);
- $payeeRepository
- ->expects('payee')->andReturn($payee);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory
->expects('from_wc_cart')
@@ -395,8 +344,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -410,15 +357,10 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
- $rawPayee = (object) ['payee' => 1];
$rawShipping = (object) ['shipping' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payee = Mockery::mock(Payee::class);
- $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
@@ -427,8 +369,6 @@ class PurchaseUnitFactoryTest extends TestCase
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -442,13 +382,11 @@ class PurchaseUnitFactoryTest extends TestCase
'soft_descriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
- 'payee' => $rawPayee,
'shipping' => $rawShipping,
];
$unit = $testee->from_paypal_response($response);
$this->assertTrue(is_a($unit, PurchaseUnit::class));
- $this->assertEquals($payee, $unit->payee());
$this->assertEquals('description', $unit->description());
$this->assertEquals('default', $unit->reference_id());
$this->assertEquals('customId', $unit->custom_id());
@@ -459,67 +397,19 @@ class PurchaseUnitFactoryTest extends TestCase
$this->assertEquals($shipping, $unit->shipping());
}
- public function testFromPayPalResponsePayeeIsNull()
- {
- $rawItem = (object) ['items' => 1];
- $rawAmount = (object) ['amount' => 1];
- $rawPayee = (object) ['payee' => 1];
- $rawShipping = (object) ['shipping' => 1];
- $amountFactory = Mockery::mock(AmountFactory::class);
- $amount = Mockery::mock(Amount::class);
- $amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
- $itemFactory = Mockery::mock(ItemFactory::class);
- $itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
- $shippingFactory = Mockery::mock(ShippingFactory::class);
- $shipping = Mockery::mock(Shipping::class);
- $shippingFactory->expects('from_paypal_response')->with($rawShipping)->andReturn($shipping);
- $paymentsFacory = Mockery::mock(PaymentsFactory::class);
- $testee = new PurchaseUnitFactory(
- $amountFactory,
- $payeeRepository,
- $payeeFactory,
- $itemFactory,
- $shippingFactory,
- $paymentsFacory
- );
-
- $response = (object) [
- 'reference_id' => 'default',
- 'description' => 'description',
- 'customId' => 'customId',
- 'invoiceId' => 'invoiceId',
- 'softDescriptor' => 'softDescriptor',
- 'amount' => $rawAmount,
- 'items' => [$rawItem],
- 'shipping' => $rawShipping,
- ];
-
- $unit = $testee->from_paypal_response($response);
- $this->assertNull($unit->payee());
- }
-
public function testFromPayPalResponseShippingIsNull()
{
$rawItem = (object) ['items' => 1];
$rawAmount = (object) ['amount' => 1];
- $rawPayee = (object) ['payee' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payee = Mockery::mock(Payee::class);
- $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($this->item);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -533,7 +423,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
- 'payee' => $rawPayee,
];
$unit = $testee->from_paypal_response($response);
@@ -543,15 +432,11 @@ class PurchaseUnitFactoryTest extends TestCase
public function testFromPayPalResponseNeedsReferenceId()
{
$amountFactory = Mockery::mock(AmountFactory::class);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$shippingFactory = Mockery::mock(ShippingFactory::class);
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFacory
@@ -564,7 +449,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => '',
'items' => [],
- 'payee' => '',
'shipping' => '',
];
@@ -576,17 +460,12 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
- $rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payee = Mockery::mock(Payee::class);
- $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item);
@@ -600,8 +479,6 @@ class PurchaseUnitFactoryTest extends TestCase
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
@@ -615,7 +492,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
- 'payee' => $rawPayee,
'shipping' => $rawShipping,
'payments' => $rawPayments,
];
@@ -628,17 +504,12 @@ class PurchaseUnitFactoryTest extends TestCase
{
$rawItem = (object)['items' => 1];
$rawAmount = (object)['amount' => 1];
- $rawPayee = (object)['payee' => 1];
$rawShipping = (object)['shipping' => 1];
$rawPayments = (object)['payments' => 1];
$amountFactory = Mockery::mock(AmountFactory::class);
$amount = Mockery::mock(Amount::class);
$amountFactory->expects('from_paypal_response')->with($rawAmount)->andReturn($amount);
- $payeeFactory = Mockery::mock(PayeeFactory::class);
- $payee = Mockery::mock(Payee::class);
- $payeeFactory->expects('from_paypal_response')->with($rawPayee)->andReturn($payee);
- $payeeRepository = Mockery::mock(PayeeRepository::class);
$itemFactory = Mockery::mock(ItemFactory::class);
$item = Mockery::mock(Item::class, ['category' => Item::PHYSICAL_GOODS]);
$itemFactory->expects('from_paypal_response')->with($rawItem)->andReturn($item);
@@ -650,8 +521,6 @@ class PurchaseUnitFactoryTest extends TestCase
$testee = new PurchaseUnitFactory(
$amountFactory,
- $payeeRepository,
- $payeeFactory,
$itemFactory,
$shippingFactory,
$paymentsFactory
@@ -665,7 +534,6 @@ class PurchaseUnitFactoryTest extends TestCase
'softDescriptor' => 'softDescriptor',
'amount' => $rawAmount,
'items' => [$rawItem],
- 'payee' => $rawPayee,
'shipping' => $rawShipping,
];
diff --git a/tests/PHPUnit/ApiClient/Repository/PayPalRequestIdRepositoryTest.php b/tests/PHPUnit/ApiClient/Repository/PayPalRequestIdRepositoryTest.php
deleted file mode 100644
index f27b0ed3e..000000000
--- a/tests/PHPUnit/ApiClient/Repository/PayPalRequestIdRepositoryTest.php
+++ /dev/null
@@ -1,58 +0,0 @@
-testee = new PayPalRequestIdRepository();
-
- when('get_option')->alias(function () {
- return $this->data;
- });
- when('update_option')->alias(function (string $key, array $data) {
- $this->data = $data;
- });
- }
-
- public function testForOrder()
- {
- $this->testee->set_for_order($this->createPaypalOrder('42'), 'request1');
- $this->testee->set_for_order($this->createPaypalOrder('43'), 'request2');
-
- self::assertEquals('request1', $this->testee->get_for_order($this->createPaypalOrder('42')));
- self::assertEquals('request2', $this->testee->get_for_order($this->createPaypalOrder('43')));
- self::assertEquals('', $this->testee->get_for_order($this->createPaypalOrder('41')));
- }
-
- public function testExpiration()
- {
- $this->testee->set_for_order($this->createPaypalOrder('42'), 'request1');
- $this->data['42']['expiration'] = time() - 1;
- $this->testee->set_for_order($this->createPaypalOrder('43'), 'request2');
-
- self::assertEquals('', $this->testee->get_for_order($this->createPaypalOrder('42')));
- self::assertEquals('request2', $this->testee->get_for_order($this->createPaypalOrder('43')));
- }
-
- private function createPaypalOrder(string $id): Order {
- $order = Mockery::mock(Order::class);
- $order
- ->shouldReceive('id')
- ->andReturn($id);
- return $order;
- }
-}
diff --git a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
index 9eb6c654b..427e9ce79 100644
--- a/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
+++ b/tests/PHPUnit/Button/Endpoint/ChangeCartEndpointTest.php
@@ -6,6 +6,7 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
+use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
use WooCommerce\PayPalCommerce\TestCase;
use Mockery;
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
@@ -26,13 +27,8 @@ class ChangeCartEndpointTest extends TestCase
->once()
->with($singleProductArray['id'])
->andReturn($products[$productKey]);
- if (! $singleProductArray['__test_data_is_variation']) {
- $cart
- ->expects('add_to_cart')
- ->with($singleProductArray['id'], $singleProductArray['quantity'])
- ->andReturnTrue();
- }
- if ($singleProductArray['__test_data_is_variation']) {
+
+ if ($singleProductArray['__test_data_is_variation'] ?? false) {
$dataStore
->expects('find_matching_product_variation')
->with($products[$productKey], $singleProductArray['__test_data_variation_map'])
@@ -47,7 +43,34 @@ class ChangeCartEndpointTest extends TestCase
)
->andReturnTrue();
}
- }
+ elseif ($singleProductArray['__test_data_is_booking'] ?? false) {
+
+ $processedBooking = array();
+ foreach ($singleProductArray['booking'] as $key => $value) {
+ $processedBooking['_processed_' . $key] = $value;
+ }
+
+ expect('wc_bookings_get_posted_data')
+ ->with($singleProductArray['booking'])
+ ->andReturn($processedBooking);
+ $cart
+ ->expects('add_to_cart')
+ ->with(
+ $singleProductArray['id'],
+ $singleProductArray['quantity'],
+ 0,
+ array(),
+ array('booking' => $processedBooking)
+ )
+ ->andReturnTrue();
+ }
+ else {
+ $cart
+ ->expects('add_to_cart')
+ ->with($singleProductArray['id'], $singleProductArray['quantity'])
+ ->andReturnTrue();
+ }
+ }
$cart
->expects('empty_cart')
->with(false);
@@ -69,12 +92,16 @@ class ChangeCartEndpointTest extends TestCase
->expects('from_wc_cart')
->andReturn($pu);
+ $productsHelper = new CartProductsHelper(
+ $dataStore
+ );
+
$testee = new ChangeCartEndpoint(
$cart,
$shipping,
$requestData,
$purchase_unit_factory,
- $dataStore,
+ $productsHelper,
new NullLogger()
);
@@ -88,6 +115,10 @@ class ChangeCartEndpointTest extends TestCase
$defaultProduct
->shouldReceive('get_id')
->andReturn(1);
+ $defaultProduct
+ ->shouldReceive('is_type')
+ ->with('booking')
+ ->andReturn(false);
$defaultProduct
->shouldReceive('is_type')
->with('variable')
@@ -97,19 +128,42 @@ class ChangeCartEndpointTest extends TestCase
$variationProduct
->shouldReceive('get_id')
->andReturn(2);
+ $variationProduct
+ ->shouldReceive('is_type')
+ ->with('booking')
+ ->andReturn(false);
$variationProduct
->shouldReceive('is_type')
->with('variable')
->andReturn(true);
- $testData = [
+ $bookingData = [
+ '_duration' => 2,
+ '_start_day' => 12,
+ '_start_month' => 6,
+ '_start_year' => 2023,
+ ];
+
+ $bookingProduct = Mockery::mock(\WC_Product::class);
+ $bookingProduct
+ ->shouldReceive('get_id')
+ ->andReturn(3);
+ $bookingProduct
+ ->shouldReceive('is_type')
+ ->with('booking')
+ ->andReturn(true);
+ $bookingProduct
+ ->shouldReceive('is_type')
+ ->with('variable')
+ ->andReturn(false);
+
+ $testData = [
'default' => [
[
'products' => [
[
'quantity' => 2,
'id' => 1,
- '__test_data_is_variation' => false,
],
]
],
@@ -121,43 +175,65 @@ class ChangeCartEndpointTest extends TestCase
]
],
'variation' => [
- [
- 'products' => [
- [
- 'quantity' => 2,
- 'id' => 1,
- '__test_data_is_variation' => false,
- ],
- [
- 'quantity' => 2,
- 'id' => 2,
- 'variations' => [
- [
- 'name' => 'variation-1',
- 'value' => 'abc',
- ],
- [
- 'name' => 'variation-2',
- 'value' => 'def',
- ],
- ],
- '__test_data_is_variation' => true,
- '__test_data_variation_id' => 123,
- '__test_data_variation_map' => [
- 'variation-1' => 'abc',
- 'variation-2' => 'def',
- ]
- ],
- ]
- ],
- [
- $defaultProduct,
- $variationProduct,
- ],
- [
- [1, 2]
- ]
- ]
+ [
+ 'products' => [
+ [
+ 'quantity' => 2,
+ 'id' => 1,
+ ],
+ [
+ 'quantity' => 2,
+ 'id' => 2,
+ 'variations' => [
+ [
+ 'name' => 'variation-1',
+ 'value' => 'abc',
+ ],
+ [
+ 'name' => 'variation-2',
+ 'value' => 'def',
+ ],
+ ],
+ '__test_data_is_variation' => true,
+ '__test_data_variation_id' => 123,
+ '__test_data_variation_map' => [
+ 'variation-1' => 'abc',
+ 'variation-2' => 'def',
+ ]
+ ],
+ ]
+ ],
+ [
+ $defaultProduct,
+ $variationProduct,
+ ],
+ [
+ [1, 2]
+ ]
+ ],
+ 'booking' => [
+ [
+ 'products' => [
+ [
+ 'quantity' => 2,
+ 'id' => 1,
+ ],
+ [
+ 'quantity' => 1,
+ 'id' => 3,
+ 'booking' => $bookingData,
+ '__test_data_is_booking' => true,
+ ],
+ ]
+ ],
+ [
+ $defaultProduct,
+ $bookingProduct,
+ ],
+ [
+ [1, 3]
+ ]
+ ],
];
return $testData;
diff --git a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php
index 4e6d82a6c..79f80de3f 100644
--- a/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php
+++ b/tests/PHPUnit/Button/Endpoint/CreateOrderEndpointTest.php
@@ -167,6 +167,9 @@ class CreateOrderEndpointTest extends TestCase
false,
CardBillingMode::MINIMAL_INPUT,
false,
+ ['checkout'],
+ false,
+ ['paypal'],
new NullLogger()
);
return array($payer_factory, $testee);
diff --git a/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php b/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php
new file mode 100644
index 000000000..9a99cf924
--- /dev/null
+++ b/tests/PHPUnit/Button/Endpoint/ValidateCheckoutEndpointTest.php
@@ -0,0 +1,96 @@
+requestData = Mockery::mock(RequestData::class);
+ $this->formValidator = Mockery::mock(CheckoutFormValidator::class);
+ $this->logger = Mockery::mock(LoggerInterface::class);
+
+ $this->sut = new ValidateCheckoutEndpoint(
+ $this->requestData,
+ $this->formValidator,
+ $this->logger
+ );
+
+ $this->requestData->expects('read_request')->andReturn(['form' => ['field1' => 'value']]);
+
+ when('WC')->alias(function () {
+ return (object) ['session' => (object) $this->session];
+ });
+ }
+
+ public function testValid()
+ {
+ $this->formValidator->expects('validate')->once();
+
+ expect('wp_send_json_success')->once();
+
+ $this->sut->handle_request();
+ }
+
+ public function testInvalid()
+ {
+ $exception = new ValidationException(['Invalid value']);
+ $this->formValidator->expects('validate')->once()
+ ->andThrow($exception);
+
+ expect('wp_send_json_error')->once()
+ ->with(['message' => $exception->getMessage(), 'errors' => ['Invalid value'], 'refresh' => false]);
+
+ $this->sut->handle_request();
+ }
+
+ public function testInvalidAndRefresh()
+ {
+ $exception = new ValidationException(['Invalid value']);
+ $this->formValidator->expects('validate')->once()
+ ->andThrow($exception);
+
+ $this->session['refresh_totals'] = true;
+
+ expect('wp_send_json_error')->once()
+ ->with(['message' => $exception->getMessage(), 'errors' => ['Invalid value'], 'refresh' => true]);
+
+ $this->sut->handle_request();
+ }
+
+ public function testFailure()
+ {
+ $exception = new Exception('BOOM');
+ $this->formValidator->expects('validate')->once()
+ ->andThrow($exception);
+
+ expect('wp_send_json_error')->once()
+ ->with(['message' => $exception->getMessage()]);
+
+ $this->logger->expects('error')->once();
+
+ $this->sut->handle_request();
+ }
+}
diff --git a/tests/PHPUnit/Helper/RedirectorStub.php b/tests/PHPUnit/Helper/RedirectorStub.php
new file mode 100644
index 000000000..9e79c6fe5
--- /dev/null
+++ b/tests/PHPUnit/Helper/RedirectorStub.php
@@ -0,0 +1,14 @@
+data = $data;
+ }
+
+ public function get($id) {
+ if ( ! $this->has( $id ) ) {
+ throw new NotFoundException();
+ }
+
+ return $this->data[$id];
+ }
+
+ public function has($id) {
+ return array_key_exists( $id, $this->data );
+ }
+
+ public function set($id, $value) {
+ $this->data[$id] = $value;
+ }
+
+ public function persist() {
+ }
+}
diff --git a/tests/PHPUnit/Helper/StubRedirectionException.php b/tests/PHPUnit/Helper/StubRedirectionException.php
new file mode 100644
index 000000000..e1f1e5aeb
--- /dev/null
+++ b/tests/PHPUnit/Helper/StubRedirectionException.php
@@ -0,0 +1,10 @@
+returnArg();
when('plugins_url')->returnArg();
when('plugin_dir_path')->alias(function ($file) { return trailingslashit(dirname($file)); });
+ when('is_plugin_active')->justReturn(true);
when('get_current_blog_id')->justReturn(42);
when('get_site_url')->justReturn('example.com');
when('get_bloginfo')->justReturn('My Shop');
@@ -38,8 +40,8 @@ class ModularTestCase extends TestCase
$wpdb->posts = '';
$wpdb->postmeta = '';
- !defined('PAYPAL_API_URL') && define('PAYPAL_API_URL', 'https://api.paypal.com');
- !defined('PAYPAL_SANDBOX_API_URL') && define('PAYPAL_SANDBOX_API_URL', 'https://api.sandbox.paypal.com');
+ !defined('PAYPAL_API_URL') && define('PAYPAL_API_URL', 'https://api-m.paypal.com');
+ !defined('PAYPAL_SANDBOX_API_URL') && define('PAYPAL_SANDBOX_API_URL', 'https://api-m.sandbox.paypal.com');
!defined('PAYPAL_INTEGRATION_DATE') && define('PAYPAL_INTEGRATION_DATE', '2020-10-15');
!defined('PPCP_FLAG_SUBSCRIPTION') && define('PPCP_FLAG_SUBSCRIPTION', true);
@@ -58,13 +60,30 @@ class ModularTestCase extends TestCase
*/
protected function bootstrapModule(array $overriddenServices = []): ContainerInterface
{
- $overridingContainer = new DelegatingContainer(new CompositeCachingServiceProvider([
- new ServiceProvider($overriddenServices, []),
- ]));
+ $overriddenServices = array_merge([
+ 'http.redirector' => function () {
+ return new RedirectorStub();
+ }
+ ], $overriddenServices);
+
+ $module = new class ($overriddenServices) implements ModuleInterface {
+ public function __construct(array $services) {
+ $this->services = $services;
+ }
+
+ public function setup(): ServiceProviderInterface{
+ return new ServiceProvider($this->services, []);
+ }
+
+ public function run(ContainerInterface $c): void {
+ }
+ };
$rootDir = ROOT_DIR;
$bootstrap = require ("$rootDir/bootstrap.php");
- $appContainer = $bootstrap($rootDir, $overridingContainer);
+ $appContainer = $bootstrap($rootDir, [], [$module]);
+
+ PPCP::init($appContainer);
return $appContainer;
}
diff --git a/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php b/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php
new file mode 100644
index 000000000..ea8b0ab0e
--- /dev/null
+++ b/tests/PHPUnit/Onboarding/Helper/OnboardingUrlTest.php
@@ -0,0 +1,194 @@
+alias(function($string) {
+ return hash('md5', $string);
+ });
+
+ $this->cache = \Mockery::mock(Cache::class);
+ $this->onboardingUrl = new OnboardingUrl($this->cache, $this->cache_key_prefix, $this->user_id);
+ }
+
+ public function test_validate_token_and_delete_valid()
+ {
+ // Prepare the data
+ $cacheData = [
+ 'hash_check' => wp_hash(''),
+ 'secret' => 'test_secret',
+ 'time' => time(),
+ 'user_id' => $this->user_id,
+ 'url' => 'https://example.com'
+ ];
+
+ $token = [
+ 'k' => $this->cache_key_prefix,
+ 'u' => $this->user_id,
+ 'h' => substr(wp_hash(implode( '|', array(
+ $this->cache_key_prefix,
+ $cacheData['user_id'],
+ $cacheData['secret'],
+ $cacheData['time'],
+ ))), 0, 32)
+ ];
+
+ $onboarding_token = UrlHelper::url_safe_base64_encode(json_encode($token));
+
+ // Expectations
+ $this->cache->shouldReceive('has')->once()->andReturn(true);
+ $this->cache->shouldReceive('get')->once()->andReturn($cacheData);
+ $this->cache->shouldReceive('set')->once();
+ $this->cache->shouldReceive('delete')->once();
+
+ $this->assertTrue(
+ OnboardingUrl::validate_token_and_delete($this->cache, $onboarding_token, $this->user_id)
+ );
+ }
+
+ public function test_load_valid()
+ {
+ // Expectations
+ $this->cache->shouldReceive('has')->once()->andReturn(true);
+ $this->cache->shouldReceive('get')->once()->andReturn([
+ 'hash_check' => wp_hash(''),
+ 'secret' => 'test_secret',
+ 'time' => time(),
+ 'user_id' => $this->user_id,
+ 'url' => 'https://example.com'
+ ]);
+
+ $this->assertTrue($this->onboardingUrl->load());
+ }
+
+ public function test_load_invalid()
+ {
+ // Expectations
+ $this->cache->shouldReceive('has')->once()->andReturn(false);
+
+ $this->assertFalse($this->onboardingUrl->load());
+ }
+
+ public function test_get_not_initialized()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Object not initialized.');
+
+ $this->onboardingUrl->get();
+ }
+
+ public function test_token_not_initialized()
+ {
+ $this->expectException(RuntimeException::class);
+ $this->expectExceptionMessage('Object not initialized.');
+
+ $this->onboardingUrl->token();
+ }
+
+ public function test_persist_not_initialized()
+ {
+ // Expectations
+ $this->cache->shouldReceive('set')->never();
+
+ $this->onboardingUrl->persist();
+
+ $this->assertTrue(true);
+ }
+
+ public function test_delete()
+ {
+ // Expectations
+ $this->cache->shouldReceive('delete')->once();
+
+ $this->onboardingUrl->delete();
+
+ $this->assertTrue(true);
+ }
+
+ public function test_init()
+ {
+ $this->onboardingUrl->init();
+
+ $token = $this->onboardingUrl->token();
+ $this->assertNotEmpty($token);
+ }
+
+ public function test_set_and_get()
+ {
+ $this->onboardingUrl->init();
+ $this->onboardingUrl->set('https://example.com');
+
+ $url = $this->onboardingUrl->get();
+ $this->assertEquals('https://example.com', $url);
+ }
+
+ public function test_persist()
+ {
+ $this->onboardingUrl->init();
+ $this->onboardingUrl->set('https://example.com');
+
+ // Expectations
+ $this->cache->shouldReceive('set')->once();
+
+ $this->onboardingUrl->persist();
+
+ $this->assertTrue(true);
+ }
+
+ public function test_token()
+ {
+ $this->onboardingUrl->init();
+ $this->onboardingUrl->set('https://example.com');
+
+ $token = $this->onboardingUrl->token();
+ $this->assertNotEmpty($token);
+ }
+
+ public function test_validate_token_and_delete_invalid()
+ {
+ // Prepare the data
+ $token = [
+ 'k' => $this->cache_key_prefix,
+ 'u' => $this->user_id,
+ 'h' => 'invalid_hash'
+ ];
+
+ $onboarding_token = UrlHelper::url_safe_base64_encode(json_encode($token));
+
+ // Expectations
+ $this->cache->shouldReceive('has')->once()->andReturn(true);
+ $this->cache->shouldReceive('get')->once()->andReturn([
+ 'hash_check' => wp_hash(''),
+ 'secret' => 'test_secret',
+ 'time' => time(),
+ 'user_id' => $this->user_id,
+ 'url' => 'https://example.com'
+ ]);
+ $this->cache->shouldReceive('delete')->never();
+
+ $this->assertFalse(
+ OnboardingUrl::validate_token_and_delete($this->cache, $onboarding_token, $this->user_id)
+ );
+ }
+
+}
diff --git a/tests/PHPUnit/Subscription/RenewalHandlerTest.php b/tests/PHPUnit/Subscription/RenewalHandlerTest.php
index d079012c3..84ecb0447 100644
--- a/tests/PHPUnit/Subscription/RenewalHandlerTest.php
+++ b/tests/PHPUnit/Subscription/RenewalHandlerTest.php
@@ -25,6 +25,7 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
+use function Brain\Monkey\Functions\when;
class RenewalHandlerTest extends TestCase
{
@@ -115,6 +116,9 @@ class RenewalHandlerTest extends TestCase
->shouldReceive('payment_source')
->andReturn(null);
+ $wcOrder
+ ->shouldReceive('get_meta')
+ ->andReturn('');
$wcOrder
->shouldReceive('get_id')
->andReturn(1);
@@ -154,6 +158,8 @@ class RenewalHandlerTest extends TestCase
->with([$purchaseUnit], 'no_shipping', $payer, $token)
->andReturn($order);
+ when('wcs_get_subscriptions_for_order')->justReturn(array());
+
$wcOrder->shouldReceive('update_status');
$wcOrder->shouldReceive('save');
diff --git a/tests/PHPUnit/TestCase.php b/tests/PHPUnit/TestCase.php
index f5a480ddc..4e6c859a1 100644
--- a/tests/PHPUnit/TestCase.php
+++ b/tests/PHPUnit/TestCase.php
@@ -28,8 +28,17 @@ class TestCase extends \PHPUnit\Framework\TestCase
when('wc_print_r')->alias(function ($value, bool $return = false) {
return print_r($value, $return);
});
+ when('wc_string_to_bool')->alias(function ($string) {
+ return is_bool( $string ) ? $string : ( 'yes' === strtolower( $string ) || 1 === $string || 'true' === strtolower( $string ) || '1' === $string );
+ });
when('get_plugin_data')->justReturn(['Version' => '1.0']);
when('plugin_basename')->justReturn('woocommerce-paypal-payments/woocommerce-paypal-payments.php');
+ when('get_transient')->returnArg();
+
+ when('wc_clean')->returnArg();
+ when('get_transient')->returnArg();
+ when('delete_transient')->returnArg();
+ when('wcs_get_subscription')->returnArg();
setUp();
}
diff --git a/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php b/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php
deleted file mode 100644
index 5cf5cde21..000000000
--- a/tests/PHPUnit/Vaulting/PaymentTokenRepositoryTest.php
+++ /dev/null
@@ -1,162 +0,0 @@
-factory = Mockery::mock(PaymentTokenFactory::class);
- $this->endpoint = Mockery::mock(PaymentTokenEndpoint::class);
- $this->sut = new PaymentTokenRepository($this->factory, $this->endpoint);
- }
-
- public function testForUserIdFromArray()
- {
- $id = 1;
- $token = ['id' => 'foo'];
- $paymentToken = Mockery::mock(PaymentToken::class);
- $paymentToken->shouldReceive('id')
- ->andReturn('foo');
-
- expect('get_user_meta')->with($id, $this->sut::USER_META, true)
- ->andReturn($token);
-
- $this->factory->shouldReceive('from_array')->with($token)
- ->andReturn($paymentToken);
-
- $result = $this->sut->for_user_id($id);
- $this->assertInstanceOf(PaymentToken::class, $result);
- }
-
- public function testFetchForUserId()
- {
- $id = 1;
- $source = new \stdClass();
- $paymentToken = new PaymentToken('foo', $source, 'PAYMENT_METHOD_TOKEN');
-
- when('get_user_meta')->justReturn([]);
- $this->endpoint->shouldReceive('for_user')
- ->with($id)
- ->andReturn([$paymentToken]);
- expect('update_user_meta')->with($id, $this->sut::USER_META, $paymentToken->to_array());
-
- $result = $this->sut->for_user_id($id);
- $this->assertInstanceOf(PaymentToken::class, $result);
- }
-
- public function testForUserIdFails()
- {
- $id = 1;
- when('get_user_meta')->justReturn([]);
-
- $this->endpoint
- ->expects('for_user')
- ->with($id)
- ->andThrow(RuntimeException::class);
-
- $result = $this->sut->for_user_id($id);
- $this->assertNull($result);
- }
-
- public function testDeleteToken()
- {
- $id = 1;
- $paymentToken = Mockery::mock(PaymentToken::class);
- $paymentToken->shouldReceive('id')
- ->andReturn('foo');
-
- expect('delete_user_meta')->with($id, $this->sut::USER_META);
- $this->endpoint->shouldReceive('delete_token')
- ->with($paymentToken);
-
- $this->sut->delete_token($id, $paymentToken);
- }
-
- public function testAllForUserId()
- {
- $id = 1;
- $tokens = [];
-
- $this->endpoint->shouldReceive('for_user')
- ->with($id)
- ->andReturn($tokens);
- expect('update_user_meta')->with($id, $this->sut::USER_META, $tokens);
-
- $result = $this->sut->all_for_user_id($id);
- $this->assertSame($tokens, $result);
- }
-
- public function test_AllForUserIdReturnsEmptyArrayIfGettingTokenFails()
- {
- $id = 1;
- $tokens = [];
-
- $this->endpoint
- ->expects('for_user')
- ->with($id)
- ->andThrow(RuntimeException::class);
-
- $result = $this->sut->all_for_user_id($id);
- $this->assertSame($tokens, $result);
- }
-
- public function testTokensContainCardReturnsTrue()
- {
- $source = new \stdClass();
- $card = new \stdClass();
- $source->card = $card;
- $token = Mockery::mock(PaymentToken::class);
- $tokens = [$token];
-
- $token->shouldReceive('source')->andReturn($source);
-
- $this->assertTrue($this->sut->tokens_contains_card($tokens));
- }
-
- public function testTokensContainCardReturnsFalse()
- {
- $tokens = [];
- $this->assertFalse($this->sut->tokens_contains_card($tokens));
- }
-
- public function testTokensContainPayPalReturnsTrue()
- {
- $source = new \stdClass();
- $paypal = new \stdClass();
- $source->paypal = $paypal;
- $token = Mockery::mock(PaymentToken::class);
- $tokens = [$token];
-
- $token->shouldReceive('source')->andReturn($source);
-
- $this->assertTrue($this->sut->tokens_contains_paypal($tokens));
- }
-
- public function testTokensContainPayPalReturnsFalse()
- {
- $tokens = [];
- $this->assertFalse($this->sut->tokens_contains_paypal($tokens));
- }
-
-
-}
diff --git a/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php b/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
index 0f7ccedfb..a20ee8b72 100644
--- a/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
+++ b/tests/PHPUnit/WcGateway/Admin/FeesRendererTest.php
@@ -43,11 +43,32 @@ class FeesRendererTest extends TestCase
],
]);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([
+ 'gross_amount' => [
+ 'currency_code' => 'USD',
+ 'value' => '20.52',
+ ],
+ 'paypal_fee' => [
+ 'currency_code' => 'USD',
+ 'value' => '0.51',
+ ],
+ 'net_amount' => [
+ 'currency_code' => 'USD',
+ 'value' => '50.01',
+ ],
+ ]);
+
$result = $this->renderer->render($wcOrder);
$this->assertStringContainsString('Fee', $result);
$this->assertStringContainsString('0.41', $result);
$this->assertStringContainsString('Payout', $result);
$this->assertStringContainsString('10.01', $result);
+ $this->assertStringContainsString('PayPal Refund Fee', $result);
+ $this->assertStringContainsString('0.51', $result);
+ $this->assertStringContainsString('PayPal Refund', $result);
+ $this->assertStringContainsString('50.01', $result);
}
public function testRenderWithoutNet() {
@@ -62,6 +83,10 @@ class FeesRendererTest extends TestCase
],
]);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([]);
+
$result = $this->renderer->render($wcOrder);
$this->assertStringContainsString('Fee', $result);
$this->assertStringContainsString('0.41', $result);
@@ -78,6 +103,10 @@ class FeesRendererTest extends TestCase
->with(PayPalGateway::FEES_META_KEY)
->andReturn($meta);
+ $wcOrder->expects('get_meta')
+ ->with(PayPalGateway::REFUND_FEES_META_KEY)
+ ->andReturn([]);
+
$this->assertSame('', $this->renderer->render($wcOrder));
}
diff --git a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
index 259a25ebb..95aa53e0c 100644
--- a/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
+++ b/tests/PHPUnit/WcGateway/Assets/SettingsPagesAssetsTest.php
@@ -3,6 +3,7 @@
namespace WooCommerce\PayPalCommerce\WcGateway\Assets;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
+use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use WooCommerce\PayPalCommerce\TestCase;
use function Brain\Monkey\Functions\when;
@@ -23,10 +24,12 @@ class SettingsPagesAssetsTest extends TestCase
'123',
'EUR',
'DE',
+ Mockery::mock(Environment::class),
true,
array(),
- array()
- );
+ array(),
+ true
+ );
when('is_admin')
->justReturn(true);
diff --git a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php
index 693658756..5e2a8bbc7 100644
--- a/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php
+++ b/tests/PHPUnit/WcGateway/Gateway/CreditCardGatewayTest.php
@@ -78,6 +78,12 @@ class CreditCardGatewayTest extends TestCase
$wc_order = Mockery::mock(WC_Order::class);
when('wc_get_order')->justReturn($wc_order);
+ $woocommerce = Mockery::mock(\WooCommerce::class);
+ $session = Mockery::mock(\WC_Session::class);
+ when('WC')->justReturn($woocommerce);
+ $woocommerce->session = $session;
+ $session->shouldReceive('set')->andReturn([]);
+
$this->orderProcessor->shouldReceive('process')
->with($wc_order)
->andReturn(true);
@@ -95,6 +101,12 @@ class CreditCardGatewayTest extends TestCase
$wc_order->shouldReceive('get_customer_id')->andReturn(1);
when('wc_get_order')->justReturn($wc_order);
+ $woocommerce = Mockery::mock(\WooCommerce::class);
+ $session = Mockery::mock(\WC_Session::class);
+ when('WC')->justReturn($woocommerce);
+ $woocommerce->session = $session;
+ $session->shouldReceive('set')->andReturn([]);
+
$savedCreditCard = 'abc123';
$_POST['saved_credit_card'] = $savedCreditCard;
diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php
index b62f013d7..aa9057c67 100644
--- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php
+++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway;
use Psr\Log\LoggerInterface;
+use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
+use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
use WooCommerce\PayPalCommerce\Onboarding\Environment;
use WooCommerce\PayPalCommerce\Onboarding\State;
use WooCommerce\PayPalCommerce\Session\SessionHandler;
@@ -39,6 +42,7 @@ class WcGatewayTest extends TestCase
private $paymentTokenRepository;
private $logger;
private $apiShopCountry;
+ private $orderEndpoint;
public function setUp(): void {
parent::setUp();
@@ -59,8 +63,12 @@ class WcGatewayTest extends TestCase
$this->environment = Mockery::mock(Environment::class);
$this->paymentTokenRepository = Mockery::mock(PaymentTokenRepository::class);
$this->logger = Mockery::mock(LoggerInterface::class);
- $this->funding_source_renderer = new FundingSourceRenderer($this->settings);
+ $this->funding_source_renderer = new FundingSourceRenderer(
+ $this->settings,
+ ['venmo' => 'Venmo', 'paylater' => 'Pay Later', 'blik' => 'BLIK']
+ );
$this->apiShopCountry = 'DE';
+ $this->orderEndpoint = Mockery::mock(OrderEndpoint::class);
$this->onboardingState->shouldReceive('current_state')->andReturn(State::STATE_ONBOARDED);
@@ -69,6 +77,11 @@ class WcGatewayTest extends TestCase
->andReturnUsing(function () {
return $this->fundingSource;
});
+ $order = Mockery::mock(Order::class);
+ $order->shouldReceive('status')->andReturn(new OrderStatus(OrderStatus::APPROVED));
+ $this->sessionHandler
+ ->shouldReceive('order')
+ ->andReturn($order);
$this->settings->shouldReceive('has')->andReturnFalse();
@@ -92,7 +105,8 @@ class WcGatewayTest extends TestCase
$this->environment,
$this->paymentTokenRepository,
$this->logger,
- $this->apiShopCountry
+ $this->apiShopCountry,
+ $this->orderEndpoint
);
}
@@ -134,6 +148,11 @@ class WcGatewayTest extends TestCase
$woocommerce->cart = $cart;
$cart->shouldReceive('empty_cart');
+ $session = Mockery::mock(\WC_Session::class);
+ $woocommerce->session = $session;
+ $session->shouldReceive('get');
+ $session->shouldReceive('set');
+
$result = $testee->process_payment($orderId);
$this->assertIsArray($result);
@@ -147,6 +166,12 @@ class WcGatewayTest extends TestCase
$testee = $this->createGateway();
+ $woocommerce = Mockery::mock(\WooCommerce::class);
+ $session = Mockery::mock(\WC_Session::class);
+ when('WC')->justReturn($woocommerce);
+ $woocommerce->session = $session;
+ $session->shouldReceive('set')->andReturn([]);
+
expect('wc_get_order')
->with($orderId)
->andReturn(false);
@@ -161,12 +186,17 @@ class WcGatewayTest extends TestCase
expect('wc_add_notice');
+ $result = $testee->process_payment($orderId);
+
+ $this->assertArrayHasKey('errorMessage', $result);
+ unset($result['errorMessage']);
+
$this->assertEquals(
[
'result' => 'failure',
- 'redirect' => $redirectUrl
+ 'redirect' => $redirectUrl,
],
- $testee->process_payment($orderId)
+ $result
);
}
@@ -200,11 +230,22 @@ class WcGatewayTest extends TestCase
when('wc_get_checkout_url')
->justReturn($redirectUrl);
- $result = $testee->process_payment($orderId);
- $this->assertEquals(
- [
- 'result' => 'failure',
- 'redirect' => $redirectUrl
+ $woocommerce = Mockery::mock(\WooCommerce::class);
+ when('WC')->justReturn($woocommerce);
+ $session = Mockery::mock(\WC_Session::class);
+ $woocommerce->session = $session;
+ $session->shouldReceive('get');
+ $session->shouldReceive('set');
+
+ $result = $testee->process_payment($orderId);
+
+ $this->assertArrayHasKey('errorMessage', $result);
+ unset($result['errorMessage']);
+
+ $this->assertEquals(
+ [
+ 'result' => 'failure',
+ 'redirect' => $redirectUrl,
],
$result
);
@@ -271,6 +312,8 @@ class WcGatewayTest extends TestCase
return [
[null, 'PayPal', 'Pay via PayPal.'],
['venmo', 'Venmo', 'Pay via Venmo.'],
+ ['paylater', 'Pay Later', 'Pay via Pay Later.'],
+ ['blik', 'BLIK (via PayPal)', 'Pay via BLIK.'],
['qwerty', 'PayPal', 'Pay via PayPal.'],
];
}
diff --git a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
index 32a2366ca..97ceca9b5 100644
--- a/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
+++ b/tests/PHPUnit/WcGateway/Processor/OrderProcessorTest.php
@@ -57,6 +57,7 @@ class OrderProcessorTest extends TestCase
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
+ $wcOrder->expects('get_items')->andReturn([]);
$wcOrder->expects('update_meta_data')
->with(PayPalGateway::ORDER_PAYMENT_MODE_META_KEY, 'live');
$wcOrder->shouldReceive('get_id')->andReturn(1);
@@ -193,7 +194,8 @@ class OrderProcessorTest extends TestCase
->andReturn($payments);
$wcOrder = Mockery::mock(\WC_Order::class);
- $orderStatus = Mockery::mock(OrderStatus::class);
+ $wcOrder->expects('get_items')->andReturn([]);
+ $orderStatus = Mockery::mock(OrderStatus::class);
$orderStatus
->shouldReceive('is')
->with(OrderStatus::APPROVED)
diff --git a/tests/PHPUnit/WcGateway/Repository/ApplicationContextRepositoryTest.php b/tests/PHPUnit/WcGateway/Repository/ApplicationContextRepositoryTest.php
index 7d3bf003a..1bd5b59f3 100644
--- a/tests/PHPUnit/WcGateway/Repository/ApplicationContextRepositoryTest.php
+++ b/tests/PHPUnit/WcGateway/Repository/ApplicationContextRepositoryTest.php
@@ -73,6 +73,7 @@ class ApplicationContextRepositoryTest extends TestCase
'container' => [
'brand_name' => 'Acme corp.',
'landing_page' => ApplicationContext::LANDING_PAGE_BILLING,
+ 'payee_preferred' => '',
],
'user_locale' => 'de_DE',
'shippingPreference' => ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
@@ -81,6 +82,7 @@ class ApplicationContextRepositoryTest extends TestCase
'brand_name' => 'Acme corp.',
'landing_page' => ApplicationContext::LANDING_PAGE_BILLING,
'shipping_preference' => ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
+ 'payment_method_preference' => ApplicationContext::PAYMENT_METHOD_UNRESTRICTED,
],
],
];
diff --git a/tests/PHPUnit/WcGateway/Settings/LocationsTest.php b/tests/PHPUnit/WcGateway/Settings/LocationsTest.php
new file mode 100644
index 000000000..45cb63ffa
--- /dev/null
+++ b/tests/PHPUnit/WcGateway/Settings/LocationsTest.php
@@ -0,0 +1,60 @@
+settings = new SettingsStub([]);
+
+ $this->appContainer = $this->bootstrapModule([
+ 'wcgateway.settings' => function () {
+ return $this->settings;
+ },
+ ]);
+ }
+
+ /**
+ * @dataProvider payLaterButtonLocationsData
+ */
+ public function testPayLaterButtonLocations(array $selectedLocations, array $expectedResult) {
+ $this->settings->set('smart_button_locations', $selectedLocations);
+
+ $result = $this->appContainer->get('wcgateway.settings.pay-later.button-locations');
+
+ self::assertEquals($expectedResult, $result);
+ }
+
+ public function payLaterButtonLocationsData()
+ {
+ yield [
+ ['product', 'cart', 'checkout', 'mini-cart'],
+ [
+ 'product' => 'Single Product',
+ 'cart' => 'Cart',
+ 'checkout' => 'Checkout',
+ 'mini-cart' => 'Mini Cart',
+ ],
+ ];
+ yield [
+ ['cart', 'checkout'],
+ [
+ 'cart' => 'Cart',
+ 'checkout' => 'Checkout',
+ ],
+ ];
+ yield [
+ [],
+ [],
+ ];
+ }
+}
diff --git a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php
index bad2dda33..c66665bbc 100644
--- a/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php
+++ b/tests/PHPUnit/WcGateway/Settings/SettingsListenerTest.php
@@ -5,6 +5,8 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
use Requests_Utility_CaseInsensitiveDictionary;
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
+use WooCommerce\PayPalCommerce\Helper\RedirectorStub;
+use WooCommerce\PayPalCommerce\Helper\StubRedirectionException;
use WooCommerce\PayPalCommerce\ModularTestCase;
use WooCommerce\PayPalCommerce\Onboarding\State;
use Mockery;
@@ -50,7 +52,10 @@ class SettingsListenerTest extends ModularTestCase
$signup_link_cache,
$signup_link_ids,
$pui_status_cache,
- $dcc_status_cache
+ $dcc_status_cache,
+ new RedirectorStub(),
+ '',
+ ''
);
$_GET['section'] = PayPalGateway::ID;
diff --git a/tests/PHPUnit/bootstrap.php b/tests/PHPUnit/bootstrap.php
index a674e4167..75145ac11 100644
--- a/tests/PHPUnit/bootstrap.php
+++ b/tests/PHPUnit/bootstrap.php
@@ -8,5 +8,6 @@ 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';
require_once TESTS_ROOT_DIR . '/stubs/WC_Ajax.php';
+require_once TESTS_ROOT_DIR . '/stubs/WC_Checkout.php';
Hamcrest\Util::registerGlobalFunctions();
diff --git a/tests/Playwright/.env.example b/tests/Playwright/.env.example
new file mode 100644
index 000000000..3de8add8c
--- /dev/null
+++ b/tests/Playwright/.env.example
@@ -0,0 +1,36 @@
+BASEURL="https://woocommerce-paypal-payments.ddev.site"
+AUTHORIZATION="Bearer ABC123"
+
+CHECKOUT_URL="/checkout"
+CHECKOUT_PAGE_ID=7
+CART_URL="/cart"
+BLOCK_CHECKOUT_URL="/checkout-block"
+BLOCK_CHECKOUT_PAGE_ID=22
+BLOCK_CART_URL="/cart-block"
+
+PRODUCT_URL="/product/prod"
+PRODUCT_ID=123
+
+SUBSCRIPTION_URL="/product/sub"
+
+APM_ID="sofort"
+
+WP_MERCHANT_USER="admin"
+WP_MERCHANT_PASSWORD="admin"
+
+WP_CUSTOMER_USER="customer"
+WP_CUSTOMER_PASSWORD="password"
+
+CUSTOMER_EMAIL="customer@example.com"
+CUSTOMER_PASSWORD="password"
+CUSTOMER_FIRST_NAME="John"
+CUSTOMER_LAST_NAME="Doe"
+CUSTOMER_COUNTRY="DE"
+CUSTOMER_ADDRESS="street 1"
+CUSTOMER_POSTCODE="12345"
+CUSTOMER_CITY="city"
+CUSTOMER_PHONE="1234567890"
+
+CREDIT_CARD_NUMBER="1234567890"
+CREDIT_CARD_EXPIRATION="01/2042"
+CREDIT_CARD_CVV="123"
diff --git a/tests/Playwright/.gitignore b/tests/Playwright/.gitignore
new file mode 100644
index 000000000..802710f25
--- /dev/null
+++ b/tests/Playwright/.gitignore
@@ -0,0 +1,6 @@
+node_modules
+pw-browsers
+test-results
+.env
+yarn.lock
+playwright-report/*
diff --git a/tests/Playwright/README.md b/tests/Playwright/README.md
new file mode 100644
index 000000000..d837443ce
--- /dev/null
+++ b/tests/Playwright/README.md
@@ -0,0 +1,22 @@
+## ddev-playwright addon
+https://github.com/julienloizelet/ddev-playwright
+
+### Install
+```
+$ ddev restart
+$ ddev playwright-install
+```
+
+### Usage
+https://github.com/julienloizelet/ddev-playwright#basic-usage
+```
+$ ddev playwright test
+```
+
+### Known issues
+It does not open browser in macOS, to make it work use `npx`:
+```
+$ cd tests/Playwright
+$ npx playwright test
+```
+
diff --git a/tests/Playwright/package.json b/tests/Playwright/package.json
new file mode 100644
index 000000000..e2e3e6465
--- /dev/null
+++ b/tests/Playwright/package.json
@@ -0,0 +1,8 @@
+{
+ "license": "MIT",
+ "dependencies": {
+ "@playwright/test": "^1.34.2",
+ "@woocommerce/woocommerce-rest-api": "^1.0.1",
+ "dotenv": "^16.0.3"
+ }
+}
diff --git a/tests/Playwright/playwright.config.js b/tests/Playwright/playwright.config.js
new file mode 100644
index 000000000..dbe3173da
--- /dev/null
+++ b/tests/Playwright/playwright.config.js
@@ -0,0 +1,78 @@
+// @ts-check
+const { defineConfig, devices } = require('@playwright/test');
+
+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',
+ },
+
+ /* Configure projects for major browsers */
+ projects: [
+ {
+ name: 'chromium',
+ use: { ...devices['Desktop Chrome'] },
+ },
+
+ // {
+ // 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 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,
+ // },
+});
diff --git a/tests/Playwright/tests/place-order.spec.js b/tests/Playwright/tests/place-order.spec.js
new file mode 100644
index 000000000..4a9f7523f
--- /dev/null
+++ b/tests/Playwright/tests/place-order.spec.js
@@ -0,0 +1,207 @@
+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 = 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 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 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);
+ });
+ });
+});
diff --git a/tests/Playwright/tests/subscriptions-api.spec.js b/tests/Playwright/tests/subscriptions-api.spec.js
new file mode 100644
index 000000000..178781a56
--- /dev/null
+++ b/tests/Playwright/tests/subscriptions-api.spec.js
@@ -0,0 +1,389 @@
+const {test, expect} = require('@playwright/test');
+
+const {loginAsAdmin, loginAsCustomer} = require('./utils/user');
+const {openPaypalPopup, loginIntoPaypal, completePaypalPayment} = require("./utils/paypal-popup");
+const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout");
+const {createProduct, deleteProduct, updateProduct, updateProductUi} = require("./utils/products");
+const {
+ AUTHORIZATION,
+ SUBSCRIPTION_URL,
+ CHECKOUT_URL,
+ CART_URL,
+} = process.env;
+
+const longText = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ultricies integer quis auctor elit sed vulputate mi. Aliquam sem et tortor consequat id porta nibh venenatis cras. Massa enim nec dui nunc. Nulla porttitor massa id neque aliquam vestibulum morbi blandit cursus. Eu lobortis elementum nibh tellus molestie nunc. Euismod nisi porta lorem mollis aliquam ut porttitor. Ultrices tincidunt arcu non sodales neque sodales ut etiam. Urna cursus eget nunc scelerisque. Pulvinar sapien et ligula ullamcorper malesuada proin libero. Convallis a cras semper auctor neque vitae tempus quam pellentesque. Phasellus egestas tellus rutrum tellus pellentesque eu tincidunt tortor aliquam. Cras tincidunt lobortis feugiat vivamus. Nec ultrices dui sapien eget mi proin sed libero enim. Neque gravida in fermentum et sollicitudin ac orci phasellus egestas. Aliquam faucibus purus in massa. Viverra accumsan in nisl nisi scelerisque eu ultrices vitae. At augue eget arcu dictum varius duis. Commodo ullamcorper a lacus vestibulum sed arcu non odio.\n' +
+ '\n' +
+ 'Id cursus metus aliquam eleifend mi in nulla. A diam sollicitudin tempor id eu nisl. Faucibus purus in massa tempor. Lacus luctus accumsan tortor posuere ac ut consequat. Mauris augue neque gravida in fermentum et sollicitudin ac. Venenatis tellus in metus vulputate. Consectetur libero id faucibus nisl tincidunt eget. Pellentesque eu tincidunt tortor aliquam nulla facilisi cras fermentum odio. Dolor sed viverra ipsum nunc aliquet bibendum. Turpis in eu mi bibendum neque. Ac tincidunt vitae semper quis lectus nulla at volutpat. Felis imperdiet proin fermentum leo vel orci porta. Sed sed risus pretium quam vulputate dignissim.\n' +
+ '\n' +
+ 'Urna et pharetra pharetra massa massa ultricies mi quis. Egestas purus viverra accumsan in nisl nisi. Elit sed vulputate mi sit amet mauris commodo. Cras fermentum odio eu feugiat pretium nibh ipsum consequat. Justo laoreet sit amet cursus sit amet dictum. Nunc id cursus metus aliquam. Tortor at auctor urna nunc id. Quis lectus nulla at volutpat diam ut. Lorem ipsum dolor sit amet consectetur adipiscing elit pellentesque. Tincidunt lobortis feugiat vivamus at augue eget arcu dictum varius.\n' +
+ '\n' +
+ 'Mattis nunc sed blandit libero. Vitae ultricies leo integer malesuada nunc vel risus. Dapibus ultrices in iaculis nunc. Interdum varius sit amet mattis. Tortor vitae purus faucibus ornare. Netus et malesuada fames ac turpis. Elit duis tristique sollicitudin nibh sit amet. Lacus suspendisse faucibus interdum posuere lorem. In pellentesque massa placerat duis. Fusce ut placerat orci nulla pellentesque dignissim. Dictum fusce ut placerat orci nulla pellentesque dignissim enim. Nibh sit amet commodo nulla facilisi. Maecenas sed enim ut sem. Non consectetur a erat nam at lectus urna duis convallis. Diam phasellus vestibulum lorem sed risus ultricies tristique nulla. Nunc congue nisi vitae suscipit. Tortor condimentum lacinia quis vel eros donec ac. Eleifend mi in nulla posuere.\n' +
+ '\n' +
+ 'Vestibulum lectus mauris ultrices eros. Massa sed elementum tempus egestas sed sed risus. Ut placerat orci nulla pellentesque dignissim enim sit. Duis ut diam quam nulla porttitor. Morbi tincidunt ornare massa eget egestas purus. Commodo sed egestas egestas fringilla phasellus faucibus scelerisque eleifend donec. Arcu odio ut sem nulla pharetra diam sit. Risus sed vulputate odio ut enim. Faucibus et molestie ac feugiat. A scelerisque purus semper eget. Odio facilisis mauris sit amet massa vitae tortor. Condimentum vitae sapien pellentesque habitant morbi tristique senectus. Nec feugiat in fermentum posuere urna. Volutpat est velit egestas dui id ornare arcu odio ut. Ullamcorper malesuada proin libero nunc consequat interdum. Suspendisse in est ante in nibh mauris cursus mattis molestie. Vel eros donec ac odio tempor orci dapibus. Et tortor at risus viverra adipiscing at in tellus. Metus aliquam eleifend mi in.'
+
+async function purchaseSubscriptionFromCart(page) {
+ await loginAsCustomer(page);
+ await page.goto(SUBSCRIPTION_URL);
+ await page.click("text=Sign up now");
+ await page.goto(CART_URL);
+
+ const popup = await openPaypalPopup(page);
+ await loginIntoPaypal(popup);
+
+ await popup.getByText('Continue', {exact: true}).click();
+ await popup.locator('#confirmButtonTop').click();
+
+ await fillCheckoutForm(page);
+
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('text=Sign up now').click(),
+ ]);
+
+ await expectOrderReceivedPage(page);
+}
+
+test.describe.serial('Subscriptions Merchant', () => {
+ const productTitle = (Math.random() + 1).toString(36).substring(7);
+ const planName = (Math.random() + 1).toString(36).substring(7);
+ let product_id = '';
+ let plan_id = '';
+
+ test('Create new subscription product', async ({page, request}) => {
+ await loginAsAdmin(page);
+
+ await page.goto('/wp-admin/post-new.php?post_type=product');
+ await page.fill('#title', productTitle);
+ await page.selectOption('select#product-type', 'subscription');
+ await page.fill('#_subscription_price', '10');
+ await page.locator('#ppcp_enable_subscription_product').check();
+ await page.fill('#ppcp_subscription_plan_name', planName);
+
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('#publish').click(),
+ ]);
+
+ const message = await page.locator('.notice-success');
+ await expect(message).toContainText('Product published.');
+
+ const products = await request.get('https://api-m.sandbox.paypal.com/v1/catalogs/products?page_size=100&page=1&total_required=true', {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(products.ok()).toBeTruthy();
+
+ const productList = await products.json();
+ const product = productList.products.find((p) => {
+ return p.name === productTitle;
+ });
+ await expect(product.id).toBeTruthy;
+
+ product_id = product.id;
+
+ const plans = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/plans?product_id=${product_id}&page_size=10&page=1&total_required=true`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(plans.ok()).toBeTruthy();
+
+ const planList = await plans.json();
+ const plan = planList.plans.find((p) => {
+ return p.product_id === product.id;
+ });
+ await expect(plan.id).toBeTruthy;
+
+ plan_id = plan.id;
+ });
+
+ test('Update subscription product', async ({page, request}) => {
+ await loginAsAdmin(page);
+
+ await page.goto('/wp-admin/edit.php?post_type=product');
+ await page.getByRole('link', {name: productTitle, exact: true}).click();
+
+ await page.fill('#title', `Updated ${productTitle}`);
+ await page.fill('#_subscription_price', '20');
+ await page.fill('#content', longText)
+
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('#publish').click(),
+ ]);
+
+ const message = await page.locator('.notice-success');
+ await expect(message).toContainText('Product updated.');
+
+ const products = await request.get('https://api-m.sandbox.paypal.com/v1/catalogs/products?page_size=100&page=1&total_required=true', {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(products.ok()).toBeTruthy();
+
+ const productList = await products.json();
+ const product = productList.products.find((p) => {
+ return p.name === `Updated ${productTitle}`;
+ });
+ await expect(product.id).toBeTruthy;
+
+ const plan = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/plans/${plan_id}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(plan.ok()).toBeTruthy();
+
+ const plan_content = await plan.json();
+ await expect(plan_content.billing_cycles[0].pricing_scheme.fixed_price.value).toBe('20.0')
+ });
+});
+
+test('Create new free trial subscription product', async ({page, request}) => {
+ const productTitle = (Math.random() + 1).toString(36).substring(7);
+ const planName = (Math.random() + 1).toString(36).substring(7);
+ await loginAsAdmin(page);
+
+ await page.goto('/wp-admin/post-new.php?post_type=product');
+ await page.fill('#title', productTitle);
+ await page.selectOption('select#product-type', 'subscription');
+ await page.fill('#_subscription_price', '42');
+ await page.fill('#_subscription_trial_length', '15');
+
+ await page.locator('#ppcp_enable_subscription_product').check();
+ await page.fill('#ppcp_subscription_plan_name', planName);
+
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('#publish').click(),
+ ]);
+
+ const message = await page.locator('.notice-success');
+ await expect(message).toContainText('Product published.');
+
+ const products = await request.get('https://api-m.sandbox.paypal.com/v1/catalogs/products?page_size=100&page=1&total_required=true', {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(products.ok()).toBeTruthy();
+
+ const productList = await products.json();
+ const product = productList.products.find((p) => {
+ return p.name === productTitle;
+ });
+ await expect(product.id).toBeTruthy;
+
+ const plans = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/plans?product_id=${product.id}&page_size=10&page=1&total_required=true`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(plans.ok()).toBeTruthy();
+
+ const planList = await plans.json();
+ const plan = planList.plans.find((p) => {
+ return p.product_id === product.id;
+ });
+ await expect(plan.id).toBeTruthy;
+
+ const planDetail = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/plans/${plan.id}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(planDetail.ok()).toBeTruthy();
+ const planDetailContent = await planDetail.json();
+
+ await expect(planDetailContent.billing_cycles[0].tenure_type).toBe('TRIAL');
+ await expect(planDetailContent.billing_cycles[0].pricing_scheme.fixed_price.value).toBe('0.0');
+ await expect(planDetailContent.billing_cycles[1].tenure_type).toBe('REGULAR');
+ await expect(planDetailContent.billing_cycles[1].pricing_scheme.fixed_price.value).toBe('42.0');
+});
+
+test.describe('Subscriber purchase a Subscription', () => {
+ test('Purchase Subscription from Checkout Page', async ({page}) => {
+ await loginAsCustomer(page);
+
+ await page.goto(SUBSCRIPTION_URL);
+ await page.click("text=Sign up now");
+ await page.goto(CHECKOUT_URL);
+ await fillCheckoutForm(page);
+
+ const popup = await openPaypalPopup(page);
+ await loginIntoPaypal(popup);
+
+ await popup.getByText('Continue', {exact: true}).click();
+
+ await Promise.all([
+ page.waitForNavigation(),
+ await popup.locator('text=Agree & Subscribe').click(),
+ ]);
+
+ await expectOrderReceivedPage(page);
+ });
+
+ test('Purchase Subscription from Single Product Page', async ({page}) => {
+ await loginAsCustomer(page);
+ await page.goto(SUBSCRIPTION_URL);
+
+ const popup = await openPaypalPopup(page);
+ await loginIntoPaypal(popup);
+
+ await popup.getByText('Continue', {exact: true}).click();
+ await popup.locator('#confirmButtonTop').click();
+
+ await fillCheckoutForm(page);
+
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('text=Sign up now').click(),
+ ]);
+
+ await expectOrderReceivedPage(page);
+ });
+
+ test('Purchase Subscription from Cart Page', async ({page}) => {
+ await purchaseSubscriptionFromCart(page);
+ });
+});
+
+test.describe('Subscriber my account actions', () => {
+ test('Subscriber Suspend Subscription', async ({page, request}) => {
+ await purchaseSubscriptionFromCart(page);
+ await page.goto('/my-account/subscriptions');
+ await page.locator('text=View').first().click();
+
+ const subscriptionId = await page.locator('#ppcp-subscription-id').textContent();
+ let subscription = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${subscriptionId}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(subscription.ok()).toBeTruthy();
+ let details = await subscription.json();
+ await expect(details.status).toBe('ACTIVE');
+
+ await page.locator('text=Suspend').click();
+ const title = page.locator('.woocommerce-message');
+ await expect(title).toHaveText('Your subscription has been cancelled.');
+
+ subscription = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${subscriptionId}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(subscription.ok()).toBeTruthy();
+
+ details = await subscription.json();
+ await expect(details.status).toBe('SUSPENDED');
+ });
+
+ test('Subscriber Cancel Subscription', async ({page, request}) => {
+ await purchaseSubscriptionFromCart(page);
+ await page.goto('/my-account/subscriptions');
+ await page.locator('text=View').first().click();
+
+ const subscriptionId = await page.locator('#ppcp-subscription-id').textContent();
+ let subscription = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${subscriptionId}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(subscription.ok()).toBeTruthy();
+ let details = await subscription.json();
+ await expect(details.status).toBe('ACTIVE');
+
+ await page.locator('text=Cancel').click();
+ const title = page.locator('.woocommerce-message');
+ await expect(title).toHaveText('Your subscription has been cancelled.');
+
+ subscription = await request.get(`https://api-m.sandbox.paypal.com/v1/billing/subscriptions/${subscriptionId}`, {
+ headers: {
+ 'Authorization': AUTHORIZATION,
+ 'Content-Type': 'application/json'
+ }
+ });
+ expect(subscription.ok()).toBeTruthy();
+
+ details = await subscription.json();
+ await expect(details.status).toBe('CANCELLED');
+ });
+});
+
+test.describe('Plan connected display buttons', () => {
+ test('Disable buttons if no plan connected', async ({page}) => {
+ const data = {
+ name: (Math.random() + 1).toString(36).substring(7),
+ type: 'subscription',
+ meta_data: [
+ {
+ key: '_subscription_price',
+ value: '10'
+ }
+ ]
+ }
+ const productId = await createProduct(data)
+
+ // for some reason product meta is not updated in frontend,
+ // so we need to manually update the product
+ await updateProductUi(productId, page);
+
+ await page.goto(`/product/?p=${productId}`)
+ await expect(page.locator('#ppc-button-ppcp-gateway')).not.toBeVisible();
+
+ await page.locator('.single_add_to_cart_button').click();
+ await page.goto('/cart');
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toHaveCSS('cursor', 'not-allowed')
+
+ await page.goto('/checkout');
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toHaveCSS('cursor', 'not-allowed')
+
+ await deleteProduct(productId)
+ })
+
+ test('Enable buttons if plan connected', async ({page}) => {
+ const data = {
+ name: (Math.random() + 1).toString(36).substring(7),
+ type: 'subscription',
+ meta_data: [
+ {
+ key: '_subscription_price',
+ value: '10'
+ }
+ ]
+ }
+ const productId = await createProduct(data)
+
+ await loginAsAdmin(page);
+ await page.goto(`/wp-admin/post.php?post=${productId}&action=edit`)
+ await page.locator('#ppcp_enable_subscription_product').check();
+ await page.locator('#ppcp_subscription_plan_name').fill('Plan name');
+ await page.locator('#publish').click();
+ await expect(page.getByText('Product updated.')).toBeVisible();
+
+ await page.goto(`/product/?p=${productId}`)
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
+
+ await page.locator('.single_add_to_cart_button').click();
+ await page.goto('/cart');
+ await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
+
+ await deleteProduct(productId)
+ })
+})
+
diff --git a/tests/Playwright/tests/utils/checkout.js b/tests/Playwright/tests/utils/checkout.js
new file mode 100644
index 000000000..a93ba8b93
--- /dev/null
+++ b/tests/Playwright/tests/utils/checkout.js
@@ -0,0 +1,42 @@
+import {expect} from "@playwright/test";
+
+const {
+ CUSTOMER_EMAIL,
+ CUSTOMER_FIRST_NAME,
+ CUSTOMER_LAST_NAME,
+ CUSTOMER_COUNTRY,
+ CUSTOMER_ADDRESS,
+ CUSTOMER_POSTCODE,
+ CUSTOMER_CITY,
+ CUSTOMER_PHONE,
+} = process.env;
+
+export const fillCheckoutForm = async (page) => {
+ await page.fill('#billing_first_name', CUSTOMER_FIRST_NAME);
+ await page.fill('#billing_last_name', CUSTOMER_LAST_NAME);
+ await page.selectOption('select#billing_country', CUSTOMER_COUNTRY);
+ await page.fill('#billing_address_1', CUSTOMER_ADDRESS);
+ await page.fill('#billing_postcode', CUSTOMER_POSTCODE);
+ await page.fill('#billing_city', CUSTOMER_CITY);
+ await page.fill('#billing_phone', CUSTOMER_PHONE);
+ await page.fill('#billing_email', CUSTOMER_EMAIL);
+
+ const differentShippingLocator = page.locator('[name="ship_to_different_address"]');
+ if (await differentShippingLocator.count() > 0) {
+ await differentShippingLocator.uncheck();
+ }
+
+ await acceptTerms(page);
+}
+
+export const acceptTerms = async (page) => {
+ const termsLocator = page.locator('[name="terms"]');
+ if (await termsLocator.count() > 0) {
+ await termsLocator.check();
+ }
+}
+
+export const expectOrderReceivedPage = async (page) => {
+ const title = await page.locator('.entry-title');
+ await expect(title).toHaveText('Order received');
+}
diff --git a/tests/Playwright/tests/utils/paypal-popup.js b/tests/Playwright/tests/utils/paypal-popup.js
new file mode 100644
index 000000000..3b84e7417
--- /dev/null
+++ b/tests/Playwright/tests/utils/paypal-popup.js
@@ -0,0 +1,110 @@
+import {expect} from "@playwright/test";
+
+const {
+ CUSTOMER_EMAIL,
+ CUSTOMER_PASSWORD,
+} = process.env;
+
+/**
+ * Opens the PayPal popup by pressing the button, and returns the popup object.
+ * @param page
+ * @param {{timeout: ?int, fundingSource: ?string}} options
+ * @param {boolean} retry Retries the button click if the popup did not appear after timeout.
+ */
+export const openPaypalPopup = async (page, options = {}, retry = true) => {
+ options = {
+ ...{
+ timeout: 5000,
+ fundingSource: 'paypal',
+ },
+ ...options
+ };
+
+ try {
+ await page.locator('.component-frame').scrollIntoViewIfNeeded();
+
+ const [popup] = await Promise.all([
+ page.waitForEvent('popup', {timeout: options.timeout}),
+ page.frameLocator('.component-frame').locator(`[data-funding-source="${options.fundingSource}"]`).click(),
+ ]);
+
+ await popup.waitForLoadState();
+
+ return popup;
+ } catch (err) {
+ try {
+ for (const f of page.mainFrame().childFrames()) {
+ if (f.name().startsWith('__paypal_checkout')) {
+ for (const f2 of f.childFrames()) {
+ if (f.name().includes('__paypal_checkout')) {
+ await f2.waitForLoadState();
+ await expect(await f2.locator('#main')).toBeVisible();
+ return f2;
+ }
+ }
+ }
+ }
+ } catch (frameErr) {
+ console.log(frameErr)
+ }
+
+ if (retry) {
+ return openPaypalPopup(page, options, false);
+ }
+ throw err;
+ }
+}
+
+export const loginIntoPaypal = async (popup, retry = true) => {
+ await Promise.any([
+ popup.locator('[name="login_email"]'),
+ popup.click("text=Log in"),
+ ]);
+
+ await popup.fill('[name="login_email"]', CUSTOMER_EMAIL);
+
+ const nextButtonLocator = popup.locator('#btnNext');
+ // Sometimes we get a popup with email and password fields at the same screen
+ if (await nextButtonLocator.count() > 0) {
+ await nextButtonLocator.click();
+ }
+
+ try {
+ await popup.fill('[name="login_password"]', CUSTOMER_PASSWORD, {timeout: 5000});
+ } catch (err) {
+ console.log('Failed to fill password, possibly need to enter email again, retrying')
+ if (retry) {
+ return loginIntoPaypal(popup, false);
+ }
+ throw err;
+ }
+
+ await popup.locator('#btnLogin').click();
+}
+
+/**
+ * Waits up to 15 sec for the shipping methods list to load.
+ * @param popup
+ * @returns {Promise}
+ */
+export const waitForPaypalShippingList = async (popup) => {
+ await expect(popup.locator('#shippingMethodsDropdown')).toBeVisible({timeout: 15000});
+}
+
+/**
+ * @param popup
+ * @param {{timeout: ?int, selector: ?string}} options
+ */
+export const completePaypalPayment = async (popup, options) => {
+ options = {
+ ...{
+ timeout: 20000,
+ selector: '#payment-submit-btn',
+ },
+ ...options
+ };
+ await Promise.all([
+ popup.waitForEvent('close', {timeout: options.timeout}),
+ popup.click(options.selector),
+ ]);
+}
diff --git a/tests/Playwright/tests/utils/products.js b/tests/Playwright/tests/utils/products.js
new file mode 100644
index 000000000..65fbdbe2e
--- /dev/null
+++ b/tests/Playwright/tests/utils/products.js
@@ -0,0 +1,59 @@
+import {loginAsAdmin} from "./user";
+import {expect} from "@playwright/test";
+
+const wcApi = require('@woocommerce/woocommerce-rest-api').default;
+const {
+ BASEURL,
+ WP_MERCHANT_USER,
+ WP_MERCHANT_PASSWORD,
+} = process.env;
+
+const wc = () => {
+ return new wcApi({
+ url: BASEURL,
+ consumerKey: WP_MERCHANT_USER,
+ consumerSecret: WP_MERCHANT_PASSWORD,
+ version: 'wc/v3',
+ });
+}
+
+export const createProduct = async (data) => {
+ const api = wc();
+
+ return await api.post('products', data)
+ .then((response) => {
+ return response.data.id
+ }).catch((error) => {
+ console.log(error)
+ })
+}
+
+export const updateProduct = async (data, id) => {
+ const api = wc();
+
+ return await api.put(`products/${id}`, data)
+ .then((response) => {
+ return response.data.id
+ }).catch((error) => {
+ console.log(error)
+ })
+}
+
+export const deleteProduct = async (id) => {
+ const api = wc();
+
+ return await api.delete(`products/${id}`)
+ .then((response) => {
+ return response.data.id
+ }).catch((error) => {
+ console.log(error)
+ })
+}
+
+export const updateProductUi = async (id, page) => {
+ await loginAsAdmin(page);
+ await page.goto(`/wp-admin/post.php?post=${id}&action=edit`)
+ await page.locator('#publish').click();
+ await expect(page.getByText('Product updated.')).toBeVisible();
+}
+
diff --git a/tests/Playwright/tests/utils/server.js b/tests/Playwright/tests/utils/server.js
new file mode 100644
index 000000000..351f7dfb8
--- /dev/null
+++ b/tests/Playwright/tests/utils/server.js
@@ -0,0 +1,27 @@
+const { exec } = require('node:child_process');
+
+/**
+ * Executes the command on the server (inside DDEV). Can be called inside and outside DDEV.
+ */
+export const serverExec = async (cmd) => {
+ const isDdev = process.env.IS_DDEV_PROJECT === 'true';
+ if (!isDdev) {
+ cmd = 'ddev exec ' + cmd;
+ }
+
+ console.log(cmd);
+
+ return new Promise((resolve) => exec(cmd, (error, stdout, stderr) => {
+ if (stderr) {
+ console.error(stderr);
+ }
+ if (stdout) {
+ console.log(stdout);
+ }
+ if (error) {
+ throw error;
+ } else {
+ resolve(stdout);
+ }
+ }))
+}
diff --git a/tests/Playwright/tests/utils/user.js b/tests/Playwright/tests/utils/user.js
new file mode 100644
index 000000000..2d793af4b
--- /dev/null
+++ b/tests/Playwright/tests/utils/user.js
@@ -0,0 +1,26 @@
+const {
+ WP_MERCHANT_USER,
+ WP_MERCHANT_PASSWORD,
+ WP_CUSTOMER_USER,
+ WP_CUSTOMER_PASSWORD,
+} = process.env;
+
+export const loginAsAdmin = async (page) => {
+ await page.goto('/wp-admin');
+ await page.locator('input[name="log"]').fill(WP_MERCHANT_USER);
+ await page.locator('input[name="pwd"]').fill(WP_MERCHANT_PASSWORD);
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('text=Log In').click()
+ ]);
+}
+
+export const loginAsCustomer = async (page) => {
+ await page.goto('/wp-admin');
+ await page.locator('input[name="log"]').fill(WP_CUSTOMER_USER);
+ await page.locator('input[name="pwd"]').fill(WP_CUSTOMER_PASSWORD);
+ await Promise.all([
+ page.waitForNavigation(),
+ page.locator('text=Log In').click()
+ ]);
+}
diff --git a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
index 5871dc100..6a72c4255 100644
--- a/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
+++ b/tests/e2e/PHPUnit/Order/PurchaseUnitTest.php
@@ -45,6 +45,10 @@ class PurchaseUnitTest extends TestCase
$this->puFactory = $this->container->get( 'api.factory.purchase-unit' );
assert($this->puFactory instanceof PurchaseUnitFactory);
+
+ add_filter('woocommerce_get_base_location', function () {
+ return 'AQ';
+ });
}
public function tearDown(): void
@@ -195,6 +199,7 @@ class PurchaseUnitTest extends TestCase
$product->set_regular_price((string) $data['price']);
$product->set_tax_status('taxable');
$product->set_tax_class('');
+ $product->set_virtual(true);
$product->save();
@@ -359,6 +364,11 @@ class PurchaseUnitTest extends TestCase
],
self::adaptAmountFormat([
'value' => 10.69,
+ 'breakdown' => [
+ 'item_total' => 10.69,
+ 'tax_total' => 0,
+ 'shipping' => 0,
+ ],
]),
];
}
@@ -427,6 +437,11 @@ class PurchaseUnitTest extends TestCase
],
self::adaptAmountFormat([
'value' => 10.69,
+ 'breakdown' => [
+ 'item_total' => 10.69,
+ 'tax_total' => 0,
+ 'shipping' => 0,
+ ],
], get_woocommerce_currency()),
];
}
diff --git a/tests/e2e/PHPUnit/TestCase.php b/tests/e2e/PHPUnit/TestCase.php
index 51f770cf9..f12f40690 100644
--- a/tests/e2e/PHPUnit/TestCase.php
+++ b/tests/e2e/PHPUnit/TestCase.php
@@ -3,7 +3,7 @@ declare(strict_types=1);
namespace WooCommerce\PayPalCommerce\Tests\E2e;
-use PPCP_E2E;
+use WooCommerce\PayPalCommerce\PPCP;
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
use WC_Cart;
use WC_Customer;
@@ -14,7 +14,7 @@ class TestCase extends \PHPUnit\Framework\TestCase
protected $container;
protected function getContainer(): ContainerInterface {
- return PPCP_E2E::$container;
+ return PPCP::container();
}
protected function cart(): WC_Cart {
diff --git a/tests/e2e/PHPUnit/Validation/ValidationTest.php b/tests/e2e/PHPUnit/Validation/ValidationTest.php
new file mode 100644
index 000000000..4c012e5c1
--- /dev/null
+++ b/tests/e2e/PHPUnit/Validation/ValidationTest.php
@@ -0,0 +1,72 @@
+container = $this->getContainer();
+
+ $this->sut = $this->container->get( 'button.validation.wc-checkout-validator' );
+ assert($this->sut instanceof CheckoutFormValidator);
+ }
+
+ public function testValid()
+ {
+ $this->sut->validate([
+ 'billing_first_name'=>'John',
+ 'billing_last_name'=>'Doe',
+ 'billing_company'=>'',
+ 'billing_country'=>'DE',
+ 'billing_address_1'=>'1 Main St',
+ 'billing_address_2'=>'city1',
+ 'billing_postcode'=>'11111',
+ 'billing_city'=>'city1',
+ 'billing_state'=>'DE-BW',
+ 'billing_phone'=>'12345678',
+ 'billing_email'=>'a@gmail.com',
+ 'terms-field'=>'1',
+ 'terms'=>'on',
+ ]);
+
+ self::assertTrue(true); // no assertions warnings
+ }
+
+ public function testInvalid()
+ {
+ $this->expectException(ValidationException::class);
+ $this->expectExceptionMessageMatches('/.+First name.+Postcode/i');
+
+ $this->sut->validate([
+ 'billing_first_name'=>'',
+ 'billing_postcode'=>'ABCDE',
+
+ 'billing_last_name'=>'Doe',
+ 'billing_company'=>'',
+ 'billing_country'=>'DE',
+ 'billing_address_1'=>'1 Main St',
+ 'billing_address_2'=>'city1',
+ 'billing_city'=>'city1',
+ 'billing_state'=>'DE-BW',
+ 'billing_phone'=>'12345678',
+ 'billing_email'=>'a@gmail.com',
+ 'terms-field'=>'1',
+ 'terms'=>'on',
+ ]);
+ }
+}
diff --git a/tests/e2e/PHPUnit/ppcp-e2e-plugin.php b/tests/e2e/PHPUnit/ppcp-e2e-plugin.php
deleted file mode 100644
index 1580fc75c..000000000
--- a/tests/e2e/PHPUnit/ppcp-e2e-plugin.php
+++ /dev/null
@@ -1,19 +0,0 @@
- $value) {
update_option($key, $value);
}
-echo 'Adding ppcp-e2e-plugin.' . PHP_EOL;
-
-$pluginDir = WP_ROOT_DIR . '/wp-content/plugins/ppcp-e2e-plugin';
-if (!is_dir($pluginDir)) {
- mkdir($pluginDir);
-}
-if (!copy(E2E_TESTS_ROOT_DIR . '/PHPUnit/ppcp-e2e-plugin.php', $pluginDir . '/ppcp-e2e-plugin.php')) {
- echo 'Failed to copy ppcp-e2e-plugin.' . PHP_EOL;
-}
-
-activate_plugin('ppcp-e2e-plugin/ppcp-e2e-plugin.php', '', true);
-
echo 'Deleting test taxes.' . PHP_EOL;
$taxRates = WC_Tax::get_rates_for_tax_class('');
diff --git a/tests/stubs/WC_Checkout.php b/tests/stubs/WC_Checkout.php
new file mode 100644
index 000000000..85cd8419e
--- /dev/null
+++ b/tests/stubs/WC_Checkout.php
@@ -0,0 +1,11 @@
+