mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-1915-hpos-compatibility-improvements
This commit is contained in:
commit
705e8eb8f1
61 changed files with 3875 additions and 278 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
|
||||
|
|
|
@ -2084,3 +2084,26 @@ function wcs_find_matching_line_item($order, $subscription_item, $match_type = '
|
|||
function wcs_order_contains_product($order, $product)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Get page ID for a specific WC resource.
|
||||
*
|
||||
* @param string $for Name of the resource.
|
||||
*
|
||||
* @return string Page ID. Empty string if resource not found.
|
||||
*/
|
||||
function wc_get_page_screen_id( $for ) {}
|
||||
|
||||
/**
|
||||
* Subscription Product Variation Class
|
||||
*
|
||||
* The subscription product variation class extends the WC_Product_Variation product class
|
||||
* to create subscription product variations.
|
||||
*
|
||||
* @class WC_Product_Subscription
|
||||
* @package WooCommerce Subscriptions
|
||||
* @category Class
|
||||
* @since 1.0.0 - Migrated from WooCommerce Subscriptions v1.3
|
||||
*
|
||||
*/
|
||||
class WC_Product_Subscription_Variation extends WC_Product_Variation {}
|
||||
|
|
|
@ -226,4 +226,40 @@ class BillingPlans {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivates a Subscription Plan.
|
||||
*
|
||||
* @param string $billing_plan_id The Plan ID.
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws RuntimeException If the request fails.
|
||||
* @throws PayPalApiException If the request fails.
|
||||
*/
|
||||
public function deactivate_plan( string $billing_plan_id ) {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v1/billing/plans/' . $billing_plan_id . '/deactivate';
|
||||
$args = array(
|
||||
'method' => 'POST',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $bearer->token(),
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
);
|
||||
|
||||
$response = $this->request( $url, $args );
|
||||
if ( is_wp_error( $response ) || ! is_array( $response ) ) {
|
||||
throw new RuntimeException( 'Could not deactivate plan.' );
|
||||
}
|
||||
|
||||
$json = json_decode( $response['body'] );
|
||||
$status_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( 204 !== $status_code ) {
|
||||
throw new PayPalApiException(
|
||||
$json,
|
||||
$status_code
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -214,4 +214,6 @@ class BillingSubscriptions {
|
|||
|
||||
return $json;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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, '.', '' );
|
||||
|
|
|
@ -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) {
|
||||
|
@ -137,16 +137,15 @@ const bootstrap = () => {
|
|||
};
|
||||
const renderer = new Renderer(creditCardRenderer, PayPalCommerceGateway, onSmartButtonClick, onSmartButtonsInit);
|
||||
const messageRenderer = new MessageRenderer(PayPalCommerceGateway.messages);
|
||||
if (context === 'mini-cart' || context === 'product') {
|
||||
if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
|
||||
const miniCartBootstrap = new MiniCartBootstap(
|
||||
PayPalCommerceGateway,
|
||||
renderer,
|
||||
errorHandler,
|
||||
);
|
||||
|
||||
miniCartBootstrap.init();
|
||||
}
|
||||
if (PayPalCommerceGateway.mini_cart_buttons_enabled === '1') {
|
||||
const miniCartBootstrap = new MiniCartBootstap(
|
||||
PayPalCommerceGateway,
|
||||
renderer,
|
||||
errorHandler,
|
||||
);
|
||||
|
||||
miniCartBootstrap.init();
|
||||
}
|
||||
|
||||
if (context === 'product' && PayPalCommerceGateway.single_product_buttons_enabled === '1') {
|
||||
|
|
|
@ -21,11 +21,11 @@ class SingleProductActionHandler {
|
|||
this.cartHelper = null;
|
||||
}
|
||||
|
||||
subscriptionsConfiguration() {
|
||||
subscriptionsConfiguration(subscription_plan) {
|
||||
return {
|
||||
createSubscription: (data, actions) => {
|
||||
return actions.subscription.create({
|
||||
'plan_id': this.config.subscription_plan_id
|
||||
'plan_id': subscription_plan
|
||||
});
|
||||
},
|
||||
onApprove: (data, actions) => {
|
||||
|
@ -73,7 +73,7 @@ class SingleProductActionHandler {
|
|||
getSubscriptionProducts()
|
||||
{
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new Product(id, 1, null)];
|
||||
return [new Product(id, 1, this.variations())];
|
||||
}
|
||||
|
||||
configuration()
|
||||
|
|
|
@ -86,6 +86,12 @@ class CartBootstrap {
|
|||
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
|
||||
) {
|
||||
this.renderer.render(actionHandler.subscriptionsConfiguration());
|
||||
|
||||
if(!PayPalCommerceGateway.subscription_product_allowed) {
|
||||
this.gateway.button.is_disabled = true;
|
||||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -106,6 +106,12 @@ class CheckoutBootstap {
|
|||
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
|
||||
) {
|
||||
this.renderer.render(actionHandler.subscriptionsConfiguration(), {}, actionHandler.configuration());
|
||||
|
||||
if(!PayPalCommerceGateway.subscription_product_allowed) {
|
||||
this.gateway.button.is_disabled = true;
|
||||
this.handleButtonStatus();
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,8 @@ import UpdateCart from "../Helper/UpdateCart";
|
|||
import SingleProductActionHandler from "../ActionHandler/SingleProductActionHandler";
|
||||
import {hide, show} from "../Helper/Hiding";
|
||||
import BootstrapHelper from "../Helper/BootstrapHelper";
|
||||
import {loadPaypalJsScript} from "../Helper/ScriptLoading";
|
||||
import {getPlanIdFromVariation} from "../Helper/Subscriptions"
|
||||
import SimulateCart from "../Helper/SimulateCart";
|
||||
import {strRemoveWord, strAddWord, throttle} from "../Helper/Utils";
|
||||
|
||||
|
@ -20,6 +22,8 @@ class SingleProductBootstap {
|
|||
this.renderer.onButtonsInit(this.gateway.button.wrapper, () => {
|
||||
this.handleChange();
|
||||
}, true);
|
||||
|
||||
this.subscriptionButtonsLoaded = false
|
||||
}
|
||||
|
||||
form() {
|
||||
|
@ -27,6 +31,8 @@ class SingleProductBootstap {
|
|||
}
|
||||
|
||||
handleChange() {
|
||||
this.subscriptionButtonsLoaded = false
|
||||
|
||||
if (!this.shouldRender()) {
|
||||
this.renderer.disableSmartButtons(this.gateway.button.wrapper);
|
||||
hide(this.gateway.button.wrapper, this.formSelector);
|
||||
|
@ -141,6 +147,25 @@ class SingleProductBootstap {
|
|||
|| document.querySelector('.wcsatt-options-prompt-label-subscription input[type="radio"]:checked') !== null; // grouped
|
||||
}
|
||||
|
||||
variations() {
|
||||
if (!this.hasVariations()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [...document.querySelector('form.cart')?.querySelectorAll("[name^='attribute_']")].map(
|
||||
(element) => {
|
||||
return {
|
||||
value: element.value,
|
||||
name: element.name
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
hasVariations() {
|
||||
return document.querySelector('form.cart')?.classList.contains('variations_form');
|
||||
}
|
||||
|
||||
render() {
|
||||
const actionHandler = new SingleProductActionHandler(
|
||||
this.gateway,
|
||||
|
@ -156,7 +181,29 @@ class SingleProductBootstap {
|
|||
PayPalCommerceGateway.data_client_id.has_subscriptions
|
||||
&& PayPalCommerceGateway.data_client_id.paypal_subscriptions_enabled
|
||||
) {
|
||||
this.renderer.render(actionHandler.subscriptionsConfiguration());
|
||||
const buttonWrapper = document.getElementById('ppc-button-ppcp-gateway');
|
||||
buttonWrapper.innerHTML = '';
|
||||
|
||||
const subscription_plan = this.variations() !== null
|
||||
? getPlanIdFromVariation(this.variations())
|
||||
: PayPalCommerceGateway.subscription_plan_id
|
||||
if(!subscription_plan) {
|
||||
return;
|
||||
}
|
||||
|
||||
if(this.subscriptionButtonsLoaded) return
|
||||
loadPaypalJsScript(
|
||||
{
|
||||
clientId: PayPalCommerceGateway.client_id,
|
||||
currency: PayPalCommerceGateway.currency,
|
||||
intent: 'subscription',
|
||||
vault: true
|
||||
},
|
||||
actionHandler.subscriptionsConfiguration(subscription_plan),
|
||||
this.gateway.button.wrapper
|
||||
);
|
||||
|
||||
this.subscriptionButtonsLoaded = true
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,3 +25,9 @@ export const loadPaypalScript = (config, onLoaded) => {
|
|||
|
||||
loadScript(scriptOptions).then(callback);
|
||||
}
|
||||
|
||||
export const loadPaypalJsScript = (options, buttons, container) => {
|
||||
loadScript(options).then((paypal) => {
|
||||
paypal.Buttons(buttons).render(container);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,3 +2,19 @@ export const isChangePaymentPage = () => {
|
|||
const urlParams = new URLSearchParams(window.location.search)
|
||||
return urlParams.has('change_payment_method');
|
||||
}
|
||||
|
||||
export const getPlanIdFromVariation = (variation) => {
|
||||
let subscription_plan = '';
|
||||
PayPalCommerceGateway.variable_paypal_subscription_variations.forEach((element) => {
|
||||
let obj = {};
|
||||
variation.forEach(({name, value}) => {
|
||||
Object.assign(obj, {[name.replace('attribute_', '')]: value});
|
||||
})
|
||||
|
||||
if(JSON.stringify(obj) === JSON.stringify(element.attributes) && element.subscription_plan !== '') {
|
||||
subscription_plan = element.subscription_plan;
|
||||
}
|
||||
});
|
||||
|
||||
return subscription_plan;
|
||||
}
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -16,7 +16,6 @@ use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\DisabledSmartButton;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
|
|
|
@ -373,6 +373,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
);
|
||||
}
|
||||
|
||||
$this->sanitize_woocommerce_filters();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -815,10 +817,12 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
$this->request_data->enqueue_nonce_fix();
|
||||
$localize = array(
|
||||
'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ),
|
||||
'url_params' => $url_params,
|
||||
'script_attributes' => $this->attributes(),
|
||||
'data_client_id' => array(
|
||||
'url' => add_query_arg( $url_params, 'https://www.paypal.com/sdk/js' ),
|
||||
'url_params' => $url_params,
|
||||
'script_attributes' => $this->attributes(),
|
||||
'client_id' => $this->client_id,
|
||||
'currency' => $this->currency,
|
||||
'data_client_id' => array(
|
||||
'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token(),
|
||||
'endpoint' => \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
|
||||
|
@ -826,9 +830,9 @@ 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() ),
|
||||
|
@ -865,14 +869,16 @@ class SmartButton implements SmartButtonInterface {
|
|||
'endpoint' => \WC_AJAX::get_endpoint( CartScriptParamsEndpoint::ENDPOINT ),
|
||||
),
|
||||
),
|
||||
'subscription_plan_id' => $this->paypal_subscription_id(),
|
||||
'enforce_vault' => $this->has_subscriptions(),
|
||||
'can_save_vault_token' => $this->can_save_vault_token(),
|
||||
'is_free_trial_cart' => $is_free_trial_cart,
|
||||
'vaulted_paypal_email' => ( is_checkout() && $is_free_trial_cart ) ? $this->get_vaulted_paypal_email() : '',
|
||||
'bn_codes' => $this->bn_codes(),
|
||||
'payer' => $this->payerData(),
|
||||
'button' => array(
|
||||
'subscription_plan_id' => $this->subscription_helper->paypal_subscription_id(),
|
||||
'variable_paypal_subscription_variations' => $this->subscription_helper->variable_paypal_subscription_variations(),
|
||||
'subscription_product_allowed' => $this->subscription_helper->checkout_subscription_product_allowed(),
|
||||
'enforce_vault' => $this->has_subscriptions(),
|
||||
'can_save_vault_token' => $this->can_save_vault_token(),
|
||||
'is_free_trial_cart' => $is_free_trial_cart,
|
||||
'vaulted_paypal_email' => ( is_checkout() && $is_free_trial_cart ) ? $this->get_vaulted_paypal_email() : '',
|
||||
'bn_codes' => $this->bn_codes(),
|
||||
'payer' => $this->payerData(),
|
||||
'button' => array(
|
||||
'wrapper' => '#ppc-button-' . PayPalGateway::ID,
|
||||
'is_disabled' => $this->is_button_disabled(),
|
||||
'mini_cart_wrapper' => '#ppc-button-minicart',
|
||||
|
@ -894,7 +900,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
'tagline' => $this->style_for_context( 'tagline', $this->context() ),
|
||||
),
|
||||
),
|
||||
'separate_buttons' => array(
|
||||
'separate_buttons' => array(
|
||||
'card' => array(
|
||||
'id' => CardButtonGateway::ID,
|
||||
'wrapper' => '#ppc-button-' . CardButtonGateway::ID,
|
||||
|
@ -904,7 +910,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
),
|
||||
),
|
||||
),
|
||||
'hosted_fields' => array(
|
||||
'hosted_fields' => array(
|
||||
'wrapper' => '#ppcp-hosted-fields',
|
||||
'labels' => array(
|
||||
'credit_card_number' => '',
|
||||
|
@ -927,8 +933,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
'valid_cards' => $this->dcc_applies->valid_cards(),
|
||||
'contingency' => $this->get_3ds_contingency(),
|
||||
),
|
||||
'messages' => $this->message_values(),
|
||||
'labels' => array(
|
||||
'messages' => $this->message_values(),
|
||||
'labels' => array(
|
||||
'error' => array(
|
||||
'generic' => __(
|
||||
'Something went wrong. Please try again or choose another payment source.',
|
||||
|
@ -955,12 +961,12 @@ class SmartButton implements SmartButtonInterface {
|
|||
// phpcs:ignore WordPress.WP.I18n
|
||||
'shipping_field' => _x( 'Shipping %s', 'checkout-validation', 'woocommerce' ),
|
||||
),
|
||||
'order_id' => 'pay-now' === $this->context() ? $this->get_order_pay_id() : 0,
|
||||
'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
|
||||
'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
|
||||
'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
|
||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
||||
'order_id' => 'pay-now' === $this->context() ? $this->get_order_pay_id() : 0,
|
||||
'single_product_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'product' ),
|
||||
'mini_cart_buttons_enabled' => $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' ),
|
||||
'basic_checkout_validation_enabled' => $this->basic_checkout_validation_enabled,
|
||||
'early_checkout_validation_enabled' => $this->early_validation_enabled,
|
||||
'funding_sources_without_redirect' => $this->funding_sources_without_redirect,
|
||||
);
|
||||
|
||||
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
|
||||
|
@ -1526,38 +1532,6 @@ class SmartButton implements SmartButtonInterface {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PayPal subscription plan id from WC subscription product.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function paypal_subscription_id(): string {
|
||||
if ( $this->subscription_helper->current_product_is_subscription() ) {
|
||||
$product = wc_get_product();
|
||||
assert( $product instanceof WC_Product );
|
||||
|
||||
if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
return $product->get_meta( 'ppcp_subscription_plan' )['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$cart = WC()->cart ?? null;
|
||||
if ( ! $cart || $cart->is_empty() ) {
|
||||
return '';
|
||||
}
|
||||
$items = $cart->get_cart_contents();
|
||||
foreach ( $items as $item ) {
|
||||
$product = wc_get_product( $item['product_id'] );
|
||||
assert( $product instanceof WC_Product );
|
||||
|
||||
if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
return $product->get_meta( 'ppcp_subscription_plan' )['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the intent.
|
||||
*
|
||||
|
@ -1591,4 +1565,34 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
return absint( $wp->query_vars['order-pay'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitize woocommerce filter on unexpected states.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function sanitize_woocommerce_filters(): void {
|
||||
|
||||
add_filter(
|
||||
'woocommerce_widget_cart_is_hidden',
|
||||
/**
|
||||
* Sometimes external plugins like "woocommerce-one-page-checkout" set the $value to null, handle that case here.
|
||||
* Here we also disable the mini-cart on cart-block and checkout-block pages where our buttons aren't supported yet.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function ( $value ) {
|
||||
if ( null === $value ) {
|
||||
if ( is_product() ) {
|
||||
return false;
|
||||
}
|
||||
return in_array( $this->context(), array( 'cart', 'checkout', 'cart-block', 'checkout-block' ), true );
|
||||
}
|
||||
return in_array( $this->context(), array( 'cart-block', 'checkout-block' ), true ) ? true : $value;
|
||||
},
|
||||
11
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -229,13 +229,15 @@ return array(
|
|||
$partner_referrals_sandbox = $container->get( 'api.endpoint.partner-referrals-sandbox' );
|
||||
$partner_referrals_data = $container->get( 'api.repository.partner-referrals-data' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
$signup_link_cache = $container->get( 'onboarding.signup-link-cache' );
|
||||
$signup_link_cache = $container->get( 'onboarding.signup-link-cache' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new OnboardingRenderer(
|
||||
$settings,
|
||||
$partner_referrals,
|
||||
$partner_referrals_sandbox,
|
||||
$partner_referrals_data,
|
||||
$signup_link_cache
|
||||
$signup_link_cache,
|
||||
$logger
|
||||
);
|
||||
},
|
||||
'onboarding.render-options' => static function ( ContainerInterface $container ) : OnboardingOptionsRenderer {
|
||||
|
|
|
@ -66,6 +66,13 @@ class OnboardingUrl {
|
|||
*/
|
||||
private $cache_ttl = 3 * MONTH_IN_SECONDS;
|
||||
|
||||
/**
|
||||
* The TTL for the previous token cache.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $previous_cache_ttl = 60;
|
||||
|
||||
/**
|
||||
* The constructor
|
||||
*
|
||||
|
@ -84,20 +91,19 @@ class OnboardingUrl {
|
|||
}
|
||||
|
||||
/**
|
||||
* Validates the token, if it's valid then delete it.
|
||||
* If it's invalid don't delete it, to prevent malicious requests from invalidating the token.
|
||||
* Instances the object with a $token.
|
||||
*
|
||||
* @param Cache $cache The cache object where the URL is stored.
|
||||
* @param string $onboarding_token The token to validate.
|
||||
* @param string $token The token to validate.
|
||||
* @param int $user_id User ID to associate the link with.
|
||||
* @return bool
|
||||
* @return false|self
|
||||
*/
|
||||
public static function validate_token_and_delete( Cache $cache, string $onboarding_token, int $user_id ): bool {
|
||||
if ( ! $onboarding_token ) {
|
||||
public static function make_from_token( Cache $cache, string $token, int $user_id ) {
|
||||
if ( ! $token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$token_data = json_decode( UrlHelper::url_safe_base64_decode( $onboarding_token ) ?: '', true );
|
||||
$token_data = json_decode( UrlHelper::url_safe_base64_decode( $token ) ?: '', true );
|
||||
|
||||
if ( ! $token_data ) {
|
||||
return false;
|
||||
|
@ -111,20 +117,57 @@ class OnboardingUrl {
|
|||
return false;
|
||||
}
|
||||
|
||||
$onboarding_url = new self( $cache, $token_data['k'], $token_data['u'] );
|
||||
return new self( $cache, $token_data['k'], $token_data['u'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the token, if it's valid then delete it.
|
||||
* If it's invalid don't delete it, to prevent malicious requests from invalidating the token.
|
||||
*
|
||||
* @param Cache $cache The cache object where the URL is stored.
|
||||
* @param string $token The token to validate.
|
||||
* @param int $user_id User ID to associate the link with.
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate_token_and_delete( Cache $cache, string $token, int $user_id ): bool {
|
||||
$onboarding_url = self::make_from_token( $cache, $token, $user_id );
|
||||
|
||||
if ( $onboarding_url === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $onboarding_url->load() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ( $onboarding_url->token() ?: '' ) !== $onboarding_token ) {
|
||||
if ( ( $onboarding_url->token() ?: '' ) !== $token ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$onboarding_url->replace_previous_token( $token );
|
||||
$onboarding_url->delete();
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the token against the previous token.
|
||||
* Useful to don't throw errors on burst calls to endpoints.
|
||||
*
|
||||
* @param Cache $cache The cache object where the URL is stored.
|
||||
* @param string $token The token to validate.
|
||||
* @param int $user_id User ID to associate the link with.
|
||||
* @return bool
|
||||
*/
|
||||
public static function validate_previous_token( Cache $cache, string $token, int $user_id ): bool {
|
||||
$onboarding_url = self::make_from_token( $cache, $token, $user_id );
|
||||
|
||||
if ( $onboarding_url === false ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $onboarding_url->check_previous_token( $token );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load cached data if is valid and initialize object.
|
||||
*
|
||||
|
@ -315,4 +358,43 @@ class OnboardingUrl {
|
|||
return implode( '_', array( $this->cache_key_prefix, $this->user_id ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the compiled cache key of the previous token
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function previous_cache_key(): string {
|
||||
return $this->cache_key() . '_previous';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks it the previous token matches the token provided.
|
||||
*
|
||||
* @param string $previous_token The previous token.
|
||||
* @return bool
|
||||
*/
|
||||
private function check_previous_token( string $previous_token ): bool {
|
||||
if ( ! $this->cache->has( $this->previous_cache_key() ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cached_token = $this->cache->get( $this->previous_cache_key() );
|
||||
|
||||
return $cached_token === $previous_token;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the previous token.
|
||||
*
|
||||
* @param string $previous_token The previous token.
|
||||
* @return void
|
||||
*/
|
||||
private function replace_previous_token( string $previous_token ): void {
|
||||
$this->cache->set(
|
||||
$this->previous_cache_key(),
|
||||
$previous_token,
|
||||
$this->previous_cache_ttl
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,12 +9,14 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Onboarding\Render;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PartnerReferrals;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\PartnerReferralsData;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Helper\OnboardingUrl;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
|
||||
/**
|
||||
* Class OnboardingRenderer
|
||||
|
@ -56,6 +58,13 @@ class OnboardingRenderer {
|
|||
*/
|
||||
protected $cache;
|
||||
|
||||
/**
|
||||
* The logger
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* OnboardingRenderer constructor.
|
||||
*
|
||||
|
@ -64,19 +73,22 @@ class OnboardingRenderer {
|
|||
* @param PartnerReferrals $sandbox_partner_referrals The PartnerReferrals for sandbox.
|
||||
* @param PartnerReferralsData $partner_referrals_data The default partner referrals data.
|
||||
* @param Cache $cache The cache.
|
||||
* @param ?LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
PartnerReferrals $production_partner_referrals,
|
||||
PartnerReferrals $sandbox_partner_referrals,
|
||||
PartnerReferralsData $partner_referrals_data,
|
||||
Cache $cache
|
||||
Cache $cache,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
$this->settings = $settings;
|
||||
$this->production_partner_referrals = $production_partner_referrals;
|
||||
$this->sandbox_partner_referrals = $sandbox_partner_referrals;
|
||||
$this->partner_referrals_data = $partner_referrals_data;
|
||||
$this->cache = $cache;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -102,9 +114,12 @@ class OnboardingRenderer {
|
|||
$onboarding_url = new OnboardingUrl( $this->cache, $cache_key, get_current_user_id() );
|
||||
|
||||
if ( $onboarding_url->load() ) {
|
||||
$this->logger->debug( 'Loaded onbording URL from cache: ' . $cache_key );
|
||||
return $onboarding_url->get() ?: '';
|
||||
}
|
||||
|
||||
$this->logger->info( 'Generating onboarding URL for: ' . $cache_key );
|
||||
|
||||
$onboarding_url->init();
|
||||
|
||||
$data = $this->partner_referrals_data
|
||||
|
@ -116,6 +131,8 @@ class OnboardingRenderer {
|
|||
$onboarding_url->set( $url );
|
||||
$onboarding_url->persist();
|
||||
|
||||
$this->logger->info( 'Persisted onboarding URL for: ' . $cache_key );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ document.addEventListener(
|
|||
() => {
|
||||
const config = PayPalCommerceGatewayOrderTrackingInfo;
|
||||
if (!typeof (PayPalCommerceGatewayOrderTrackingInfo)) {
|
||||
console.error('trackign cannot be set.');
|
||||
console.error('tracking cannot be set.');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
11
modules/ppcp-subscription/.babelrc
Normal file
11
modules/ppcp-subscription/.babelrc
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": "3.25.0"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
31
modules/ppcp-subscription/package.json
Normal file
31
modules/ppcp-subscription/package.json
Normal file
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "ppcp-subscription",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"Safari >= 8",
|
||||
"Chrome >= 41",
|
||||
"Firefox >= 43",
|
||||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"babel-loader": "^8.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.1.0",
|
||||
"webpack": "^5.76",
|
||||
"webpack-cli": "^4.10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
|
||||
"dev": "cross-env BABEL_ENV=default webpack --watch"
|
||||
}
|
||||
}
|
102
modules/ppcp-subscription/resources/js/paypal-subscription.js
Normal file
102
modules/ppcp-subscription/resources/js/paypal-subscription.js
Normal file
|
@ -0,0 +1,102 @@
|
|||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
const variations = document.querySelector('.woocommerce_variations');
|
||||
const disableFields = (productId) => {
|
||||
if(variations) {
|
||||
const children = variations.children;
|
||||
for(let i=0; i < children.length; i++) {
|
||||
const variableId = children[i].querySelector('h3').getElementsByClassName('variable_post_id')[0].value
|
||||
if (parseInt(variableId) === productId) {
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_period_interval')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_period')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_trial_length')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_trial_period')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
children[i].querySelector('.woocommerce_variable_attributes')
|
||||
.getElementsByClassName('wc_input_subscription_length')[0]
|
||||
.setAttribute('disabled', 'disabled');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const periodInterval = document.querySelector('#_subscription_period_interval');
|
||||
periodInterval.setAttribute('disabled', 'disabled');
|
||||
|
||||
const subscriptionPeriod = document.querySelector('#_subscription_period');
|
||||
subscriptionPeriod.setAttribute('disabled', 'disabled');
|
||||
|
||||
const subscriptionLength = document.querySelector('._subscription_length_field');
|
||||
subscriptionLength.style.display = 'none';
|
||||
|
||||
const subscriptionTrial = document.querySelector('._subscription_trial_length_field');
|
||||
subscriptionTrial.style.display = 'none';
|
||||
}
|
||||
|
||||
const setupProducts = () => {
|
||||
PayPalCommerceGatewayPayPalSubscriptionProducts?.forEach((product) => {
|
||||
if(product.product_connected === 'yes') {
|
||||
disableFields(product.product_id);
|
||||
}
|
||||
|
||||
const unlinkBtn = document.getElementById(`ppcp-unlink-sub-plan-${product.product_id}`);
|
||||
unlinkBtn?.addEventListener('click', (event)=>{
|
||||
event.preventDefault();
|
||||
unlinkBtn.disabled = true;
|
||||
const spinner = document.getElementById('spinner-unlink-plan');
|
||||
spinner.style.display = 'inline-block';
|
||||
|
||||
fetch(product.ajax.deactivate_plan.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: product.ajax.deactivate_plan.nonce,
|
||||
plan_id: product.plan_id,
|
||||
product_id: product.product_id
|
||||
})
|
||||
}).then(function (res) {
|
||||
return res.json();
|
||||
}).then(function (data) {
|
||||
if (!data.success) {
|
||||
unlinkBtn.disabled = false;
|
||||
spinner.style.display = 'none';
|
||||
console.error(data);
|
||||
throw Error(data.data.message);
|
||||
}
|
||||
|
||||
const enableSubscription = document.getElementById('ppcp-enable-subscription');
|
||||
const product = document.getElementById('pcpp-product');
|
||||
const plan = document.getElementById('pcpp-plan');
|
||||
enableSubscription.style.display = 'none';
|
||||
product.style.display = 'none';
|
||||
plan.style.display = 'none';
|
||||
|
||||
const enable_subscription_product = document.getElementById('ppcp_enable_subscription_product');
|
||||
enable_subscription_product.disabled = true;
|
||||
|
||||
const planUnlinked = document.getElementById('pcpp-plan-unlinked');
|
||||
planUnlinked.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
location.reload();
|
||||
}, 1000)
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
setupProducts();
|
||||
jQuery( '#woocommerce-product-data' ).on('woocommerce_variations_loaded', () => {
|
||||
setupProducts();
|
||||
});
|
||||
});
|
|
@ -15,7 +15,7 @@ use WooCommerce\PayPalCommerce\Vaulting\PaymentTokenRepository;
|
|||
|
||||
return array(
|
||||
'subscription.helper' => static function ( ContainerInterface $container ): SubscriptionHelper {
|
||||
return new SubscriptionHelper();
|
||||
return new SubscriptionHelper( $container->get( 'wcgateway.settings' ) );
|
||||
},
|
||||
'subscription.renewal-handler' => static function ( ContainerInterface $container ): RenewalHandler {
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
|
@ -54,4 +54,21 @@ return array(
|
|||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
'subscription.module.url' => static function ( ContainerInterface $container ): string {
|
||||
/**
|
||||
* The path cannot be false.
|
||||
*
|
||||
* @psalm-suppress PossiblyFalseArgument
|
||||
*/
|
||||
return plugins_url(
|
||||
'/modules/ppcp-subscription/',
|
||||
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
'subscription.deactivate-plan-endpoint' => static function ( ContainerInterface $container ): DeactivatePlanEndpoint {
|
||||
return new DeactivatePlanEndpoint(
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'api.endpoint.billing-plans' )
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
86
modules/ppcp-subscription/src/DeactivatePlanEndpoint.php
Normal file
86
modules/ppcp-subscription/src/DeactivatePlanEndpoint.php
Normal file
|
@ -0,0 +1,86 @@
|
|||
<?php
|
||||
/**
|
||||
* The deactivate Subscription Plan Endpoint.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\OrderTracking\Endpoint
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Subscription;
|
||||
|
||||
use Exception;
|
||||
use WC_Product;
|
||||
use WC_Subscriptions_Product;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
|
||||
/**
|
||||
* Class DeactivatePlanEndpoint
|
||||
*/
|
||||
class DeactivatePlanEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-deactivate-plan';
|
||||
|
||||
/**
|
||||
* The request data.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The billing plans.
|
||||
*
|
||||
* @var BillingPlans
|
||||
*/
|
||||
private $billing_plans;
|
||||
|
||||
/**
|
||||
* DeactivatePlanEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The request data.
|
||||
* @param BillingPlans $billing_plans The billing plans.
|
||||
*/
|
||||
public function __construct( RequestData $request_data, BillingPlans $billing_plans ) {
|
||||
$this->request_data = $request_data;
|
||||
$this->billing_plans = $billing_plans;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function handle_request(): void {
|
||||
if ( ! current_user_can( 'manage_woocommerce' ) ) {
|
||||
wp_send_json_error( 'Not admin.', 403 );
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$data = $this->request_data->read_request( self::ENDPOINT );
|
||||
|
||||
$plan_id = $data['plan_id'] ?? '';
|
||||
if ( $plan_id ) {
|
||||
$this->billing_plans->deactivate_plan( $plan_id );
|
||||
}
|
||||
|
||||
$product_id = $data['product_id'] ?? '';
|
||||
if ( $product_id ) {
|
||||
$product = wc_get_product( $product_id );
|
||||
if ( is_a( $product, WC_Product::class ) && WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
$product->delete_meta_data( '_ppcp_enable_subscription_product' );
|
||||
$product->delete_meta_data( '_ppcp_subscription_plan_name' );
|
||||
$product->delete_meta_data( 'ppcp_subscription_product' );
|
||||
$product->delete_meta_data( 'ppcp_subscription_plan' );
|
||||
$product->save();
|
||||
}
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -10,8 +10,6 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Subscription;
|
||||
|
||||
use WC_Order;
|
||||
use WC_Product;
|
||||
use WC_Subscription;
|
||||
use WC_Subscriptions_Product;
|
||||
use WC_Subscriptions_Synchroniser;
|
||||
|
||||
|
@ -58,7 +56,10 @@ trait FreeTrialHandlerTrait {
|
|||
|
||||
$product = wc_get_product();
|
||||
|
||||
if ( ! $product || ! WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
if (
|
||||
! $product || ! WC_Subscriptions_Product::is_subscription( $product )
|
||||
|| $product->get_meta( '_ppcp_enable_subscription_product' ) === 'yes'
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,13 +12,32 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Subscription\Helper;
|
||||
|
||||
use WC_Product;
|
||||
use WC_Product_Subscription_Variation;
|
||||
use WC_Subscriptions_Product;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
|
||||
/**
|
||||
* Class SubscriptionHelper
|
||||
*/
|
||||
class SubscriptionHelper {
|
||||
|
||||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* SubscriptionHelper constructor.
|
||||
*
|
||||
* @param Settings $settings The settings.
|
||||
*/
|
||||
public function __construct( Settings $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the current product is a subscription.
|
||||
*
|
||||
|
@ -150,4 +169,109 @@ class SubscriptionHelper {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if subscription product is allowed.
|
||||
*
|
||||
* @return bool
|
||||
* @throws NotFoundException If setting is not found.
|
||||
*/
|
||||
public function checkout_subscription_product_allowed(): bool {
|
||||
if (
|
||||
! $this->paypal_subscription_id()
|
||||
|| ! $this->cart_contains_only_one_item()
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns PayPal subscription plan id from WC subscription product.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function paypal_subscription_id(): string {
|
||||
if ( $this->current_product_is_subscription() ) {
|
||||
$product = wc_get_product();
|
||||
assert( $product instanceof WC_Product );
|
||||
|
||||
if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
return $product->get_meta( 'ppcp_subscription_plan' )['id'];
|
||||
}
|
||||
}
|
||||
|
||||
$cart = WC()->cart ?? null;
|
||||
if ( ! $cart || $cart->is_empty() ) {
|
||||
return '';
|
||||
}
|
||||
$items = $cart->get_cart_contents();
|
||||
foreach ( $items as $item ) {
|
||||
$product = wc_get_product( $item['product_id'] );
|
||||
assert( $product instanceof WC_Product );
|
||||
|
||||
if ( $product->get_type() === 'subscription' && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
return $product->get_meta( 'ppcp_subscription_plan' )['id'];
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns variations for variable PayPal subscription product.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function variable_paypal_subscription_variations(): array {
|
||||
$variations = array();
|
||||
if ( ! $this->current_product_is_subscription() ) {
|
||||
return $variations;
|
||||
}
|
||||
|
||||
$product = wc_get_product();
|
||||
assert( $product instanceof WC_Product );
|
||||
if ( $product->get_type() !== 'variable-subscription' ) {
|
||||
return $variations;
|
||||
}
|
||||
|
||||
$variation_ids = $product->get_children();
|
||||
foreach ( $variation_ids as $id ) {
|
||||
$product = wc_get_product( $id );
|
||||
if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscription_plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
|
||||
$variations[] = array(
|
||||
'id' => $product->get_id(),
|
||||
'attributes' => $product->get_attributes(),
|
||||
'subscription_plan' => $subscription_plan['id'] ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
return $variations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if cart contains only one item.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function cart_contains_only_one_item(): bool {
|
||||
if ( ! $this->plugin_is_active() ) {
|
||||
return false;
|
||||
}
|
||||
$cart = WC()->cart;
|
||||
if ( ! $cart || $cart->is_empty() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( count( $cart->get_cart() ) > 1 ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,6 +11,10 @@ namespace WooCommerce\PayPalCommerce\Subscription;
|
|||
|
||||
use Exception;
|
||||
use WC_Product;
|
||||
use WC_Product_Subscription_Variation;
|
||||
use WC_Product_Variable;
|
||||
use WC_Product_Variable_Subscription;
|
||||
use WC_Subscriptions_Product;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
|
@ -28,6 +32,7 @@ use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface
|
|||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class SubscriptionModule
|
||||
|
@ -155,6 +160,109 @@ class SubscriptionModule implements ModuleInterface {
|
|||
if ( defined( 'PPCP_FLAG_SUBSCRIPTIONS_API' ) && PPCP_FLAG_SUBSCRIPTIONS_API ) {
|
||||
$this->subscriptions_api_integration( $c );
|
||||
}
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $hook ) use ( $c ) {
|
||||
if ( ! is_string( $hook ) ) {
|
||||
return;
|
||||
}
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
$subscription_mode = $settings->has( 'subscriptions_mode' ) ? $settings->get( 'subscriptions_mode' ) : '';
|
||||
if ( $hook !== 'post.php' || $subscription_mode !== 'subscriptions_api' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
//phpcs:disable WordPress.Security.NonceVerification.Recommended
|
||||
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
|
||||
$product = wc_get_product( $post_id );
|
||||
if ( ! ( is_a( $product, WC_Product::class ) || is_a( $product, WC_Product_Subscription_Variation::class ) ) || ! WC_Subscriptions_Product::is_subscription( $product ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$module_url = $c->get( 'subscription.module.url' );
|
||||
wp_enqueue_script(
|
||||
'ppcp-paypal-subscription',
|
||||
untrailingslashit( $module_url ) . '/assets/js/paypal-subscription.js',
|
||||
array( 'jquery' ),
|
||||
$c->get( 'ppcp.asset-version' ),
|
||||
true
|
||||
);
|
||||
|
||||
$products = array( $this->set_product_config( $product ) );
|
||||
if ( $product->get_type() === 'variable-subscription' ) {
|
||||
$products = array();
|
||||
assert( $product instanceof WC_Product_Variable );
|
||||
$available_variations = $product->get_available_variations();
|
||||
foreach ( $available_variations as $variation ) {
|
||||
/**
|
||||
* The method is defined in WooCommerce.
|
||||
*
|
||||
* @psalm-suppress UndefinedMethod
|
||||
*/
|
||||
$variation = wc_get_product_object( 'variation', $variation['variation_id'] );
|
||||
$products[] = $this->set_product_config( $variation );
|
||||
}
|
||||
}
|
||||
|
||||
wp_localize_script(
|
||||
'ppcp-paypal-subscription',
|
||||
'PayPalCommerceGatewayPayPalSubscriptionProducts',
|
||||
$products
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
$endpoint = $c->get( 'subscription.deactivate-plan-endpoint' );
|
||||
assert( $endpoint instanceof DeactivatePlanEndpoint );
|
||||
add_action(
|
||||
'wc_ajax_' . DeactivatePlanEndpoint::ENDPOINT,
|
||||
array( $endpoint, 'handle_request' )
|
||||
);
|
||||
|
||||
add_action(
|
||||
'add_meta_boxes',
|
||||
function( string $post_type, WP_Post $post ) use ( $c ) {
|
||||
if ( $post_type !== 'shop_subscription' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $post->ID );
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( ! $subscription_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen_id = wc_get_page_screen_id( 'shop_subscription' );
|
||||
remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' );
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
add_meta_box(
|
||||
'ppcp_paypal_subscription',
|
||||
__( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
|
||||
function() use ( $subscription_id, $environment ) {
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
$url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id;
|
||||
echo '<p>' . esc_html__( 'This subscription is linked to a PayPal Subscription, Cancel it to unlink.', 'woocommerce-paypal-payments' ) . '</p>';
|
||||
echo '<p><strong>' . esc_html__( 'Subscription:', 'woocommerce-paypal-payments' ) . '</strong> <a href="' . esc_url( $url ) . '" target="_blank">' . esc_attr( $subscription_id ) . '</a></p>';
|
||||
},
|
||||
$post_type,
|
||||
'side'
|
||||
);
|
||||
|
||||
},
|
||||
30,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,40 +470,41 @@ class SubscriptionModule implements ModuleInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
|
||||
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
|
||||
$product->save();
|
||||
|
||||
if ( $product->get_type() === 'subscription' && $enable_subscription_product === 'yes' ) {
|
||||
$subscriptions_api_handler = $c->get( 'subscription.api-handler' );
|
||||
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
|
||||
|
||||
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
$subscriptions_api_handler->update_product( $product );
|
||||
$subscriptions_api_handler->update_plan( $product );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) {
|
||||
$subscriptions_api_handler->create_product( $product );
|
||||
}
|
||||
|
||||
if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
$subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) );
|
||||
if ( ! is_string( $subscription_plan_name ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name );
|
||||
$product->save();
|
||||
|
||||
$subscriptions_api_handler->create_plan( $subscription_plan_name, $product );
|
||||
}
|
||||
}
|
||||
$subscriptions_api_handler = $c->get( 'subscription.api-handler' );
|
||||
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
|
||||
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
|
||||
},
|
||||
12
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_save_product_variation',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $variation_id ) use ( $c ) {
|
||||
$wcsnonce_save_variations = wc_clean( wp_unslash( $_POST['_wcsnonce_save_variations'] ?? '' ) );
|
||||
if (
|
||||
! WC_Subscriptions_Product::is_subscription( $variation_id )
|
||||
|| ! is_string( $wcsnonce_save_variations )
|
||||
|| ! wp_verify_nonce( $wcsnonce_save_variations, 'wcs_subscription_variations' )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product = wc_get_product( $variation_id );
|
||||
if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscriptions_api_handler = $c->get( 'subscription.api-handler' );
|
||||
assert( $subscriptions_api_handler instanceof SubscriptionsApiHandler );
|
||||
$this->update_subscription_product_meta( $product, $subscriptions_api_handler );
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_process_shop_subscription_meta',
|
||||
/**
|
||||
|
@ -604,50 +713,6 @@ class SubscriptionModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_product_options_general_product_data',
|
||||
function() use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
if ( $subscriptions_mode === 'subscriptions_api' ) {
|
||||
/**
|
||||
* Needed for getting global post object.
|
||||
*
|
||||
* @psalm-suppress InvalidGlobal
|
||||
*/
|
||||
global $post;
|
||||
$product = wc_get_product( $post->ID );
|
||||
if ( ! is_a( $product, WC_Product::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enable_subscription_product = $product->get_meta( '_ppcp_enable_subscription_product' );
|
||||
$subscription_plan_name = $product->get_meta( '_ppcp_subscription_plan_name' );
|
||||
|
||||
echo '<div class="options_group subscription_pricing show_if_subscription hidden">';
|
||||
echo '<p class="form-field"><label for="_ppcp_enable_subscription_product">Connect to PayPal</label><input type="checkbox" id="ppcp_enable_subscription_product" name="_ppcp_enable_subscription_product" value="yes" ' . checked( $enable_subscription_product, 'yes', false ) . '/><span class="description">Connect Product to PayPal Subscriptions Plan</span></p>';
|
||||
|
||||
$subscription_product = $product->get_meta( 'ppcp_subscription_product' );
|
||||
$subscription_plan = $product->get_meta( 'ppcp_subscription_plan' );
|
||||
if ( $subscription_product && $subscription_plan ) {
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
echo '<p class="form-field"><label>Product</label><a href="' . esc_url( $host . '/billing/plans/products/' . $subscription_product['id'] ) . '" target="_blank">' . esc_attr( $subscription_product['id'] ) . '</a></p>';
|
||||
echo '<p class="form-field"><label>Plan</label><a href="' . esc_url( $host . '/billing/plans/' . $subscription_plan['id'] ) . '" target="_blank">' . esc_attr( $subscription_plan['id'] ) . '</a></p>';
|
||||
} else {
|
||||
echo '<p class="form-field"><label for="_ppcp_subscription_plan_name">Plan Name</label><input type="text" class="short" id="ppcp_subscription_plan_name" name="_ppcp_subscription_plan_name" value="' . esc_attr( $subscription_plan_name ) . '"></p>';
|
||||
}
|
||||
echo '</div>';
|
||||
}
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
'woocommerce_order_data_store_cpt_get_orders_query',
|
||||
/**
|
||||
|
@ -724,5 +789,201 @@ class SubscriptionModule implements ModuleInterface {
|
|||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_product_options_general_product_data',
|
||||
function() use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
if ( $subscriptions_mode === 'subscriptions_api' ) {
|
||||
/**
|
||||
* Needed for getting global post object.
|
||||
*
|
||||
* @psalm-suppress InvalidGlobal
|
||||
*/
|
||||
global $post;
|
||||
$product = wc_get_product( $post->ID );
|
||||
if ( ! is_a( $product, WC_Product::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
echo '<div class="options_group subscription_pricing show_if_subscription hidden">';
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
echo '</div>';
|
||||
|
||||
}
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'woocommerce_variation_options_pricing',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $loop, $variation_data, $variation ) use ( $c ) {
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
try {
|
||||
$subscriptions_mode = $settings->get( 'subscriptions_mode' );
|
||||
if ( $subscriptions_mode === 'subscriptions_api' ) {
|
||||
$product = wc_get_product( $variation->ID );
|
||||
if ( ! is_a( $product, WC_Product_Subscription_Variation::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
$this->render_paypal_subscription_fields( $product, $environment );
|
||||
|
||||
}
|
||||
} catch ( NotFoundException $exception ) {
|
||||
return;
|
||||
}
|
||||
},
|
||||
10,
|
||||
3
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render PayPal Subscriptions fields.
|
||||
*
|
||||
* @param WC_Product $product WC Product.
|
||||
* @param Environment $environment The environment.
|
||||
* @return void
|
||||
*/
|
||||
private function render_paypal_subscription_fields( WC_Product $product, Environment $environment ): void {
|
||||
$enable_subscription_product = $product->get_meta( '_ppcp_enable_subscription_product' );
|
||||
$style = $product->get_type() === 'subscription_variation' ? 'float:left; width:150px;' : '';
|
||||
|
||||
echo '<p class="form-field">';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are label open and close tags.
|
||||
esc_html__( '%1$sConnect to PayPal%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<label for="_ppcp_enable_subscription_product" style="' . esc_attr( $style ) . '">',
|
||||
'</label>'
|
||||
);
|
||||
echo '<input type="checkbox" id="ppcp_enable_subscription_product" name="_ppcp_enable_subscription_product" value="yes" ' . checked( $enable_subscription_product, 'yes', false ) . '/>';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are label open and close tags.
|
||||
esc_html__( '%1$sConnect Product to PayPal Subscriptions Plan%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<span class="description">',
|
||||
'</span>'
|
||||
);
|
||||
|
||||
echo wc_help_tip( esc_html__( 'Create a subscription product and plan to bill customers at regular intervals. Be aware that certain subscription settings cannot be modified once the PayPal Subscription is linked to this product. Unlink the product to edit disabled fields.', 'woocommerce-paypal-payments' ) );
|
||||
echo '</p>';
|
||||
|
||||
$subscription_product = $product->get_meta( 'ppcp_subscription_product' );
|
||||
$subscription_plan = $product->get_meta( 'ppcp_subscription_plan' );
|
||||
$subscription_plan_name = $product->get_meta( '_ppcp_subscription_plan_name' );
|
||||
if ( $subscription_product && $subscription_plan ) {
|
||||
if ( $enable_subscription_product !== 'yes' ) {
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are button and wrapper html tags.
|
||||
esc_html__( '%1$sUnlink PayPal Subscription Plan%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="ppcp-enable-subscription"><label></label><button class="button" id="ppcp-unlink-sub-plan-' . esc_attr( (string) $product->get_id() ) . '">',
|
||||
'</button><span class="spinner is-active" id="spinner-unlink-plan" style="float: none; display:none;"></span></p>'
|
||||
);
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s is open and closing paragraph tag.
|
||||
esc_html__( '%1$sPlan unlinked successfully ✔️%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-plan-unlinked" style="display: none;">',
|
||||
'</p>'
|
||||
);
|
||||
}
|
||||
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sProduct%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-product"><label style="' . esc_attr( $style ) . '">',
|
||||
'</label><a href="' . esc_url( $host . '/billing/plans/products/' . $subscription_product['id'] ) . '" target="_blank">' . esc_attr( $subscription_product['id'] ) . '</a></p>'
|
||||
);
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sPlan%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field" id="pcpp-plan"><label style="' . esc_attr( $style ) . '">',
|
||||
'</label><a href="' . esc_url( $host . '/billing/plans/' . $subscription_plan['id'] ) . '" target="_blank">' . esc_attr( $subscription_plan['id'] ) . '</a></p>'
|
||||
);
|
||||
} else {
|
||||
echo sprintf(
|
||||
// translators: %1$s and %2$s are wrapper html tags.
|
||||
esc_html__( '%1$sPlan Name%2$s', 'woocommerce-paypal-payments' ),
|
||||
'<p class="form-field"><label for="_ppcp_subscription_plan_name">',
|
||||
'</label><input type="text" class="short" id="ppcp_subscription_plan_name" name="_ppcp_subscription_plan_name" value="' . esc_attr( $subscription_plan_name ) . '"></p>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates subscription product meta.
|
||||
*
|
||||
* @param WC_Product $product The product.
|
||||
* @param SubscriptionsApiHandler $subscriptions_api_handler The subscription api handler.
|
||||
* @return void
|
||||
*/
|
||||
private function update_subscription_product_meta( WC_Product $product, SubscriptionsApiHandler $subscriptions_api_handler ): void {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$enable_subscription_product = wc_clean( wp_unslash( $_POST['_ppcp_enable_subscription_product'] ?? '' ) );
|
||||
$product->update_meta_data( '_ppcp_enable_subscription_product', $enable_subscription_product );
|
||||
$product->save();
|
||||
|
||||
if ( ( $product->get_type() === 'subscription' || $product->get_type() === 'subscription_variation' ) && $enable_subscription_product === 'yes' ) {
|
||||
if ( $product->meta_exists( 'ppcp_subscription_product' ) && $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
$subscriptions_api_handler->update_product( $product );
|
||||
$subscriptions_api_handler->update_plan( $product );
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $product->meta_exists( 'ppcp_subscription_product' ) ) {
|
||||
$subscriptions_api_handler->create_product( $product );
|
||||
}
|
||||
|
||||
if ( $product->meta_exists( 'ppcp_subscription_product' ) && ! $product->meta_exists( 'ppcp_subscription_plan' ) ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$subscription_plan_name = wc_clean( wp_unslash( $_POST['_ppcp_subscription_plan_name'] ?? '' ) );
|
||||
if ( ! is_string( $subscription_plan_name ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$product->update_meta_data( '_ppcp_subscription_plan_name', $subscription_plan_name );
|
||||
$product->save();
|
||||
|
||||
$subscriptions_api_handler->create_plan( $subscription_plan_name, $product );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns subscription product configuration.
|
||||
*
|
||||
* @param WC_Product $product The product.
|
||||
* @return array
|
||||
*/
|
||||
private function set_product_config( WC_Product $product ): array {
|
||||
$plan = $product->get_meta( 'ppcp_subscription_plan' ) ?? array();
|
||||
$plan_id = $plan['id'] ?? '';
|
||||
|
||||
return array(
|
||||
'product_connected' => $product->get_meta( '_ppcp_enable_subscription_product' ) ?? '',
|
||||
'plan_id' => $plan_id,
|
||||
'product_id' => $product->get_id(),
|
||||
'ajax' => array(
|
||||
'deactivate_plan' => array(
|
||||
'endpoint' => \WC_AJAX::get_endpoint( DeactivatePlanEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( DeactivatePlanEndpoint::ENDPOINT ),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -265,7 +265,7 @@ class SubscriptionsApiHandler {
|
|||
'REGULAR',
|
||||
array(
|
||||
'fixed_price' => array(
|
||||
'value' => $product->get_meta( '_subscription_price' ),
|
||||
'value' => $product->get_meta( '_subscription_price' ) ?: $product->get_price(),
|
||||
'currency_code' => $this->currency,
|
||||
),
|
||||
),
|
||||
|
|
35
modules/ppcp-subscription/webpack.config.js
Normal file
35
modules/ppcp-subscription/webpack.config.js
Normal file
|
@ -0,0 +1,35 @@
|
|||
const path = require('path');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
module.exports = {
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
target: 'web',
|
||||
entry: {
|
||||
'paypal-subscription': path.resolve('./resources/js/paypal-subscription.js'),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
filename: 'js/[name].js',
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.js?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'css/[name].css',
|
||||
}
|
||||
},
|
||||
{loader:'sass-loader'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
};
|
2185
modules/ppcp-subscription/yarn.lock
Normal file
2185
modules/ppcp-subscription/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -149,7 +149,8 @@ return array(
|
|||
$session_handler = $container->get( 'session.handler' );
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
$settings_status = $container->get( 'wcgateway.settings.status' );
|
||||
return new DisableGateways( $session_handler, $settings, $settings_status );
|
||||
$subscription_helper = $container->get( 'subscription.helper' );
|
||||
return new DisableGateways( $session_handler, $settings, $settings_status, $subscription_helper );
|
||||
},
|
||||
|
||||
'wcgateway.is-wc-payments-page' => static function ( ContainerInterface $container ): bool {
|
||||
|
@ -292,6 +293,7 @@ return array(
|
|||
$signup_link_ids = $container->get( 'onboarding.signup-link-ids' );
|
||||
$pui_status_cache = $container->get( 'pui.status-cache' );
|
||||
$dcc_status_cache = $container->get( 'dcc.status-cache' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new SettingsListener(
|
||||
$settings,
|
||||
$fields,
|
||||
|
@ -304,7 +306,8 @@ return array(
|
|||
$signup_link_ids,
|
||||
$pui_status_cache,
|
||||
$dcc_status_cache,
|
||||
$container->get( 'http.redirector' )
|
||||
$container->get( 'http.redirector' ),
|
||||
$logger
|
||||
);
|
||||
},
|
||||
'wcgateway.order-processor' => static function ( ContainerInterface $container ): OrderProcessor {
|
||||
|
@ -810,7 +813,14 @@ return array(
|
|||
),
|
||||
'paypal_saved_payments' => array(
|
||||
'heading' => __( 'Saved payments', 'woocommerce-paypal-payments' ),
|
||||
'description' => __( 'PayPal can save your customers’ payment methods.', 'woocommerce-paypal-payments' ),
|
||||
'description' => sprintf(
|
||||
// translators: %1$s, %2$s, %3$s and %4$s are a link tags.
|
||||
__( 'PayPal can securely store your customers\' payment methods for %1$sfuture payments%2$s and %3$ssubscriptions%4$s, simplifying the checkout process and enabling recurring transactions on your website.', 'woocommerce-paypal-payments' ),
|
||||
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#vaulting-saving-a-payment-method" target="_blank">',
|
||||
'</a>',
|
||||
'<a href="https://woocommerce.com/document/woocommerce-paypal-payments/#subscriptions-faq" target="_blank">',
|
||||
'</a>'
|
||||
),
|
||||
'type' => 'ppcp-heading',
|
||||
'screens' => array(
|
||||
State::STATE_START,
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\WcGateway\Checkout;
|
|||
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
@ -44,22 +45,32 @@ class DisableGateways {
|
|||
*/
|
||||
protected $settings_status;
|
||||
|
||||
/**
|
||||
* The subscription helper.
|
||||
*
|
||||
* @var SubscriptionHelper
|
||||
*/
|
||||
private $subscription_helper;
|
||||
|
||||
/**
|
||||
* DisableGateways constructor.
|
||||
*
|
||||
* @param SessionHandler $session_handler The Session Handler.
|
||||
* @param ContainerInterface $settings The Settings.
|
||||
* @param SettingsStatus $settings_status The Settings status helper.
|
||||
* @param SubscriptionHelper $subscription_helper The subscription helper.
|
||||
*/
|
||||
public function __construct(
|
||||
SessionHandler $session_handler,
|
||||
ContainerInterface $settings,
|
||||
SettingsStatus $settings_status
|
||||
SettingsStatus $settings_status,
|
||||
SubscriptionHelper $subscription_helper
|
||||
) {
|
||||
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->settings_status = $settings_status;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->settings_status = $settings_status;
|
||||
$this->subscription_helper = $subscription_helper;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Settings;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Entity\Message;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
|
@ -23,6 +24,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\WebhookRegistrar;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\WooCommerce\Logging\Logger\NullLogger;
|
||||
|
||||
/**
|
||||
* Class SettingsListener
|
||||
|
@ -122,6 +124,27 @@ class SettingsListener {
|
|||
*/
|
||||
protected $redirector;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* Max onboarding URL retries.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $onboarding_max_retries = 5;
|
||||
|
||||
/**
|
||||
* Delay between onboarding URL retries.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $onboarding_retry_delay = 2;
|
||||
|
||||
/**
|
||||
* SettingsListener constructor.
|
||||
*
|
||||
|
@ -137,6 +160,7 @@ class SettingsListener {
|
|||
* @param Cache $pui_status_cache The PUI status cache.
|
||||
* @param Cache $dcc_status_cache The DCC status cache.
|
||||
* @param RedirectorInterface $redirector The HTTP redirector.
|
||||
* @param ?LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
Settings $settings,
|
||||
|
@ -150,7 +174,8 @@ class SettingsListener {
|
|||
array $signup_link_ids,
|
||||
Cache $pui_status_cache,
|
||||
Cache $dcc_status_cache,
|
||||
RedirectorInterface $redirector
|
||||
RedirectorInterface $redirector,
|
||||
LoggerInterface $logger = null
|
||||
) {
|
||||
|
||||
$this->settings = $settings;
|
||||
|
@ -165,6 +190,7 @@ class SettingsListener {
|
|||
$this->pui_status_cache = $pui_status_cache;
|
||||
$this->dcc_status_cache = $dcc_status_cache;
|
||||
$this->redirector = $redirector;
|
||||
$this->logger = $logger ?: new NullLogger();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -187,16 +213,48 @@ class SettingsListener {
|
|||
$merchant_id = sanitize_text_field( wp_unslash( $_GET['merchantIdInPayPal'] ) );
|
||||
$merchant_email = sanitize_text_field( wp_unslash( $_GET['merchantId'] ) );
|
||||
$onboarding_token = sanitize_text_field( wp_unslash( $_GET['ppcpToken'] ) );
|
||||
$retry_count = isset( $_GET['ppcpRetry'] ) ? ( (int) sanitize_text_field( wp_unslash( $_GET['ppcpRetry'] ) ) ) : 0;
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Missing
|
||||
// phpcs:enable WordPress.Security.NonceVerification.Recommended
|
||||
|
||||
$this->settings->set( 'merchant_id', $merchant_id );
|
||||
$this->settings->set( 'merchant_email', $merchant_email );
|
||||
|
||||
if ( ! OnboardingUrl::validate_token_and_delete( $this->signup_link_cache, $onboarding_token, get_current_user_id() ) ) {
|
||||
$this->onboarding_redirect( false );
|
||||
// If no client_id is present we will try to wait for PayPal to invoke LoginSellerEndpoint.
|
||||
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
|
||||
|
||||
// Try at most {onboarding_max_retries} times ({onboarding_retry_delay} seconds delay). Then give up and just fill the merchant fields like before.
|
||||
if ( $retry_count < $this->onboarding_max_retries ) {
|
||||
|
||||
if ( $this->onboarding_retry_delay > 0 ) {
|
||||
sleep( $this->onboarding_retry_delay );
|
||||
}
|
||||
|
||||
$retry_count++;
|
||||
$this->logger->info( 'Retrying onboarding return URL, retry nr: ' . ( (string) $retry_count ) );
|
||||
$redirect_url = add_query_arg( 'ppcpRetry', $retry_count );
|
||||
$this->redirector->redirect( $redirect_url );
|
||||
}
|
||||
}
|
||||
|
||||
// Process token validation.
|
||||
$onboarding_token_sample = ( (string) substr( $onboarding_token, 0, 2 ) ) . '...' . ( (string) substr( $onboarding_token, -6 ) );
|
||||
$this->logger->debug( 'Validating onboarding ppcpToken: ' . $onboarding_token_sample );
|
||||
|
||||
if ( ! OnboardingUrl::validate_token_and_delete( $this->signup_link_cache, $onboarding_token, get_current_user_id() ) ) {
|
||||
if ( OnboardingUrl::validate_previous_token( $this->signup_link_cache, $onboarding_token, get_current_user_id() ) ) {
|
||||
// It's a valid token used previously, don't do anything but silently redirect.
|
||||
$this->logger->info( 'Validated previous token, silently redirecting: ' . $onboarding_token_sample );
|
||||
$this->onboarding_redirect();
|
||||
} else {
|
||||
$this->logger->error( 'Failed to validate onboarding ppcpToken: ' . $onboarding_token_sample );
|
||||
$this->onboarding_redirect( false );
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->info( 'Validated onboarding ppcpToken: ' . $onboarding_token_sample );
|
||||
|
||||
// Save the merchant data.
|
||||
$is_sandbox = $this->settings->has( 'sandbox_on' ) && $this->settings->get( 'sandbox_on' );
|
||||
if ( $is_sandbox ) {
|
||||
$this->settings->set( 'merchant_id_sandbox', $merchant_id );
|
||||
|
@ -212,6 +270,7 @@ class SettingsListener {
|
|||
*/
|
||||
do_action( 'woocommerce_paypal_payments_onboarding_before_redirect' );
|
||||
|
||||
// If after all the retry redirects there still isn't a valid client_id then just send an error.
|
||||
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
|
||||
$this->onboarding_redirect( false );
|
||||
}
|
||||
|
@ -230,6 +289,10 @@ class SettingsListener {
|
|||
|
||||
if ( ! $success ) {
|
||||
$redirect_url = add_query_arg( 'ppcp-onboarding-error', '1', $redirect_url );
|
||||
$this->logger->info( 'Redirect ERROR: ' . $redirect_url );
|
||||
} else {
|
||||
$redirect_url = remove_query_arg( 'ppcp-onboarding-error', $redirect_url );
|
||||
$this->logger->info( 'Redirect OK: ' . $redirect_url );
|
||||
}
|
||||
|
||||
$this->redirector->redirect( $redirect_url );
|
||||
|
|
|
@ -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,6 +12,7 @@
|
|||
"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",
|
||||
|
@ -20,6 +21,7 @@
|
|||
"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",
|
||||
|
@ -29,6 +31,7 @@
|
|||
"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",
|
||||
|
@ -69,8 +72,5 @@
|
|||
"dependencies": {
|
||||
"dotenv": "^16.0.3",
|
||||
"npm-run-all": "^4.1.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.31.1"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,12 +0,0 @@
|
|||
require('dotenv').config({ path: '.env.e2e' });
|
||||
|
||||
const config = {
|
||||
testDir: './tests/playwright',
|
||||
timeout: 60000,
|
||||
use: {
|
||||
baseURL: process.env.BASEURL,
|
||||
ignoreHTTPSErrors: true,
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = config;
|
|
@ -685,10 +685,11 @@
|
|||
<code>listen_for_merchant_id</code>
|
||||
<code>listen_for_vaulting_enabled</code>
|
||||
</MissingReturnType>
|
||||
<PossiblyInvalidArgument occurrences="4">
|
||||
<PossiblyInvalidArgument occurrences="5">
|
||||
<code>wp_unslash( $_GET['merchantId'] )</code>
|
||||
<code>wp_unslash( $_GET['merchantIdInPayPal'] )</code>
|
||||
<code>wp_unslash( $_GET['ppcpToken'] )</code>
|
||||
<code>wp_unslash( $_GET['ppcpRetry'] )</code>
|
||||
<code>wp_unslash( $_POST['ppcp-nonce'] )</code>
|
||||
</PossiblyInvalidArgument>
|
||||
</file>
|
||||
|
|
|
@ -58,6 +58,7 @@ class OnboardingUrlTest extends TestCase
|
|||
// Expectations
|
||||
$this->cache->shouldReceive('has')->once()->andReturn(true);
|
||||
$this->cache->shouldReceive('get')->once()->andReturn($cacheData);
|
||||
$this->cache->shouldReceive('set')->once();
|
||||
$this->cache->shouldReceive('delete')->once();
|
||||
|
||||
$this->assertTrue(
|
||||
|
|
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
|
||||
```
|
||||
|
8
tests/Playwright/package.json
Normal file
8
tests/Playwright/package.json
Normal file
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@playwright/test": "^1.34.2",
|
||||
"@woocommerce/woocommerce-rest-api": "^1.0.1",
|
||||
"dotenv": "^16.0.3"
|
||||
}
|
||||
}
|
78
tests/Playwright/playwright.config.js
Normal file
78
tests/Playwright/playwright.config.js
Normal file
|
@ -0,0 +1,78 @@
|
|||
// @ts-check
|
||||
const { defineConfig, devices } = require('@playwright/test');
|
||||
|
||||
require('dotenv').config({ path: '.env' });
|
||||
|
||||
/**
|
||||
* @see https://playwright.dev/docs/test-configuration
|
||||
*/
|
||||
module.exports = defineConfig({
|
||||
timeout: 60000,
|
||||
testDir: './tests',
|
||||
/* Run tests in files in parallel */
|
||||
fullyParallel: true,
|
||||
/* Fail the build on CI if you accidentally left test.only in the source code. */
|
||||
forbidOnly: !!process.env.CI,
|
||||
/* Retry on CI only */
|
||||
retries: process.env.CI ? 2 : 0,
|
||||
/* Opt out of parallel tests on CI. */
|
||||
workers: process.env.CI ? 1 : undefined,
|
||||
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
|
||||
reporter: [
|
||||
[process.env.CI ? 'github' : 'list'],
|
||||
['html', {open: 'never'}],
|
||||
],
|
||||
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
|
||||
use: {
|
||||
/* Base URL to use in actions like `await page.goto('/')`. */
|
||||
baseURL: process.env.BASEURL,
|
||||
ignoreHTTPSErrors: true,
|
||||
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
|
||||
trace: 'on-first-retry',
|
||||
},
|
||||
|
||||
/* Configure projects for major browsers */
|
||||
projects: [
|
||||
{
|
||||
name: 'chromium',
|
||||
use: { ...devices['Desktop Chrome'] },
|
||||
},
|
||||
|
||||
// {
|
||||
// name: 'firefox',
|
||||
// use: { ...devices['Desktop Firefox'] },
|
||||
// },
|
||||
//
|
||||
// {
|
||||
// name: 'webkit',
|
||||
// use: { ...devices['Desktop Safari'] },
|
||||
// },
|
||||
|
||||
/* Test against mobile viewports. */
|
||||
// {
|
||||
// name: 'Mobile Chrome',
|
||||
// use: { ...devices['Pixel 5'] },
|
||||
// },
|
||||
// {
|
||||
// name: 'Mobile Safari',
|
||||
// use: { ...devices['iPhone 12'] },
|
||||
// },
|
||||
|
||||
/* Test against branded browsers. */
|
||||
// {
|
||||
// name: 'Microsoft Edge',
|
||||
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
|
||||
// },
|
||||
// {
|
||||
// name: 'Google Chrome',
|
||||
// use: { ..devices['Desktop Chrome'], channel: 'chrome' },
|
||||
// },
|
||||
],
|
||||
|
||||
/* Run your local dev server before starting the tests */
|
||||
// webServer: {
|
||||
// command: 'npm run start',
|
||||
// url: 'http://127.0.0.1:3000',
|
||||
// reuseExistingServer: !process.env.CI,
|
||||
// },
|
||||
});
|
|
@ -65,7 +65,7 @@ test.describe('Classic checkout', () => {
|
|||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', async ({page}) => {
|
||||
test('Advanced Credit and Debit Card place order from Checkout page', async ({page}) => {
|
||||
await page.goto(PRODUCT_URL);
|
||||
await page.locator('.single_add_to_cart_button').click();
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
const {test, expect} = require('@playwright/test');
|
||||
|
||||
const {loginAsAdmin, loginAsCustomer} = require('./utils/user');
|
||||
const {openPaypalPopup, loginIntoPaypal, completePaypalPayment} = require("./utils/paypal-popup");
|
||||
const {fillCheckoutForm, expectOrderReceivedPage} = require("./utils/checkout");
|
||||
const {createProduct, deleteProduct, updateProduct, updateProductUi} = require("./utils/products");
|
||||
const {
|
||||
AUTHORIZATION,
|
||||
SUBSCRIPTION_URL,
|
||||
|
@ -18,7 +20,7 @@ async function purchaseSubscriptionFromCart(page) {
|
|||
const popup = await openPaypalPopup(page);
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await popup.getByText('Continue', { exact: true }).click();
|
||||
await popup.getByText('Continue', {exact: true}).click();
|
||||
await popup.locator('#confirmButtonTop').click();
|
||||
|
||||
await fillCheckoutForm(page);
|
||||
|
@ -92,7 +94,7 @@ test.describe.serial('Subscriptions Merchant', () => {
|
|||
await loginAsAdmin(page);
|
||||
|
||||
await page.goto('/wp-admin/edit.php?post_type=product');
|
||||
await page.getByRole('link', { name: productTitle, exact: true }).click();
|
||||
await page.getByRole('link', {name: productTitle, exact: true}).click();
|
||||
|
||||
await page.fill('#title', `Updated ${productTitle}`);
|
||||
await page.fill('#_subscription_price', '20');
|
||||
|
@ -209,7 +211,7 @@ test.describe('Subscriber purchase a Subscription', () => {
|
|||
const popup = await openPaypalPopup(page);
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await popup.getByText('Continue', { exact: true }).click();
|
||||
await popup.getByText('Continue', {exact: true}).click();
|
||||
|
||||
await Promise.all([
|
||||
page.waitForNavigation(),
|
||||
|
@ -226,7 +228,7 @@ test.describe('Subscriber purchase a Subscription', () => {
|
|||
const popup = await openPaypalPopup(page);
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await popup.getByText('Continue', { exact: true }).click();
|
||||
await popup.getByText('Continue', {exact: true}).click();
|
||||
await popup.locator('#confirmButtonTop').click();
|
||||
|
||||
await fillCheckoutForm(page);
|
||||
|
@ -308,4 +310,69 @@ test.describe('Subscriber my account actions', () => {
|
|||
details = await subscription.json();
|
||||
await expect(details.status).toBe('CANCELLED');
|
||||
});
|
||||
}) ;
|
||||
});
|
||||
|
||||
test.describe('Plan connected display buttons', () => {
|
||||
test('Disable buttons if no plan connected', async ({page}) => {
|
||||
const data = {
|
||||
name: (Math.random() + 1).toString(36).substring(7),
|
||||
type: 'subscription',
|
||||
meta_data: [
|
||||
{
|
||||
key: '_subscription_price',
|
||||
value: '10'
|
||||
}
|
||||
]
|
||||
}
|
||||
const productId = await createProduct(data)
|
||||
|
||||
// for some reason product meta is not updated in frontend,
|
||||
// so we need to manually update the product
|
||||
await updateProductUi(productId, page);
|
||||
|
||||
await page.goto(`/product/?p=${productId}`)
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).not.toBeVisible();
|
||||
|
||||
await page.locator('.single_add_to_cart_button').click();
|
||||
await page.goto('/cart');
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toHaveCSS('cursor', 'not-allowed')
|
||||
|
||||
await page.goto('/checkout');
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toHaveCSS('cursor', 'not-allowed')
|
||||
|
||||
await deleteProduct(productId)
|
||||
})
|
||||
|
||||
test('Enable buttons if plan connected', async ({page}) => {
|
||||
const data = {
|
||||
name: (Math.random() + 1).toString(36).substring(7),
|
||||
type: 'subscription',
|
||||
meta_data: [
|
||||
{
|
||||
key: '_subscription_price',
|
||||
value: '10'
|
||||
}
|
||||
]
|
||||
}
|
||||
const productId = await createProduct(data)
|
||||
|
||||
await loginAsAdmin(page);
|
||||
await page.goto(`/wp-admin/post.php?post=${productId}&action=edit`)
|
||||
await page.locator('#ppcp_enable_subscription_product').check();
|
||||
await page.locator('#ppcp_subscription_plan_name').fill('Plan name');
|
||||
await page.locator('#publish').click();
|
||||
await expect(page.getByText('Product updated.')).toBeVisible();
|
||||
|
||||
await page.goto(`/product/?p=${productId}`)
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
|
||||
|
||||
await page.locator('.single_add_to_cart_button').click();
|
||||
await page.goto('/cart');
|
||||
await expect(page.locator('#ppc-button-ppcp-gateway')).toBeVisible();
|
||||
|
||||
await deleteProduct(productId)
|
||||
})
|
||||
})
|
||||
|
59
tests/Playwright/tests/utils/products.js
Normal file
59
tests/Playwright/tests/utils/products.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
import {loginAsAdmin} from "./user";
|
||||
import {expect} from "@playwright/test";
|
||||
|
||||
const wcApi = require('@woocommerce/woocommerce-rest-api').default;
|
||||
const {
|
||||
BASEURL,
|
||||
WP_MERCHANT_USER,
|
||||
WP_MERCHANT_PASSWORD,
|
||||
} = process.env;
|
||||
|
||||
const wc = () => {
|
||||
return new wcApi({
|
||||
url: BASEURL,
|
||||
consumerKey: WP_MERCHANT_USER,
|
||||
consumerSecret: WP_MERCHANT_PASSWORD,
|
||||
version: 'wc/v3',
|
||||
});
|
||||
}
|
||||
|
||||
export const createProduct = async (data) => {
|
||||
const api = wc();
|
||||
|
||||
return await api.post('products', data)
|
||||
.then((response) => {
|
||||
return response.data.id
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
export const updateProduct = async (data, id) => {
|
||||
const api = wc();
|
||||
|
||||
return await api.put(`products/${id}`, data)
|
||||
.then((response) => {
|
||||
return response.data.id
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
export const deleteProduct = async (id) => {
|
||||
const api = wc();
|
||||
|
||||
return await api.delete(`products/${id}`)
|
||||
.then((response) => {
|
||||
return response.data.id
|
||||
}).catch((error) => {
|
||||
console.log(error)
|
||||
})
|
||||
}
|
||||
|
||||
export const updateProductUi = async (id, page) => {
|
||||
await loginAsAdmin(page);
|
||||
await page.goto(`/wp-admin/post.php?post=${id}&action=edit`)
|
||||
await page.locator('#publish').click();
|
||||
await expect(page.getByText('Product updated.')).toBeVisible();
|
||||
}
|
||||
|
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.
|
25
yarn.lock
25
yarn.lock
|
@ -2,21 +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==
|
||||
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
|
@ -146,11 +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==
|
||||
|
||||
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"
|
||||
|
@ -447,11 +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==
|
||||
|
||||
read-pkg@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue