mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge trunk
This commit is contained in:
commit
2d0726044f
51 changed files with 1480 additions and 688 deletions
14
.ddev/addon-metadata/playwright/manifest.yaml
Normal file
14
.ddev/addon-metadata/playwright/manifest.yaml
Normal file
|
@ -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: []
|
14
.ddev/commands/playwright/playwright
Executable file
14
.ddev/commands/playwright/playwright
Executable file
|
@ -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 "$@"
|
17
.ddev/commands/playwright/playwright-install
Executable file
17
.ddev/commands/playwright/playwright-install
Executable file
|
@ -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
|
38
.ddev/docker-compose.playwright.yaml
Normal file
38
.ddev/docker-compose.playwright.yaml
Normal file
|
@ -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
|
57
.ddev/playwright-build/Dockerfile
Normal file
57
.ddev/playwright-build/Dockerfile
Normal file
|
@ -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"
|
18
.ddev/playwright-build/entrypoint.sh
Executable file
18
.ddev/playwright-build/entrypoint.sh
Executable file
|
@ -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
|
14
.ddev/playwright-build/kasmvnc.yaml
Normal file
14
.ddev/playwright-build/kasmvnc.yaml
Normal file
|
@ -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
|
32
.ddev/playwright-build/xstartup
Executable file
32
.ddev/playwright-build/xstartup
Executable file
|
@ -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
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -11,3 +11,5 @@ modules/ppcp-wc-gateway/assets/css
|
|||
.env
|
||||
.env.e2e
|
||||
auth.json
|
||||
.DS_Store
|
||||
tests/.DS_Store
|
||||
|
|
|
@ -233,9 +233,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 +269,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, '.', '' );
|
||||
|
|
|
@ -13,7 +13,7 @@ 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';
|
||||
|
@ -123,7 +123,7 @@ const bootstrap = () => {
|
|||
return actions.reject();
|
||||
}
|
||||
|
||||
if (context === 'checkout' && !PayPalCommerceGateway.funding_sources_without_redirect.includes(data.fundingSource)) {
|
||||
if (context === 'checkout') {
|
||||
try {
|
||||
await formSaver.save(form);
|
||||
} catch (error) {
|
||||
|
@ -194,9 +194,6 @@ const bootstrap = () => {
|
|||
payNowBootstrap.init();
|
||||
}
|
||||
|
||||
if (context !== 'checkout') {
|
||||
messageRenderer.render();
|
||||
}
|
||||
};
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
|
|
|
@ -40,8 +40,7 @@ class SingleProductActionHandler {
|
|||
}).then((res)=>{
|
||||
return res.json();
|
||||
}).then(() => {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
const products = [new Product(id, 1, [])];
|
||||
const products = this.getSubscriptionProducts();
|
||||
|
||||
fetch(this.config.ajax.change_cart.endpoint, {
|
||||
method: 'POST',
|
||||
|
@ -71,6 +70,12 @@ class SingleProductActionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
getSubscriptionProducts()
|
||||
{
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new Product(id, 1, [])];
|
||||
}
|
||||
|
||||
configuration()
|
||||
{
|
||||
return {
|
||||
|
@ -98,43 +103,38 @@ class SingleProductActionHandler {
|
|||
}
|
||||
}
|
||||
|
||||
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"))];
|
||||
} 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));
|
||||
})
|
||||
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)];
|
||||
}
|
||||
}
|
||||
|
||||
createOrder()
|
||||
{
|
||||
this.cartHelper = null;
|
||||
|
||||
let getProducts = (() => {
|
||||
if ( this.isBookingProduct() ) {
|
||||
return () => {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
}
|
||||
} else if ( this.isGroupedProduct() ) {
|
||||
return () => {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
return () => {
|
||||
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)];
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
||||
return (data, actions) => {
|
||||
this.errorHandler.clear();
|
||||
|
||||
|
@ -170,7 +170,7 @@ class SingleProductActionHandler {
|
|||
});
|
||||
};
|
||||
|
||||
return this.updateCart.update(onResolve, getProducts());
|
||||
return this.updateCart.update(onResolve, this.getProducts());
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import CartActionHandler from '../ActionHandler/CartActionHandler';
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {setVisible} from "../Helper/Hiding";
|
||||
|
||||
class CartBootstrap {
|
||||
constructor(gateway, renderer, messages, errorHandler) {
|
||||
|
@ -40,11 +39,21 @@ class CartBootstrap {
|
|||
return;
|
||||
}
|
||||
|
||||
// handle script reload
|
||||
const newParams = result.data.url_params;
|
||||
const reloadRequired = this.gateway.url_params.intent !== newParams.intent;
|
||||
const reloadRequired = JSON.stringify(this.gateway.url_params) !== JSON.stringify(newParams);
|
||||
|
||||
// TODO: should reload the script instead
|
||||
setVisible(this.gateway.button.wrapper, !reloadRequired)
|
||||
if (reloadRequired) {
|
||||
this.gateway.url_params = newParams;
|
||||
jQuery(this.gateway.button.wrapper).trigger('ppcp-reload-buttons');
|
||||
}
|
||||
|
||||
// handle button status
|
||||
if (result.data.button || result.data.messages) {
|
||||
this.gateway.button = result.data.button;
|
||||
this.gateway.messages = result.data.messages;
|
||||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
if (this.lastAmount !== result.data.amount) {
|
||||
this.lastAmount = result.data.amount;
|
||||
|
|
|
@ -6,7 +6,6 @@ import {
|
|||
PaymentMethods
|
||||
} from "../Helper/CheckoutMethodState";
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {disable, enable} from "../Helper/ButtonDisabler";
|
||||
|
||||
class CheckoutBootstap {
|
||||
constructor(gateway, renderer, messages, spinner, errorHandler) {
|
||||
|
|
|
@ -4,6 +4,8 @@ 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";
|
||||
|
||||
class SingleProductBootstap {
|
||||
constructor(gateway, renderer, messages, errorHandler) {
|
||||
|
@ -14,6 +16,9 @@ class SingleProductBootstap {
|
|||
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, 5000);
|
||||
|
||||
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
|
||||
this.handleChange();
|
||||
}, true);
|
||||
|
@ -40,10 +45,14 @@ class SingleProductBootstap {
|
|||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
handleButtonStatus() {
|
||||
handleButtonStatus(simulateCart = true) {
|
||||
BootstrapHelper.handleButtonStatus(this, {
|
||||
formSelector: this.formSelector
|
||||
});
|
||||
|
||||
if (simulateCart) {
|
||||
this.simulateCartThrottled();
|
||||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
|
@ -55,12 +64,6 @@ class SingleProductBootstap {
|
|||
|
||||
jQuery(document).on('change', this.formSelector, () => {
|
||||
this.handleChange();
|
||||
|
||||
setTimeout(() => { // Wait for the DOM to be fully updated
|
||||
// For the moment renderWithAmount should only be done here to prevent undesired side effects due to priceAmount()
|
||||
// not being correctly formatted in some cases, can be moved to handleButtonStatus() once this issue is fixed
|
||||
this.messages.renderWithAmount(this.priceAmount());
|
||||
}, 100);
|
||||
});
|
||||
this.mutationObserver.observe(form, { childList: true, subtree: true });
|
||||
|
||||
|
@ -201,6 +204,62 @@ class SingleProductBootstap {
|
|||
actionHandler.configuration()
|
||||
);
|
||||
}
|
||||
|
||||
simulateCart() {
|
||||
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) => {
|
||||
|
||||
this.messages.renderWithAmount(data.total);
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
if (typeof data.button.is_disabled === 'boolean') {
|
||||
this.gateway.button.is_disabled = data.button.is_disabled;
|
||||
}
|
||||
if (typeof data.messages.is_hidden === 'boolean') {
|
||||
this.gateway.messages.is_hidden = data.messages.is_hidden;
|
||||
}
|
||||
|
||||
this.handleButtonStatus(false);
|
||||
|
||||
}, products);
|
||||
}
|
||||
}
|
||||
|
||||
export default SingleProductBootstap;
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import {loadScript} from "@paypal/paypal-js";
|
||||
import widgetBuilder from "./Renderer/WidgetBuilder";
|
||||
|
||||
const storageKey = 'ppcp-data-client-id';
|
||||
|
||||
const validateToken = (token, user) => {
|
||||
|
@ -24,7 +27,7 @@ const storeToken = (token) => {
|
|||
sessionStorage.setItem(storageKey, JSON.stringify(token));
|
||||
}
|
||||
|
||||
const dataClientIdAttributeHandler = (script, config) => {
|
||||
const dataClientIdAttributeHandler = (scriptOptions, config, callback) => {
|
||||
fetch(config.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@ -42,8 +45,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);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import {disable, enable} from "./ButtonDisabler";
|
||||
import {hide, show} from "./Hiding";
|
||||
|
||||
/**
|
||||
* Common Bootstrap methods to avoid code repetition.
|
||||
|
@ -11,20 +12,28 @@ export default class BootstrapHelper {
|
|||
options.messagesWrapper = options.messagesWrapper || bs.gateway.messages.wrapper;
|
||||
options.skipMessages = options.skipMessages || false;
|
||||
|
||||
if (!bs.shouldEnable()) {
|
||||
// Handle messages hide / show
|
||||
if (this.shouldShowMessages(bs, options)) {
|
||||
show(options.messagesWrapper);
|
||||
} else {
|
||||
hide(options.messagesWrapper);
|
||||
}
|
||||
|
||||
// Handle enable / disable
|
||||
if (bs.shouldEnable()) {
|
||||
bs.renderer.enableSmartButtons(options.wrapper);
|
||||
enable(options.wrapper);
|
||||
|
||||
if (!options.skipMessages) {
|
||||
enable(options.messagesWrapper);
|
||||
}
|
||||
} else {
|
||||
bs.renderer.disableSmartButtons(options.wrapper);
|
||||
disable(options.wrapper, options.formSelector || null);
|
||||
|
||||
if (!options.skipMessages) {
|
||||
disable(options.messagesWrapper);
|
||||
}
|
||||
return;
|
||||
}
|
||||
bs.renderer.enableSmartButtons(options.wrapper);
|
||||
enable(options.wrapper);
|
||||
|
||||
if (!options.skipMessages) {
|
||||
enable(options.messagesWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,4 +46,13 @@ export default class BootstrapHelper {
|
|||
return bs.shouldRender()
|
||||
&& options.isDisabled !== true;
|
||||
}
|
||||
|
||||
static shouldShowMessages(bs, options) {
|
||||
options = options || {};
|
||||
if (typeof options.isMessagesHidden === 'undefined') {
|
||||
options.isMessagesHidden = bs.gateway.messages.is_hidden;
|
||||
}
|
||||
|
||||
return options.isMessagesHidden !== true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import dataClientIdAttributeHandler from "../DataClientIdAttributeHandler";
|
||||
import {loadScript} from "@paypal/paypal-js";
|
||||
import widgetBuilder from "../Renderer/WidgetBuilder";
|
||||
import merge from "deepmerge";
|
||||
import {keysToCamelCase} from "./Utils";
|
||||
|
||||
export const loadPaypalScript = (config, onLoaded) => {
|
||||
if (typeof paypal !== 'undefined') {
|
||||
|
@ -7,21 +10,20 @@ export const loadPaypalScript = (config, onLoaded) => {
|
|||
return;
|
||||
}
|
||||
|
||||
const script = document.createElement('script');
|
||||
script.addEventListener('load', onLoaded);
|
||||
script.setAttribute('src', config.url);
|
||||
Object.entries(config.script_attributes).forEach(
|
||||
(keyValue) => {
|
||||
script.setAttribute(keyValue[0], keyValue[1]);
|
||||
}
|
||||
);
|
||||
const callback = (paypal) => {
|
||||
widgetBuilder.setPaypal(paypal);
|
||||
onLoaded();
|
||||
}
|
||||
|
||||
let scriptOptions = keysToCamelCase(config.url_params);
|
||||
scriptOptions = merge(scriptOptions, config.script_attributes);
|
||||
|
||||
if (config.data_client_id.set_attribute) {
|
||||
dataClientIdAttributeHandler(script, config.data_client_id);
|
||||
dataClientIdAttributeHandler(scriptOptions, config.data_client_id, callback);
|
||||
return;
|
||||
}
|
||||
|
||||
document.body.appendChild(script);
|
||||
loadScript(scriptOptions).then(callback);
|
||||
}
|
||||
|
||||
export const loadPaypalJsScript = (options, buttons, container) => {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
class SimulateCart {
|
||||
|
||||
constructor(endpoint, nonce)
|
||||
{
|
||||
this.endpoint = endpoint;
|
||||
this.nonce = nonce;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param onResolve
|
||||
* @param {Product[]} products
|
||||
* @returns {Promise<unknown>}
|
||||
*/
|
||||
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;
|
69
modules/ppcp-button/resources/js/modules/Helper/Utils.js
Normal file
69
modules/ppcp-button/resources/js/modules/Helper/Utils.js
Normal file
|
@ -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;
|
|
@ -122,24 +122,17 @@ 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 = event.cards.length ? 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) => {
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import widgetBuilder from "./WidgetBuilder";
|
||||
|
||||
class MessageRenderer {
|
||||
|
||||
constructor(config) {
|
||||
|
@ -28,7 +30,8 @@ class MessageRenderer {
|
|||
oldWrapper.parentElement.removeChild(oldWrapper);
|
||||
sibling.parentElement.insertBefore(newWrapper, sibling);
|
||||
|
||||
paypal.Messages(options).render(this.config.wrapper);
|
||||
widgetBuilder.registerMessages(this.config.wrapper, options);
|
||||
widgetBuilder.renderMessages(this.config.wrapper);
|
||||
}
|
||||
|
||||
optionsEqual(options) {
|
||||
|
@ -44,7 +47,7 @@ class MessageRenderer {
|
|||
|
||||
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)) {
|
||||
|
|
|
@ -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) {
|
||||
|
@ -11,6 +14,8 @@ class Renderer {
|
|||
this.onButtonsInitListeners = {};
|
||||
|
||||
this.renderedSources = new Set();
|
||||
|
||||
this.reloadEventName = 'ppcp-reload-buttons';
|
||||
}
|
||||
|
||||
render(contextConfig, settingsOverride = {}, contextConfigOverride = () => {}) {
|
||||
|
@ -68,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;
|
||||
}
|
||||
|
||||
|
@ -76,35 +83,50 @@ class Renderer {
|
|||
contextConfig.fundingSource = fundingSource;
|
||||
}
|
||||
|
||||
const btn = paypal.Buttons({
|
||||
style,
|
||||
...contextConfig,
|
||||
onClick: this.onSmartButtonClick,
|
||||
onInit: (data, actions) => {
|
||||
if (this.onSmartButtonsInit) {
|
||||
this.onSmartButtonsInit(data, actions);
|
||||
}
|
||||
this.handleOnButtonsInit(wrapper, data, actions);
|
||||
},
|
||||
});
|
||||
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 ?? '');
|
||||
isAlreadyRendered(wrapper, fundingSource) {
|
||||
return this.renderedSources.has(wrapper + (fundingSource ?? ''));
|
||||
}
|
||||
|
||||
disableCreditCardFields() {
|
||||
|
|
|
@ -0,0 +1,177 @@
|
|||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (this.hasRendered(wrapper)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = this.messages.get(wrapper);
|
||||
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(`<div class="${itemClass}"></div>`);
|
||||
$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;
|
||||
}
|
||||
}
|
||||
|
||||
export default new WidgetBuilder();
|
|
@ -11,6 +11,7 @@ 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\CheckoutFormSaver;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
|
@ -123,6 +124,17 @@ return array(
|
|||
'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' );
|
||||
$data_store = \WC_Data_Store::load( 'product' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new SimulateCartEndpoint( $smart_button, $cart, $request_data, $data_store, $logger );
|
||||
},
|
||||
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
|
||||
if ( ! \WC()->cart ) {
|
||||
throw new RuntimeException( 'cant initialize endpoint at this moment' );
|
||||
|
|
|
@ -25,6 +25,7 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
|
@ -683,6 +684,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(
|
||||
|
@ -826,9 +828,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
'has_subscriptions' => $this->has_subscriptions(),
|
||||
'paypal_subscriptions_enabled' => $this->paypal_subscriptions_enabled(),
|
||||
),
|
||||
'redirect' => wc_get_checkout_url(),
|
||||
'context' => $this->context(),
|
||||
'ajax' => 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() ),
|
||||
|
@ -1076,8 +1082,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
$enable_funding = array( 'venmo' );
|
||||
|
||||
if ( $this->settings_status->is_pay_later_button_enabled_for_location( $context ) ||
|
||||
$this->settings_status->is_pay_later_messaging_enabled_for_location( $context )
|
||||
if ( $this->is_pay_later_button_enabled_for_location( $context ) ||
|
||||
$this->is_pay_later_messaging_enabled_for_location( $context )
|
||||
) {
|
||||
$enable_funding[] = 'paylater';
|
||||
} else {
|
||||
|
@ -1366,49 +1372,112 @@ 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
|
||||
*/
|
||||
protected function is_button_disabled( string $context = null ): bool {
|
||||
public function is_button_disabled( string $context = null, array $context_data = array() ): bool {
|
||||
if ( null === $context ) {
|
||||
$context = $this->context();
|
||||
}
|
||||
|
||||
if ( 'product' === $context ) {
|
||||
$product = wc_get_product();
|
||||
|
||||
/**
|
||||
* Allows to decide if the button should be disabled for a given product
|
||||
* Allows to decide if the button should be disabled for a given product.
|
||||
*/
|
||||
$is_disabled = apply_filters(
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_product_buttons_disabled',
|
||||
null,
|
||||
$product
|
||||
false,
|
||||
$this->product_filter_context_data( $context_data )
|
||||
);
|
||||
|
||||
if ( $is_disabled !== null ) {
|
||||
return $is_disabled;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to decide if the button should be disabled globally or on a given context
|
||||
* Allows to decide if the button should be disabled globally or on a given context.
|
||||
*/
|
||||
$is_disabled = apply_filters(
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_buttons_disabled',
|
||||
null,
|
||||
false,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
if ( $is_disabled !== null ) {
|
||||
return $is_disabled;
|
||||
/**
|
||||
* 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 )
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
/**
|
||||
* 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 );
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -12,6 +12,7 @@ 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;
|
||||
|
@ -120,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 ) {
|
||||
|
|
327
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
327
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
|
@ -0,0 +1,327 @@
|
|||
<?php
|
||||
/**
|
||||
* Abstract class for cart Endpoints.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
|
||||
/**
|
||||
* Abstract Class AbstractCartEndpoint
|
||||
*/
|
||||
abstract class AbstractCartEndpoint implements EndpointInterface {
|
||||
|
||||
const ENDPOINT = '';
|
||||
|
||||
/**
|
||||
* The current cart object.
|
||||
*
|
||||
* @var \WC_Cart
|
||||
*/
|
||||
protected $cart;
|
||||
|
||||
/**
|
||||
* The product data store.
|
||||
*
|
||||
* @var \WC_Data_Store
|
||||
*/
|
||||
protected $product_data_store;
|
||||
|
||||
/**
|
||||
* The request data helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
protected $request_data;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The tag to be added to logs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $logger_tag = '';
|
||||
|
||||
/**
|
||||
* The added cart item IDs
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cart_item_keys = array();
|
||||
|
||||
/**
|
||||
* The nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return static::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
return $this->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 );
|
||||
|
||||
$success = true;
|
||||
foreach ( $products as $product ) {
|
||||
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 ) {
|
||||
$this->handle_error();
|
||||
}
|
||||
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function handle_error(): 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();
|
||||
}
|
||||
|
||||
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->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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product information from a data array.
|
||||
*
|
||||
* @param array $data The data array.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
protected function products_from_data( array $data ): ?array {
|
||||
|
||||
$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' => $product['variations'] ?? null,
|
||||
'booking' => $product['booking'] ?? 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 {
|
||||
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
|
||||
|
||||
$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.
|
||||
*/
|
||||
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.
|
||||
$cart_item_key = $this->cart->add_to_cart(
|
||||
$product->get_id(),
|
||||
$quantity,
|
||||
$variation_id,
|
||||
$variations
|
||||
);
|
||||
|
||||
$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.
|
||||
*/
|
||||
private function add_booking_product(
|
||||
\WC_Product $product,
|
||||
array $data
|
||||
): bool {
|
||||
|
||||
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 );
|
||||
|
||||
$this->cart_item_keys[] = $cart_item_key;
|
||||
return false !== $cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes stored cart items from WooCommerce cart.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_cart_items(): void {
|
||||
foreach ( $this->cart_item_keys as $cart_item_key ) {
|
||||
$this->cart->remove_cart_item( $cart_item_key );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -65,11 +65,17 @@ class CartScriptParamsEndpoint implements EndpointInterface {
|
|||
*/
|
||||
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();
|
||||
|
||||
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' ),
|
||||
)
|
||||
);
|
||||
|
|
|
@ -11,26 +11,15 @@ 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;
|
||||
|
||||
/**
|
||||
* 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 +27,6 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
*/
|
||||
private $shipping;
|
||||
|
||||
/**
|
||||
* The request data helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The PurchaseUnit factory.
|
||||
*
|
||||
|
@ -52,20 +34,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.
|
||||
*
|
||||
|
@ -91,38 +59,8 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->product_data_store = $product_data_store;
|
||||
$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,195 +69,21 @@ 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 {
|
||||
$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 ) {
|
||||
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 ) {
|
||||
$this->handle_error();
|
||||
return $success;
|
||||
}
|
||||
|
||||
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' => $product['variations'] ?? null,
|
||||
'booking' => $product['booking'] ?? 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
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds variations 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.
|
||||
*/
|
||||
private function add_booking_product(
|
||||
\WC_Product $product,
|
||||
array $data
|
||||
): bool {
|
||||
|
||||
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
|
||||
if ( ! $this->add_products( $products ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cart_item_data = array(
|
||||
'booking' => wc_bookings_get_posted_data( $data, $product ),
|
||||
);
|
||||
|
||||
return false !== $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
|
||||
wp_send_json_success( $this->generate_purchase_units() );
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
122
modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
Normal file
122
modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
Normal file
|
@ -0,0 +1,122 @@
|
|||
<?php
|
||||
/**
|
||||
* Endpoint to simulate adding products to the cart.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
|
||||
/**
|
||||
* Class SimulateCartEndpoint
|
||||
*/
|
||||
class SimulateCartEndpoint extends AbstractCartEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-simulate-cart';
|
||||
|
||||
/**
|
||||
* The SmartButton.
|
||||
*
|
||||
* @var SmartButton
|
||||
*/
|
||||
private $smart_button;
|
||||
|
||||
/**
|
||||
* ChangeCartEndpoint constructor.
|
||||
*
|
||||
* @param SmartButton $smart_button The SmartButton.
|
||||
* @param \WC_Cart $cart The current WC cart object.
|
||||
* @param RequestData $request_data The request data helper.
|
||||
* @param \WC_Data_Store $product_data_store The data store for products.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
SmartButton $smart_button,
|
||||
\WC_Cart $cart,
|
||||
RequestData $request_data,
|
||||
\WC_Data_Store $product_data_store,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->smart_button = $smart_button;
|
||||
$this->cart = clone $cart;
|
||||
$this->request_data = $request_data;
|
||||
$this->product_data_store = $product_data_store;
|
||||
$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;
|
||||
}
|
||||
|
||||
// Set WC default cart as the clone.
|
||||
// Store a reference to the real cart.
|
||||
$active_cart = WC()->cart;
|
||||
WC()->cart = $this->cart;
|
||||
|
||||
if ( ! $this->add_products( $products ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->cart->calculate_totals();
|
||||
$total = (float) $this->cart->get_total( 'numeric' );
|
||||
|
||||
// Remove from cart because some plugins reserve resources internally when adding to cart.
|
||||
$this->remove_cart_items();
|
||||
|
||||
// Restore cart and unset cart clone.
|
||||
WC()->cart = $active_cart;
|
||||
unset( $this->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 );
|
||||
}
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'total' => $total,
|
||||
'funding' => array(
|
||||
'paylater' => array(
|
||||
'enabled' => $pay_later_enabled,
|
||||
),
|
||||
),
|
||||
'button' => array(
|
||||
'is_disabled' => ! $button_enabled,
|
||||
),
|
||||
'messages' => array(
|
||||
'is_hidden' => ! $pay_later_messaging_enabled,
|
||||
),
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -11,7 +11,6 @@ namespace WooCommerce\PayPalCommerce\Onboarding;
|
|||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Assets\OnboardingAssets;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Endpoint\LoginSellerEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
|
||||
|
@ -88,9 +87,9 @@ class OnboardingModule implements ModuleInterface {
|
|||
$endpoint = $c->get( 'onboarding.endpoint.login-seller' );
|
||||
|
||||
/**
|
||||
* The ChangeCartEndpoint.
|
||||
* The LoginSellerEndpoint.
|
||||
*
|
||||
* @var ChangeCartEndpoint $endpoint
|
||||
* @var LoginSellerEndpoint $endpoint
|
||||
*/
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
|
|
|
@ -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){
|
||||
|
@ -138,6 +139,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);
|
||||
|
|
|
@ -145,6 +145,10 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
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 );
|
||||
|
@ -159,6 +163,14 @@ class CheckoutOrderApproved implements RequestHandler {
|
|||
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'] ?: ''
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$checkout = new WC_Checkout();
|
||||
$wc_order_id = $checkout->create_order( $form );
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
"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-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",
|
||||
|
@ -21,7 +20,6 @@
|
|||
"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-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",
|
||||
|
@ -31,7 +29,6 @@
|
|||
"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-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",
|
||||
|
@ -72,9 +69,5 @@
|
|||
"dependencies": {
|
||||
"dotenv": "^16.0.3",
|
||||
"npm-run-all": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.31.1",
|
||||
"@woocommerce/woocommerce-rest-api": "^1.0.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
require('dotenv').config({ path: '.env.e2e' });
|
||||
|
||||
const config = {
|
||||
testDir: './tests/playwright',
|
||||
timeout: 30000,
|
||||
use: {
|
||||
baseURL: process.env.BASEURL,
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
36
tests/Playwright/.env.example
Normal file
36
tests/Playwright/.env.example
Normal file
|
@ -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"
|
6
tests/Playwright/.gitignore
vendored
Normal file
6
tests/Playwright/.gitignore
vendored
Normal file
|
@ -0,0 +1,6 @@
|
|||
node_modules
|
||||
pw-browsers
|
||||
test-results
|
||||
.env
|
||||
yarn.lock
|
||||
playwright-report/*
|
22
tests/Playwright/README.md
Normal file
22
tests/Playwright/README.md
Normal file
|
@ -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
|
||||
```
|
||||
|
7
tests/Playwright/package.json
Normal file
7
tests/Playwright/package.json
Normal file
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.34.2",
|
||||
"dotenv": "^16.0.3"
|
||||
}
|
||||
}
|
77
tests/Playwright/playwright.config.js
Normal file
77
tests/Playwright/playwright.config.js
Normal file
|
@ -0,0 +1,77 @@
|
|||
// @ts-check
|
||||
const { defineConfig, devices } = require('@playwright/test');
|
||||
|
||||
require('dotenv').config({ path: '.env' });
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
module.exports = defineConfig({
|
||||
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,
|
||||
// },
|
||||
});
|
3
tests/playwright/.gitignore
vendored
3
tests/playwright/.gitignore
vendored
|
@ -1,3 +0,0 @@
|
|||
test-results/
|
||||
playwright-report/
|
||||
.cache/
|
|
@ -1,40 +0,0 @@
|
|||
# Playwright Testing
|
||||
|
||||
## Set Environment Variables
|
||||
|
||||
Duplicate [.env.e2e.example](/.env.e2e.example) and rename it to `.env.e2e`, set the values needed for the tests, like account credentials, card numbers.
|
||||
|
||||
## Install Playwright dependencies (browsers)
|
||||
|
||||
```
|
||||
$ yarn ddev:pw-install
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
```
|
||||
$ yarn ddev:pw-tests
|
||||
```
|
||||
|
||||
You can also choose which tests to run filtering by name
|
||||
```
|
||||
$ yarn ddev:pw-tests --grep "Test name or part of the name"
|
||||
```
|
||||
|
||||
Or run without the headless mode (show the browser)
|
||||
```
|
||||
$ yarn pw-tests-headed
|
||||
```
|
||||
|
||||
Or run with [the test debugger](https://playwright.dev/docs/debug)
|
||||
```
|
||||
$ yarn playwright test --debug
|
||||
```
|
||||
|
||||
For the headed/debug mode (currently works only outside DDEV) you may need to re-install the deps if you are not on Linux.
|
||||
|
||||
```
|
||||
$ yarn pw-install
|
||||
```
|
||||
|
||||
---
|
||||
See [Playwright docs](https://playwright.dev/docs/intro) for more info.
|
|
@ -1,59 +0,0 @@
|
|||
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();
|
||||
}
|
||||
|
180
yarn.lock
180
yarn.lock
|
@ -2,31 +2,6 @@
|
|||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@playwright/test@^1.31.1":
|
||||
version "1.31.1"
|
||||
resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.31.1.tgz#39d6873dc46af135f12451d79707db7d1357455d"
|
||||
integrity sha512-IsytVZ+0QLDh1Hj83XatGp/GsI1CDJWbyDaBGbainsh0p2zC7F4toUocqowmjS6sQff2NGT3D9WbDj/3K2CJiA==
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
playwright-core "1.31.1"
|
||||
optionalDependencies:
|
||||
fsevents "2.3.2"
|
||||
|
||||
"@types/node@*":
|
||||
version "18.14.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.14.1.tgz#90dad8476f1e42797c49d6f8b69aaf9f876fc69f"
|
||||
integrity sha512-QH+37Qds3E0eDlReeboBxfHbX9omAcBCXEzswCu6jySP642jiM3cYSIkU/REqwhCUqXdonHFuBfJDiAJxMNhaQ==
|
||||
|
||||
"@woocommerce/woocommerce-rest-api@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@woocommerce/woocommerce-rest-api/-/woocommerce-rest-api-1.0.1.tgz#112b998e8e3758203a71d5e3e0b24efcb1cff842"
|
||||
integrity sha512-YBk3EEYE0zax/egx6Rhpbu6hcCFyZpYQrjH9JO4NUGU3n3T0W9Edn7oAUbjL/c7Oezcg+UaQluCaKjY/B3zwxg==
|
||||
dependencies:
|
||||
axios "^0.19.0"
|
||||
create-hmac "^1.1.7"
|
||||
oauth-1.0a "^2.2.6"
|
||||
url-parse "^1.4.7"
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
|
@ -34,13 +9,6 @@ ansi-styles@^3.2.1:
|
|||
dependencies:
|
||||
color-convert "^1.9.0"
|
||||
|
||||
axios@^0.19.0:
|
||||
version "0.19.2"
|
||||
resolved "https://registry.yarnpkg.com/axios/-/axios-0.19.2.tgz#3ea36c5d8818d0d5f8a8a97a6d36b86cdc00cb27"
|
||||
integrity sha512-fjgm5MvRHLhx+osE2xoekY70AhARk3a6hkN+3Io1jc00jtquGvxYlKlsFUhmUET0V5te6CcZI7lcv2Ym61mjHA==
|
||||
dependencies:
|
||||
follow-redirects "1.5.10"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||
|
@ -71,14 +39,6 @@ chalk@^2.4.1:
|
|||
escape-string-regexp "^1.0.5"
|
||||
supports-color "^5.3.0"
|
||||
|
||||
cipher-base@^1.0.1, cipher-base@^1.0.3:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de"
|
||||
integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
color-convert@^1.9.0:
|
||||
version "1.9.3"
|
||||
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
|
||||
|
@ -96,29 +56,6 @@ concat-map@0.0.1:
|
|||
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
|
||||
integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
|
||||
|
||||
create-hash@^1.1.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196"
|
||||
integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==
|
||||
dependencies:
|
||||
cipher-base "^1.0.1"
|
||||
inherits "^2.0.1"
|
||||
md5.js "^1.3.4"
|
||||
ripemd160 "^2.0.1"
|
||||
sha.js "^2.4.0"
|
||||
|
||||
create-hmac@^1.1.7:
|
||||
version "1.1.7"
|
||||
resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff"
|
||||
integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==
|
||||
dependencies:
|
||||
cipher-base "^1.0.3"
|
||||
create-hash "^1.1.0"
|
||||
inherits "^2.0.1"
|
||||
ripemd160 "^2.0.0"
|
||||
safe-buffer "^5.0.1"
|
||||
sha.js "^2.4.8"
|
||||
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
|
@ -130,13 +67,6 @@ cross-spawn@^6.0.5:
|
|||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
debug@=3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||
integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==
|
||||
dependencies:
|
||||
ms "2.0.0"
|
||||
|
||||
define-properties@^1.1.3, define-properties@^1.1.4:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.4.tgz#0b14d7bd7fbeb2f3572c3a7eda80ea5d57fb05b1"
|
||||
|
@ -201,18 +131,6 @@ escape-string-regexp@^1.0.5:
|
|||
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
|
||||
integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
|
||||
|
||||
follow-redirects@1.5.10:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.5.10.tgz#7b7a9f9aea2fdff36786a94ff643ed07f4ff5e2a"
|
||||
integrity sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==
|
||||
dependencies:
|
||||
debug "=3.1.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"
|
||||
|
@ -291,25 +209,11 @@ has@^1.0.3:
|
|||
dependencies:
|
||||
function-bind "^1.1.1"
|
||||
|
||||
hash-base@^3.0.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.1.0.tgz#55c381d9e06e1d2997a883b4a3fddfe7f0d3af33"
|
||||
integrity sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==
|
||||
dependencies:
|
||||
inherits "^2.0.4"
|
||||
readable-stream "^3.6.0"
|
||||
safe-buffer "^5.2.0"
|
||||
|
||||
hosted-git-info@^2.1.4:
|
||||
version "2.8.9"
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
internal-slot@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c"
|
||||
|
@ -426,15 +330,6 @@ load-json-file@^4.0.0:
|
|||
pify "^3.0.0"
|
||||
strip-bom "^3.0.0"
|
||||
|
||||
md5.js@^1.3.4:
|
||||
version "1.3.5"
|
||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||
integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==
|
||||
dependencies:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.1.2"
|
||||
|
||||
memorystream@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||
|
@ -447,11 +342,6 @@ minimatch@^3.0.4:
|
|||
dependencies:
|
||||
brace-expansion "^1.1.7"
|
||||
|
||||
ms@2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||
integrity sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==
|
||||
|
||||
nice-try@^1.0.4:
|
||||
version "1.0.5"
|
||||
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
|
||||
|
@ -482,11 +372,6 @@ npm-run-all@^4.1.5:
|
|||
shell-quote "^1.6.1"
|
||||
string.prototype.padend "^3.0.0"
|
||||
|
||||
oauth-1.0a@^2.2.6:
|
||||
version "2.2.6"
|
||||
resolved "https://registry.yarnpkg.com/oauth-1.0a/-/oauth-1.0a-2.2.6.tgz#eadbccdb3bceea412d24586e6f39b2b412f0e491"
|
||||
integrity sha512-6bkxv3N4Gu5lty4viIcIAnq5GbxECviMBeKR3WX/q87SPQ8E8aursPZUtsXDnxCs787af09WPRBLqYrf/lwoYQ==
|
||||
|
||||
object-inspect@^1.12.2, object-inspect@^1.9.0:
|
||||
version "1.12.2"
|
||||
resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea"
|
||||
|
@ -542,16 +427,6 @@ pify@^3.0.0:
|
|||
resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
|
||||
integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
|
||||
|
||||
playwright-core@1.31.1:
|
||||
version "1.31.1"
|
||||
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.31.1.tgz#4deeebbb8fb73b512593fe24bea206d8fd85ff7f"
|
||||
integrity sha512-JTyX4kV3/LXsvpHkLzL2I36aCdml4zeE35x+G5aPc4bkLsiRiQshU5lWeVpHFAuC8xAcbI6FDcw/8z3q2xtJSQ==
|
||||
|
||||
querystringify@^2.1.1:
|
||||
version "2.2.0"
|
||||
resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
|
||||
integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
|
||||
|
||||
read-pkg@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
|
||||
|
@ -561,15 +436,6 @@ read-pkg@^3.0.0:
|
|||
normalize-package-data "^2.3.2"
|
||||
path-type "^3.0.0"
|
||||
|
||||
readable-stream@^3.6.0:
|
||||
version "3.6.2"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.2.tgz#56a9b36ea965c00c5a93ef31eb111a0f11056967"
|
||||
integrity sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==
|
||||
dependencies:
|
||||
inherits "^2.0.3"
|
||||
string_decoder "^1.1.1"
|
||||
util-deprecate "^1.0.1"
|
||||
|
||||
regexp.prototype.flags@^1.4.3:
|
||||
version "1.4.3"
|
||||
resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz#87cab30f80f66660181a3bb7bf5981a872b367ac"
|
||||
|
@ -579,11 +445,6 @@ regexp.prototype.flags@^1.4.3:
|
|||
define-properties "^1.1.3"
|
||||
functions-have-names "^1.2.2"
|
||||
|
||||
requires-port@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||
integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
|
||||
|
||||
resolve@^1.10.0:
|
||||
version "1.22.1"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177"
|
||||
|
@ -593,19 +454,6 @@ resolve@^1.10.0:
|
|||
path-parse "^1.0.7"
|
||||
supports-preserve-symlinks-flag "^1.0.0"
|
||||
|
||||
ripemd160@^2.0.0, ripemd160@^2.0.1:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c"
|
||||
integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==
|
||||
dependencies:
|
||||
hash-base "^3.0.0"
|
||||
inherits "^2.0.1"
|
||||
|
||||
safe-buffer@^5.0.1, safe-buffer@^5.1.2, safe-buffer@^5.2.0, safe-buffer@~5.2.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-regex-test@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
|
||||
|
@ -620,14 +468,6 @@ safe-regex-test@^1.0.0:
|
|||
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
|
||||
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
|
||||
|
||||
sha.js@^2.4.0, sha.js@^2.4.8:
|
||||
version "2.4.11"
|
||||
resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7"
|
||||
integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==
|
||||
dependencies:
|
||||
inherits "^2.0.1"
|
||||
safe-buffer "^5.0.1"
|
||||
|
||||
shebang-command@^1.2.0:
|
||||
version "1.2.0"
|
||||
resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
|
||||
|
@ -707,13 +547,6 @@ string.prototype.trimstart@^1.0.5:
|
|||
define-properties "^1.1.4"
|
||||
es-abstract "^1.19.5"
|
||||
|
||||
string_decoder@^1.1.1:
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e"
|
||||
integrity sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==
|
||||
dependencies:
|
||||
safe-buffer "~5.2.0"
|
||||
|
||||
strip-bom@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
|
||||
|
@ -741,19 +574,6 @@ unbox-primitive@^1.0.2:
|
|||
has-symbols "^1.0.3"
|
||||
which-boxed-primitive "^1.0.2"
|
||||
|
||||
url-parse@^1.4.7:
|
||||
version "1.5.10"
|
||||
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
|
||||
integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
|
||||
dependencies:
|
||||
querystringify "^2.1.1"
|
||||
requires-port "^1.0.0"
|
||||
|
||||
util-deprecate@^1.0.1:
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
|
||||
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
|
||||
|
||||
validate-npm-package-license@^3.0.1:
|
||||
version "3.0.4"
|
||||
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue