mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-08-30 05:00:51 +08:00
Merge branch 'trunk' into PCP-1877-improve-line-item-mismatch-handling
This commit is contained in:
commit
fcaf4d495e
121 changed files with 6058 additions and 498 deletions
10
.ddev/addon-metadata/phpmyadmin/manifest.yaml
Normal file
10
.ddev/addon-metadata/phpmyadmin/manifest.yaml
Normal file
|
@ -0,0 +1,10 @@
|
|||
name: phpmyadmin
|
||||
repository: ddev/ddev-phpmyadmin
|
||||
version: v0.3.0
|
||||
install_date: "2023-08-02T12:20:36+02:00"
|
||||
project_files:
|
||||
- docker-compose.phpmyadmin.yaml
|
||||
- docker-compose.phpmyadmin-norouter.yaml
|
||||
- commands/host/phpmyadmin
|
||||
global_files: []
|
||||
removal_actions: []
|
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: []
|
52
.ddev/commands/host/phpmyadmin
Executable file
52
.ddev/commands/host/phpmyadmin
Executable file
|
@ -0,0 +1,52 @@
|
|||
#!/bin/bash
|
||||
|
||||
## #ddev-generated: If you want to edit and own this file, remove this line.
|
||||
## Description: Launch a browser with PhpMyAdmin
|
||||
## Usage: phpmyadmin
|
||||
## Example: "ddev phpmyadmin"
|
||||
|
||||
DDEV_PHPMYADMIN_PORT=8036
|
||||
DDEV_PHPMYADMIN_HTTPS_PORT=8037
|
||||
|
||||
FULLURL=${DDEV_PRIMARY_URL}
|
||||
HTTPS=""
|
||||
if [ ${DDEV_PRIMARY_URL%://*} = "https" ]; then HTTPS=true; fi
|
||||
|
||||
if [[ ! -z "${GITPOD_INSTANCE_ID}" ]] || [[ "${CODESPACES}" == "true" ]]; then
|
||||
FULLURL="${FULLURL/-${DDEV_HOST_WEBSERVER_PORT}/-${DDEV_PHPMYADMIN_PORT}}"
|
||||
else
|
||||
if [ "${HTTPS}" = "" ]; then
|
||||
FULLURL="${FULLURL%:[0-9]*}:${DDEV_PHPMYADMIN_PORT}"
|
||||
else
|
||||
FULLURL="${FULLURL%:[0-9]*}:${DDEV_PHPMYADMIN_HTTPS_PORT}"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "${1:-}" ] ; then
|
||||
if [[ ${1::1} != "/" ]] ; then
|
||||
FULLURL="${FULLURL}/";
|
||||
fi
|
||||
|
||||
FULLURL="${FULLURL}${1}";
|
||||
fi
|
||||
|
||||
if [ "${DDEV_DEBUG:-}" = "true" ]; then
|
||||
printf "FULLURL $FULLURL\n" && exit 0
|
||||
fi
|
||||
|
||||
case $OSTYPE in
|
||||
linux-gnu)
|
||||
if [[ ! -z "${GITPOD_INSTANCE_ID}" ]]; then
|
||||
gp preview ${FULLURL}
|
||||
else
|
||||
xdg-open ${FULLURL}
|
||||
fi
|
||||
;;
|
||||
"darwin"*)
|
||||
open ${FULLURL}
|
||||
;;
|
||||
"win*"* | "msys"*)
|
||||
start ${FULLURL}
|
||||
;;
|
||||
esac
|
||||
|
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
|
4
.ddev/docker-compose.phpmyadmin-norouter.yaml
Normal file
4
.ddev/docker-compose.phpmyadmin-norouter.yaml
Normal file
|
@ -0,0 +1,4 @@
|
|||
#ddev-generated
|
||||
# If omit_containers[ddev-router] then this file will be replaced
|
||||
# with another with a `ports` statement to directly expose port 80 to 8036
|
||||
services: {}
|
27
.ddev/docker-compose.phpmyadmin.yaml
Normal file
27
.ddev/docker-compose.phpmyadmin.yaml
Normal file
|
@ -0,0 +1,27 @@
|
|||
#ddev-generated
|
||||
services:
|
||||
phpmyadmin:
|
||||
container_name: ddev-${DDEV_SITENAME}-phpmyadmin
|
||||
image: phpmyadmin:5
|
||||
working_dir: "/root"
|
||||
restart: "no"
|
||||
labels:
|
||||
com.ddev.site-name: ${DDEV_SITENAME}
|
||||
com.ddev.approot: $DDEV_APPROOT
|
||||
expose:
|
||||
- "80"
|
||||
environment:
|
||||
- PMA_USER=root
|
||||
- PMA_PASSWORD=root
|
||||
- PMA_HOST=db
|
||||
- PMA_PORT=3306
|
||||
- VIRTUAL_HOST=$DDEV_HOSTNAME
|
||||
- UPLOAD_LIMIT=4000M
|
||||
- HTTP_EXPOSE=8036:80
|
||||
- HTTPS_EXPOSE=8037:80
|
||||
healthcheck:
|
||||
interval: 120s
|
||||
timeout: 2s
|
||||
retries: 1
|
||||
depends_on:
|
||||
- db
|
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 {}
|
||||
|
|
|
@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\PPCP;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundProcessor;
|
||||
|
||||
|
@ -107,3 +108,14 @@ function ppcp_void_order( WC_Order $wc_order ): void {
|
|||
|
||||
$refund_processor->void( $order );
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the PayPal refund fees totals on an order.
|
||||
*
|
||||
* @param WC_Order $wc_order The WC order.
|
||||
*/
|
||||
function ppcp_update_order_refund_fees( WC_Order $wc_order ): void {
|
||||
$updater = PPCP::container()->get( 'wcgateway.helper.refund-fees-updater' );
|
||||
assert( $updater instanceof RefundFeesUpdater );
|
||||
$updater->update( $wc_order );
|
||||
}
|
||||
|
|
|
@ -1,5 +1,30 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 2.2.2 - 2023-08-29 =
|
||||
* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529
|
||||
* Enhancement - HPOS compatibility issues #1594
|
||||
* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607
|
||||
|
||||
= 2.2.1 - 2023-08-24 =
|
||||
* Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536
|
||||
* Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565
|
||||
* Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533
|
||||
* Fix - Send payee_preferred correctly for instant payments #1489
|
||||
* Fix - Auto-disabled ACDC vaulting after updating to 2.1.0 #1490
|
||||
* Fix - PayPal Payments serializing formData of array inputs #1501
|
||||
* Fix - Buttons not working on single product page for WooCommerce Bookings product #1478
|
||||
* Enhancement - PayPal Later message price amount doesn't update dynamically #1585
|
||||
* Enhancement - Improve WC order creation in webhook #1530
|
||||
* Enhancement - Refactor hosted fields for early card detection #1554
|
||||
* Enhancement - Pay Later button and message get hidden when product/cart/checkout value is outside of range #1511
|
||||
* Enhancement - Add link to manual credential docs #1430
|
||||
* Enhancement - Validate Merchant ID field format when saving settings #1509
|
||||
* Enhancement - Include soft descriptor for card's activity #1427
|
||||
* Enhancement - Update Pay Later amount on the cart page and checkout when total changes #1441
|
||||
* Enhancement - Log Subscription Mode configuration in system report #1507
|
||||
* Enhancement - HPOS compatibility issues #1555
|
||||
* Feature preview - PayPal Subscriptions API fixes and improvements #1443
|
||||
|
||||
= 2.2.0 - 2023-07-17 =
|
||||
* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
|
||||
* Fix - Use order currency instead of shop currency on order-pay page #1363
|
||||
|
|
|
@ -10,6 +10,20 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\ApiClient;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Common\Pattern\SingletonDecorator;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingSubscriptions;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\CatalogProducts;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingPlans;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerPayableBreakdown;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\RefundPayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\SellerPayableBreakdownFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\BillingAgreementsEndpoint;
|
||||
|
@ -292,6 +306,14 @@ return array(
|
|||
$container->get( 'api.factory.fraud-processor-response' )
|
||||
);
|
||||
},
|
||||
'api.factory.refund' => static function ( ContainerInterface $container ): RefundFactory {
|
||||
$amount_factory = $container->get( 'api.factory.amount' );
|
||||
return new RefundFactory(
|
||||
$amount_factory,
|
||||
$container->get( 'api.factory.seller-payable-breakdown' ),
|
||||
$container->get( 'api.factory.refund_payer' )
|
||||
);
|
||||
},
|
||||
'api.factory.purchase-unit' => static function ( ContainerInterface $container ): PurchaseUnitFactory {
|
||||
|
||||
$amount_factory = $container->get( 'api.factory.amount' );
|
||||
|
@ -356,6 +378,9 @@ return array(
|
|||
$address_factory = $container->get( 'api.factory.address' );
|
||||
return new PayerFactory( $address_factory );
|
||||
},
|
||||
'api.factory.refund_payer' => static function ( ContainerInterface $container ): RefundPayerFactory {
|
||||
return new RefundPayerFactory();
|
||||
},
|
||||
'api.factory.address' => static function ( ContainerInterface $container ): AddressFactory {
|
||||
return new AddressFactory();
|
||||
},
|
||||
|
@ -379,7 +404,8 @@ return array(
|
|||
'api.factory.payments' => static function ( ContainerInterface $container ): PaymentsFactory {
|
||||
$authorizations_factory = $container->get( 'api.factory.authorization' );
|
||||
$capture_factory = $container->get( 'api.factory.capture' );
|
||||
return new PaymentsFactory( $authorizations_factory, $capture_factory );
|
||||
$refund_factory = $container->get( 'api.factory.refund' );
|
||||
return new PaymentsFactory( $authorizations_factory, $capture_factory, $refund_factory );
|
||||
},
|
||||
'api.factory.authorization' => static function ( ContainerInterface $container ): AuthorizationFactory {
|
||||
return new AuthorizationFactory();
|
||||
|
@ -400,6 +426,12 @@ return array(
|
|||
$container->get( 'api.factory.platform-fee' )
|
||||
);
|
||||
},
|
||||
'api.factory.seller-payable-breakdown' => static function ( ContainerInterface $container ): SellerPayableBreakdownFactory {
|
||||
return new SellerPayableBreakdownFactory(
|
||||
$container->get( 'api.factory.money' ),
|
||||
$container->get( 'api.factory.platform-fee' )
|
||||
);
|
||||
},
|
||||
'api.factory.fraud-processor-response' => static function ( ContainerInterface $container ): FraudProcessorResponseFactory {
|
||||
return new FraudProcessorResponseFactory();
|
||||
},
|
||||
|
|
|
@ -54,8 +54,6 @@ class BillingPlans {
|
|||
private $plan_factory;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
|
@ -226,4 +224,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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -236,9 +236,15 @@ class PayUponInvoiceOrderEndpoint {
|
|||
* @return array
|
||||
*/
|
||||
private function ensure_taxes( WC_Order $wc_order, array $data ): array {
|
||||
$tax_total = $data['purchase_units'][0]['amount']['breakdown']['tax_total']['value'];
|
||||
$item_total = $data['purchase_units'][0]['amount']['breakdown']['item_total']['value'];
|
||||
$shipping = $data['purchase_units'][0]['amount']['breakdown']['shipping']['value'];
|
||||
$tax_total = $data['purchase_units'][0]['amount']['breakdown']['tax_total']['value'];
|
||||
$item_total = $data['purchase_units'][0]['amount']['breakdown']['item_total']['value'];
|
||||
$shipping = $data['purchase_units'][0]['amount']['breakdown']['shipping']['value'];
|
||||
|
||||
$handling = isset( $data['purchase_units'][0]['amount']['breakdown']['handling'] ) ? $data['purchase_units'][0]['amount']['breakdown']['handling']['value'] : 0;
|
||||
$insurance = isset( $data['purchase_units'][0]['amount']['breakdown']['insurance'] ) ? $data['purchase_units'][0]['amount']['breakdown']['insurance']['value'] : 0;
|
||||
$shipping_discount = isset( $data['purchase_units'][0]['amount']['breakdown']['shipping_discount'] ) ? $data['purchase_units'][0]['amount']['breakdown']['shipping_discount']['value'] : 0;
|
||||
$discount = isset( $data['purchase_units'][0]['amount']['breakdown']['discount'] ) ? $data['purchase_units'][0]['amount']['breakdown']['discount']['value'] : 0;
|
||||
|
||||
$order_tax_total = $wc_order->get_total_tax();
|
||||
$tax_rate = round( ( $order_tax_total / $item_total ) * 100, 1 );
|
||||
|
||||
|
@ -266,7 +272,7 @@ class PayUponInvoiceOrderEndpoint {
|
|||
);
|
||||
|
||||
$total_amount = $data['purchase_units'][0]['amount']['value'];
|
||||
$breakdown_total = $item_total + $tax_total + $shipping;
|
||||
$breakdown_total = $item_total + $tax_total + $shipping + $handling + $insurance - $shipping_discount - $discount;
|
||||
$diff = round( $total_amount - $breakdown_total, 2 );
|
||||
if ( $diff === -0.01 || $diff === 0.01 ) {
|
||||
$data['purchase_units'][0]['amount']['value'] = number_format( $breakdown_total, 2, '.', '' );
|
||||
|
|
|
@ -19,6 +19,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenActionLinksFactory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentTokenFactory;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\CustomerRepository;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class PaymentTokenEndpoint
|
||||
|
@ -97,7 +98,7 @@ class PaymentTokenEndpoint {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the payment tokens for a user.
|
||||
* Returns the payment tokens for the given user id.
|
||||
*
|
||||
* @param int $id The user id.
|
||||
*
|
||||
|
@ -118,7 +119,67 @@ class PaymentTokenEndpoint {
|
|||
$response = $this->request( $url, $args );
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new RuntimeException(
|
||||
__( 'Could not fetch payment token.', 'woocommerce-paypal-payments' )
|
||||
__( 'Could not fetch payment token for customer id.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
'response' => $response,
|
||||
)
|
||||
);
|
||||
throw $error;
|
||||
}
|
||||
$json = json_decode( $response['body'] );
|
||||
$status_code = (int) wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $status_code ) {
|
||||
$error = new PayPalApiException(
|
||||
$json,
|
||||
$status_code
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
'response' => $response,
|
||||
)
|
||||
);
|
||||
throw $error;
|
||||
}
|
||||
|
||||
$tokens = array();
|
||||
foreach ( $json->payment_tokens as $token_value ) {
|
||||
$tokens[] = $this->factory->from_paypal_response( $token_value );
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the payment tokens for the given guest customer id.
|
||||
*
|
||||
* @param string $customer_id The guest customer id.
|
||||
*
|
||||
* @return PaymentToken[]
|
||||
* @throws RuntimeException If the request fails.
|
||||
*/
|
||||
public function for_guest( string $customer_id ): array {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v2/vault/payment-tokens/?customer_id=' . $customer_id;
|
||||
$args = array(
|
||||
'method' => 'GET',
|
||||
'headers' => array(
|
||||
'Authorization' => 'Bearer ' . $bearer->token(),
|
||||
'Content-Type' => 'application/json',
|
||||
),
|
||||
);
|
||||
|
||||
$response = $this->request( $url, $args );
|
||||
if ( $response instanceof WP_Error ) {
|
||||
$error = new RuntimeException(
|
||||
__( 'Could not fetch payment token for guest customer id.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
|
|
|
@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundCapture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\AuthorizationFactory;
|
||||
|
@ -196,13 +196,13 @@ class PaymentsEndpoint {
|
|||
/**
|
||||
* Refunds a payment.
|
||||
*
|
||||
* @param Refund $refund The refund to be processed.
|
||||
* @param RefundCapture $refund The refund to be processed.
|
||||
*
|
||||
* @return string Refund ID.
|
||||
* @throws RuntimeException If the request fails.
|
||||
* @throws PayPalApiException If the request fails.
|
||||
*/
|
||||
public function refund( Refund $refund ) : string {
|
||||
public function refund( RefundCapture $refund ) : string {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v2/payments/captures/' . $refund->for_capture()->id() . '/refund';
|
||||
$args = array(
|
||||
|
|
|
@ -28,13 +28,21 @@ class Payments {
|
|||
*/
|
||||
private $captures;
|
||||
|
||||
/**
|
||||
* The Refunds.
|
||||
*
|
||||
* @var Refund[]
|
||||
*/
|
||||
private $refunds;
|
||||
|
||||
/**
|
||||
* Payments constructor.
|
||||
*
|
||||
* @param array $authorizations The Authorizations.
|
||||
* @param array $captures The Captures.
|
||||
* @param array $refunds The Refunds.
|
||||
*/
|
||||
public function __construct( array $authorizations, array $captures ) {
|
||||
public function __construct( array $authorizations, array $captures, array $refunds = array() ) {
|
||||
foreach ( $authorizations as $key => $authorization ) {
|
||||
if ( is_a( $authorization, Authorization::class ) ) {
|
||||
continue;
|
||||
|
@ -47,8 +55,15 @@ class Payments {
|
|||
}
|
||||
unset( $captures[ $key ] );
|
||||
}
|
||||
foreach ( $refunds as $key => $refund ) {
|
||||
if ( is_a( $refund, Refund::class ) ) {
|
||||
continue;
|
||||
}
|
||||
unset( $refunds[ $key ] );
|
||||
}
|
||||
$this->authorizations = $authorizations;
|
||||
$this->captures = $captures;
|
||||
$this->refunds = $refunds;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -70,6 +85,12 @@ class Payments {
|
|||
},
|
||||
$this->captures()
|
||||
),
|
||||
'refunds' => array_map(
|
||||
static function ( Refund $refund ): array {
|
||||
return $refund->to_array();
|
||||
},
|
||||
$this->refunds()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -90,4 +111,13 @@ class Payments {
|
|||
public function captures(): array {
|
||||
return $this->captures;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Refunds.
|
||||
*
|
||||
* @return Refund[]
|
||||
**/
|
||||
public function refunds(): array {
|
||||
return $this->refunds;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<?php
|
||||
/**
|
||||
* The refund object.
|
||||
* The refund entity.
|
||||
*
|
||||
* @link https://developer.paypal.com/docs/api/orders/v2/#definition-refund
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
@ -15,11 +17,32 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
|||
class Refund {
|
||||
|
||||
/**
|
||||
* The Capture.
|
||||
* The ID.
|
||||
*
|
||||
* @var Capture
|
||||
* @var string
|
||||
*/
|
||||
private $capture;
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The status.
|
||||
*
|
||||
* @var RefundStatus
|
||||
*/
|
||||
private $status;
|
||||
|
||||
/**
|
||||
* The amount.
|
||||
*
|
||||
* @var Amount
|
||||
*/
|
||||
private $amount;
|
||||
|
||||
/**
|
||||
* The detailed breakdown of the refund activity (fees, ...).
|
||||
*
|
||||
* @var SellerPayableBreakdown|null
|
||||
*/
|
||||
private $seller_payable_breakdown;
|
||||
|
||||
/**
|
||||
* The invoice id.
|
||||
|
@ -29,50 +52,97 @@ class Refund {
|
|||
private $invoice_id;
|
||||
|
||||
/**
|
||||
* The note to the payer.
|
||||
* The custom id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $custom_id;
|
||||
|
||||
/**
|
||||
* The acquirer reference number.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $acquirer_reference_number;
|
||||
|
||||
/**
|
||||
* The acquirer reference number.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $note_to_payer;
|
||||
|
||||
/**
|
||||
* The Amount.
|
||||
* The payer of the refund.
|
||||
*
|
||||
* @var Amount|null
|
||||
* @var ?RefundPayer
|
||||
*/
|
||||
private $amount;
|
||||
private $payer;
|
||||
|
||||
/**
|
||||
* Refund constructor.
|
||||
*
|
||||
* @param Capture $capture The capture where the refund is supposed to be applied at.
|
||||
* @param string $invoice_id The invoice id.
|
||||
* @param string $note_to_payer The note to the payer.
|
||||
* @param Amount|null $amount The Amount.
|
||||
* @param string $id The ID.
|
||||
* @param RefundStatus $status The status.
|
||||
* @param Amount $amount The amount.
|
||||
* @param string $invoice_id The invoice id.
|
||||
* @param string $custom_id The custom id.
|
||||
* @param SellerPayableBreakdown|null $seller_payable_breakdown The detailed breakdown of the refund activity (fees, ...).
|
||||
* @param string $acquirer_reference_number The acquirer reference number.
|
||||
* @param string $note_to_payer The note to payer.
|
||||
* @param RefundPayer|null $payer The payer.
|
||||
*/
|
||||
public function __construct(
|
||||
Capture $capture,
|
||||
string $id,
|
||||
RefundStatus $status,
|
||||
Amount $amount,
|
||||
string $invoice_id,
|
||||
string $note_to_payer = '',
|
||||
Amount $amount = null
|
||||
string $custom_id,
|
||||
?SellerPayableBreakdown $seller_payable_breakdown,
|
||||
string $acquirer_reference_number,
|
||||
string $note_to_payer,
|
||||
?RefundPayer $payer
|
||||
) {
|
||||
$this->capture = $capture;
|
||||
$this->invoice_id = $invoice_id;
|
||||
$this->note_to_payer = $note_to_payer;
|
||||
$this->amount = $amount;
|
||||
$this->id = $id;
|
||||
$this->status = $status;
|
||||
$this->amount = $amount;
|
||||
$this->invoice_id = $invoice_id;
|
||||
$this->custom_id = $custom_id;
|
||||
$this->seller_payable_breakdown = $seller_payable_breakdown;
|
||||
$this->acquirer_reference_number = $acquirer_reference_number;
|
||||
$this->note_to_payer = $note_to_payer;
|
||||
$this->payer = $payer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the capture for the refund.
|
||||
* Returns the ID.
|
||||
*
|
||||
* @return Capture
|
||||
* @return string
|
||||
*/
|
||||
public function for_capture() : Capture {
|
||||
return $this->capture;
|
||||
public function id() : string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the invoice id.
|
||||
* Returns the status.
|
||||
*
|
||||
* @return RefundStatus
|
||||
*/
|
||||
public function status() : RefundStatus {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the amount.
|
||||
*
|
||||
* @return Amount
|
||||
*/
|
||||
public function amount() : Amount {
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the invoice id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -81,7 +151,34 @@ class Refund {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the note to the payer.
|
||||
* Returns the custom id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function custom_id() : string {
|
||||
return $this->custom_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the detailed breakdown of the refund activity (fees, ...).
|
||||
*
|
||||
* @return SellerPayableBreakdown|null
|
||||
*/
|
||||
public function seller_payable_breakdown() : ?SellerPayableBreakdown {
|
||||
return $this->seller_payable_breakdown;
|
||||
}
|
||||
|
||||
/**
|
||||
* The acquirer reference number.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function acquirer_reference_number() : string {
|
||||
return $this->acquirer_reference_number;
|
||||
}
|
||||
|
||||
/**
|
||||
* The note to payer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
|
@ -90,28 +187,38 @@ class Refund {
|
|||
}
|
||||
|
||||
/**
|
||||
* Returns the Amount.
|
||||
* Returns the refund payer.
|
||||
*
|
||||
* @return Amount|null
|
||||
* @return RefundPayer|null
|
||||
*/
|
||||
public function amount() {
|
||||
return $this->amount;
|
||||
public function payer() : ?RefundPayer {
|
||||
return $this->payer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
* Returns the entity as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array() : array {
|
||||
$data = array(
|
||||
'invoice_id' => $this->invoice_id(),
|
||||
$data = array(
|
||||
'id' => $this->id(),
|
||||
'status' => $this->status()->name(),
|
||||
'amount' => $this->amount()->to_array(),
|
||||
'invoice_id' => $this->invoice_id(),
|
||||
'custom_id' => $this->custom_id(),
|
||||
'acquirer_reference_number' => $this->acquirer_reference_number(),
|
||||
'note_to_payer' => (array) $this->note_to_payer(),
|
||||
);
|
||||
if ( $this->note_to_payer() ) {
|
||||
$data['note_to_payer'] = $this->note_to_payer();
|
||||
$details = $this->status()->details();
|
||||
if ( $details ) {
|
||||
$data['status_details'] = array( 'reason' => $details->reason() );
|
||||
}
|
||||
if ( $this->amount() ) {
|
||||
$data['amount'] = $this->amount()->to_array();
|
||||
if ( $this->seller_payable_breakdown ) {
|
||||
$data['seller_payable_breakdown'] = $this->seller_payable_breakdown->to_array();
|
||||
}
|
||||
if ( $this->payer ) {
|
||||
$data['payer'] = $this->payer->to_array();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
|
|
118
modules/ppcp-api-client/src/Entity/RefundCapture.php
Normal file
118
modules/ppcp-api-client/src/Entity/RefundCapture.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
/**
|
||||
* The refund capture object.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class RefundCapture
|
||||
*/
|
||||
class RefundCapture {
|
||||
|
||||
/**
|
||||
* The Capture.
|
||||
*
|
||||
* @var Capture
|
||||
*/
|
||||
private $capture;
|
||||
|
||||
/**
|
||||
* The invoice id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $invoice_id;
|
||||
|
||||
/**
|
||||
* The note to the payer.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $note_to_payer;
|
||||
|
||||
/**
|
||||
* The Amount.
|
||||
*
|
||||
* @var Amount|null
|
||||
*/
|
||||
private $amount;
|
||||
|
||||
/**
|
||||
* Refund constructor.
|
||||
*
|
||||
* @param Capture $capture The capture where the refund is supposed to be applied at.
|
||||
* @param string $invoice_id The invoice id.
|
||||
* @param string $note_to_payer The note to the payer.
|
||||
* @param Amount|null $amount The Amount.
|
||||
*/
|
||||
public function __construct(
|
||||
Capture $capture,
|
||||
string $invoice_id,
|
||||
string $note_to_payer = '',
|
||||
Amount $amount = null
|
||||
) {
|
||||
$this->capture = $capture;
|
||||
$this->invoice_id = $invoice_id;
|
||||
$this->note_to_payer = $note_to_payer;
|
||||
$this->amount = $amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the capture for the refund.
|
||||
*
|
||||
* @return Capture
|
||||
*/
|
||||
public function for_capture() : Capture {
|
||||
return $this->capture;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the invoice id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function invoice_id() : string {
|
||||
return $this->invoice_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the note to the payer.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function note_to_payer() : string {
|
||||
return $this->note_to_payer;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the Amount.
|
||||
*
|
||||
* @return Amount|null
|
||||
*/
|
||||
public function amount() {
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array() : array {
|
||||
$data = array(
|
||||
'invoice_id' => $this->invoice_id(),
|
||||
);
|
||||
if ( $this->note_to_payer() ) {
|
||||
$data['note_to_payer'] = $this->note_to_payer();
|
||||
}
|
||||
if ( $this->amount ) {
|
||||
$data['amount'] = $this->amount->to_array();
|
||||
}
|
||||
return $data;
|
||||
}
|
||||
}
|
79
modules/ppcp-api-client/src/Entity/RefundPayer.php
Normal file
79
modules/ppcp-api-client/src/Entity/RefundPayer.php
Normal file
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
/**
|
||||
* The refund payer object.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class RefundPayer
|
||||
* The customer who sends the money.
|
||||
*/
|
||||
class RefundPayer {
|
||||
|
||||
/**
|
||||
* The email address.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $email_address;
|
||||
|
||||
/**
|
||||
* The merchant id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $merchant_id;
|
||||
|
||||
/**
|
||||
* RefundPayer constructor.
|
||||
*
|
||||
* @param string $email_address The email.
|
||||
* @param string $merchant_id The merchant id.
|
||||
*/
|
||||
public function __construct(
|
||||
string $email_address,
|
||||
string $merchant_id
|
||||
) {
|
||||
|
||||
$this->email_address = $email_address;
|
||||
$this->merchant_id = $merchant_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the email address.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function email_address(): string {
|
||||
return $this->email_address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the merchant id.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function merchant_id(): string {
|
||||
return $this->merchant_id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array() {
|
||||
$payer = array(
|
||||
'email_address' => $this->email_address(),
|
||||
);
|
||||
if ( $this->merchant_id ) {
|
||||
$payer['merchant_id'] = $this->merchant_id();
|
||||
}
|
||||
return $payer;
|
||||
}
|
||||
}
|
77
modules/ppcp-api-client/src/Entity/RefundStatus.php
Normal file
77
modules/ppcp-api-client/src/Entity/RefundStatus.php
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?php
|
||||
/**
|
||||
* The RefundStatus object.
|
||||
*
|
||||
* @see https://developer.paypal.com/docs/api/orders/v2/#definition-refund_status
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class RefundStatus
|
||||
*/
|
||||
class RefundStatus {
|
||||
|
||||
const COMPLETED = 'COMPLETED';
|
||||
const CANCELLED = 'CANCELLED';
|
||||
const FAILED = 'FAILED';
|
||||
const PENDING = 'PENDING';
|
||||
|
||||
/**
|
||||
* The status.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $status;
|
||||
|
||||
/**
|
||||
* The details.
|
||||
*
|
||||
* @var RefundStatusDetails|null
|
||||
*/
|
||||
private $details;
|
||||
|
||||
/**
|
||||
* RefundStatus constructor.
|
||||
*
|
||||
* @param string $status The status.
|
||||
* @param RefundStatusDetails|null $details The details.
|
||||
*/
|
||||
public function __construct( string $status, ?RefundStatusDetails $details = null ) {
|
||||
$this->status = $status;
|
||||
$this->details = $details;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current status with a given one.
|
||||
*
|
||||
* @param string $status The status to compare with.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is( string $status ): bool {
|
||||
return $this->status === $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string {
|
||||
return $this->status;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the details.
|
||||
*
|
||||
* @return RefundStatusDetails|null
|
||||
*/
|
||||
public function details(): ?RefundStatusDetails {
|
||||
return $this->details;
|
||||
}
|
||||
}
|
71
modules/ppcp-api-client/src/Entity/RefundStatusDetails.php
Normal file
71
modules/ppcp-api-client/src/Entity/RefundStatusDetails.php
Normal file
|
@ -0,0 +1,71 @@
|
|||
<?php
|
||||
/**
|
||||
* The RefundStatusDetails object.
|
||||
*
|
||||
* @see https://developer.paypal.com/docs/api/payments/v2/#definition-refund_status_details
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class RefundStatusDetails
|
||||
*/
|
||||
class RefundStatusDetails {
|
||||
|
||||
const ECHECK = 'ECHECK';
|
||||
|
||||
/**
|
||||
* The reason.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $reason;
|
||||
|
||||
/**
|
||||
* RefundStatusDetails constructor.
|
||||
*
|
||||
* @param string $reason The reason explaining refund status.
|
||||
*/
|
||||
public function __construct( string $reason ) {
|
||||
$this->reason = $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the current reason with a given one.
|
||||
*
|
||||
* @param string $reason The reason to compare with.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is( string $reason ): bool {
|
||||
return $this->reason === $reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the reason explaining refund status.
|
||||
* One of RefundStatusDetails constants.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function reason(): string {
|
||||
return $this->reason;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the human-readable reason text explaining refund status.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function text(): string {
|
||||
switch ( $this->reason ) {
|
||||
case self::ECHECK:
|
||||
return __( 'The payer paid by an eCheck that has not yet cleared.', 'woocommerce-paypal-payments' );
|
||||
default:
|
||||
return $this->reason;
|
||||
}
|
||||
}
|
||||
}
|
202
modules/ppcp-api-client/src/Entity/SellerPayableBreakdown.php
Normal file
202
modules/ppcp-api-client/src/Entity/SellerPayableBreakdown.php
Normal file
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
/**
|
||||
* The info about fees and amount that will be paid by the seller.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class SellerPayableBreakdown
|
||||
*/
|
||||
class SellerPayableBreakdown {
|
||||
|
||||
/**
|
||||
* The amount for this refunded payment in the currency of the transaction.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $gross_amount;
|
||||
|
||||
/**
|
||||
* The applicable fee for this refunded payment in the currency of the transaction.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $paypal_fee;
|
||||
|
||||
/**
|
||||
* The applicable fee for this captured payment in the receivable currency.
|
||||
*
|
||||
* Present only in cases the fee is charged in the receivable currency.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $paypal_fee_in_receivable_currency;
|
||||
|
||||
/**
|
||||
* The net amount that the payee receives for this refunded payment in their PayPal account.
|
||||
*
|
||||
* Computed as gross_amount minus the paypal_fee minus the platform_fees.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $net_amount;
|
||||
|
||||
/**
|
||||
* The net amount for this refunded payment in the receivable currency.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $net_amount_in_receivable_currency;
|
||||
|
||||
/**
|
||||
* The total amount for this refund.
|
||||
*
|
||||
* @var Money|null
|
||||
*/
|
||||
private $total_refunded_amount;
|
||||
|
||||
/**
|
||||
* An array of platform or partner fees, commissions, or brokerage fees that associated with the captured payment.
|
||||
*
|
||||
* @var PlatformFee[]
|
||||
*/
|
||||
private $platform_fees;
|
||||
|
||||
/**
|
||||
* SellerPayableBreakdown constructor.
|
||||
*
|
||||
* @param Money|null $gross_amount The amount for this refunded payment in the currency of the transaction.
|
||||
* @param Money|null $paypal_fee The applicable fee for this refunded payment in the currency of the transaction.
|
||||
* @param Money|null $paypal_fee_in_receivable_currency The applicable fee for this refunded payment in the receivable currency.
|
||||
* @param Money|null $net_amount The net amount that the payee receives for this refunded payment in their PayPal account.
|
||||
* @param Money|null $net_amount_in_receivable_currency The net amount for this refunded payment in the receivable currency.
|
||||
* @param Money|null $total_refunded_amount The total amount for this refund.
|
||||
* @param PlatformFee[] $platform_fees An array of platform or partner fees, commissions, or brokerage fees that associated with the captured payment.
|
||||
*/
|
||||
public function __construct(
|
||||
?Money $gross_amount,
|
||||
?Money $paypal_fee,
|
||||
?Money $paypal_fee_in_receivable_currency,
|
||||
?Money $net_amount,
|
||||
?Money $net_amount_in_receivable_currency,
|
||||
?Money $total_refunded_amount,
|
||||
array $platform_fees
|
||||
) {
|
||||
$this->gross_amount = $gross_amount;
|
||||
$this->paypal_fee = $paypal_fee;
|
||||
$this->paypal_fee_in_receivable_currency = $paypal_fee_in_receivable_currency;
|
||||
$this->net_amount = $net_amount;
|
||||
$this->net_amount_in_receivable_currency = $net_amount_in_receivable_currency;
|
||||
$this->total_refunded_amount = $total_refunded_amount;
|
||||
$this->platform_fees = $platform_fees;
|
||||
}
|
||||
|
||||
/**
|
||||
* The amount for this refunded payment in the currency of the transaction.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function gross_amount(): ?Money {
|
||||
return $this->gross_amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* The applicable fee for this refunded payment in the currency of the transaction.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function paypal_fee(): ?Money {
|
||||
return $this->paypal_fee;
|
||||
}
|
||||
|
||||
/**
|
||||
* The applicable fee for this refunded payment in the receivable currency.
|
||||
*
|
||||
* Present only in cases the fee is charged in the receivable currency.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function paypal_fee_in_receivable_currency(): ?Money {
|
||||
return $this->paypal_fee_in_receivable_currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The net amount that the payee receives for this refunded payment in their PayPal account.
|
||||
*
|
||||
* Computed as gross_amount minus the paypal_fee minus the platform_fees.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function net_amount(): ?Money {
|
||||
return $this->net_amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* The net amount for this refunded payment in the receivable currency.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function net_amount_in_receivable_currency(): ?Money {
|
||||
return $this->net_amount_in_receivable_currency;
|
||||
}
|
||||
|
||||
/**
|
||||
* The total amount for this refund.
|
||||
*
|
||||
* @return Money|null
|
||||
*/
|
||||
public function total_refunded_amount(): ?Money {
|
||||
return $this->total_refunded_amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of platform or partner fees, commissions, or brokerage fees that associated with the refunded payment.
|
||||
*
|
||||
* @return PlatformFee[]
|
||||
*/
|
||||
public function platform_fees(): array {
|
||||
return $this->platform_fees;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array(): array {
|
||||
$data = array();
|
||||
if ( $this->gross_amount ) {
|
||||
$data['gross_amount'] = $this->gross_amount->to_array();
|
||||
}
|
||||
if ( $this->paypal_fee ) {
|
||||
$data['paypal_fee'] = $this->paypal_fee->to_array();
|
||||
}
|
||||
if ( $this->paypal_fee_in_receivable_currency ) {
|
||||
$data['paypal_fee_in_receivable_currency'] = $this->paypal_fee_in_receivable_currency->to_array();
|
||||
}
|
||||
if ( $this->net_amount ) {
|
||||
$data['net_amount'] = $this->net_amount->to_array();
|
||||
}
|
||||
if ( $this->net_amount_in_receivable_currency ) {
|
||||
$data['net_amount_in_receivable_currency'] = $this->net_amount_in_receivable_currency->to_array();
|
||||
}
|
||||
if ( $this->total_refunded_amount ) {
|
||||
$data['total_refunded_amount'] = $this->total_refunded_amount->to_array();
|
||||
}
|
||||
if ( $this->platform_fees ) {
|
||||
$data['platform_fees'] = array_map(
|
||||
function ( PlatformFee $fee ) {
|
||||
return $fee->to_array();
|
||||
},
|
||||
$this->platform_fees
|
||||
);
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
|||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Authorization;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Capture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
|
||||
|
||||
/**
|
||||
|
@ -32,19 +33,29 @@ class PaymentsFactory {
|
|||
*/
|
||||
private $capture_factory;
|
||||
|
||||
/**
|
||||
* The Refund factory.
|
||||
*
|
||||
* @var RefundFactory
|
||||
*/
|
||||
private $refund_factory;
|
||||
|
||||
/**
|
||||
* PaymentsFactory constructor.
|
||||
*
|
||||
* @param AuthorizationFactory $authorization_factory The Authorization factory.
|
||||
* @param CaptureFactory $capture_factory The Capture factory.
|
||||
* @param RefundFactory $refund_factory The Refund factory.
|
||||
*/
|
||||
public function __construct(
|
||||
AuthorizationFactory $authorization_factory,
|
||||
CaptureFactory $capture_factory
|
||||
CaptureFactory $capture_factory,
|
||||
RefundFactory $refund_factory
|
||||
) {
|
||||
|
||||
$this->authorization_factory = $authorization_factory;
|
||||
$this->capture_factory = $capture_factory;
|
||||
$this->refund_factory = $refund_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -62,12 +73,18 @@ class PaymentsFactory {
|
|||
isset( $data->authorizations ) ? $data->authorizations : array()
|
||||
);
|
||||
$captures = array_map(
|
||||
function ( \stdClass $authorization ): Capture {
|
||||
return $this->capture_factory->from_paypal_response( $authorization );
|
||||
function ( \stdClass $capture ): Capture {
|
||||
return $this->capture_factory->from_paypal_response( $capture );
|
||||
},
|
||||
isset( $data->captures ) ? $data->captures : array()
|
||||
);
|
||||
$payments = new Payments( $authorizations, $captures );
|
||||
$refunds = array_map(
|
||||
function ( \stdClass $refund ): Refund {
|
||||
return $this->refund_factory->from_paypal_response( $refund );
|
||||
},
|
||||
isset( $data->refunds ) ? $data->refunds : array()
|
||||
);
|
||||
$payments = new Payments( $authorizations, $captures, $refunds );
|
||||
return $payments;
|
||||
}
|
||||
}
|
||||
|
|
91
modules/ppcp-api-client/src/Factory/RefundFactory.php
Normal file
91
modules/ppcp-api-client/src/Factory/RefundFactory.php
Normal file
|
@ -0,0 +1,91 @@
|
|||
<?php
|
||||
/**
|
||||
* The refund factory.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundStatusDetails;
|
||||
|
||||
/**
|
||||
* Class RefundFactory
|
||||
*/
|
||||
class RefundFactory {
|
||||
|
||||
/**
|
||||
* The Amount factory.
|
||||
*
|
||||
* @var AmountFactory
|
||||
*/
|
||||
private $amount_factory;
|
||||
|
||||
/**
|
||||
* The SellerPayableBreakdownFactory factory.
|
||||
*
|
||||
* @var SellerPayableBreakdownFactory
|
||||
*/
|
||||
private $seller_payable_breakdown_factory;
|
||||
|
||||
/**
|
||||
* The RefundPayerFactory factory.
|
||||
*
|
||||
* @var RefundPayerFactory
|
||||
*/
|
||||
private $refund_payer_factory;
|
||||
|
||||
/**
|
||||
* RefundFactory constructor.
|
||||
*
|
||||
* @param AmountFactory $amount_factory The amount factory.
|
||||
* @param SellerPayableBreakdownFactory $seller_payable_breakdown_factory The payable breakdown factory.
|
||||
* @param RefundPayerFactory $refund_payer_factory The payer breakdown factory.
|
||||
*/
|
||||
public function __construct(
|
||||
AmountFactory $amount_factory,
|
||||
SellerPayableBreakdownFactory $seller_payable_breakdown_factory,
|
||||
RefundPayerFactory $refund_payer_factory
|
||||
) {
|
||||
$this->amount_factory = $amount_factory;
|
||||
$this->seller_payable_breakdown_factory = $seller_payable_breakdown_factory;
|
||||
$this->refund_payer_factory = $refund_payer_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the refund object based off the PayPal response.
|
||||
*
|
||||
* @param \stdClass $data The PayPal response.
|
||||
*
|
||||
* @return Refund
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ) : Refund {
|
||||
$reason = $data->status_details->reason ?? null;
|
||||
$seller_payable_breakdown = isset( $data->seller_payable_breakdown ) ?
|
||||
$this->seller_payable_breakdown_factory->from_paypal_response( $data->seller_payable_breakdown )
|
||||
: null;
|
||||
|
||||
$payer = isset( $data->payer ) ?
|
||||
$this->refund_payer_factory->from_paypal_response( $data->payer )
|
||||
: null;
|
||||
|
||||
return new Refund(
|
||||
(string) $data->id,
|
||||
new RefundStatus(
|
||||
(string) $data->status,
|
||||
$reason ? new RefundStatusDetails( $reason ) : null
|
||||
),
|
||||
$this->amount_factory->from_paypal_response( $data->amount ),
|
||||
(string) ( $data->invoice_id ?? '' ),
|
||||
(string) ( $data->custom_id ?? '' ),
|
||||
$seller_payable_breakdown,
|
||||
(string) ( $data->acquirer_reference_number ?? '' ),
|
||||
(string) ( $data->note_to_payer ?? '' ),
|
||||
$payer
|
||||
);
|
||||
}
|
||||
}
|
39
modules/ppcp-api-client/src/Factory/RefundPayerFactory.php
Normal file
39
modules/ppcp-api-client/src/Factory/RefundPayerFactory.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
/**
|
||||
* The RefundPayerFactory factory.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Address;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PayerName;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PayerTaxInfo;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Phone;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PhoneWithType;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundPayer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class RefundPayerFactory
|
||||
*/
|
||||
class RefundPayerFactory {
|
||||
|
||||
/**
|
||||
* Returns a Refund Payer object based off a PayPal Response.
|
||||
*
|
||||
* @param \stdClass $data The JSON object.
|
||||
*
|
||||
* @return RefundPayer
|
||||
*/
|
||||
public function from_paypal_response( \stdClass $data ): RefundPayer {
|
||||
return new RefundPayer(
|
||||
isset( $data->email_address ) ? $data->email_address : '',
|
||||
isset( $data->merchant_id ) ? $data->merchant_id : ''
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
/**
|
||||
* The SellerPayableBreakdownFactory Factory.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use stdClass;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PlatformFee;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\SellerPayableBreakdown;
|
||||
|
||||
/**
|
||||
* Class SellerPayableBreakdownFactory
|
||||
*/
|
||||
class SellerPayableBreakdownFactory {
|
||||
|
||||
/**
|
||||
* The Money factory.
|
||||
*
|
||||
* @var MoneyFactory
|
||||
*/
|
||||
private $money_factory;
|
||||
|
||||
/**
|
||||
* The PlatformFee factory.
|
||||
*
|
||||
* @var PlatformFeeFactory
|
||||
*/
|
||||
private $platform_fee_factory;
|
||||
|
||||
/**
|
||||
* SellerPayableBreakdownFactory constructor.
|
||||
*
|
||||
* @param MoneyFactory $money_factory The Money factory.
|
||||
* @param PlatformFeeFactory $platform_fee_factory The PlatformFee factory.
|
||||
*/
|
||||
public function __construct(
|
||||
MoneyFactory $money_factory,
|
||||
PlatformFeeFactory $platform_fee_factory
|
||||
) {
|
||||
$this->money_factory = $money_factory;
|
||||
$this->platform_fee_factory = $platform_fee_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a SellerPayableBreakdownFactory object based off a PayPal Response.
|
||||
*
|
||||
* @param stdClass $data The JSON object.
|
||||
*
|
||||
* @return SellerPayableBreakdown
|
||||
*/
|
||||
public function from_paypal_response( stdClass $data ): SellerPayableBreakdown {
|
||||
|
||||
$gross_amount = ( isset( $data->gross_amount ) ) ? $this->money_factory->from_paypal_response( $data->gross_amount ) : null;
|
||||
$paypal_fee = ( isset( $data->paypal_fee ) ) ? $this->money_factory->from_paypal_response( $data->paypal_fee ) : null;
|
||||
$paypal_fee_in_receivable_currency = ( isset( $data->paypal_fee_in_receivable_currency ) ) ? $this->money_factory->from_paypal_response( $data->paypal_fee_in_receivable_currency ) : null;
|
||||
$net_amount = ( isset( $data->net_amount ) ) ? $this->money_factory->from_paypal_response( $data->net_amount ) : null;
|
||||
$net_amount_in_receivable_currency = ( isset( $data->net_amount_in_receivable_currency ) ) ? $this->money_factory->from_paypal_response( $data->net_amount_in_receivable_currency ) : null;
|
||||
$total_refunded_amount = ( isset( $data->total_refunded_amount ) ) ? $this->money_factory->from_paypal_response( $data->total_refunded_amount ) : null;
|
||||
$platform_fees = ( isset( $data->platform_fees ) ) ? array_map(
|
||||
function ( stdClass $fee_data ): PlatformFee {
|
||||
return $this->platform_fee_factory->from_paypal_response( $fee_data );
|
||||
},
|
||||
$data->platform_fees
|
||||
) : array();
|
||||
|
||||
return new SellerPayableBreakdown(
|
||||
$gross_amount,
|
||||
$paypal_fee,
|
||||
$paypal_fee_in_receivable_currency,
|
||||
$net_amount,
|
||||
$net_amount_in_receivable_currency,
|
||||
$total_refunded_amount,
|
||||
$platform_fees
|
||||
);
|
||||
}
|
||||
}
|
|
@ -15,3 +15,7 @@
|
|||
.ppcp-dcc-order-button {
|
||||
float: right;
|
||||
}
|
||||
|
||||
iframe[id^="hosted-fields-tokenization-frame_"] {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -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,19 +137,24 @@ 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') {
|
||||
if (
|
||||
context === 'product'
|
||||
&& (
|
||||
PayPalCommerceGateway.single_product_buttons_enabled === '1'
|
||||
|| hasMessages()
|
||||
)
|
||||
) {
|
||||
const singleProductBootstrap = new SingleProductBootstap(
|
||||
PayPalCommerceGateway,
|
||||
renderer,
|
||||
|
@ -195,6 +200,12 @@ const bootstrap = () => {
|
|||
}
|
||||
|
||||
};
|
||||
|
||||
const hasMessages = () => {
|
||||
return PayPalCommerceGateway.messages.is_hidden === false
|
||||
&& document.querySelector(PayPalCommerceGateway.messages.wrapper);
|
||||
}
|
||||
|
||||
document.addEventListener(
|
||||
'DOMContentLoaded',
|
||||
() => {
|
||||
|
|
|
@ -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(), this.extraFields())];
|
||||
}
|
||||
|
||||
configuration()
|
||||
|
@ -107,7 +107,7 @@ class SingleProductActionHandler {
|
|||
{
|
||||
if ( this.isBookingProduct() ) {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"))];
|
||||
return [new BookingProduct(id, 1, FormHelper.getPrefixedFields(this.formElement, "wc_bookings_field"), this.extraFields())];
|
||||
} else if ( this.isGroupedProduct() ) {
|
||||
const products = [];
|
||||
this.formElement.querySelectorAll('input[type="number"]').forEach((element) => {
|
||||
|
@ -120,17 +120,25 @@ class SingleProductActionHandler {
|
|||
}
|
||||
const id = parseInt(elementName[1]);
|
||||
const quantity = parseInt(element.value);
|
||||
products.push(new Product(id, quantity, null));
|
||||
products.push(new Product(id, quantity, null, this.extraFields()));
|
||||
})
|
||||
return products;
|
||||
} else {
|
||||
const id = document.querySelector('[name="add-to-cart"]').value;
|
||||
const qty = document.querySelector('[name="quantity"]').value;
|
||||
const variations = this.variations();
|
||||
return [new Product(id, qty, variations)];
|
||||
return [new Product(id, qty, variations, this.extraFields())];
|
||||
}
|
||||
}
|
||||
|
||||
extraFields() {
|
||||
return FormHelper.getFilteredFields(
|
||||
this.formElement,
|
||||
['add-to-cart', 'quantity', 'product_id', 'variation_id'],
|
||||
['attribute_', 'wc_bookings_field']
|
||||
);
|
||||
}
|
||||
|
||||
createOrder()
|
||||
{
|
||||
this.cartHelper = null;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -187,6 +234,10 @@ class SingleProductBootstap {
|
|||
|
||||
this.messages.renderWithAmount(data.total);
|
||||
|
||||
if ( this.gateway.single_product_buttons_enabled !== '1' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
let enableFunding = this.gateway.url_params['enable-funding'];
|
||||
let disableFunding = this.gateway.url_params['disable-funding'];
|
||||
|
||||
|
|
|
@ -2,8 +2,8 @@ import Product from "./Product";
|
|||
|
||||
class BookingProduct extends Product {
|
||||
|
||||
constructor(id, quantity, booking) {
|
||||
super(id, quantity, null);
|
||||
constructor(id, quantity, booking, extra) {
|
||||
super(id, quantity, null, extra);
|
||||
this.booking = booking;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,18 +1,19 @@
|
|||
class Product {
|
||||
|
||||
constructor(id, quantity, variations) {
|
||||
constructor(id, quantity, variations, extra) {
|
||||
this.id = id;
|
||||
this.quantity = quantity;
|
||||
this.variations = variations;
|
||||
this.extra = extra;
|
||||
}
|
||||
|
||||
data() {
|
||||
return {
|
||||
id:this.id,
|
||||
quantity:this.quantity,
|
||||
variations:this.variations
|
||||
quantity: this.quantity,
|
||||
variations: this.variations,
|
||||
extra: this.extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Product;
|
||||
export default Product;
|
||||
|
|
|
@ -5,13 +5,46 @@
|
|||
export default class FormHelper {
|
||||
|
||||
static getPrefixedFields(formElement, prefix) {
|
||||
const formData = new FormData(formElement);
|
||||
let fields = {};
|
||||
for(const element of formElement.elements) {
|
||||
if( element.name.startsWith(prefix) ) {
|
||||
fields[element.name] = element.value;
|
||||
|
||||
for (const [name, value] of formData.entries()) {
|
||||
if (!prefix || name.startsWith(prefix)) {
|
||||
fields[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
|
||||
static getFilteredFields(formElement, exactFilters, prefixFilters) {
|
||||
const formData = new FormData(formElement);
|
||||
let fields = {};
|
||||
let counters = {};
|
||||
|
||||
for (let [name, value] of formData.entries()) {
|
||||
|
||||
// Handle array format
|
||||
if (name.indexOf('[]') !== -1) {
|
||||
const k = name;
|
||||
counters[k] = counters[k] || 0;
|
||||
name = name.replace('[]', `[${counters[k]}]`);
|
||||
counters[k]++;
|
||||
}
|
||||
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
if (exactFilters && (exactFilters.indexOf(name) !== -1)) {
|
||||
continue;
|
||||
}
|
||||
if (prefixFilters && prefixFilters.some(prefixFilter => name.startsWith(prefixFilter))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
fields[name] = value;
|
||||
}
|
||||
|
||||
return fields;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -112,6 +112,18 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
|
|||
|
||||
$success = true;
|
||||
foreach ( $products as $product ) {
|
||||
|
||||
// Add extras to POST, they are usually added by custom plugins.
|
||||
if ( $product['extra'] && is_array( $product['extra'] ) ) {
|
||||
// Handle cases like field[].
|
||||
$query = http_build_query( $product['extra'] );
|
||||
parse_str( $query, $extra );
|
||||
|
||||
foreach ( $extra as $key => $value ) {
|
||||
$_POST[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $product['product']->is_type( 'booking' ) ) {
|
||||
$success = $success && $this->add_booking_product(
|
||||
$product['product'],
|
||||
|
@ -229,6 +241,7 @@ abstract class AbstractCartEndpoint implements EndpointInterface {
|
|||
'quantity' => (int) $product['quantity'],
|
||||
'variations' => $product['variations'] ?? null,
|
||||
'booking' => $product['booking'] ?? null,
|
||||
'extra' => $product['extra'] ?? null,
|
||||
);
|
||||
}
|
||||
return $products;
|
||||
|
|
|
@ -75,7 +75,7 @@ return array(
|
|||
return new CompatAssets(
|
||||
$container->get( 'compat.module.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
$container->get( 'order-tracking.is-paypal-order-edit-page' ) && $container->get( 'compat.should-initialize-gzd-compat-layer' )
|
||||
$container->get( 'compat.should-initialize-gzd-compat-layer' )
|
||||
);
|
||||
},
|
||||
|
||||
|
|
39
modules/ppcp-compat/src/AdminContextTrait.php
Normal file
39
modules/ppcp-compat/src/AdminContextTrait.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
/**
|
||||
* The compatibility module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Compat
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Compat;
|
||||
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
trait AdminContextTrait {
|
||||
|
||||
/**
|
||||
* Checks if current post id is from a PayPal order.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_paypal_order_edit_page(): bool {
|
||||
$post_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( ! $post_id ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$order = wc_get_order( $post_id );
|
||||
if ( ! is_a( $order, WC_Order::class ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -20,6 +20,7 @@ use WC_Order;
|
|||
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
|
||||
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WP_Theme;
|
||||
|
||||
|
@ -28,6 +29,8 @@ use WP_Theme;
|
|||
*/
|
||||
class CompatModule implements ModuleInterface {
|
||||
|
||||
use AdminContextTrait;
|
||||
|
||||
/**
|
||||
* Setup the compatibility module.
|
||||
*
|
||||
|
@ -48,14 +51,9 @@ class CompatModule implements ModuleInterface {
|
|||
public function run( ContainerInterface $c ): void {
|
||||
$this->initialize_ppec_compat_layer( $c );
|
||||
$this->fix_site_ground_optimizer_compatibility( $c );
|
||||
|
||||
$this->initialize_gzd_compat_layer( $c );
|
||||
|
||||
$asset_loader = $c->get( 'compat.assets' );
|
||||
assert( $asset_loader instanceof CompatAssets );
|
||||
|
||||
add_action( 'init', array( $asset_loader, 'register' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $asset_loader, 'enqueue' ) );
|
||||
|
||||
$this->migrate_pay_later_settings( $c );
|
||||
$this->migrate_smart_button_settings( $c );
|
||||
|
||||
|
@ -128,6 +126,26 @@ class CompatModule implements ModuleInterface {
|
|||
return;
|
||||
}
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $hook ) use ( $c ): void {
|
||||
if ( $hook !== 'post.php' || ! $this->is_paypal_order_edit_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_loader = $c->get( 'compat.assets' );
|
||||
assert( $asset_loader instanceof CompatAssets );
|
||||
|
||||
$asset_loader->register();
|
||||
$asset_loader->enqueue();
|
||||
}
|
||||
);
|
||||
|
||||
$endpoint = $c->get( 'order-tracking.endpoint.controller' );
|
||||
assert( $endpoint instanceof OrderTrackingEndpoint );
|
||||
|
||||
|
@ -331,15 +349,20 @@ class CompatModule implements ModuleInterface {
|
|||
* @return void
|
||||
*/
|
||||
protected function fix_page_builders(): void {
|
||||
if ( $this->is_elementor_pro_active() || $this->is_divi_theme_active() ) {
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_single_product_renderer_hook',
|
||||
function(): string {
|
||||
return 'woocommerce_after_add_to_cart_form';
|
||||
},
|
||||
5
|
||||
);
|
||||
}
|
||||
add_action(
|
||||
'init',
|
||||
function() {
|
||||
if ( $this->is_elementor_pro_active() || $this->is_divi_theme_active() ) {
|
||||
add_filter(
|
||||
'woocommerce_paypal_payments_single_product_renderer_hook',
|
||||
function(): string {
|
||||
return 'woocommerce_after_add_to_cart_form';
|
||||
},
|
||||
5
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,8 @@
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\PPEC;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
|
||||
/**
|
||||
* Helper class with various constants associated to the PayPal Express Checkout plugin, as well as methods for figuring
|
||||
* out the status of the gateway.
|
||||
|
@ -73,16 +75,25 @@ class PPECHelper {
|
|||
}
|
||||
|
||||
global $wpdb;
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT 1 FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
|
||||
if ( class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled() ) {
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT 1 FROM {$wpdb->wc_orders} WHERE payment_method = %s",
|
||||
self::PPEC_GATEWAY_ID
|
||||
)
|
||||
);
|
||||
} else {
|
||||
$result = $wpdb->get_var(
|
||||
$wpdb->prepare(
|
||||
"SELECT 1 FROM {$wpdb->posts} p JOIN {$wpdb->postmeta} pm ON pm.post_id = p.ID
|
||||
WHERE p.post_type = %s AND p.post_status != %s AND pm.meta_key = %s AND pm.meta_value = %s LIMIT 1",
|
||||
'shop_subscription',
|
||||
'trash',
|
||||
'_payment_method',
|
||||
self::PPEC_GATEWAY_ID
|
||||
)
|
||||
);
|
||||
'shop_subscription',
|
||||
'trash',
|
||||
'_payment_method',
|
||||
self::PPEC_GATEWAY_ID
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
set_transient(
|
||||
'ppcp_has_ppec_subscriptions',
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Compat\PPEC;
|
||||
|
||||
use Automattic\WooCommerce\Utilities\OrderUtil;
|
||||
use stdClass;
|
||||
use WooCommerce\PayPalCommerce\Subscription\RenewalHandler;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
|
@ -183,22 +184,29 @@ class SubscriptionsHandler {
|
|||
return true;
|
||||
}
|
||||
|
||||
// Are we on the WC > Subscriptions screen?
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$post_type = wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) );
|
||||
if ( $post_type === 'shop_subscription' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Are we editing an order or subscription tied to PPEC?
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$order_id = wc_clean( wp_unslash( $_GET['post'] ?? $_POST['post_ID'] ?? '' ) );
|
||||
$order_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? $_POST['post_ID'] ?? '' ) );
|
||||
if ( $order_id ) {
|
||||
$order = wc_get_order( $order_id );
|
||||
return ( $order && PPECHelper::PPEC_GATEWAY_ID === $order->get_payment_method() );
|
||||
}
|
||||
|
||||
// Are we on the WC > Subscriptions screen?
|
||||
/**
|
||||
* Class exist in WooCommerce.
|
||||
*
|
||||
* @psalm-suppress UndefinedClass
|
||||
*/
|
||||
$post_type_or_page = class_exists( OrderUtil::class ) && OrderUtil::custom_orders_table_usage_is_enabled()
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
? wc_clean( wp_unslash( $_GET['page'] ?? '' ) )
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
: wc_clean( wp_unslash( $_GET['post_type'] ?? $_POST['post_type'] ?? '' ) );
|
||||
if ( $post_type_or_page === 'shop_subscription' || $post_type_or_page === 'wc-orders--shop_subscription' ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@ namespace WooCommerce\PayPalCommerce\OrderTracking;
|
|||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\OrderTracking\Assets\OrderEditPageAssets;
|
||||
use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
||||
return array(
|
||||
'order-tracking.assets' => function( ContainerInterface $container ) : OrderEditPageAssets {
|
||||
|
@ -74,14 +73,4 @@ return array(
|
|||
),
|
||||
);
|
||||
},
|
||||
'order-tracking.is-paypal-order-edit-page' => static function ( ContainerInterface $container ): bool {
|
||||
$order_id = isset( $_GET['post'] ) ? (int) $_GET['post'] : ''; // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( empty( $order_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$meta = get_post_meta( $order_id, PayPalGateway::ORDER_ID_META_KEY, true );
|
||||
|
||||
return ! empty( $meta );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -70,10 +70,12 @@ class MetaBoxRenderer {
|
|||
/**
|
||||
* Renders the order tracking MetaBox.
|
||||
*
|
||||
* @param WP_Post $post The post object.
|
||||
* @param mixed $post_or_order_object Either WP_Post or WC_Order when COT is data source.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function render( WP_Post $post ): void {
|
||||
$wc_order = wc_get_order( $post->ID );
|
||||
public function render( $post_or_order_object ): void {
|
||||
$wc_order = ( $post_or_order_object instanceof WP_Post ) ? wc_get_order( $post_or_order_object->ID ) : $post_or_order_object;
|
||||
if ( ! is_a( $wc_order, WC_Order::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
@ -122,7 +124,7 @@ class MetaBoxRenderer {
|
|||
<?php endforeach; ?>
|
||||
</select>
|
||||
</p>
|
||||
<input type="hidden" class="ppcp-order_id" name="<?php echo esc_attr( self::NAME_PREFIX ); ?>[order_id]" value="<?php echo intval( $post->ID ); ?>"/>
|
||||
<input type="hidden" class="ppcp-order_id" name="<?php echo esc_attr( self::NAME_PREFIX ); ?>[order_id]" value="<?php echo (int) $wc_order->get_id(); ?>"/>
|
||||
<p>
|
||||
<button type="button" class="button submit_tracking_info" data-action="<?php echo esc_attr( $action ); ?>"><?php echo esc_html( ucfirst( $action ) ); ?></button></p>
|
||||
<?php
|
||||
|
|
|
@ -9,6 +9,8 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\OrderTracking;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use WooCommerce\PayPalCommerce\Compat\AdminContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use Exception;
|
||||
|
@ -21,13 +23,14 @@ use WooCommerce\PayPalCommerce\OrderTracking\Endpoint\OrderTrackingEndpoint;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\SettingsListener;
|
||||
|
||||
/**
|
||||
* Class OrderTrackingModule
|
||||
*/
|
||||
class OrderTrackingModule implements ModuleInterface {
|
||||
|
||||
use AdminContextTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -57,15 +60,10 @@ class OrderTrackingModule implements ModuleInterface {
|
|||
}
|
||||
|
||||
$tracking_enabled = $settings->has( 'tracking_enabled' ) && $settings->get( 'tracking_enabled' );
|
||||
|
||||
if ( ! $tracking_enabled ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_loader = $c->get( 'order-tracking.assets' );
|
||||
assert( $asset_loader instanceof OrderEditPageAssets );
|
||||
$is_paypal_order_edit_page = $c->get( 'order-tracking.is-paypal-order-edit-page' );
|
||||
|
||||
$endpoint = $c->get( 'order-tracking.endpoint.controller' );
|
||||
assert( $endpoint instanceof OrderTrackingEndpoint );
|
||||
|
||||
|
@ -73,23 +71,21 @@ class OrderTrackingModule implements ModuleInterface {
|
|||
assert( $logger instanceof LoggerInterface );
|
||||
|
||||
add_action(
|
||||
'init',
|
||||
static function () use ( $asset_loader, $is_paypal_order_edit_page ) {
|
||||
if ( ! $is_paypal_order_edit_page ) {
|
||||
'admin_enqueue_scripts',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function ( $hook ) use ( $c ): void {
|
||||
if ( $hook !== 'post.php' || ! $this->is_paypal_order_edit_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_loader = $c->get( 'order-tracking.assets' );
|
||||
assert( $asset_loader instanceof OrderEditPageAssets );
|
||||
|
||||
$asset_loader->register();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
static function () use ( $asset_loader, $is_paypal_order_edit_page ) {
|
||||
if ( ! $is_paypal_order_edit_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$asset_loader->enqueue();
|
||||
}
|
||||
);
|
||||
|
@ -99,18 +95,38 @@ class OrderTrackingModule implements ModuleInterface {
|
|||
array( $endpoint, 'handle_request' )
|
||||
);
|
||||
|
||||
$meta_box_renderer = $c->get( 'order-tracking.meta-box.renderer' );
|
||||
add_action(
|
||||
'add_meta_boxes',
|
||||
static function() use ( $meta_box_renderer, $is_paypal_order_edit_page ) {
|
||||
if ( ! $is_paypal_order_edit_page ) {
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( $post_type ) use ( $c ) {
|
||||
/**
|
||||
* Class and function exist in WooCommerce.
|
||||
*
|
||||
* @psalm-suppress UndefinedClass
|
||||
* @psalm-suppress UndefinedFunction
|
||||
*/
|
||||
$screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
|
||||
? wc_get_page_screen_id( 'shop-order' )
|
||||
: 'shop_order';
|
||||
if ( $post_type !== $screen || ! $this->is_paypal_order_edit_page() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_meta_box( 'ppcp_order-tracking', __( 'Tracking Information', 'woocommerce-paypal-payments' ), array( $meta_box_renderer, 'render' ), 'shop_order', 'side' );
|
||||
$meta_box_renderer = $c->get( 'order-tracking.meta-box.renderer' );
|
||||
add_meta_box(
|
||||
'ppcp_order-tracking',
|
||||
__( 'Tracking Information', 'woocommerce-paypal-payments' ),
|
||||
array( $meta_box_renderer, 'render' ),
|
||||
$screen,
|
||||
'side'
|
||||
);
|
||||
},
|
||||
10,
|
||||
2
|
||||
1
|
||||
);
|
||||
|
||||
add_action(
|
||||
|
|
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Subscription;
|
||||
|
||||
use WC_Subscription;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
|
@ -139,8 +140,16 @@ class RenewalHandler {
|
|||
*
|
||||
* @param \WC_Order $wc_order The WooCommerce order.
|
||||
*/
|
||||
public function renew( \WC_Order $wc_order ) {
|
||||
public function renew( \WC_Order $wc_order ): void {
|
||||
try {
|
||||
$subscription = wcs_get_subscription( $wc_order->get_id() );
|
||||
if ( is_a( $subscription, WC_Subscription::class ) ) {
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $subscription_id ) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$this->process_order( $wc_order );
|
||||
} catch ( \Exception $exception ) {
|
||||
$error = $exception->getMessage();
|
||||
|
@ -204,6 +213,12 @@ class RenewalHandler {
|
|||
$transaction_id = $this->get_paypal_order_transaction_id( $order );
|
||||
if ( $transaction_id ) {
|
||||
$this->update_transaction_id( $transaction_id, $wc_order );
|
||||
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id(), array( 'order_type' => 'any' ) );
|
||||
foreach ( $subscriptions as $id => $subscription ) {
|
||||
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
||||
$subscription->save();
|
||||
}
|
||||
}
|
||||
|
||||
$this->handle_new_order_status( $order, $wc_order );
|
||||
|
@ -255,8 +270,7 @@ class RenewalHandler {
|
|||
|
||||
$subscription = function_exists( 'wcs_get_subscription' ) ? wcs_get_subscription( $wc_order->get_meta( '_subscription_renewal' ) ) : null;
|
||||
if ( $subscription ) {
|
||||
$subscription_id = $subscription->get_id();
|
||||
$token_id = get_post_meta( $subscription_id, 'payment_token_id', true );
|
||||
$token_id = $subscription->get_meta( 'payment_token_id' );
|
||||
if ( $token_id ) {
|
||||
foreach ( $tokens as $token ) {
|
||||
if ( $token_id === $token->id() ) {
|
||||
|
|
|
@ -11,8 +11,13 @@ 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\ApiClient\Repository\OrderRepository;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
@ -26,14 +31,18 @@ use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WP_Post;
|
||||
|
||||
/**
|
||||
* Class SubscriptionModule
|
||||
*/
|
||||
class SubscriptionModule implements ModuleInterface {
|
||||
|
||||
use TransactionIdHandlingTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
@ -69,10 +78,28 @@ class SubscriptionModule implements ModuleInterface {
|
|||
add_action(
|
||||
'woocommerce_subscription_payment_complete',
|
||||
function ( $subscription ) use ( $c ) {
|
||||
$paypal_subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( $paypal_subscription_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$payment_token_repository = $c->get( 'vaulting.repository.payment-token' );
|
||||
$logger = $c->get( 'woocommerce.logger.woocommerce' );
|
||||
|
||||
$this->add_payment_token_id( $subscription, $payment_token_repository, $logger );
|
||||
|
||||
if ( count( $subscription->get_related_orders() ) === 1 ) {
|
||||
$parent_order = $subscription->get_parent();
|
||||
if ( is_a( $parent_order, WC_Order::class ) ) {
|
||||
$order_repository = $c->get( 'api.repository.order' );
|
||||
$order = $order_repository->for_wc_order( $parent_order );
|
||||
$transaction_id = $this->get_paypal_order_transaction_id( $order );
|
||||
if ( $transaction_id ) {
|
||||
$subscription->update_meta_data( 'ppcp_previous_transaction_reference', $transaction_id );
|
||||
$subscription->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -123,28 +150,20 @@ class SubscriptionModule implements ModuleInterface {
|
|||
&& isset( $data['payment_source']['token'] ) && $data['payment_source']['token']['type'] === 'PAYMENT_METHOD_TOKEN'
|
||||
&& isset( $data['payment_source']['token']['source']->card )
|
||||
) {
|
||||
$renewal_order_id = absint( $data['purchase_units'][0]['custom_id'] );
|
||||
$subscriptions = wcs_get_subscriptions_for_renewal_order( $renewal_order_id );
|
||||
$subscriptions_values = array_values( $subscriptions );
|
||||
$latest_subscription = array_shift( $subscriptions_values );
|
||||
if ( is_a( $latest_subscription, WC_Subscription::class ) ) {
|
||||
$related_renewal_orders = $latest_subscription->get_related_orders( 'ids', 'renewal' );
|
||||
$latest_order_id_with_transaction = array_slice( $related_renewal_orders, 1, 1, false );
|
||||
$order_id = ! empty( $latest_order_id_with_transaction ) ? $latest_order_id_with_transaction[0] : 0;
|
||||
if ( count( $related_renewal_orders ) === 1 ) {
|
||||
$order_id = $latest_subscription->get_parent_id();
|
||||
}
|
||||
|
||||
$wc_order = wc_get_order( $order_id );
|
||||
if ( is_a( $wc_order, WC_Order::class ) ) {
|
||||
$transaction_id = $wc_order->get_transaction_id();
|
||||
$data['application_context']['stored_payment_source'] = array(
|
||||
$data['payment_source'] = array(
|
||||
'card' => array(
|
||||
'vault_id' => $data['payment_source']['token']['id'],
|
||||
'stored_credential' => array(
|
||||
'payment_initiator' => 'MERCHANT',
|
||||
'payment_type' => 'RECURRING',
|
||||
'usage' => 'SUBSEQUENT',
|
||||
'previous_transaction_reference' => $transaction_id,
|
||||
);
|
||||
}
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
$previous_transaction_reference = $subscription->get_meta( 'ppcp_previous_transaction_reference' );
|
||||
if ( $previous_transaction_reference ) {
|
||||
$data['payment_source']['card']['stored_credential']['previous_transaction_reference'] = $previous_transaction_reference;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,6 +174,122 @@ 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',
|
||||
/**
|
||||
* Param types removed to avoid third-party issues.
|
||||
*
|
||||
* @psalm-suppress MissingClosureParamType
|
||||
*/
|
||||
function( string $post_type, $post_or_order_object ) use ( $c ) {
|
||||
if ( ! function_exists( 'wcs_get_subscription' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$order = ( $post_or_order_object instanceof WP_Post )
|
||||
? wc_get_order( $post_or_order_object->ID )
|
||||
: $post_or_order_object;
|
||||
|
||||
if ( ! is_a( $order, WC_Order::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription = wcs_get_subscription( $order->get_id() );
|
||||
if ( ! is_a( $subscription, WC_Subscription::class ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$subscription_id = $subscription->get_meta( 'ppcp_subscription' ) ?? '';
|
||||
if ( ! $subscription_id ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$screen_id = wc_get_page_screen_id( 'shop_subscription' );
|
||||
remove_meta_box( 'woocommerce-subscription-schedule', $screen_id, 'side' );
|
||||
|
||||
$environment = $c->get( 'onboarding.environment' );
|
||||
add_meta_box(
|
||||
'ppcp_paypal_subscription',
|
||||
__( 'PayPal Subscription', 'woocommerce-paypal-payments' ),
|
||||
function() use ( $subscription_id, $environment ) {
|
||||
$host = $environment->current_environment_is( Environment::SANDBOX ) ? 'https://www.sandbox.paypal.com' : 'https://www.paypal.com';
|
||||
$url = trailingslashit( $host ) . 'billing/subscriptions/' . $subscription_id;
|
||||
echo '<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 +497,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 +740,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 +816,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
|
@ -30,6 +30,7 @@ return array(
|
|||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'wcgateway.processor.authorized-payments' ),
|
||||
$container->get( 'api.endpoint.payments' ),
|
||||
$container->get( 'api.endpoint.payment-token' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
|
|
@ -14,6 +14,8 @@ use Psr\Log\LoggerInterface;
|
|||
use RuntimeException;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PaymentTokenEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Repository\OrderRepository;
|
||||
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
|
@ -67,6 +69,13 @@ class PaymentTokenChecker {
|
|||
*/
|
||||
protected $payments_endpoint;
|
||||
|
||||
/**
|
||||
* The payment token endpoint.
|
||||
*
|
||||
* @var PaymentTokenEndpoint
|
||||
*/
|
||||
protected $payment_token_endpoint;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -82,6 +91,7 @@ class PaymentTokenChecker {
|
|||
* @param Settings $settings The settings.
|
||||
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The authorized payments processor.
|
||||
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
|
||||
* @param PaymentTokenEndpoint $payment_token_endpoint The payment token endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -90,6 +100,7 @@ class PaymentTokenChecker {
|
|||
Settings $settings,
|
||||
AuthorizedPaymentsProcessor $authorized_payments_processor,
|
||||
PaymentsEndpoint $payments_endpoint,
|
||||
PaymentTokenEndpoint $payment_token_endpoint,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->payment_token_repository = $payment_token_repository;
|
||||
|
@ -97,6 +108,7 @@ class PaymentTokenChecker {
|
|||
$this->settings = $settings;
|
||||
$this->authorized_payments_processor = $authorized_payments_processor;
|
||||
$this->payments_endpoint = $payments_endpoint;
|
||||
$this->payment_token_endpoint = $payment_token_endpoint;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -130,7 +142,7 @@ class PaymentTokenChecker {
|
|||
return;
|
||||
}
|
||||
|
||||
$tokens = $this->payment_token_repository->all_for_user_id( $customer_id );
|
||||
$tokens = $this->tokens_for_user( $customer_id );
|
||||
if ( $tokens ) {
|
||||
try {
|
||||
$this->capture_authorized_payment( $wc_order );
|
||||
|
@ -215,11 +227,11 @@ class PaymentTokenChecker {
|
|||
$wc_order->update_status( 'failed', $error_message );
|
||||
|
||||
/**
|
||||
* Function already exist in Subscription plugin
|
||||
* Function already exist in WC Subscriptions plugin.
|
||||
*
|
||||
* @psalm-suppress UndefinedFunction
|
||||
*/
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $wc_order->get_id() );
|
||||
$subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $wc_order->get_id() ) : array();
|
||||
foreach ( $subscriptions as $key => $subscription ) {
|
||||
if ( $subscription->get_parent_id() === $wc_order->get_id() ) {
|
||||
try {
|
||||
|
@ -231,4 +243,32 @@ class PaymentTokenChecker {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns customer tokens either from guest or customer id.
|
||||
*
|
||||
* @param int $customer_id The customer id.
|
||||
* @return PaymentToken[]
|
||||
*/
|
||||
private function tokens_for_user( int $customer_id ): array {
|
||||
$tokens = array();
|
||||
|
||||
$guest_customer_id = get_user_meta( $customer_id, 'ppcp_guest_customer_id', true );
|
||||
if ( $guest_customer_id ) {
|
||||
$tokens = $this->payment_token_endpoint->for_guest( $guest_customer_id );
|
||||
}
|
||||
|
||||
if ( ! $tokens ) {
|
||||
$guest_customer_id = get_user_meta( $customer_id, 'ppcp_customer_id', true );
|
||||
if ( $guest_customer_id ) {
|
||||
$tokens = $this->payment_token_endpoint->for_guest( $guest_customer_id );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $tokens ) {
|
||||
$tokens = $this->payment_token_repository->all_for_user_id( $customer_id );
|
||||
}
|
||||
|
||||
return $tokens;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ class VaultingModule implements ModuleInterface {
|
|||
'woocommerce_paypal_payments_check_saved_payment',
|
||||
function ( int $order_id, int $customer_id, string $intent ) use ( $container ) {
|
||||
$payment_token_checker = $container->get( 'vaulting.payment-token-checker' );
|
||||
assert( $payment_token_checker instanceof PaymentTokenChecker );
|
||||
$payment_token_checker->check_and_update( $order_id, $customer_id, $intent );
|
||||
},
|
||||
10,
|
||||
|
|
|
@ -53,6 +53,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Helper\DCCProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceProductStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Notice\ConnectAdminNotice;
|
||||
|
@ -149,7 +150,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 +294,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 +307,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 {
|
||||
|
@ -333,10 +337,11 @@ return array(
|
|||
);
|
||||
},
|
||||
'wcgateway.processor.refunds' => static function ( ContainerInterface $container ): RefundProcessor {
|
||||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
$payments_endpoint = $container->get( 'api.endpoint.payments' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new RefundProcessor( $order_endpoint, $payments_endpoint, $logger );
|
||||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
$payments_endpoint = $container->get( 'api.endpoint.payments' );
|
||||
$refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new RefundProcessor( $order_endpoint, $payments_endpoint, $refund_fees_updater, $logger );
|
||||
},
|
||||
'wcgateway.processor.authorized-payments' => static function ( ContainerInterface $container ): AuthorizedPaymentsProcessor {
|
||||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
|
@ -810,7 +815,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,
|
||||
|
@ -1000,6 +1012,12 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'wcgateway.helper.refund-fees-updater' => static function ( ContainerInterface $container ): RefundFeesUpdater {
|
||||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
$logger = $container->get( 'woocommerce.logger.woocommerce' );
|
||||
return new RefundFeesUpdater( $order_endpoint, $logger );
|
||||
},
|
||||
|
||||
'button.helper.messages-disclaimers' => static function ( ContainerInterface $container ): MessagesDisclaimers {
|
||||
return new MessagesDisclaimers(
|
||||
$container->get( 'api.shop.country' )
|
||||
|
|
|
@ -24,11 +24,18 @@ class FeesRenderer {
|
|||
* @return string
|
||||
*/
|
||||
public function render( WC_Order $wc_order ) : string {
|
||||
$breakdown = $wc_order->get_meta( PayPalGateway::FEES_META_KEY );
|
||||
$breakdown = $wc_order->get_meta( PayPalGateway::FEES_META_KEY );
|
||||
$refund_breakdown = $wc_order->get_meta( PayPalGateway::REFUND_FEES_META_KEY ) ?: array();
|
||||
|
||||
if ( ! is_array( $breakdown ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$refund_fee = $refund_breakdown['paypal_fee'] ?? array();
|
||||
$refund_amount = $refund_breakdown['net_amount'] ?? array();
|
||||
$refund_total = ( $refund_fee['value'] ?? 0 ) + ( $refund_amount['value'] ?? 0 );
|
||||
$refund_currency = ( ( $refund_amount['currency_code'] ?? '' ) === ( $refund_fee['currency_code'] ?? '' ) ) ? ( $refund_amount['currency_code'] ?? '' ) : '';
|
||||
|
||||
$html = '';
|
||||
|
||||
$fee = $breakdown['paypal_fee'] ?? null;
|
||||
|
@ -42,6 +49,28 @@ class FeesRenderer {
|
|||
);
|
||||
}
|
||||
|
||||
if ( $refund_fee ) {
|
||||
$html .= $this->render_money_row(
|
||||
__( 'PayPal Refund Fee:', 'woocommerce-paypal-payments' ),
|
||||
__( 'The fee PayPal collects for the refund transactions.', 'woocommerce-paypal-payments' ),
|
||||
$refund_fee['value'],
|
||||
$refund_fee['currency_code'],
|
||||
true,
|
||||
'refunded-total'
|
||||
);
|
||||
}
|
||||
|
||||
if ( $refund_amount ) {
|
||||
$html .= $this->render_money_row(
|
||||
__( 'PayPal Refunded:', 'woocommerce-paypal-payments' ),
|
||||
__( 'The net amount that was refunded.', 'woocommerce-paypal-payments' ),
|
||||
$refund_amount['value'],
|
||||
$refund_amount['currency_code'],
|
||||
true,
|
||||
'refunded-total'
|
||||
);
|
||||
}
|
||||
|
||||
$net = $breakdown['net_amount'] ?? null;
|
||||
if ( is_array( $net ) ) {
|
||||
$html .= $this->render_money_row(
|
||||
|
@ -50,6 +79,15 @@ class FeesRenderer {
|
|||
$net['value'],
|
||||
$net['currency_code']
|
||||
);
|
||||
|
||||
if ( ( $refund_total > 0.0 && $refund_currency === $net['currency_code'] ) ) {
|
||||
$html .= $this->render_money_row(
|
||||
__( 'PayPal Net Total:', 'woocommerce-paypal-payments' ),
|
||||
__( 'The net total that will be credited to your PayPal account minus the refunds.', 'woocommerce-paypal-payments' ),
|
||||
$net['value'] - $refund_total,
|
||||
$net['currency_code']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $html;
|
||||
|
@ -63,9 +101,10 @@ class FeesRenderer {
|
|||
* @param string|float $value The money value.
|
||||
* @param string $currency The currency code.
|
||||
* @param bool $negative Whether to add the minus sign.
|
||||
* @param string $html_class Html class to add to the elements.
|
||||
* @return string
|
||||
*/
|
||||
private function render_money_row( string $title, string $tooltip, $value, string $currency, bool $negative = false ): string {
|
||||
private function render_money_row( string $title, string $tooltip, $value, string $currency, bool $negative = false, string $html_class = '' ): string {
|
||||
/**
|
||||
* Bad type hint in WC phpdoc.
|
||||
*
|
||||
|
@ -73,10 +112,10 @@ class FeesRenderer {
|
|||
*/
|
||||
return '
|
||||
<tr>
|
||||
<td class="label">' . wc_help_tip( $tooltip ) . ' ' . esc_html( $title ) . '
|
||||
<td class="' . trim( 'label ' . $html_class ) . '">' . wc_help_tip( $tooltip ) . ' ' . esc_html( $title ) . '
|
||||
</td>
|
||||
<td width="1%"></td>
|
||||
<td class="total">
|
||||
<td class="' . trim( 'total ' . $html_class ) . '">
|
||||
' .
|
||||
( $negative ? ' - ' : '' ) .
|
||||
wc_price( $value, array( 'currency' => $currency ) ) . '
|
||||
|
|
|
@ -127,8 +127,10 @@ class SettingsPageAssets {
|
|||
|
||||
/**
|
||||
* Register assets provided by this module.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function register_assets() {
|
||||
public function register_assets(): void {
|
||||
add_action(
|
||||
'admin_enqueue_scripts',
|
||||
function() {
|
||||
|
|
|
@ -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\Gateway\OXXO;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
|
||||
|
||||
|
@ -154,9 +155,19 @@ class OXXO {
|
|||
add_action(
|
||||
'add_meta_boxes',
|
||||
function( string $post_type ) {
|
||||
if ( $post_type === 'shop_order' ) {
|
||||
/**
|
||||
* Class and function exist in WooCommerce.
|
||||
*
|
||||
* @psalm-suppress UndefinedClass
|
||||
* @psalm-suppress UndefinedFunction
|
||||
*/
|
||||
$screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
|
||||
? wc_get_page_screen_id( 'shop-order' )
|
||||
: 'shop_order';
|
||||
|
||||
if ( $post_type === $screen ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
|
||||
$post_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) );
|
||||
$order = wc_get_order( $post_id );
|
||||
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === OXXOGateway::ID ) {
|
||||
$payer_action = $order->get_meta( 'ppcp_oxxo_payer_action' );
|
||||
|
@ -167,7 +178,7 @@ class OXXO {
|
|||
function() use ( $payer_action ) {
|
||||
echo '<p><a class="button" href="' . esc_url( $payer_action ) . '" target="_blank">' . esc_html__( 'See OXXO voucher', 'woocommerce-paypal-payments' ) . '</a></p>';
|
||||
},
|
||||
$post_type,
|
||||
$screen,
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
|
|
|
@ -48,6 +48,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
|
|||
const ORDER_PAYMENT_MODE_META_KEY = '_ppcp_paypal_payment_mode';
|
||||
const ORDER_PAYMENT_SOURCE_META_KEY = '_ppcp_paypal_payment_source';
|
||||
const FEES_META_KEY = '_ppcp_paypal_fees';
|
||||
const REFUND_FEES_META_KEY = '_ppcp_paypal_refund_fees';
|
||||
const REFUNDS_META_KEY = '_ppcp_refunds';
|
||||
|
||||
/**
|
||||
|
@ -525,7 +526,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
|
|||
$order = $this->session_handler->order();
|
||||
$this->add_paypal_meta( $wc_order, $order, $this->environment );
|
||||
|
||||
$subscriptions = wcs_get_subscriptions_for_order( $order_id );
|
||||
$subscriptions = function_exists( 'wcs_get_subscriptions_for_order' ) ? wcs_get_subscriptions_for_order( $order_id ) : array();
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
$subscription->update_meta_data( 'ppcp_subscription', $paypal_subscription_id );
|
||||
$subscription->save();
|
||||
|
|
|
@ -9,15 +9,13 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Gateway\PayUponInvoice;
|
||||
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\CustomOrdersTableController;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Email;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\PayUponInvoiceOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\CaptureFactory;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FraudNet\FraudNet;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\CheckoutHelper;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\PayUponInvoiceHelper;
|
||||
|
@ -515,9 +513,19 @@ class PayUponInvoice {
|
|||
add_action(
|
||||
'add_meta_boxes',
|
||||
function( string $post_type ) {
|
||||
if ( $post_type === 'shop_order' ) {
|
||||
/**
|
||||
* Class and function exist in WooCommerce.
|
||||
*
|
||||
* @psalm-suppress UndefinedClass
|
||||
* @psalm-suppress UndefinedFunction
|
||||
*/
|
||||
$screen = class_exists( CustomOrdersTableController::class ) && wc_get_container()->get( CustomOrdersTableController::class )->custom_orders_table_usage_is_enabled()
|
||||
? wc_get_page_screen_id( 'shop-order' )
|
||||
: 'shop_order';
|
||||
|
||||
if ( $post_type === $screen ) {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
$post_id = wc_clean( wp_unslash( $_GET['post'] ?? '' ) );
|
||||
$post_id = wc_clean( wp_unslash( $_GET['id'] ?? $_GET['post'] ?? '' ) );
|
||||
$order = wc_get_order( $post_id );
|
||||
if ( is_a( $order, WC_Order::class ) && $order->get_payment_method() === PayUponInvoiceGateway::ID ) {
|
||||
$instructions = $order->get_meta( 'ppcp_ratepay_payment_instructions_payment_reference' );
|
||||
|
@ -540,7 +548,7 @@ class PayUponInvoice {
|
|||
echo wp_kses_post( "<li>Verwendungszweck: {$payment_reference}</li>" );
|
||||
echo '</ul>';
|
||||
},
|
||||
$post_type,
|
||||
$screen,
|
||||
'side',
|
||||
'high'
|
||||
);
|
||||
|
|
|
@ -40,7 +40,7 @@ trait ProcessPaymentTrait {
|
|||
* @param int $customer_id The customer ID.
|
||||
*/
|
||||
protected function schedule_saved_payment_check( int $wc_order_id, int $customer_id ): void {
|
||||
$timestamp = 1 * MINUTE_IN_SECONDS;
|
||||
$timestamp = 3 * MINUTE_IN_SECONDS;
|
||||
if (
|
||||
$this->config->has( 'subscription_behavior_when_vault_fails' )
|
||||
&& $this->config->get( 'subscription_behavior_when_vault_fails' ) === 'capture_auth'
|
||||
|
|
195
modules/ppcp-wc-gateway/src/Helper/RefundFeesUpdater.php
Normal file
195
modules/ppcp-wc-gateway/src/Helper/RefundFeesUpdater.php
Normal file
|
@ -0,0 +1,195 @@
|
|||
<?php
|
||||
/**
|
||||
* The RefundFeesUpdater helper.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcGateway\Helper;
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Helper;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WP_Comment;
|
||||
|
||||
/**
|
||||
* CheckoutHelper class.
|
||||
*/
|
||||
class RefundFeesUpdater {
|
||||
|
||||
/**
|
||||
* The Order Endpoint.
|
||||
*
|
||||
* @var OrderEndpoint
|
||||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* RefundFeesUpdater constructor.
|
||||
*
|
||||
* @param OrderEndpoint $order_endpoint The Order Endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct( OrderEndpoint $order_endpoint, LoggerInterface $logger ) {
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the fees meta for a given order.
|
||||
*
|
||||
* @param WC_Order $wc_order The WooCommerce order.
|
||||
* @return void
|
||||
*/
|
||||
public function update( WC_Order $wc_order ): void {
|
||||
$paypal_order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
|
||||
|
||||
if ( ! $paypal_order_id ) {
|
||||
$this->logger->error(
|
||||
sprintf( 'Failed to update order paypal refund fees. No PayPal order_id. [wc_order: %s]', $wc_order->get_id() )
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf( 'Updating order paypal refund fees. [wc_order: %s, paypal_order: %s]', $wc_order->get_id(), $paypal_order_id )
|
||||
);
|
||||
|
||||
$paypal_order = $this->order_endpoint->order( $paypal_order_id );
|
||||
$purchase_units = $paypal_order->purchase_units();
|
||||
|
||||
$gross_amount_total = 0.0;
|
||||
$fee_total = 0.0;
|
||||
$net_amount_total = 0.0;
|
||||
$currency_codes = array();
|
||||
$refunds_ids = array();
|
||||
|
||||
foreach ( $purchase_units as $purchase_unit ) {
|
||||
$payments = $purchase_unit->payments();
|
||||
|
||||
if ( ! $payments ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$refunds = $payments->refunds();
|
||||
|
||||
foreach ( $refunds as $refund ) {
|
||||
$breakdown = $refund->seller_payable_breakdown();
|
||||
$refunds_ids[] = $refund->id();
|
||||
|
||||
if ( ! $breakdown ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$gross_amount = $breakdown->gross_amount();
|
||||
if ( $gross_amount ) {
|
||||
$gross_amount_total += $gross_amount->value();
|
||||
$currency_codes[] = $gross_amount->currency_code();
|
||||
}
|
||||
|
||||
$paypal_fee = $breakdown->paypal_fee();
|
||||
if ( $paypal_fee ) {
|
||||
$fee_total += $paypal_fee->value();
|
||||
$currency_codes[] = $paypal_fee->currency_code();
|
||||
}
|
||||
|
||||
$net_amount = $breakdown->net_amount();
|
||||
if ( $net_amount ) {
|
||||
$net_amount_total += $net_amount->value();
|
||||
$currency_codes[] = $net_amount->currency_code();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$currency_codes = array_unique( $currency_codes );
|
||||
|
||||
if ( count( $currency_codes ) > 1 ) {
|
||||
// There are multiple different currencies codes in the refunds.
|
||||
|
||||
$this->logger->warning(
|
||||
sprintf(
|
||||
'Updating order paypal refund fees. Multiple currencies detected. [wc_order: %s, paypal_order: %s, currencies: %s]',
|
||||
$wc_order->get_id(),
|
||||
$paypal_order_id,
|
||||
implode( ',', $currency_codes )
|
||||
)
|
||||
);
|
||||
|
||||
$wc_order->update_meta_data( PayPalGateway::REFUND_FEES_META_KEY, array() );
|
||||
return;
|
||||
}
|
||||
|
||||
$currency_code = current( $currency_codes ) ?: '';
|
||||
|
||||
$meta_data = array(
|
||||
'gross_amount' => ( new Money( $gross_amount_total, $currency_code ) )->to_array(),
|
||||
'paypal_fee' => ( new Money( $fee_total, $currency_code ) )->to_array(),
|
||||
'net_amount' => ( new Money( $net_amount_total, $currency_code ) )->to_array(),
|
||||
);
|
||||
|
||||
$wc_order->update_meta_data( PayPalGateway::REFUND_FEES_META_KEY, $meta_data );
|
||||
$wc_order->save();
|
||||
|
||||
$order_notes = $this->get_order_notes( $wc_order );
|
||||
|
||||
foreach ( $refunds_ids as $refund_id ) {
|
||||
$has_note = false;
|
||||
foreach ( $order_notes as $order_note ) {
|
||||
if ( strpos( $order_note->comment_content, $refund_id ) !== false ) {
|
||||
$has_note = true;
|
||||
}
|
||||
}
|
||||
if ( ! $has_note ) {
|
||||
$wc_order->add_order_note( sprintf( 'PayPal refund ID: %s', $refund_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf( 'Updated order paypal refund fees. [wc_order: %s, paypal_order: %s]', $wc_order->get_id(), $paypal_order_id )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all order notes
|
||||
* Based on WC_Order::get_customer_order_notes
|
||||
*
|
||||
* @param WC_Order $wc_order The WooCommerce order.
|
||||
* @return WP_Comment[]
|
||||
*/
|
||||
private function get_order_notes( WC_Order $wc_order ): array {
|
||||
$notes = array();
|
||||
$args = array(
|
||||
'post_id' => $wc_order->get_id(),
|
||||
);
|
||||
|
||||
// By default, WooCommerce excludes comments of the comment_type order_note.
|
||||
// We need to remove this filter to get the order notes.
|
||||
remove_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
|
||||
|
||||
$comments = get_comments( $args );
|
||||
|
||||
if ( is_array( $comments ) ) {
|
||||
foreach ( $comments as $comment ) {
|
||||
if ( $comment instanceof WP_Comment ) {
|
||||
$notes[] = $comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_filter( 'comments_clauses', array( 'WC_Comments', 'exclude_order_comments' ) );
|
||||
|
||||
return $notes;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,9 +20,10 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payments;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Refund;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\RefundCapture;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
|
||||
/**
|
||||
* Class RefundProcessor
|
||||
|
@ -55,18 +56,27 @@ class RefundProcessor {
|
|||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* The refund fees updater.
|
||||
*
|
||||
* @var RefundFeesUpdater
|
||||
*/
|
||||
private $refund_fees_updater;
|
||||
|
||||
/**
|
||||
* RefundProcessor constructor.
|
||||
*
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param PaymentsEndpoint $payments_endpoint The payments endpoint.
|
||||
* @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct( OrderEndpoint $order_endpoint, PaymentsEndpoint $payments_endpoint, LoggerInterface $logger ) {
|
||||
public function __construct( OrderEndpoint $order_endpoint, PaymentsEndpoint $payments_endpoint, RefundFeesUpdater $refund_fees_updater, LoggerInterface $logger ) {
|
||||
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->payments_endpoint = $payments_endpoint;
|
||||
$this->logger = $logger;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->payments_endpoint = $payments_endpoint;
|
||||
$this->refund_fees_updater = $refund_fees_updater;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -106,6 +116,7 @@ class RefundProcessor {
|
|||
$refund_id = $this->refund( $order, $wc_order, $amount, $reason );
|
||||
|
||||
$this->add_refund_to_meta( $wc_order, $refund_id );
|
||||
$this->refund_fees_updater->update( $wc_order );
|
||||
|
||||
break;
|
||||
case self::REFUND_MODE_VOID:
|
||||
|
@ -151,7 +162,7 @@ class RefundProcessor {
|
|||
}
|
||||
|
||||
$capture = $captures[0];
|
||||
$refund = new Refund(
|
||||
$refund = new RefundCapture(
|
||||
$capture,
|
||||
$capture->invoice_id(),
|
||||
$reason,
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -80,6 +80,7 @@ return array(
|
|||
$order_endpoint = $container->get( 'api.endpoint.order' );
|
||||
$authorized_payments_processor = $container->get( 'wcgateway.processor.authorized-payments' );
|
||||
$payment_token_factory = $container->get( 'vaulting.payment-token-factory' );
|
||||
$refund_fees_updater = $container->get( 'wcgateway.helper.refund-fees-updater' );
|
||||
|
||||
return array(
|
||||
new CheckoutOrderApproved(
|
||||
|
@ -91,14 +92,14 @@ return array(
|
|||
),
|
||||
new CheckoutOrderCompleted( $logger ),
|
||||
new CheckoutPaymentApprovalReversed( $logger ),
|
||||
new PaymentCaptureRefunded( $logger ),
|
||||
new PaymentCaptureRefunded( $logger, $refund_fees_updater ),
|
||||
new PaymentCaptureReversed( $logger ),
|
||||
new PaymentCaptureCompleted( $logger, $order_endpoint ),
|
||||
new VaultPaymentTokenCreated( $logger, $prefix, $authorized_payments_processor, $payment_token_factory ),
|
||||
new VaultPaymentTokenDeleted( $logger ),
|
||||
new PaymentCapturePending( $logger ),
|
||||
new PaymentSaleCompleted( $logger ),
|
||||
new PaymentSaleRefunded( $logger ),
|
||||
new PaymentSaleRefunded( $logger, $refund_fees_updater ),
|
||||
new BillingSubscriptionCancelled( $logger ),
|
||||
new BillingPlanPricingChangeActivated( $logger ),
|
||||
new CatalogProductUpdated( $logger ),
|
||||
|
|
|
@ -81,7 +81,7 @@ class BillingSubscriptionCancelled implements RequestHandler {
|
|||
),
|
||||
),
|
||||
);
|
||||
$subscriptions = wcs_get_subscriptions( $args );
|
||||
$subscriptions = function_exists( 'wcs_get_subscriptions' ) ? wcs_get_subscriptions( $args ) : array();
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
$subscription->update_status( 'cancelled' );
|
||||
}
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
|||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||
use WP_Error;
|
||||
|
@ -31,13 +32,22 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* The refund fees updater.
|
||||
*
|
||||
* @var RefundFeesUpdater
|
||||
*/
|
||||
private $refund_fees_updater;
|
||||
|
||||
/**
|
||||
* PaymentCaptureRefunded constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
public function __construct( LoggerInterface $logger, RefundFeesUpdater $refund_fees_updater ) {
|
||||
$this->logger = $logger;
|
||||
$this->refund_fees_updater = $refund_fees_updater;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -68,9 +78,11 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
* @return WP_REST_Response
|
||||
*/
|
||||
public function handle_request( WP_REST_Request $request ): WP_REST_Response {
|
||||
$order_id = isset( $request['resource']['custom_id'] ) ?
|
||||
$request['resource']['custom_id'] : 0;
|
||||
$refund_id = (string) ( $request['resource']['id'] ?? '' );
|
||||
$resource = ( $request['resource'] ?? array() ) ?: array();
|
||||
|
||||
$order_id = $resource['custom_id'] ?? 0;
|
||||
$refund_id = (string) ( $resource['id'] ?? '' );
|
||||
|
||||
if ( ! $order_id ) {
|
||||
$message = sprintf(
|
||||
'No order for webhook event %s was found.',
|
||||
|
@ -122,6 +134,7 @@ class PaymentCaptureRefunded implements RequestHandler {
|
|||
if ( $refund_id ) {
|
||||
$this->update_transaction_id( $refund_id, $wc_order, $this->logger );
|
||||
$this->add_refund_to_meta( $wc_order, $refund_id );
|
||||
$this->refund_fees_updater->update( $wc_order );
|
||||
}
|
||||
|
||||
return $this->success_response();
|
||||
|
|
|
@ -71,10 +71,13 @@ class PaymentSaleCompleted implements RequestHandler {
|
|||
return $this->failure_response();
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'wcs_get_subscriptions' ) ) {
|
||||
return $this->failure_response( 'WooCommerce Subscriptions plugin is not active.' );
|
||||
}
|
||||
|
||||
$billing_agreement_id = wc_clean( wp_unslash( $request['resource']['billing_agreement_id'] ?? '' ) );
|
||||
if ( ! $billing_agreement_id ) {
|
||||
$message = 'Could not retrieve billing agreement id for subscription.';
|
||||
return $this->failure_response( $message );
|
||||
return $this->failure_response( 'Could not retrieve billing agreement id for subscription.' );
|
||||
}
|
||||
|
||||
$args = array(
|
||||
|
@ -89,10 +92,24 @@ class PaymentSaleCompleted implements RequestHandler {
|
|||
);
|
||||
$subscriptions = wcs_get_subscriptions( $args );
|
||||
foreach ( $subscriptions as $subscription ) {
|
||||
$parent_order = wc_get_order( $subscription->get_parent() );
|
||||
$transaction_id = wc_clean( wp_unslash( $request['resource']['id'] ?? '' ) );
|
||||
if ( $transaction_id && is_string( $transaction_id ) && is_a( $parent_order, WC_Order::class ) ) {
|
||||
$this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
|
||||
if ( $transaction_id && is_string( $transaction_id ) ) {
|
||||
$is_renewal = $subscription->get_meta( '_ppcp_is_subscription_renewal' ) ?? '';
|
||||
if ( $is_renewal ) {
|
||||
$renewal_order = wcs_create_renewal_order( $subscription );
|
||||
if ( is_a( $renewal_order, WC_Order::class ) ) {
|
||||
$renewal_order->payment_complete();
|
||||
$this->update_transaction_id( $transaction_id, $renewal_order, $this->logger );
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$parent_order = wc_get_order( $subscription->get_parent() );
|
||||
if ( is_a( $parent_order, WC_Order::class ) ) {
|
||||
$subscription->update_meta_data( '_ppcp_is_subscription_renewal', 'true' );
|
||||
$subscription->save_meta_data();
|
||||
$this->update_transaction_id( $transaction_id, $parent_order, $this->logger );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\Webhooks\Handler;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\RefundMetaTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\TransactionIdHandlingTrait;
|
||||
use WP_Error;
|
||||
|
@ -30,13 +31,22 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* The refund fees updater.
|
||||
*
|
||||
* @var RefundFeesUpdater
|
||||
*/
|
||||
private $refund_fees_updater;
|
||||
|
||||
/**
|
||||
* PaymentSaleRefunded constructor.
|
||||
*
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
* @param RefundFeesUpdater $refund_fees_updater The refund fees updater.
|
||||
*/
|
||||
public function __construct( LoggerInterface $logger ) {
|
||||
$this->logger = $logger;
|
||||
public function __construct( LoggerInterface $logger, RefundFeesUpdater $refund_fees_updater ) {
|
||||
$this->logger = $logger;
|
||||
$this->refund_fees_updater = $refund_fees_updater;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -120,6 +130,7 @@ class PaymentSaleRefunded implements RequestHandler {
|
|||
|
||||
$this->update_transaction_id( $refund_id, $wc_order, $this->logger );
|
||||
$this->add_refund_to_meta( $wc_order, $refund_id );
|
||||
$this->refund_fees_updater->update( $wc_order );
|
||||
}
|
||||
|
||||
return $this->success_response();
|
||||
|
|
|
@ -107,12 +107,18 @@ class VaultPaymentTokenCreated implements RequestHandler {
|
|||
$customer_id = null !== $request['resource'] && isset( $request['resource']['customer_id'] )
|
||||
? $request['resource']['customer_id']
|
||||
: '';
|
||||
|
||||
if ( ! $customer_id ) {
|
||||
$message = 'No customer id was found.';
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$wc_customer_id = (int) str_replace( $this->prefix, '', $customer_id );
|
||||
$wc_customer_id = $this->wc_customer_id_from( $customer_id );
|
||||
if ( ! $wc_customer_id ) {
|
||||
$message = "No WC customer id was found from PayPal customer id {$customer_id}";
|
||||
return $this->failure_response( $message );
|
||||
}
|
||||
|
||||
$this->authorized_payments_processor->capture_authorized_payments_for_customer( $wc_customer_id );
|
||||
|
||||
if ( ! is_null( $request['resource'] ) && isset( $request['resource']['id'] ) ) {
|
||||
|
@ -149,4 +155,29 @@ class VaultPaymentTokenCreated implements RequestHandler {
|
|||
|
||||
return $this->success_response();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns WC customer id from PayPal customer id.
|
||||
*
|
||||
* @param string $customer_id The customer ID from PayPal.
|
||||
* @return int
|
||||
*/
|
||||
private function wc_customer_id_from( string $customer_id ): int {
|
||||
$customers = get_users(
|
||||
array(
|
||||
'meta_key' => 'ppcp_guest_customer_id', //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key
|
||||
'meta_value' => $customer_id, //phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_value
|
||||
'fields' => 'ids',
|
||||
'number' => 1,
|
||||
)
|
||||
);
|
||||
|
||||
$wc_customer_id = $customers[0] ?? '';
|
||||
if ( $wc_customer_id ) {
|
||||
return (int) $wc_customer_id;
|
||||
}
|
||||
|
||||
$id = str_replace( $this->prefix, '', $customer_id );
|
||||
return is_numeric( $id ) ? (int) $id : 0;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,6 +208,15 @@ class IncomingWebhookEndpoint {
|
|||
public function handle_request( \WP_REST_Request $request ): \WP_REST_Response {
|
||||
$event = $this->event_from_request( $request );
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf(
|
||||
'Webhook %s received of type %s and by resource "%s"',
|
||||
$event->id(),
|
||||
$event->event_type(),
|
||||
$event->resource_type()
|
||||
)
|
||||
);
|
||||
|
||||
$this->last_webhook_event_storage->save( $event );
|
||||
|
||||
if ( $this->simulation->is_simulation_event( $event ) ) {
|
||||
|
@ -218,11 +227,21 @@ class IncomingWebhookEndpoint {
|
|||
|
||||
foreach ( $this->handlers as $handler ) {
|
||||
if ( $handler->responsible_for_request( $request ) ) {
|
||||
$event_type = ( $handler->event_types() ? current( $handler->event_types() ) : '' ) ?: '';
|
||||
|
||||
$this->logger->debug(
|
||||
sprintf(
|
||||
'Webhook is going to be handled by %s on %s',
|
||||
$event_type,
|
||||
get_class( $handler )
|
||||
)
|
||||
);
|
||||
$response = $handler->handle_request( $request );
|
||||
$this->logger->info(
|
||||
sprintf(
|
||||
'Webhook has been handled by %s',
|
||||
( $handler->event_types() ) ? current( $handler->event_types() ) : ''
|
||||
'Webhook has been handled by %s on %s',
|
||||
$event_type,
|
||||
get_class( $handler )
|
||||
)
|
||||
);
|
||||
return $response;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "woocommerce-paypal-payments",
|
||||
"version": "2.2.0",
|
||||
"version": "2.2.2",
|
||||
"description": "WooCommerce PayPal Payments",
|
||||
"repository": "https://github.com/woocommerce/woocommerce-paypal-payments",
|
||||
"license": "GPL-2.0",
|
||||
|
@ -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;
|
|
@ -690,10 +690,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>
|
||||
|
|
29
readme.txt
29
readme.txt
|
@ -2,9 +2,9 @@
|
|||
Contributors: woocommerce, automattic, inpsyde
|
||||
Tags: woocommerce, paypal, payments, ecommerce, e-commerce, store, sales, sell, shop, shopping, cart, checkout
|
||||
Requires at least: 5.3
|
||||
Tested up to: 6.2
|
||||
Tested up to: 6.3
|
||||
Requires PHP: 7.2
|
||||
Stable tag: 2.2.0
|
||||
Stable tag: 2.2.2
|
||||
License: GPLv2
|
||||
License URI: http://www.gnu.org/licenses/gpl-2.0.html
|
||||
|
||||
|
@ -81,6 +81,31 @@ Follow the steps below to connect the plugin to your PayPal account:
|
|||
|
||||
== Changelog ==
|
||||
|
||||
= 2.2.2 - 2023-08-29 =
|
||||
* Fix - High rate of auth voids on vaulted subscriptions for guest users #1529
|
||||
* Enhancement - HPOS compatibility issues #1594
|
||||
* Feature preview - PayPal Subscriptions API fixes and improvements #1600 #1607
|
||||
|
||||
= 2.2.1 - 2023-08-24 =
|
||||
* Fix - One-page checkout causes mini cart not showing the PP button on certain pages #1536
|
||||
* Fix - When onboarding loading the return_url too fast may cause the onboarding to fail #1565
|
||||
* Fix - PayPal button doesn't work for variable products on product page after recent 2.2.0 release #1533
|
||||
* Fix - Send payee_preferred correctly for instant payments #1489
|
||||
* Fix - Auto-disabled ACDC vaulting after updating to 2.1.0 #1490
|
||||
* Fix - PayPal Payments serializing formData of array inputs #1501
|
||||
* Fix - Buttons not working on single product page for WooCommerce Bookings product #1478
|
||||
* Enhancement - PayPal Later message price amount doesn't update dynamically #1585
|
||||
* Enhancement - Improve WC order creation in webhook #1530
|
||||
* Enhancement - Refactor hosted fields for early card detection #1554
|
||||
* Enhancement - Pay Later button and message get hidden when product/cart/checkout value is outside of range #1511
|
||||
* Enhancement - Add link to manual credential docs #1430
|
||||
* Enhancement - Validate Merchant ID field format when saving settings #1509
|
||||
* Enhancement - Include soft descriptor for card's activity #1427
|
||||
* Enhancement - Update Pay Later amount on the cart page and checkout when total changes #1441
|
||||
* Enhancement - Log Subscription Mode configuration in system report #1507
|
||||
* Enhancement - HPOS compatibility issues #1555
|
||||
* Feature preview - PayPal Subscriptions API fixes and improvements #1443
|
||||
|
||||
= 2.2.0 - 2023-07-17 =
|
||||
* Fix - Improve handling of APM payments when buyer did not return to Checkout #1233
|
||||
* Fix - Use order currency instead of shop currency on order-pay page #1363
|
||||
|
|
130
tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
Normal file
130
tests/PHPUnit/Api/OrderRefundFeesUpdateTest.php
Normal file
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Api;
|
||||
|
||||
use Mockery;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ModularTestCase;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\RefundFeesUpdater;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WC_Order;
|
||||
use function Brain\Monkey\Functions\when;
|
||||
|
||||
class OrderRefundFeesUpdateTest extends ModularTestCase
|
||||
{
|
||||
|
||||
private $order_endpoint;
|
||||
private $logger;
|
||||
private $refundFeesUpdater;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
$this->order_endpoint = $this->createMock(OrderEndpoint::class);
|
||||
$this->logger = $this->createMock(LoggerInterface::class);
|
||||
$this->refundFeesUpdater = new RefundFeesUpdater($this->order_endpoint, $this->logger);
|
||||
}
|
||||
|
||||
public function testUpdateWithoutPaypalOrderId(): void
|
||||
{
|
||||
$wc_order_id = 123;
|
||||
|
||||
$wc_order = Mockery::mock(WC_Order::class);
|
||||
$wc_order->expects('get_meta')
|
||||
->with(PayPalGateway::ORDER_ID_META_KEY)
|
||||
->andReturn(null);
|
||||
|
||||
$wc_order->expects('get_id')->andReturn($wc_order_id);
|
||||
|
||||
$this->logger->expects($this->once())
|
||||
->method('error');
|
||||
|
||||
$this->refundFeesUpdater->update($wc_order);
|
||||
}
|
||||
|
||||
public function testUpdateWithValidData(): void
|
||||
{
|
||||
$wc_order_id = 123;
|
||||
$paypal_order_id = 'test_order_id';
|
||||
$refund_id = 'XYZ123';
|
||||
$meta_data = [
|
||||
'gross_amount' => ['value' => 10.0, 'currency_code' => 'USD'],
|
||||
'paypal_fee' => ['value' => 7.0, 'currency_code' => 'USD'],
|
||||
'net_amount' => ['value' => 3.0, 'currency_code' => 'USD'],
|
||||
];
|
||||
|
||||
when('get_comments')->justReturn([]);
|
||||
|
||||
$wc_order = Mockery::mock(WC_Order::class);
|
||||
$wc_order->expects('get_meta')
|
||||
->with(PayPalGateway::ORDER_ID_META_KEY)
|
||||
->andReturn($paypal_order_id);
|
||||
|
||||
$wc_order->expects('get_id')
|
||||
->times(3)
|
||||
->andReturn($wc_order_id);
|
||||
|
||||
$wc_order->expects('update_meta_data')
|
||||
->once()
|
||||
->with('_ppcp_paypal_refund_fees', $meta_data);
|
||||
|
||||
$wc_order->expects('add_order_note')
|
||||
->once()
|
||||
->withArgs(function ($arg) use ($refund_id) {
|
||||
return strpos($arg, $refund_id) !== false;
|
||||
});
|
||||
|
||||
$wc_order->expects('save')->once();
|
||||
|
||||
$moneyGross = Mockery::mock(Money::class);
|
||||
$moneyGross->expects('value')->once()->andReturn($meta_data['gross_amount']['value']);
|
||||
$moneyGross->expects('currency_code')->once()->andReturn($meta_data['gross_amount']['currency_code']);
|
||||
|
||||
$moneyFee = Mockery::mock(Money::class);
|
||||
$moneyFee->expects('value')->once()->andReturn($meta_data['paypal_fee']['value']);
|
||||
$moneyFee->expects('currency_code')->once()->andReturn($meta_data['paypal_fee']['currency_code']);
|
||||
|
||||
$moneyNet = Mockery::mock(Money::class);
|
||||
$moneyNet->expects('value')->once()->andReturn($meta_data['net_amount']['value']);
|
||||
$moneyNet->expects('currency_code')->once()->andReturn($meta_data['net_amount']['currency_code']);
|
||||
|
||||
$breakdown = $this->getMockBuilder(\stdClass::class)
|
||||
->addMethods(['gross_amount', 'paypal_fee', 'net_amount'])
|
||||
->getMock();
|
||||
$breakdown->method('gross_amount')->willReturn($moneyGross);
|
||||
$breakdown->method('paypal_fee')->willReturn($moneyFee);
|
||||
$breakdown->method('net_amount')->willReturn($moneyNet);
|
||||
|
||||
$refund = $this->getMockBuilder(\stdClass::class)
|
||||
->addMethods(['id', 'seller_payable_breakdown'])
|
||||
->getMock();
|
||||
$refund->method('id')->willReturn($refund_id);
|
||||
$refund->method('seller_payable_breakdown')->willReturn($breakdown);
|
||||
|
||||
$payments = $this->getMockBuilder(\stdClass::class)
|
||||
->addMethods(['refunds'])
|
||||
->getMock();
|
||||
$payments->method('refunds')->willReturn([$refund]);
|
||||
|
||||
$purchase_unit = $this->getMockBuilder(\stdClass::class)
|
||||
->addMethods(['payments'])
|
||||
->getMock();
|
||||
$purchase_unit->method('payments')->willReturn($payments);
|
||||
|
||||
$paypal_order = Mockery::mock(Order::class);
|
||||
$paypal_order->expects('purchase_units')->andReturn([$purchase_unit]);
|
||||
|
||||
$this->order_endpoint->method('order')->with($paypal_order_id)->willReturn($paypal_order);
|
||||
|
||||
$this->logger->expects($this->exactly(2))
|
||||
->method('debug')
|
||||
->withConsecutive(
|
||||
[$this->stringContains('Updating order paypal refund fees.')],
|
||||
[$this->stringContains('Updated order paypal refund fees.')]
|
||||
);
|
||||
|
||||
$this->refundFeesUpdater->update($wc_order);
|
||||
}
|
||||
}
|
|
@ -43,10 +43,19 @@ class PaymentsTest extends TestCase
|
|||
'status' => 'CREATED',
|
||||
]
|
||||
);
|
||||
$captures = [$capture];
|
||||
$authorizations = [$authorization];
|
||||
$refund = \Mockery::mock(Refund::class);
|
||||
$refund->shouldReceive('to_array')->andReturn(
|
||||
[
|
||||
'id' => 'refund',
|
||||
'status' => 'CREATED',
|
||||
]
|
||||
);
|
||||
|
||||
$testee = new Payments($authorizations, $captures);
|
||||
$authorizations = [$authorization];
|
||||
$captures = [$capture];
|
||||
$refunds = [$refund];
|
||||
|
||||
$testee = new Payments($authorizations, $captures, $refunds);
|
||||
|
||||
$this->assertEquals(
|
||||
[
|
||||
|
@ -62,6 +71,12 @@ class PaymentsTest extends TestCase
|
|||
'status' => 'CREATED',
|
||||
],
|
||||
],
|
||||
'refunds' => [
|
||||
[
|
||||
'id' => 'refund',
|
||||
'status' => 'CREATED',
|
||||
],
|
||||
],
|
||||
],
|
||||
$testee->to_array()
|
||||
);
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue