mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-05 08:59:14 +08:00
Merge branch 'trunk' into update/add-title-description-and-gateway
This commit is contained in:
commit
b8a7a533c2
190 changed files with 6070 additions and 1680 deletions
|
@ -1,7 +1,7 @@
|
|||
name: woocommerce-paypal-payments
|
||||
type: php
|
||||
docroot: .ddev/wordpress
|
||||
php_version: "7.2"
|
||||
php_version: "7.4"
|
||||
webserver_type: apache-fpm
|
||||
router_http_port: "80"
|
||||
router_https_port: "443"
|
||||
|
@ -18,7 +18,7 @@ hooks:
|
|||
pre-start:
|
||||
- exec-host: "mkdir -p .ddev/wordpress/wp-content/plugins/${DDEV_PROJECT}"
|
||||
web_environment:
|
||||
- WP_VERSION=6.2.2
|
||||
- WP_VERSION=6.3.3
|
||||
- WP_LOCALE=en_US
|
||||
- WP_TITLE=WooCommerce PayPal Payments
|
||||
- WP_MULTISITE=true
|
||||
|
|
|
@ -14,7 +14,8 @@ tests
|
|||
.phpunit.result.cache
|
||||
babel.config.json
|
||||
node_modules
|
||||
resources
|
||||
modules/*/resources/css
|
||||
modules/*/resources/js/**/*.js
|
||||
*.lock
|
||||
webpack.config.js
|
||||
wp-cli.yml
|
||||
|
|
4
.github/workflows/e2e.yml
vendored
4
.github/workflows/e2e.yml
vendored
|
@ -7,8 +7,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.3', '7.4', '8.2']
|
||||
wc-versions: ['5.9.5', '7.7.2']
|
||||
php-versions: ['7.4', '8.2']
|
||||
wc-versions: ['6.9.4', '7.7.2']
|
||||
|
||||
name: PHP ${{ matrix.php-versions }} WC ${{ matrix.wc-versions }}
|
||||
steps:
|
||||
|
|
5
.github/workflows/package-new.yml
vendored
5
.github/workflows/package-new.yml
vendored
|
@ -27,10 +27,11 @@ jobs:
|
|||
|
||||
create_archive:
|
||||
needs: check_version
|
||||
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@feature/prefixed-archives
|
||||
uses: inpsyde/reusable-workflows/.github/workflows/build-plugin-archive.yml@main
|
||||
with:
|
||||
PHP_VERSION: 7.2
|
||||
PHP_VERSION: 7.4
|
||||
PLUGIN_MAIN_FILE: ./woocommerce-paypal-payments.php
|
||||
PLUGIN_VERSION: ${{ needs.check_version.outputs.version }}
|
||||
PLUGIN_FOLDER_NAME: woocommerce-paypal-payments
|
||||
ARCHIVE_NAME: woocommerce-paypal-payments-${{ needs.check_version.outputs.version }}
|
||||
COMPILE_ASSETS_ARGS: '-vv --env=root'
|
||||
|
|
2
.github/workflows/package.yml
vendored
2
.github/workflows/package.yml
vendored
|
@ -27,7 +27,7 @@ jobs:
|
|||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
with:
|
||||
php-version: 7.2
|
||||
php-version: 7.4
|
||||
|
||||
- name: Fix plugin version input # Add the version number if only suffix entered
|
||||
run: echo "PACKAGE_VERSION=$(sed -nE '/Version:/s/.* ([0-9.]+).*/\1/p' woocommerce-paypal-payments.php)-$PACKAGE_VERSION" >> $GITHUB_ENV
|
||||
|
|
2
.github/workflows/php.yml
vendored
2
.github/workflows/php.yml
vendored
|
@ -7,7 +7,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
php-versions: ['7.2', '7.3', '7.4', '8.0', '8.1', '8.2', '8.3']
|
||||
php-versions: ['7.4', '8.0', '8.1', '8.2', '8.3']
|
||||
|
||||
name: PHP ${{ matrix.php-versions }}
|
||||
steps:
|
||||
|
|
|
@ -4,9 +4,9 @@ PayPal's latest complete payments processing solution. Accept PayPal, Pay Later,
|
|||
|
||||
## Dependencies
|
||||
|
||||
* PHP >= 7.2
|
||||
* WordPress >=5.3
|
||||
* WooCommerce >=4.5
|
||||
* PHP >= 7.4
|
||||
* WordPress >= 6.3
|
||||
* WooCommerce >= 6.9
|
||||
|
||||
## Development
|
||||
|
||||
|
|
|
@ -5,13 +5,8 @@
|
|||
* @package WooCommerce\PayPalCommerce
|
||||
*/
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CachingContainer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeCachingServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\CompositeContainer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\DelegatingContainer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ProxyContainer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\PluginProperties;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return function (
|
||||
|
@ -34,38 +29,15 @@ return function (
|
|||
*/
|
||||
$modules = apply_filters( 'woocommerce_paypal_payments_modules', $modules );
|
||||
|
||||
$providers = array_map(
|
||||
function ( ModuleInterface $module ): ServiceProviderInterface {
|
||||
return $module->setup();
|
||||
},
|
||||
$modules
|
||||
);
|
||||
|
||||
$provider = new CompositeCachingServiceProvider( $providers );
|
||||
$proxy_container = new ProxyContainer();
|
||||
// TODO: caching does not work currently,
|
||||
// may want to consider fixing it later (pass proxy as parent to DelegatingContainer)
|
||||
// for now not fixed since we were using this behavior for long time and fixing it now may break things.
|
||||
$container = new DelegatingContainer( $provider );
|
||||
/**
|
||||
* Skip iterable vs array check.
|
||||
*
|
||||
* @psalm-suppress PossiblyInvalidArgument
|
||||
*/
|
||||
$app_container = new CachingContainer(
|
||||
new CompositeContainer(
|
||||
array_merge(
|
||||
$additional_containers,
|
||||
array( $container )
|
||||
)
|
||||
)
|
||||
);
|
||||
$proxy_container->setInnerContainer( $app_container );
|
||||
// Initialize plugin.
|
||||
$properties = PluginProperties::new( __FILE__ );
|
||||
$bootstrap = Package::new( $properties );
|
||||
|
||||
foreach ( $modules as $module ) {
|
||||
/* @var $module ModuleInterface module */
|
||||
$module->run( $app_container );
|
||||
$bootstrap->addModule( $module );
|
||||
}
|
||||
|
||||
return $app_container;
|
||||
$bootstrap->boot();
|
||||
|
||||
return $bootstrap->container();
|
||||
};
|
||||
|
|
|
@ -1,5 +1,22 @@
|
|||
*** Changelog ***
|
||||
|
||||
= 2.9.1 - xxxx-xx-xx =
|
||||
* Fix - Improve card fields hiding #2574
|
||||
* Fix - Google Pay: Shipping callback not calculating totals correctly on Single Product page #2513
|
||||
* Fix - Fix shipping callback condition in status report #2578
|
||||
* Fix - Can't Disconnect Account #2539
|
||||
* Fix - Google Pay billing data without shipping callback #2525
|
||||
* Fix - Standard payment tab - Google Pay and Apple Pay button - Shape from one location is applied to all until saving changes #2419
|
||||
* Enhancement - Allow to override the list of Pay Later supported countries #2563
|
||||
* Enhancement - Add more feature statuses into system report #2550
|
||||
* Enhancement - Use SVG for APM gateway icons #2509
|
||||
* Enhancement - Add inline notice to inform users about ACDC block Checkout support if the store uses a Classic Checkout setup #2422
|
||||
* Enhancement - Remove leftover console.log #2589
|
||||
* Enhancement - Require PHP 7.4+, WP 6.3+, WC 6.9+ #2556
|
||||
* Enhancement - Modularity module migration #1944
|
||||
* Enhancement - Keep only 5 tags in readme.txt #2562
|
||||
* Enhancement - Select ACDC by default during onboarding for China store locations #2619
|
||||
|
||||
= 2.9.0 - 2024-09-02 =
|
||||
* Fix - Fatal error in Block Editor when using WooCommerce blocks #2534
|
||||
* Fix - Can't pay from block pages when the shipping callback is enabled and no shipping methods defined #2429
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "PayPal Commerce Platform for WooCommerce",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"ext-json": "*",
|
||||
"psr/log": "^1.1",
|
||||
"ralouphie/getallheaders": "^3.0",
|
||||
|
@ -19,6 +19,7 @@
|
|||
"dhii/module-interface": "^0.2 || ^0.3",
|
||||
"container-interop/service-provider": "^0.4.0",
|
||||
"dhii/containers": "^0.1.0-alpha1",
|
||||
"inpsyde/modularity": "^1.7",
|
||||
"woocommerce/woocommerce-sniffs": "^0.1.0",
|
||||
"phpunit/phpunit": "^7.0 | ^8.0 | ^9.0",
|
||||
"brain/monkey": "^2.4",
|
||||
|
@ -87,12 +88,16 @@
|
|||
"packages": [
|
||||
"psr/container",
|
||||
"dhii/containers",
|
||||
"dhii/module-interface"
|
||||
"dhii/module-interface",
|
||||
"inpsyde/modularity"
|
||||
],
|
||||
"delete_vendor_directories": true
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.4"
|
||||
},
|
||||
"allow-plugins": {
|
||||
"inpsyde/composer-assets-compiler": true,
|
||||
"dealerdirect/phpcodesniffer-composer-installer": true,
|
||||
|
|
1104
composer.lock
generated
1104
composer.lock
generated
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,153 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerExceptionInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
class ContainerConfigurator
|
||||
{
|
||||
/**
|
||||
* @var array<string, callable(ContainerInterface $container):mixed>
|
||||
*/
|
||||
private $services = [];
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $factoryIds = [];
|
||||
|
||||
/**
|
||||
* @var array<string, array<callable(mixed $service, ContainerInterface $container):mixed>>
|
||||
*/
|
||||
private $extensions = [];
|
||||
|
||||
/**
|
||||
* @var ContainerInterface[]
|
||||
*/
|
||||
private $containers = [];
|
||||
|
||||
/**
|
||||
* @var null|ContainerInterface
|
||||
*/
|
||||
private $compiledContainer;
|
||||
|
||||
/**
|
||||
* ContainerConfigurator constructor.
|
||||
*
|
||||
* @param ContainerInterface[] $containers
|
||||
*/
|
||||
public function __construct(array $containers = [])
|
||||
{
|
||||
array_map([$this, 'addContainer'], $containers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowing to add child containers.
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*/
|
||||
public function addContainer(ContainerInterface $container): void
|
||||
{
|
||||
$this->containers[] = $container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param callable(ContainerInterface $container):mixed $factory
|
||||
*/
|
||||
public function addFactory(string $id, callable $factory): void
|
||||
{
|
||||
$this->addService($id, $factory);
|
||||
// We're using a hash table to detect later
|
||||
// via isset() if a Service as a Factory.
|
||||
$this->factoryIds[$id] = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param callable(ContainerInterface $container):mixed $service
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addService(string $id, callable $service): void
|
||||
{
|
||||
/*
|
||||
* We are being intentionally permissive here,
|
||||
* allowing a simple workflow for *intentional* overrides
|
||||
* while accepting the (small?) risk of *accidental* overrides
|
||||
* that could be hard to notice and debug.
|
||||
*
|
||||
* Clear a factory flag in case it was a factory.
|
||||
* If needs be, it will get re-added after this function completes.
|
||||
*/
|
||||
unset($this->factoryIds[$id]);
|
||||
|
||||
$this->services[$id] = $service;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasService(string $id): bool
|
||||
{
|
||||
if (array_key_exists($id, $this->services)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->containers as $container) {
|
||||
if ($container->has($id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param callable(mixed $service, ContainerInterface $container):mixed $extender
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addExtension(string $id, callable $extender): void
|
||||
{
|
||||
if (!isset($this->extensions[$id])) {
|
||||
$this->extensions[$id] = [];
|
||||
}
|
||||
|
||||
$this->extensions[$id][] = $extender;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function hasExtension(string $id): bool
|
||||
{
|
||||
return isset($this->extensions[$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a read only version of this Container.
|
||||
*
|
||||
* @return ContainerInterface
|
||||
*/
|
||||
public function createReadOnlyContainer(): ContainerInterface
|
||||
{
|
||||
if (!$this->compiledContainer) {
|
||||
$this->compiledContainer = new ReadOnlyContainer(
|
||||
$this->services,
|
||||
$this->factoryIds,
|
||||
$this->extensions,
|
||||
$this->containers
|
||||
);
|
||||
}
|
||||
|
||||
return $this->compiledContainer;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,102 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Package;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerExceptionInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
class PackageProxyContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var Package
|
||||
*/
|
||||
private $package;
|
||||
|
||||
/**
|
||||
* @var ContainerInterface|null
|
||||
*/
|
||||
private $container;
|
||||
|
||||
/**
|
||||
* @param Package $package
|
||||
*/
|
||||
public function __construct(Package $package)
|
||||
{
|
||||
$this->package = $package;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return mixed
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function get(string $id)
|
||||
{
|
||||
$this->assertPackageBooted($id);
|
||||
|
||||
return $this->container->get($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return $this->tryContainer() && $this->container->has($id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Exception
|
||||
* @psalm-assert-if-true ContainerInterface $this->container
|
||||
*/
|
||||
private function tryContainer(): bool
|
||||
{
|
||||
if ($this->container) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** TODO: We need a better way to deal with status checking besides equality */
|
||||
if (
|
||||
$this->package->statusIs(Package::STATUS_READY)
|
||||
|| $this->package->statusIs(Package::STATUS_BOOTED)
|
||||
) {
|
||||
$this->container = $this->package->container();
|
||||
}
|
||||
|
||||
return (bool)$this->container;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @return void
|
||||
*
|
||||
* @throws \Exception
|
||||
*
|
||||
* @psalm-assert ContainerInterface $this->container
|
||||
*/
|
||||
private function assertPackageBooted(string $id): void
|
||||
{
|
||||
if ($this->tryContainer()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$name = $this->package->name();
|
||||
$status = $this->package->statusIs(Package::STATUS_FAILED)
|
||||
? 'is errored'
|
||||
: 'is not ready yet';
|
||||
|
||||
throw new class ("Error retrieving service {$id} because package {$name} {$status}.")
|
||||
extends \Exception
|
||||
implements ContainerExceptionInterface {
|
||||
};
|
||||
}
|
||||
}
|
138
lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php
Normal file
138
lib/packages/Inpsyde/Modularity/Container/ReadOnlyContainer.php
Normal file
|
@ -0,0 +1,138 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\NotFoundExceptionInterface;
|
||||
|
||||
class ReadOnlyContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
|
||||
*/
|
||||
private $services;
|
||||
|
||||
/**
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $factoryIds;
|
||||
|
||||
/**
|
||||
* @var array<string, array<callable(mixed, ContainerInterface $container):mixed>>
|
||||
*/
|
||||
private $extensions;
|
||||
|
||||
/**
|
||||
* Resolved factories.
|
||||
*
|
||||
* @var array<string, mixed>
|
||||
*/
|
||||
private $resolvedServices = [];
|
||||
|
||||
/**
|
||||
* @var ContainerInterface[]
|
||||
*/
|
||||
private $containers;
|
||||
|
||||
/**
|
||||
* ReadOnlyContainer constructor.
|
||||
*
|
||||
* @param array<string, callable(ContainerInterface $container):mixed> $services
|
||||
* @param array<string, bool> $factoryIds
|
||||
* @param array<string, array<callable(mixed, ContainerInterface $container):mixed>> $extensions
|
||||
* @param ContainerInterface[] $containers
|
||||
*/
|
||||
public function __construct(
|
||||
array $services,
|
||||
array $factoryIds,
|
||||
array $extensions,
|
||||
array $containers
|
||||
) {
|
||||
$this->services = $services;
|
||||
$this->factoryIds = $factoryIds;
|
||||
$this->extensions = $extensions;
|
||||
$this->containers = $containers;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $id)
|
||||
{
|
||||
if (array_key_exists($id, $this->resolvedServices)) {
|
||||
return $this->resolvedServices[$id];
|
||||
}
|
||||
|
||||
if (array_key_exists($id, $this->services)) {
|
||||
$service = $this->services[$id]($this);
|
||||
$resolved = $this->resolveExtensions($id, $service);
|
||||
|
||||
if (!isset($this->factoryIds[$id])) {
|
||||
$this->resolvedServices[$id] = $resolved;
|
||||
unset($this->services[$id]);
|
||||
}
|
||||
|
||||
return $resolved;
|
||||
}
|
||||
|
||||
foreach ($this->containers as $container) {
|
||||
if ($container->has($id)) {
|
||||
$service = $container->get($id);
|
||||
|
||||
return $this->resolveExtensions($id, $service);
|
||||
}
|
||||
}
|
||||
|
||||
throw new class ("Service with ID {$id} not found.")
|
||||
extends \Exception
|
||||
implements NotFoundExceptionInterface {
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $id): bool
|
||||
{
|
||||
if (array_key_exists($id, $this->services)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (array_key_exists($id, $this->resolvedServices)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ($this->containers as $container) {
|
||||
if ($container->has($id)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $id
|
||||
* @param mixed $service
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
private function resolveExtensions(string $id, $service)
|
||||
{
|
||||
if (!isset($this->extensions[$id])) {
|
||||
return $service;
|
||||
}
|
||||
|
||||
foreach ($this->extensions[$id] as $extender) {
|
||||
$service = $extender($service, $this);
|
||||
}
|
||||
|
||||
return $service;
|
||||
}
|
||||
}
|
21
lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php
Normal file
21
lib/packages/Inpsyde/Modularity/Module/ExecutableModule.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
interface ExecutableModule extends Module
|
||||
{
|
||||
|
||||
/**
|
||||
* Perform actions with objects retrieved from the container. Usually, adding WordPress hooks.
|
||||
* Return true to signal a success, false to signal a failure.
|
||||
*
|
||||
* @param ContainerInterface $container
|
||||
*
|
||||
* @return bool true when successfully booted, otherwise false.
|
||||
*/
|
||||
public function run(ContainerInterface $container): bool;
|
||||
}
|
24
lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php
Normal file
24
lib/packages/Inpsyde/Modularity/Module/ExtendingModule.php
Normal file
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
interface ExtendingModule extends Module
|
||||
{
|
||||
|
||||
/**
|
||||
* Return application services' extensions.
|
||||
*
|
||||
* Array keys will be services' IDs in the container, array values are callback that
|
||||
* accepts as parameters the original service and a PSR-11 container and return an instance of
|
||||
* the extended service.
|
||||
*
|
||||
* It is possible to explicitly extend extensions made by other modules.
|
||||
* That is done by using as ID (array key in the `extensions` method) the target module ID
|
||||
* and the service ID.
|
||||
*
|
||||
* @return array<string, callable(mixed $service, \WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
|
||||
*/
|
||||
public function extensions(): array;
|
||||
}
|
18
lib/packages/Inpsyde/Modularity/Module/FactoryModule.php
Normal file
18
lib/packages/Inpsyde/Modularity/Module/FactoryModule.php
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
interface FactoryModule extends Module
|
||||
{
|
||||
/**
|
||||
* Return application factories.
|
||||
*
|
||||
* Similar to `services`, but object created by given factories are not "cached", but a *new*
|
||||
* instance is returned everytime `get()` is called in the container.
|
||||
*
|
||||
* @return array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
|
||||
*/
|
||||
public function factories(): array;
|
||||
}
|
20
lib/packages/Inpsyde/Modularity/Module/Module.php
Normal file
20
lib/packages/Inpsyde/Modularity/Module/Module.php
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
/**
|
||||
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module
|
||||
*/
|
||||
interface Module
|
||||
{
|
||||
|
||||
/**
|
||||
* Unique identifier for your Module.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string;
|
||||
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
/**
|
||||
* Trait ModuleClassNameIdTrait
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module
|
||||
*/
|
||||
trait ModuleClassNameIdTrait
|
||||
{
|
||||
|
||||
/**
|
||||
* @return string
|
||||
* @see Module::id()
|
||||
*/
|
||||
public function id(): string
|
||||
{
|
||||
return __CLASS__;
|
||||
}
|
||||
}
|
21
lib/packages/Inpsyde/Modularity/Module/ServiceModule.php
Normal file
21
lib/packages/Inpsyde/Modularity/Module/ServiceModule.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module;
|
||||
|
||||
interface ServiceModule extends Module
|
||||
{
|
||||
|
||||
/**
|
||||
* Return application services' factories.
|
||||
*
|
||||
* Array keys will be services' IDs in the container, array values are callback that
|
||||
* accepts a PSR-11 container as parameter and return an instance of the service.
|
||||
* Services are "cached", so the given factory is called once the first time `get()` is called
|
||||
* in the container, and on subsequent `get()` the same instance is returned again and again.
|
||||
*
|
||||
* @return array<string, callable(\WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface $container):mixed>
|
||||
*/
|
||||
public function services(): array;
|
||||
}
|
727
lib/packages/Inpsyde/Modularity/Package.php
Normal file
727
lib/packages/Inpsyde/Modularity/Package.php
Normal file
|
@ -0,0 +1,727 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\ContainerConfigurator;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Container\PackageProxyContainer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\FactoryModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\Module;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties\Properties;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
class Package
|
||||
{
|
||||
/**
|
||||
* All the hooks fired in this class use this prefix.
|
||||
* @var string
|
||||
*/
|
||||
private const HOOK_PREFIX = 'inpsyde.modularity.';
|
||||
|
||||
/**
|
||||
* Identifier to access Properties in Container.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
* $package->boot();
|
||||
*
|
||||
* $container = $package->container();
|
||||
* $container->has(Package::PROPERTIES);
|
||||
* $container->get(Package::PROPERTIES);
|
||||
* </code>
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public const PROPERTIES = 'properties';
|
||||
|
||||
/**
|
||||
* Custom action to be used to add Modules to the package.
|
||||
* It might also be used to access package properties.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
*
|
||||
* add_action(
|
||||
* $package->hookName(Package::ACTION_INIT),
|
||||
* $callback
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
public const ACTION_INIT = 'init';
|
||||
|
||||
/**
|
||||
* Custom action which is triggered after the application
|
||||
* is booted to access container and properties.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
*
|
||||
* add_action(
|
||||
* $package->hookName(Package::ACTION_READY),
|
||||
* $callback
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
public const ACTION_READY = 'ready';
|
||||
|
||||
/**
|
||||
* Custom action which is triggered when a failure happens during the building stage.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
*
|
||||
* add_action(
|
||||
* $package->hookName(Package::ACTION_FAILED_BUILD),
|
||||
* $callback
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
public const ACTION_FAILED_BUILD = 'failed-build';
|
||||
|
||||
/**
|
||||
* Custom action which is triggered when a failure happens during the booting stage.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
*
|
||||
* add_action(
|
||||
* $package->hookName(Package::ACTION_FAILED_BOOT),
|
||||
* $callback
|
||||
* );
|
||||
* </code>
|
||||
*/
|
||||
public const ACTION_FAILED_BOOT = 'failed-boot';
|
||||
|
||||
/**
|
||||
* Custom action which is triggered when a package is connected.
|
||||
*/
|
||||
public const ACTION_PACKAGE_CONNECTED = 'package-connected';
|
||||
|
||||
/**
|
||||
* Custom action which is triggered when a package cannot be connected.
|
||||
*/
|
||||
public const ACTION_FAILED_CONNECTION = 'failed-connection';
|
||||
|
||||
/**
|
||||
* Module states can be used to get information about your module.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
* $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // false
|
||||
* $package->boot(new SomeModule());
|
||||
* $package->moduleIs(SomeModule::class, Package::MODULE_ADDED); // true
|
||||
* </code>
|
||||
*/
|
||||
public const MODULE_ADDED = 'added';
|
||||
public const MODULE_NOT_ADDED = 'not-added';
|
||||
public const MODULE_REGISTERED = 'registered';
|
||||
public const MODULE_REGISTERED_FACTORIES = 'registered-factories';
|
||||
public const MODULE_EXTENDED = 'extended';
|
||||
public const MODULE_EXECUTED = 'executed';
|
||||
public const MODULE_EXECUTION_FAILED = 'executed-failed';
|
||||
public const MODULES_ALL = '*';
|
||||
|
||||
/**
|
||||
* Custom states for the class.
|
||||
*
|
||||
* @example
|
||||
* <code>
|
||||
* $package = Package::new();
|
||||
* $package->statusIs(Package::IDLE); // true
|
||||
* $package->boot();
|
||||
* $package->statusIs(Package::BOOTED); // true
|
||||
* </code>
|
||||
*/
|
||||
public const STATUS_IDLE = 2;
|
||||
public const STATUS_INITIALIZED = 4;
|
||||
public const STATUS_MODULES_ADDED = 5;
|
||||
public const STATUS_READY = 7;
|
||||
public const STATUS_BOOTED = 8;
|
||||
public const STATUS_FAILED = -8;
|
||||
|
||||
/**
|
||||
* Current state of the application.
|
||||
*
|
||||
* @see Package::STATUS_*
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $status = self::STATUS_IDLE;
|
||||
|
||||
/**
|
||||
* Contains the progress of all modules.
|
||||
*
|
||||
* @see Package::moduleProgress()
|
||||
*
|
||||
* @var array<string, list<string>>
|
||||
*/
|
||||
private $moduleStatus = [self::MODULES_ALL => []];
|
||||
|
||||
/**
|
||||
* Hashmap of where keys are names of connected packages, and values are boolean, true
|
||||
* if connection was successful.
|
||||
*
|
||||
* @see Package::connect()
|
||||
*
|
||||
* @var array<string, bool>
|
||||
*/
|
||||
private $connectedPackages = [];
|
||||
|
||||
/**
|
||||
* @var list<ExecutableModule>
|
||||
*/
|
||||
private $executables = [];
|
||||
|
||||
/**
|
||||
* @var Properties
|
||||
*/
|
||||
private $properties;
|
||||
|
||||
/**
|
||||
* @var ContainerConfigurator
|
||||
*/
|
||||
private $containerConfigurator;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $built = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $hasContainer = false;
|
||||
|
||||
/**
|
||||
* @var \Throwable|null
|
||||
*/
|
||||
private $lastError = null;
|
||||
|
||||
/**
|
||||
* @param Properties $properties
|
||||
* @param ContainerInterface[] $containers
|
||||
*
|
||||
* @return Package
|
||||
*/
|
||||
public static function new(Properties $properties, ContainerInterface ...$containers): Package
|
||||
{
|
||||
return new self($properties, ...$containers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Properties $properties
|
||||
* @param ContainerInterface[] $containers
|
||||
*/
|
||||
private function __construct(Properties $properties, ContainerInterface ...$containers)
|
||||
{
|
||||
$this->properties = $properties;
|
||||
|
||||
$this->containerConfigurator = new ContainerConfigurator($containers);
|
||||
$this->containerConfigurator->addService(
|
||||
self::PROPERTIES,
|
||||
static function () use ($properties) {
|
||||
return $properties;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Module $module
|
||||
*
|
||||
* @return static
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function addModule(Module $module): Package
|
||||
{
|
||||
try {
|
||||
$this->assertStatus(self::STATUS_IDLE, sprintf('add module %s', $module->id()));
|
||||
|
||||
$registeredServices = $this->addModuleServices(
|
||||
$module,
|
||||
self::MODULE_REGISTERED
|
||||
);
|
||||
$registeredFactories = $this->addModuleServices(
|
||||
$module,
|
||||
self::MODULE_REGISTERED_FACTORIES
|
||||
);
|
||||
$extended = $this->addModuleServices(
|
||||
$module,
|
||||
self::MODULE_EXTENDED
|
||||
);
|
||||
$isExecutable = $module instanceof ExecutableModule;
|
||||
|
||||
// ExecutableModules are collected and executed on Package::boot()
|
||||
// when the Container is being compiled.
|
||||
if ($isExecutable) {
|
||||
/** @var ExecutableModule $module */
|
||||
$this->executables[] = $module;
|
||||
}
|
||||
|
||||
$added = $registeredServices || $registeredFactories || $extended || $isExecutable;
|
||||
$status = $added ? self::MODULE_ADDED : self::MODULE_NOT_ADDED;
|
||||
$this->moduleProgress($module->id(), $status);
|
||||
} catch (\Throwable $throwable) {
|
||||
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Package $package
|
||||
* @return bool
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function connect(Package $package): bool
|
||||
{
|
||||
try {
|
||||
if ($package === $this) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$packageName = $package->name();
|
||||
$errorData = ['package' => $packageName, 'status' => $this->status];
|
||||
$errorMessage = "Failed connecting package {$packageName}";
|
||||
|
||||
// Don't connect, if already connected
|
||||
if (array_key_exists($packageName, $this->connectedPackages)) {
|
||||
$error = "{$errorMessage} because it was already connected.";
|
||||
do_action(
|
||||
$this->hookName(self::ACTION_FAILED_CONNECTION),
|
||||
$packageName,
|
||||
new \WP_Error('already_connected', $error, $errorData)
|
||||
);
|
||||
|
||||
throw new \Exception($error, 0, $this->lastError);
|
||||
}
|
||||
|
||||
// Don't connect, if already booted or boot failed
|
||||
$failed = $this->statusIs(self::STATUS_FAILED);
|
||||
if ($failed || $this->statusIs(self::STATUS_BOOTED)) {
|
||||
$status = $failed ? 'errored' : 'booted';
|
||||
$error = "{$errorMessage} to a {$status} package.";
|
||||
do_action(
|
||||
$this->hookName(self::ACTION_FAILED_CONNECTION),
|
||||
$packageName,
|
||||
new \WP_Error("no_connect_on_{$status}", $error, $errorData)
|
||||
);
|
||||
|
||||
throw new \Exception($error, 0, $this->lastError);
|
||||
}
|
||||
|
||||
$this->connectedPackages[$packageName] = true;
|
||||
|
||||
// We put connected package's properties in this package's container, so that in modules
|
||||
// "run" method we can access them if we need to.
|
||||
$this->containerConfigurator->addService(
|
||||
sprintf('%s.%s', $package->name(), self::PROPERTIES),
|
||||
static function () use ($package): Properties {
|
||||
return $package->properties();
|
||||
}
|
||||
);
|
||||
|
||||
// If the other package is booted, we can obtain a container, otherwise
|
||||
// we build a proxy container
|
||||
$container = $package->statusIs(self::STATUS_BOOTED)
|
||||
? $package->container()
|
||||
: new PackageProxyContainer($package);
|
||||
|
||||
$this->containerConfigurator->addContainer($container);
|
||||
|
||||
do_action(
|
||||
$this->hookName(self::ACTION_PACKAGE_CONNECTED),
|
||||
$packageName,
|
||||
$this->status,
|
||||
$container instanceof PackageProxyContainer
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch (\Throwable $throwable) {
|
||||
if (isset($packageName)) {
|
||||
$this->connectedPackages[$packageName] = false;
|
||||
}
|
||||
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return static
|
||||
*/
|
||||
public function build(): Package
|
||||
{
|
||||
try {
|
||||
// Don't allow building the application multiple times.
|
||||
$this->assertStatus(self::STATUS_IDLE, 'build package');
|
||||
|
||||
do_action(
|
||||
$this->hookName(self::ACTION_INIT),
|
||||
$this
|
||||
);
|
||||
// Changing the status here ensures we can not call this method again, and also we can not
|
||||
// add new modules, because both this and `addModule()` methods check for idle status.
|
||||
// For backward compatibility, adding new modules via `boot()` will still be possible, even
|
||||
// if deprecated, at the condition that the container was not yet accessed at that point.
|
||||
$this->progress(self::STATUS_INITIALIZED);
|
||||
} catch (\Throwable $throwable) {
|
||||
$this->handleFailure($throwable, self::ACTION_FAILED_BUILD);
|
||||
} finally {
|
||||
$this->built = true;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Module ...$defaultModules Deprecated, use `addModule()` to add default modules.
|
||||
* @return bool
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function boot(Module ...$defaultModules): bool
|
||||
{
|
||||
try {
|
||||
// Call build() if not called yet, and ensure any new module passed here is added
|
||||
// as well, throwing if the container was already built.
|
||||
$this->doBuild(...$defaultModules);
|
||||
|
||||
// Don't allow booting the application multiple times.
|
||||
$this->assertStatus(self::STATUS_MODULES_ADDED, 'boot application', '<');
|
||||
$this->assertStatus(self::STATUS_FAILED, 'boot application', '!=');
|
||||
|
||||
$this->progress(self::STATUS_MODULES_ADDED);
|
||||
|
||||
$this->doExecute();
|
||||
|
||||
$this->progress(self::STATUS_READY);
|
||||
|
||||
do_action(
|
||||
$this->hookName(self::ACTION_READY),
|
||||
$this
|
||||
);
|
||||
} catch (\Throwable $throwable) {
|
||||
$this->handleFailure($throwable, self::ACTION_FAILED_BOOT);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->progress(self::STATUS_BOOTED);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Module ...$defaultModules
|
||||
* @return void
|
||||
*/
|
||||
private function doBuild(Module ...$defaultModules): void
|
||||
{
|
||||
if ($defaultModules) {
|
||||
$this->deprecatedArgument(
|
||||
sprintf(
|
||||
'Passing default modules to %1$s::boot() is deprecated since version 1.7.0.'
|
||||
. ' Please add modules via %1$s::addModule().',
|
||||
__CLASS__
|
||||
),
|
||||
__METHOD__,
|
||||
'1.7.0'
|
||||
);
|
||||
}
|
||||
|
||||
if (!$this->built) {
|
||||
array_map([$this, 'addModule'], $defaultModules);
|
||||
$this->build();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (
|
||||
!$defaultModules
|
||||
|| ($this->status >= self::STATUS_MODULES_ADDED)
|
||||
|| ($this->statusIs(self::STATUS_FAILED))
|
||||
) {
|
||||
// if we don't have default modules, there's nothing to do, and if the status is beyond
|
||||
// "modules added" or is failed, we do nothing as well and let `boot()` throw.
|
||||
return;
|
||||
}
|
||||
|
||||
$backup = $this->status;
|
||||
|
||||
try {
|
||||
// simulate idle status to prevent `addModule()` from throwing
|
||||
// only if we don't have a container yet
|
||||
$this->hasContainer or $this->status = self::STATUS_IDLE;
|
||||
|
||||
foreach ($defaultModules as $defaultModule) {
|
||||
// If a module was added by `build()` or `addModule()` we can skip it, a
|
||||
// deprecation was trigger to make it noticeable without breakage
|
||||
if (!$this->moduleIs($defaultModule->id(), self::MODULE_ADDED)) {
|
||||
$this->addModule($defaultModule);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
$this->status = $backup;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Module $module
|
||||
* @param string $status
|
||||
* @return bool
|
||||
*/
|
||||
private function addModuleServices(Module $module, string $status): bool
|
||||
{
|
||||
$services = null;
|
||||
$addCallback = null;
|
||||
switch ($status) {
|
||||
case self::MODULE_REGISTERED:
|
||||
$services = $module instanceof ServiceModule ? $module->services() : null;
|
||||
$addCallback = [$this->containerConfigurator, 'addService'];
|
||||
break;
|
||||
case self::MODULE_REGISTERED_FACTORIES:
|
||||
$services = $module instanceof FactoryModule ? $module->factories() : null;
|
||||
$addCallback = [$this->containerConfigurator, 'addFactory'];
|
||||
break;
|
||||
case self::MODULE_EXTENDED:
|
||||
$services = $module instanceof ExtendingModule ? $module->extensions() : null;
|
||||
$addCallback = [$this->containerConfigurator, 'addExtension'];
|
||||
break;
|
||||
}
|
||||
|
||||
if (!$services) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$ids = [];
|
||||
array_walk(
|
||||
$services,
|
||||
static function (callable $service, string $id) use ($addCallback, &$ids) {
|
||||
/** @var callable(string, callable) $addCallback */
|
||||
$addCallback($id, $service);
|
||||
/** @var list<string> $ids */
|
||||
$ids[] = $id;
|
||||
}
|
||||
);
|
||||
/** @var list<string> $ids */
|
||||
$this->moduleProgress($module->id(), $status, $ids);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
*
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function doExecute(): void
|
||||
{
|
||||
foreach ($this->executables as $executable) {
|
||||
$success = $executable->run($this->container());
|
||||
$this->moduleProgress(
|
||||
$executable->id(),
|
||||
$success
|
||||
? self::MODULE_EXECUTED
|
||||
: self::MODULE_EXECUTION_FAILED
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $moduleId
|
||||
* @param string $status
|
||||
* @param list<string>|null $serviceIds
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function moduleProgress(string $moduleId, string $status, ?array $serviceIds = null)
|
||||
{
|
||||
isset($this->moduleStatus[$status]) or $this->moduleStatus[$status] = [];
|
||||
$this->moduleStatus[$status][] = $moduleId;
|
||||
|
||||
if (!$serviceIds || !$this->properties->isDebug()) {
|
||||
$this->moduleStatus[self::MODULES_ALL][] = "{$moduleId} {$status}";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$description = sprintf('%s %s (%s)', $moduleId, $status, implode(', ', $serviceIds));
|
||||
$this->moduleStatus[self::MODULES_ALL][] = $description;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
public function modulesStatus(): array
|
||||
{
|
||||
return $this->moduleStatus;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
public function connectedPackages(): array
|
||||
{
|
||||
return $this->connectedPackages;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageName
|
||||
* @return bool
|
||||
*/
|
||||
public function isPackageConnected(string $packageName): bool
|
||||
{
|
||||
return $this->connectedPackages[$packageName] ?? false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $moduleId
|
||||
* @param string $status
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function moduleIs(string $moduleId, string $status): bool
|
||||
{
|
||||
return in_array($moduleId, $this->moduleStatus[$status] ?? [], true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the filter name to be used to extend modules of the plugin.
|
||||
*
|
||||
* If the plugin is single file `my-plugin.php` in plugins folder the filter name will be:
|
||||
* `inpsyde.modularity.my-plugin`.
|
||||
*
|
||||
* If the plugin is in a sub-folder e.g. `my-plugin/index.php` the filter name will be:
|
||||
* `inpsyde.modularity.my-plugin` anyway, so the file name is not relevant.
|
||||
*
|
||||
* @param string $suffix
|
||||
*
|
||||
* @return string
|
||||
* @see Package::name()
|
||||
*
|
||||
*/
|
||||
public function hookName(string $suffix = ''): string
|
||||
{
|
||||
$filter = self::HOOK_PREFIX . $this->properties->baseName();
|
||||
|
||||
if ($suffix) {
|
||||
$filter .= '.' . $suffix;
|
||||
}
|
||||
|
||||
return $filter;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Properties
|
||||
*/
|
||||
public function properties(): Properties
|
||||
{
|
||||
return $this->properties;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ContainerInterface
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function container(): ContainerInterface
|
||||
{
|
||||
$this->assertStatus(self::STATUS_INITIALIZED, 'obtain the container instance', '>=');
|
||||
$this->hasContainer = true;
|
||||
|
||||
return $this->containerConfigurator->createReadOnlyContainer();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return $this->properties->baseName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*/
|
||||
private function progress(int $status): void
|
||||
{
|
||||
$this->status = $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function statusIs(int $status): bool
|
||||
{
|
||||
return $this->status === $status;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Throwable $throwable
|
||||
* @param Package::ACTION_FAILED_* $action
|
||||
* @return void
|
||||
* @throws \Throwable
|
||||
*/
|
||||
private function handleFailure(\Throwable $throwable, string $action): void
|
||||
{
|
||||
$this->progress(self::STATUS_FAILED);
|
||||
$hook = $this->hookName($action);
|
||||
did_action($hook) or do_action($hook, $throwable);
|
||||
|
||||
if ($this->properties->isDebug()) {
|
||||
throw $throwable;
|
||||
}
|
||||
|
||||
$this->lastError = $throwable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $status
|
||||
* @param string $action
|
||||
* @param string $operator
|
||||
*
|
||||
* @throws \Exception
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
*/
|
||||
private function assertStatus(int $status, string $action, string $operator = '=='): void
|
||||
{
|
||||
if (!version_compare((string) $this->status, (string) $status, $operator)) {
|
||||
throw new \Exception(
|
||||
sprintf("Can't %s at this point of application.", $action),
|
||||
0,
|
||||
$this->lastError
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Similar to WP's `_deprecated_argument()`, but executes regardless of WP_DEBUG and without
|
||||
* translated message (so without attempting loading translation files).
|
||||
*
|
||||
* @param string $message
|
||||
* @param string $function
|
||||
* @param string $version
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function deprecatedArgument(string $message, string $function, string $version): void
|
||||
{
|
||||
do_action('deprecated_argument_run', $function, $message, $version);
|
||||
|
||||
if (apply_filters('deprecated_argument_trigger_error', true)) {
|
||||
trigger_error($message, \E_USER_DEPRECATED);
|
||||
}
|
||||
}
|
||||
}
|
217
lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php
Normal file
217
lib/packages/Inpsyde/Modularity/Properties/BaseProperties.php
Normal file
|
@ -0,0 +1,217 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
|
||||
|
||||
class BaseProperties implements Properties
|
||||
{
|
||||
/**
|
||||
* @var null|bool
|
||||
*/
|
||||
protected $isDebug = null;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $baseName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $basePath;
|
||||
|
||||
/**
|
||||
* @var string|null
|
||||
*/
|
||||
protected $baseUrl;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $properties;
|
||||
|
||||
/**
|
||||
* @param string $baseName
|
||||
* @param string $basePath
|
||||
* @param string|null $baseUrl
|
||||
* @param array $properties
|
||||
*/
|
||||
protected function __construct(
|
||||
string $baseName,
|
||||
string $basePath,
|
||||
string $baseUrl = null,
|
||||
array $properties = []
|
||||
) {
|
||||
$baseName = $this->sanitizeBaseName($baseName);
|
||||
$basePath = (string) trailingslashit($basePath);
|
||||
if ($baseUrl) {
|
||||
$baseUrl = (string) trailingslashit($baseUrl);
|
||||
}
|
||||
|
||||
$this->baseName = $baseName;
|
||||
$this->basePath = $basePath;
|
||||
$this->baseUrl = $baseUrl;
|
||||
$this->properties = array_replace(Properties::DEFAULT_PROPERTIES, $properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $name
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function sanitizeBaseName(string $name): string
|
||||
{
|
||||
substr_count($name, '/') and $name = dirname($name);
|
||||
|
||||
return strtolower(pathinfo($name, PATHINFO_FILENAME));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function baseName(): string
|
||||
{
|
||||
return $this->baseName;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function basePath(): string
|
||||
{
|
||||
return $this->basePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function baseUrl(): ?string
|
||||
{
|
||||
return $this->baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function author(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_AUTHOR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function authorUri(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_AUTHOR_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_DESCRIPTION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function textDomain(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_TEXTDOMAIN);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function domainPath(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_DOMAIN_PATH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_NAME);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function uri(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_URI);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function version(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_VERSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function requiresWp(): ?string
|
||||
{
|
||||
$value = $this->get(self::PROP_REQUIRES_WP);
|
||||
|
||||
return $value && is_string($value) ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function requiresPhp(): ?string
|
||||
{
|
||||
$value = $this->get(self::PROP_REQUIRES_PHP);
|
||||
|
||||
return $value && is_string($value) ? $value : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function tags(): array
|
||||
{
|
||||
return (array) $this->get(self::PROP_TAGS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param null $default
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null)
|
||||
{
|
||||
return $this->properties[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool
|
||||
{
|
||||
return isset($this->properties[$key]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
* @see Properties::isDebug()
|
||||
*/
|
||||
public function isDebug(): bool
|
||||
{
|
||||
if ($this->isDebug === null) {
|
||||
$this->isDebug = defined('WP_DEBUG') && WP_DEBUG;
|
||||
}
|
||||
|
||||
return $this->isDebug;
|
||||
}
|
||||
}
|
209
lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php
Normal file
209
lib/packages/Inpsyde/Modularity/Properties/LibraryProperties.php
Normal file
|
@ -0,0 +1,209 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
|
||||
|
||||
/**
|
||||
* Class LibraryProperties
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
|
||||
*/
|
||||
class LibraryProperties extends BaseProperties
|
||||
{
|
||||
/**
|
||||
* Allowed configuration in composer.json "extra.modularity".
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public const EXTRA_KEYS = [
|
||||
self::PROP_DOMAIN_PATH,
|
||||
self::PROP_NAME,
|
||||
self::PROP_TEXTDOMAIN,
|
||||
self::PROP_URI,
|
||||
self::PROP_VERSION,
|
||||
self::PROP_REQUIRES_WP,
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $composerJsonFile
|
||||
* @param string|null $baseUrl
|
||||
*
|
||||
* @return LibraryProperties
|
||||
*
|
||||
* @throws \Exception
|
||||
* @psalm-suppress MixedArrayAccess
|
||||
*/
|
||||
public static function new(string $composerJsonFile, ?string $baseUrl = null): LibraryProperties
|
||||
{
|
||||
if (!\is_file($composerJsonFile) || !\is_readable($composerJsonFile)) {
|
||||
throw new \Exception("File {$composerJsonFile} does not exist or is not readable.");
|
||||
}
|
||||
|
||||
$content = (string) file_get_contents($composerJsonFile);
|
||||
/** @var array $composerJsonData */
|
||||
$composerJsonData = json_decode($content, true);
|
||||
|
||||
$properties = Properties::DEFAULT_PROPERTIES;
|
||||
$properties[self::PROP_DESCRIPTION] = $composerJsonData['description'] ?? '';
|
||||
$properties[self::PROP_TAGS] = $composerJsonData['keywords'] ?? [];
|
||||
|
||||
$authors = $composerJsonData['authors'] ?? [];
|
||||
$names = [];
|
||||
foreach ((array) $authors as $author) {
|
||||
$name = $author['name'] ?? null;
|
||||
if ($name && is_string($name)) {
|
||||
$names[] = $name;
|
||||
}
|
||||
$url = $author['homepage'] ?? null;
|
||||
if ($url && !$properties['authorUri'] && is_string($url)) {
|
||||
$properties[self::PROP_AUTHOR_URI] = $url;
|
||||
}
|
||||
}
|
||||
if (count($names) > 0) {
|
||||
$properties[self::PROP_AUTHOR] = implode(', ', $names);
|
||||
}
|
||||
|
||||
// Custom settings which can be stored in composer.json "extra.modularity"
|
||||
$extra = $composerJsonData['extra']['modularity'] ?? [];
|
||||
foreach (self::EXTRA_KEYS as $key) {
|
||||
$properties[$key] = $extra[$key] ?? '';
|
||||
}
|
||||
|
||||
// PHP requirement in composer.json "require" or "require-dev"
|
||||
$properties[self::PROP_REQUIRES_PHP] = self::extractPhpVersion($composerJsonData);
|
||||
|
||||
// composer.json might have "version" in root
|
||||
$version = $composerJsonData['version'] ?? null;
|
||||
if ($version && is_string($version)) {
|
||||
$properties[self::PROP_VERSION] = $version;
|
||||
}
|
||||
|
||||
[$baseName, $name] = static::buildNames($composerJsonData);
|
||||
$basePath = dirname($composerJsonFile);
|
||||
if (empty($properties[self::PROP_NAME])) {
|
||||
$properties[self::PROP_NAME] = $name;
|
||||
}
|
||||
|
||||
return new self(
|
||||
$baseName,
|
||||
$basePath,
|
||||
$baseUrl,
|
||||
$properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $composerJsonData
|
||||
*
|
||||
* @return array{string, string}
|
||||
*/
|
||||
private static function buildNames(array $composerJsonData): array
|
||||
{
|
||||
$composerName = (string) ($composerJsonData['name'] ?? '');
|
||||
$packageNamePieces = explode('/', $composerName, 2);
|
||||
$basename = implode('-', $packageNamePieces);
|
||||
// "inpsyde/foo-bar-baz" => "Inpsyde Foo Bar Baz"
|
||||
$name = mb_convert_case(
|
||||
str_replace(['-', '_', '.'], ' ', implode(' ', $packageNamePieces)),
|
||||
MB_CASE_TITLE
|
||||
);
|
||||
|
||||
return [$basename, $name];
|
||||
}
|
||||
|
||||
/**
|
||||
* Check PHP version in require, require-dev.
|
||||
*
|
||||
* Attempt to parse requirements to find the _minimum_ accepted version (consistent with WP).
|
||||
* Composer requirements are parsed in a way that, for example:
|
||||
* `>=7.2` returns `7.2`
|
||||
* `^7.3` returns `7.3`
|
||||
* `5.6 || >= 7.1` returns `5.6`
|
||||
* `>= 7.1 < 8` returns `7.1`
|
||||
*
|
||||
* @param array $composerData
|
||||
* @param string $key
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private static function extractPhpVersion(array $composerData, string $key = 'require'): ?string
|
||||
{
|
||||
$nextKey = ($key === 'require')
|
||||
? 'require-dev'
|
||||
: null;
|
||||
$base = (array) ($composerData[$key] ?? []);
|
||||
$requirement = $base['php'] ?? null;
|
||||
$version = ($requirement && is_string($requirement))
|
||||
? trim($requirement)
|
||||
: null;
|
||||
if (!$version) {
|
||||
return $nextKey
|
||||
? static::extractPhpVersion($composerData, $nextKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
static $matcher;
|
||||
$matcher or $matcher = static function (string $version): ?string {
|
||||
$version = trim($version);
|
||||
if (!$version) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// versions range like `>= 7.2.4 < 8`
|
||||
if (preg_match('{>=?([\s0-9\.]+)<}', $version, $matches)) {
|
||||
return trim($matches[1], " \t\n\r\0\x0B.");
|
||||
}
|
||||
|
||||
// aliases like `dev-src#abcde as 7.4`
|
||||
if (preg_match('{as\s*([\s0-9\.]+)}', $version, $matches)) {
|
||||
return trim($matches[1], " \t\n\r\0\x0B.");
|
||||
}
|
||||
|
||||
// Basic requirements like 7.2, >=7.2, ^7.2, ~7.2
|
||||
if (preg_match('{^(?:[>=\s~\^]+)?([0-9\.]+)}', $version, $matches)) {
|
||||
return trim($matches[1], " \t\n\r\0\x0B.");
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
// support for simpler requirements like `7.3`, `>=7.4` or alternative like `5.6 || >=7`
|
||||
|
||||
$alternatives = explode('||', $version);
|
||||
$found = null;
|
||||
foreach ($alternatives as $alternative) {
|
||||
/** @var callable(string):?string $matcher */
|
||||
$itemFound = $matcher($alternative);
|
||||
if ($itemFound && (!$found || version_compare($itemFound, $found, '<'))) {
|
||||
$found = $itemFound;
|
||||
}
|
||||
}
|
||||
|
||||
if ($found) {
|
||||
return $found;
|
||||
}
|
||||
|
||||
return $nextKey
|
||||
? static::extractPhpVersion($composerData, $nextKey)
|
||||
: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $url
|
||||
*
|
||||
* @return static
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function withBaseUrl(string $url): LibraryProperties
|
||||
{
|
||||
if ($this->baseUrl !== null) {
|
||||
throw new \Exception(sprintf('%s::$baseUrl property is not overridable.', __CLASS__));
|
||||
}
|
||||
|
||||
$this->baseUrl = trailingslashit($url);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
176
lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php
Normal file
176
lib/packages/Inpsyde/Modularity/Properties/PluginProperties.php
Normal file
|
@ -0,0 +1,176 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
|
||||
|
||||
/**
|
||||
* Class PluginProperties
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
|
||||
*
|
||||
* @psalm-suppress PossiblyFalseArgument, InvalidArgument
|
||||
*/
|
||||
class PluginProperties extends BaseProperties
|
||||
{
|
||||
/**
|
||||
* Custom properties for Plugins.
|
||||
*/
|
||||
public const PROP_NETWORK = 'network';
|
||||
/**
|
||||
* Available methods of Properties::__call()
|
||||
* from plugin headers.
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/functions/get_plugin_data/
|
||||
*/
|
||||
protected const HEADERS = [
|
||||
self::PROP_AUTHOR => 'Author',
|
||||
self::PROP_AUTHOR_URI => 'AuthorURI',
|
||||
self::PROP_DESCRIPTION => 'Description',
|
||||
self::PROP_DOMAIN_PATH => 'DomainPath',
|
||||
self::PROP_NAME => 'Name',
|
||||
self::PROP_TEXTDOMAIN => 'TextDomain',
|
||||
self::PROP_URI => 'PluginURI',
|
||||
self::PROP_VERSION => 'Version',
|
||||
self::PROP_REQUIRES_WP => 'RequiresWP',
|
||||
self::PROP_REQUIRES_PHP => 'RequiresPHP',
|
||||
|
||||
// additional headers
|
||||
self::PROP_NETWORK => 'Network',
|
||||
];
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $pluginMainFile;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $pluginBaseName;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $isMu;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $isActive;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
protected $isNetworkActive;
|
||||
|
||||
/**
|
||||
* @param string $pluginMainFile
|
||||
*
|
||||
* @return PluginProperties
|
||||
*/
|
||||
public static function new(string $pluginMainFile): PluginProperties
|
||||
{
|
||||
return new self($pluginMainFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* PluginProperties constructor.
|
||||
*
|
||||
* @param string $pluginMainFile
|
||||
*/
|
||||
protected function __construct(string $pluginMainFile)
|
||||
{
|
||||
if (!function_exists('get_plugin_data')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
|
||||
$pluginData = get_plugin_data($pluginMainFile);
|
||||
$properties = Properties::DEFAULT_PROPERTIES;
|
||||
|
||||
// Map pluginData to internal structure.
|
||||
foreach (self::HEADERS as $key => $pluginDataKey) {
|
||||
$properties[$key] = $pluginData[$pluginDataKey] ?? '';
|
||||
unset($pluginData[$pluginDataKey]);
|
||||
}
|
||||
$properties = array_merge($properties, $pluginData);
|
||||
|
||||
$this->pluginMainFile = wp_normalize_path($pluginMainFile);
|
||||
|
||||
$this->pluginBaseName = plugin_basename($pluginMainFile);
|
||||
$basePath = plugin_dir_path($pluginMainFile);
|
||||
$baseUrl = plugins_url('/', $pluginMainFile);
|
||||
|
||||
parent::__construct(
|
||||
$this->pluginBaseName,
|
||||
$basePath,
|
||||
$baseUrl,
|
||||
$properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function pluginMainFile(): string
|
||||
{
|
||||
return $this->pluginMainFile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*
|
||||
* @psalm-suppress PossiblyFalseArgument
|
||||
*/
|
||||
public function network(): bool
|
||||
{
|
||||
return (bool) $this->get(self::PROP_NETWORK, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isActive(): bool
|
||||
{
|
||||
if ($this->isActive === null) {
|
||||
if (!function_exists('is_plugin_active')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$this->isActive = is_plugin_active($this->pluginBaseName);
|
||||
}
|
||||
|
||||
return $this->isActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isNetworkActive(): bool
|
||||
{
|
||||
if ($this->isNetworkActive === null) {
|
||||
if (!function_exists('is_plugin_active_for_network')) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$this->isNetworkActive = is_plugin_active_for_network($this->pluginBaseName);
|
||||
}
|
||||
|
||||
return $this->isNetworkActive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isMuPlugin(): bool
|
||||
{
|
||||
if ($this->isMu === null) {
|
||||
/**
|
||||
* @psalm-suppress UndefinedConstant
|
||||
* @psalm-suppress MixedArgument
|
||||
*/
|
||||
$muPluginDir = wp_normalize_path(WPMU_PLUGIN_DIR);
|
||||
$this->isMu = strpos($this->pluginMainFile, $muPluginDir) === 0;
|
||||
}
|
||||
|
||||
return $this->isMu;
|
||||
}
|
||||
}
|
139
lib/packages/Inpsyde/Modularity/Properties/Properties.php
Normal file
139
lib/packages/Inpsyde/Modularity/Properties/Properties.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
|
||||
|
||||
interface Properties
|
||||
{
|
||||
public const PROP_AUTHOR = 'author';
|
||||
public const PROP_AUTHOR_URI = 'authorUri';
|
||||
public const PROP_DESCRIPTION = 'description';
|
||||
public const PROP_DOMAIN_PATH = 'domainPath';
|
||||
public const PROP_NAME = 'name';
|
||||
public const PROP_TEXTDOMAIN = 'textDomain';
|
||||
public const PROP_URI = 'uri';
|
||||
public const PROP_VERSION = 'version';
|
||||
public const PROP_REQUIRES_WP = 'requiresWp';
|
||||
public const PROP_REQUIRES_PHP = 'requiresPhp';
|
||||
public const PROP_TAGS = 'tags';
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public const DEFAULT_PROPERTIES = [
|
||||
self::PROP_AUTHOR => '',
|
||||
self::PROP_AUTHOR_URI => '',
|
||||
self::PROP_DESCRIPTION => '',
|
||||
self::PROP_DOMAIN_PATH => '',
|
||||
self::PROP_NAME => '',
|
||||
self::PROP_TEXTDOMAIN => '',
|
||||
self::PROP_URI => '',
|
||||
self::PROP_VERSION => '',
|
||||
self::PROP_REQUIRES_WP => null,
|
||||
self::PROP_REQUIRES_PHP => null,
|
||||
self::PROP_TAGS => [],
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @param null $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $key, $default = null);
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has(string $key): bool;
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isDebug(): bool;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function baseName(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function basePath(): string;
|
||||
|
||||
/**
|
||||
* @return string|null
|
||||
*/
|
||||
public function baseUrl(): ?string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function author(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function authorUri(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function description(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function textDomain(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function domainPath(): string;
|
||||
|
||||
/**
|
||||
* The name of the plugin, theme or library.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function name(): string;
|
||||
|
||||
/**
|
||||
* The home page of the plugin, theme or library.
|
||||
* @return string
|
||||
*/
|
||||
public function uri(): string;
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function version(): string;
|
||||
|
||||
/**
|
||||
* Optional. Specify the minimum required WordPress version.
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
public function requiresWp(): ?string;
|
||||
|
||||
/**
|
||||
* Optional. Specify the minimum required PHP version.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function requiresPhp(): ?string;
|
||||
|
||||
/**
|
||||
* Optional. Currently, only available for Theme and Library.
|
||||
* Plugins do not have support for "tags"/"keywords" in header.
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_theme/#properties
|
||||
* @link https://getcomposer.org/doc/04-schema.md#keywords
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function tags(): array;
|
||||
}
|
131
lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php
Normal file
131
lib/packages/Inpsyde/Modularity/Properties/ThemeProperties.php
Normal file
|
@ -0,0 +1,131 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties;
|
||||
|
||||
/**
|
||||
* Class ThemeProperties
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Properties
|
||||
*
|
||||
* @psalm-suppress PossiblyFalseArgument, InvalidArgument
|
||||
*/
|
||||
class ThemeProperties extends BaseProperties
|
||||
{
|
||||
/**
|
||||
* Additional properties specific for themes.
|
||||
*/
|
||||
public const PROP_STATUS = 'status';
|
||||
public const PROP_TEMPLATE = 'template';
|
||||
/**
|
||||
* Available methods of Properties::__call()
|
||||
* from theme headers.
|
||||
*
|
||||
* @link https://developer.wordpress.org/reference/classes/wp_theme/
|
||||
*/
|
||||
protected const HEADERS = [
|
||||
self::PROP_AUTHOR => 'Author',
|
||||
self::PROP_AUTHOR_URI => 'AuthorURI',
|
||||
self::PROP_DESCRIPTION => 'Description',
|
||||
self::PROP_DOMAIN_PATH => 'DomainPath',
|
||||
self::PROP_NAME => 'Name',
|
||||
self::PROP_TEXTDOMAIN => 'TextDomain',
|
||||
self::PROP_URI => 'ThemeURI',
|
||||
self::PROP_VERSION => 'Version',
|
||||
self::PROP_REQUIRES_WP => 'RequiresWP',
|
||||
self::PROP_REQUIRES_PHP => 'RequiresPHP',
|
||||
|
||||
// additional headers
|
||||
self::PROP_STATUS => 'Status',
|
||||
self::PROP_TAGS => 'Tags',
|
||||
self::PROP_TEMPLATE => 'Template',
|
||||
];
|
||||
|
||||
/**
|
||||
* @param string $themeDirectory
|
||||
*
|
||||
* @return ThemeProperties
|
||||
*/
|
||||
public static function new(string $themeDirectory): ThemeProperties
|
||||
{
|
||||
return new self($themeDirectory);
|
||||
}
|
||||
|
||||
/**
|
||||
* ThemeProperties constructor.
|
||||
*
|
||||
* @param string $themeDirectory
|
||||
*/
|
||||
protected function __construct(string $themeDirectory)
|
||||
{
|
||||
if (!function_exists('wp_get_theme')) {
|
||||
require_once ABSPATH . 'wp-includes/theme.php';
|
||||
}
|
||||
|
||||
$theme = wp_get_theme($themeDirectory);
|
||||
$properties = Properties::DEFAULT_PROPERTIES;
|
||||
|
||||
foreach (self::HEADERS as $key => $themeKey) {
|
||||
/** @psalm-suppress DocblockTypeContradiction */
|
||||
$properties[$key] = $theme->get($themeKey) ?? '';
|
||||
}
|
||||
|
||||
$baseName = $theme->get_stylesheet();
|
||||
$basePath = $theme->get_stylesheet_directory();
|
||||
$baseUrl = (string) trailingslashit($theme->get_stylesheet_directory_uri());
|
||||
|
||||
parent::__construct(
|
||||
$baseName,
|
||||
$basePath,
|
||||
$baseUrl,
|
||||
$properties
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If the theme is published.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function status(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_STATUS);
|
||||
}
|
||||
|
||||
public function template(): string
|
||||
{
|
||||
return (string) $this->get(self::PROP_TEMPLATE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isChildTheme(): bool
|
||||
{
|
||||
return (bool) $this->template();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function isCurrentTheme(): bool
|
||||
{
|
||||
return get_stylesheet() === $this->baseName();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return ThemeProperties|null
|
||||
*/
|
||||
public function parentThemeProperties(): ?ThemeProperties
|
||||
{
|
||||
$template = $this->template();
|
||||
if (!$template) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$parent = wp_get_theme($template, get_theme_root($template));
|
||||
|
||||
return static::new($parent->get_template_directory());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
|
||||
*/
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Psr\Container;
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
<?php
|
||||
/**
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Psr\Container;
|
||||
|
||||
|
@ -20,7 +19,7 @@ interface ContainerInterface
|
|||
*
|
||||
* @return mixed Entry.
|
||||
*/
|
||||
public function get($id);
|
||||
public function get(string $id);
|
||||
|
||||
/**
|
||||
* Returns true if the container can return an entry for the given identifier.
|
||||
|
@ -33,5 +32,5 @@ interface ContainerInterface
|
|||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function has($id);
|
||||
public function has(string $id);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,4 @@
|
|||
<?php
|
||||
/**
|
||||
* @license http://www.opensource.org/licenses/mit-license.php MIT (see the LICENSE file)
|
||||
*/
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Vendor\Psr\Container;
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Admin notices module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\AdminNotices;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): AdminNotices {
|
||||
return new AdminNotices();
|
||||
};
|
||||
|
|
|
@ -10,9 +10,10 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\AdminNotices;
|
||||
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Repository\Repository;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Endpoint\MuteMessageEndpoint;
|
||||
use WooCommerce\PayPalCommerce\AdminNotices\Renderer\RendererInterface;
|
||||
|
@ -21,22 +22,27 @@ use WooCommerce\PayPalCommerce\AdminNotices\Entity\PersistentMessage;
|
|||
/**
|
||||
* Class AdminNotices
|
||||
*/
|
||||
class AdminNotices implements ModuleInterface {
|
||||
class AdminNotices implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
$renderer = $c->get( 'admin-notices.renderer' );
|
||||
assert( $renderer instanceof RendererInterface );
|
||||
|
||||
|
@ -103,13 +109,7 @@ class AdminNotices implements ModuleInterface {
|
|||
PersistentMessage::clear_all();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "API client module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return function (): ModuleInterface {
|
||||
return function (): ApiModule {
|
||||
return new ApiModule();
|
||||
};
|
||||
|
|
|
@ -1642,6 +1642,21 @@ return array(
|
|||
'SE',
|
||||
);
|
||||
},
|
||||
|
||||
'api.paylater-countries' => static function ( ContainerInterface $container ) : array {
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_supported_paylater_countries',
|
||||
array(
|
||||
'US',
|
||||
'DE',
|
||||
'GB',
|
||||
'FR',
|
||||
'AU',
|
||||
'IT',
|
||||
'ES',
|
||||
)
|
||||
);
|
||||
},
|
||||
'api.order-helper' => static function( ContainerInterface $container ): OrderHelper {
|
||||
return new OrderHelper();
|
||||
},
|
||||
|
|
|
@ -14,31 +14,37 @@ use WooCommerce\PayPalCommerce\ApiClient\Authentication\UserIdToken;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Helper\Cache;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\FailureRegistry;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
|
||||
/**
|
||||
* Class ApiModule
|
||||
*/
|
||||
class ApiModule implements ModuleInterface {
|
||||
class ApiModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
add_action(
|
||||
'woocommerce_after_calculate_totals',
|
||||
function ( \WC_Cart $cart ) {
|
||||
|
@ -96,13 +102,7 @@ class ApiModule implements ModuleInterface {
|
|||
10,
|
||||
2
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 14 KiB |
83
modules/ppcp-applepay/assets/images/applepay.svg
Normal file
83
modules/ppcp-applepay/assets/images/applepay.svg
Normal file
|
@ -0,0 +1,83 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="78.102px" height="50px" viewBox="-5 -5 175.52107 115.9651" enable-background="new 0 0 165.52107 105.9651"
|
||||
xml:space="preserve">
|
||||
<g>
|
||||
<path id="XMLID_4_" d="M150.69807,0H14.82318c-0.5659,0-1.1328,0-1.69769,0.0033c-0.47751,0.0034-0.95391,0.0087-1.43031,0.0217
|
||||
c-1.039,0.0281-2.0869,0.0894-3.1129,0.2738c-1.0424,0.1876-2.0124,0.4936-2.9587,0.9754
|
||||
c-0.9303,0.4731-1.782,1.0919-2.52009,1.8303c-0.73841,0.7384-1.35721,1.5887-1.83021,2.52
|
||||
c-0.4819,0.9463-0.7881,1.9166-0.9744,2.9598c-0.18539,1.0263-0.2471,2.074-0.2751,3.1119
|
||||
c-0.0128,0.4764-0.01829,0.9528-0.0214,1.4291c-0.0033,0.5661-0.0022,1.1318-0.0022,1.6989V91.142
|
||||
c0,0.5671-0.0011,1.13181,0.0022,1.69901c0.00311,0.4763,0.0086,0.9527,0.0214,1.4291
|
||||
c0.028,1.03699,0.08971,2.08469,0.2751,3.11069c0.1863,1.0436,0.4925,2.0135,0.9744,2.9599
|
||||
c0.473,0.9313,1.0918,1.7827,1.83021,2.52c0.73809,0.7396,1.58979,1.3583,2.52009,1.8302
|
||||
c0.9463,0.4831,1.9163,0.7892,2.9587,0.9767c1.026,0.1832,2.0739,0.2456,3.1129,0.2737c0.4764,0.0108,0.9528,0.0172,1.43031,0.0194
|
||||
c0.56489,0.0044,1.13179,0.0044,1.69769,0.0044h135.87489c0.5649,0,1.13181,0,1.69659-0.0044
|
||||
c0.47641-0.0022,0.95282-0.0086,1.4314-0.0194c1.0368-0.0281,2.0845-0.0905,3.11301-0.2737
|
||||
c1.041-0.1875,2.0112-0.4936,2.9576-0.9767c0.9313-0.4719,1.7805-1.0906,2.52011-1.8302c0.7372-0.7373,1.35599-1.5887,1.8302-2.52
|
||||
c0.48299-0.9464,0.78889-1.9163,0.97429-2.9599c0.1855-1.026,0.2457-2.0737,0.2738-3.11069
|
||||
c0.013-0.4764,0.01941-0.9528,0.02161-1.4291c0.00439-0.5672,0.00439-1.1319,0.00439-1.69901V14.8242
|
||||
c0-0.5671,0-1.1328-0.00439-1.6989c-0.0022-0.4763-0.00861-0.9527-0.02161-1.4291c-0.02811-1.0379-0.0883-2.0856-0.2738-3.1119
|
||||
c-0.18539-1.0432-0.4913-2.0135-0.97429-2.9598c-0.47421-0.9313-1.093-1.7816-1.8302-2.52
|
||||
c-0.73961-0.7384-1.58881-1.3572-2.52011-1.8303c-0.9464-0.4818-1.9166-0.7878-2.9576-0.9754
|
||||
c-1.0285-0.1844-2.0762-0.2457-3.11301-0.2738c-0.47858-0.013-0.95499-0.0183-1.4314-0.0217C151.82988,0,151.26297,0,150.69807,0
|
||||
L150.69807,0z"/>
|
||||
<path id="XMLID_3_" fill="#FFFFFF" d="M150.69807,3.532l1.67149,0.0032c0.4528,0.0032,0.90561,0.0081,1.36092,0.0205
|
||||
c0.79201,0.0214,1.71849,0.0643,2.58209,0.2191c0.7507,0.1352,1.38029,0.3408,1.9845,0.6484
|
||||
c0.5965,0.3031,1.14301,0.7003,1.62019,1.1768c0.479,0.4797,0.87671,1.0271,1.18381,1.6302
|
||||
c0.30589,0.5995,0.51019,1.2261,0.64459,1.9823c0.1544,0.8542,0.1971,1.7832,0.21881,2.5801
|
||||
c0.01219,0.4498,0.01819,0.8996,0.0204,1.3601c0.00429,0.5569,0.0042,1.1135,0.0042,1.6715V91.142
|
||||
c0,0.558,0.00009,1.1136-0.0043,1.6824c-0.00211,0.4497-0.0081,0.8995-0.0204,1.3501c-0.02161,0.7957-0.0643,1.7242-0.2206,2.5885
|
||||
c-0.13251,0.7458-0.3367,1.3725-0.64429,1.975c-0.30621,0.6016-0.70331,1.1484-1.18022,1.6251
|
||||
c-0.47989,0.48-1.0246,0.876-1.62819,1.1819c-0.5997,0.3061-1.22821,0.51151-1.97151,0.6453
|
||||
c-0.88109,0.157-1.84639,0.2002-2.57339,0.2199c-0.4574,0.0103-0.9126,0.01649-1.37889,0.0187
|
||||
c-0.55571,0.0043-1.1134,0.0042-1.6692,0.0042H14.82318c-0.0074,0-0.0146,0-0.0221,0c-0.5494,0-1.0999,0-1.6593-0.0043
|
||||
c-0.4561-0.00211-0.9112-0.0082-1.3512-0.0182c-0.7436-0.0201-1.7095-0.0632-2.5834-0.2193
|
||||
c-0.74969-0.1348-1.3782-0.3402-1.9858-0.6503c-0.59789-0.3032-1.1422-0.6988-1.6223-1.1797
|
||||
c-0.4764-0.4756-0.8723-1.0207-1.1784-1.6232c-0.3064-0.6019-0.5114-1.2305-0.64619-1.9852
|
||||
c-0.15581-0.8626-0.19861-1.7874-0.22-2.5777c-0.01221-0.4525-0.01731-0.9049-0.02021-1.3547l-0.0022-1.3279l0.0001-0.3506V14.8242
|
||||
l-0.0001-0.3506l0.0021-1.3251c0.003-0.4525,0.0081-0.9049,0.02031-1.357c0.02139-0.7911,0.06419-1.7163,0.22129-2.5861
|
||||
c0.1336-0.7479,0.3385-1.3765,0.6465-1.9814c0.3037-0.5979,0.7003-1.1437,1.17921-1.6225
|
||||
c0.477-0.4772,1.02309-0.8739,1.62479-1.1799c0.6011-0.3061,1.2308-0.5116,1.9805-0.6465c0.8638-0.1552,1.7909-0.198,2.5849-0.2195
|
||||
c0.4526-0.0123,0.9052-0.0172,1.3544-0.0203l1.6771-0.0033H150.69807"/>
|
||||
<g>
|
||||
<g>
|
||||
<path d="M45.1862,35.64053c1.41724-1.77266,2.37897-4.15282,2.12532-6.58506c-2.07464,0.10316-4.60634,1.36871-6.07207,3.14276
|
||||
c-1.31607,1.5192-2.4809,3.99902-2.17723,6.3293C41.39111,38.72954,43.71785,37.36345,45.1862,35.64053"/>
|
||||
<path d="M47.28506,38.98252c-3.38211-0.20146-6.25773,1.91951-7.87286,1.91951c-1.61602,0-4.08931-1.81799-6.76438-1.76899
|
||||
c-3.48177,0.05114-6.71245,2.01976-8.4793,5.15079c-3.63411,6.2636-0.95904,15.55471,2.57494,20.65606
|
||||
c1.71618,2.5238,3.78447,5.30269,6.50976,5.20287c2.57494-0.10104,3.58421-1.66732,6.71416-1.66732
|
||||
c3.12765,0,4.03679,1.66732,6.76252,1.61681c2.82665-0.05054,4.59381-2.52506,6.30997-5.05132
|
||||
c1.96878-2.877,2.77473-5.65498,2.82542-5.80748c-0.0507-0.05051-5.45058-2.12204-5.50065-8.33358
|
||||
c-0.05098-5.20101,4.23951-7.6749,4.44144-7.82832C52.3832,39.4881,48.5975,39.08404,47.28506,38.98252"/>
|
||||
</g>
|
||||
<g>
|
||||
<path d="M76.73385,31.94381c7.35096,0,12.4697,5.06708,12.4697,12.44437c0,7.40363-5.22407,12.49704-12.65403,12.49704h-8.13892
|
||||
v12.94318h-5.88037v-37.8846H76.73385z M68.41059,51.9493h6.74732c5.11975,0,8.0336-2.75636,8.0336-7.53479
|
||||
c0-4.77792-2.91385-7.50845-8.00727-7.50845h-6.77365V51.9493z"/>
|
||||
<path d="M90.73997,61.97864c0-4.8311,3.70182-7.79761,10.26583-8.16526l7.56061-0.44614v-2.12639
|
||||
c0-3.07185-2.07423-4.90959-5.53905-4.90959c-3.28251,0-5.33041,1.57492-5.82871,4.04313h-5.35574
|
||||
c0.31499-4.98859,4.56777-8.66407,11.3941-8.66407c6.69466,0,10.97377,3.54432,10.97377,9.08388v19.03421h-5.43472v-4.54194
|
||||
h-0.13065c-1.60125,3.07185-5.09341,5.01441-8.71623,5.01441C94.52078,70.30088,90.73997,66.94038,90.73997,61.97864z
|
||||
M108.56641,59.4846v-2.17905l-6.8,0.41981c-3.38683,0.23649-5.30306,1.73291-5.30306,4.09579
|
||||
c0,2.41504,1.99523,3.99046,5.04075,3.99046C105.46823,65.81161,108.56641,63.08108,108.56641,59.4846z"/>
|
||||
<path d="M119.34167,79.9889v-4.5946c0.4193,0.10483,1.36425,0.10483,1.83723,0.10483c2.6252,0,4.04313-1.10245,4.90908-3.9378
|
||||
c0-0.05267,0.49931-1.68025,0.49931-1.70658l-9.97616-27.64562h6.14268l6.98432,22.47371h0.10432l6.98433-22.47371h5.9857
|
||||
l-10.34483,29.06304c-2.36186,6.69517-5.0924,8.84789-10.81577,8.84789C121.17891,80.12006,119.76098,80.06739,119.34167,79.9889
|
||||
z"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 6.1 KiB |
|
@ -4,7 +4,7 @@
|
|||
"description": "Applepay module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
|
||||
|
||||
return array(
|
||||
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
|
||||
'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array {
|
||||
|
||||
// Used in various places to mark fields for the preview button.
|
||||
$apm_name = 'ApplePay';
|
||||
|
@ -101,7 +101,7 @@ return array(
|
|||
'applepay_button_enabled' => array(
|
||||
'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ),
|
||||
'title_html' => sprintf(
|
||||
'<img src="%sassets/images/applepay.png" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
'<img src="%sassets/images/applepay.svg" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
$module_url,
|
||||
__( 'Apple Pay', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
|
@ -155,7 +155,7 @@ return array(
|
|||
'applepay_button_enabled' => array(
|
||||
'title' => __( 'Apple Pay Button', 'woocommerce-paypal-payments' ),
|
||||
'title_html' => sprintf(
|
||||
'<img src="%sassets/images/applepay.png" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
'<img src="%sassets/images/applepay.svg" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
$module_url,
|
||||
__( 'Apple Pay', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Applepay;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): ApplepayModule {
|
||||
return new ApplepayModule();
|
||||
};
|
||||
|
|
|
@ -23,6 +23,11 @@
|
|||
&.ppcp-button-minicart {
|
||||
--apple-pay-button-display: block;
|
||||
}
|
||||
|
||||
&.ppcp-preview-button.ppcp-button-dummy {
|
||||
/* URL must specify the correct module-folder! */
|
||||
--apm-button-dummy-background: url(../../../ppcp-applepay/assets/images/applepay.png);
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-checkout, .wp-block-woocommerce-cart {
|
||||
|
|
|
@ -57,6 +57,8 @@ const CONTEXT = {
|
|||
* On a single page, multiple Apple Pay buttons can be displayed, which also means multiple
|
||||
* ApplePayButton instances exist. A typical case is on the product page, where one Apple Pay button
|
||||
* is located inside the minicart-popup, and another pay-now button is in the product context.
|
||||
*
|
||||
* TODO - extend from PaymentButton (same as we do in GooglepayButton.js)
|
||||
*/
|
||||
class ApplePayButton {
|
||||
/**
|
||||
|
@ -490,6 +492,7 @@ class ApplePayButton {
|
|||
const ppcpStyle = this.ppcpStyle;
|
||||
|
||||
wrapper.innerHTML = `<apple-pay-button id='${ id }' buttonstyle='${ style.color }' type='${ style.type }' locale='${ style.lang }' />`;
|
||||
wrapper.classList.remove( 'ppcp-button-rect', 'ppcp-button-pill' );
|
||||
wrapper.classList.add(
|
||||
`ppcp-button-${ ppcpStyle.shape }`,
|
||||
'ppcp-button-apm',
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
import ApplepayButton from '../ApplepayButton';
|
||||
import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButton';
|
||||
|
||||
/**
|
||||
* A single Apple Pay preview button instance.
|
||||
*/
|
||||
export default class ApplePayPreviewButton extends PreviewButton {
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
this.selector = `${ args.selector }ApplePay`;
|
||||
this.defaultAttributes = {
|
||||
button: {
|
||||
type: 'pay',
|
||||
color: 'black',
|
||||
lang: 'en',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
const button = new ApplepayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
|
||||
button.init( this.apiConfig );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge form details into the config object for preview.
|
||||
* Mutates the previewConfig object; no return value.
|
||||
* @param buttonConfig
|
||||
* @param ppcpConfig
|
||||
*/
|
||||
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
|
||||
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix!
|
||||
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(
|
||||
/^#/,
|
||||
''
|
||||
);
|
||||
|
||||
// Merge the current form-values into the preview-button configuration.
|
||||
if ( ppcpConfig.button ) {
|
||||
buttonConfig.button.type = ppcpConfig.button.style.type;
|
||||
buttonConfig.button.color = ppcpConfig.button.style.color;
|
||||
buttonConfig.button.lang =
|
||||
ppcpConfig.button.style?.lang ||
|
||||
ppcpConfig.button.style.language;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
import PreviewButtonManager from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButtonManager';
|
||||
import ApplePayPreviewButton from './ApplePayPreviewButton';
|
||||
|
||||
/**
|
||||
* Manages all Apple Pay preview buttons on this page.
|
||||
*/
|
||||
export default class ApplePayPreviewButtonManager extends PreviewButtonManager {
|
||||
constructor() {
|
||||
const args = {
|
||||
methodName: 'ApplePay',
|
||||
buttonConfig: window.wc_ppcp_applepay_admin,
|
||||
};
|
||||
|
||||
super( args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||
* method.
|
||||
*
|
||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||
* @return {Promise<{}>}
|
||||
*/
|
||||
async fetchConfig( payPal ) {
|
||||
const apiMethod = payPal?.Applepay()?.config;
|
||||
|
||||
if ( ! apiMethod ) {
|
||||
this.error(
|
||||
'configuration object cannot be retrieved from PayPal'
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return await apiMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||
*
|
||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||
* @return {ApplePayPreviewButton}
|
||||
*/
|
||||
createButtonInstance( wrapperId ) {
|
||||
return new ApplePayPreviewButton( {
|
||||
selector: wrapperId,
|
||||
apiConfig: this.apiConfig,
|
||||
methodName: this.methodName,
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -1,6 +1,4 @@
|
|||
import ApplePayButton from './ApplepayButton';
|
||||
import PreviewButton from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButton';
|
||||
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
||||
import ApplePayPreviewButtonManager from './Preview/ApplePayPreviewButtonManager';
|
||||
|
||||
/**
|
||||
* Accessor that creates and returns a single PreviewButtonManager instance.
|
||||
|
@ -14,111 +12,5 @@ const buttonManager = () => {
|
|||
return ApplePayPreviewButtonManager.instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages all Apple Pay preview buttons on this page.
|
||||
*/
|
||||
class ApplePayPreviewButtonManager extends PreviewButtonManager {
|
||||
constructor() {
|
||||
const args = {
|
||||
methodName: 'ApplePay',
|
||||
buttonConfig: window.wc_ppcp_applepay_admin,
|
||||
};
|
||||
|
||||
super( args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||
* method.
|
||||
*
|
||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||
* @return {Promise<{}>}
|
||||
*/
|
||||
async fetchConfig( payPal ) {
|
||||
const apiMethod = payPal?.Applepay()?.config;
|
||||
|
||||
if ( ! apiMethod ) {
|
||||
this.error(
|
||||
'configuration object cannot be retrieved from PayPal'
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
return await apiMethod();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||
*
|
||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||
* @return {ApplePayPreviewButton}
|
||||
*/
|
||||
createButtonInstance( wrapperId ) {
|
||||
return new ApplePayPreviewButton( {
|
||||
selector: wrapperId,
|
||||
apiConfig: this.apiConfig,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A single Apple Pay preview button instance.
|
||||
*/
|
||||
class ApplePayPreviewButton extends PreviewButton {
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
this.selector = `${ args.selector }ApplePay`;
|
||||
this.defaultAttributes = {
|
||||
button: {
|
||||
type: 'pay',
|
||||
color: 'black',
|
||||
lang: 'en',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createNewWrapper() {
|
||||
const element = super.createNewWrapper();
|
||||
element.addClass( 'ppcp-button-applepay' );
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
const button = new ApplePayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
|
||||
button.init( this.apiConfig );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge form details into the config object for preview.
|
||||
* Mutates the previewConfig object; no return value.
|
||||
* @param buttonConfig
|
||||
* @param ppcpConfig
|
||||
*/
|
||||
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
|
||||
// The Apple Pay button expects the "wrapper" to be an ID without `#` prefix!
|
||||
buttonConfig.button.wrapper = buttonConfig.button.wrapper.replace(
|
||||
/^#/,
|
||||
''
|
||||
);
|
||||
|
||||
// Merge the current form-values into the preview-button configuration.
|
||||
if ( ppcpConfig.button ) {
|
||||
buttonConfig.button.type = ppcpConfig.button.style.type;
|
||||
buttonConfig.button.color = ppcpConfig.button.style.color;
|
||||
buttonConfig.button.lang =
|
||||
ppcpConfig.button.style?.lang ||
|
||||
ppcpConfig.button.style.language;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the preview button manager.
|
||||
buttonManager();
|
||||
|
|
|
@ -112,7 +112,7 @@ class ApplePayGateway extends WC_Payment_Gateway {
|
|||
$this->description = $this->get_option( 'description', '' );
|
||||
|
||||
$this->module_url = $module_url;
|
||||
$this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.png';
|
||||
$this->icon = esc_url( $this->module_url ) . 'assets/images/applepay.svg';
|
||||
|
||||
$this->init_form_fields();
|
||||
$this->init_settings();
|
||||
|
|
|
@ -18,30 +18,37 @@ use WooCommerce\PayPalCommerce\Button\Assets\ButtonInterface;
|
|||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
use WooCommerce\PayPalCommerce\Applepay\Helper\AvailabilityNotice;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Environment;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class ApplepayModule
|
||||
*/
|
||||
class ApplepayModule implements ModuleInterface {
|
||||
class ApplepayModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
$module = $this;
|
||||
|
||||
// Clears product status when appropriate.
|
||||
|
@ -160,14 +167,8 @@ class ApplepayModule implements ModuleInterface {
|
|||
echo '<div id="ppc-button-' . esc_attr( ApplePayGateway::ID ) . '"></div>';
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Axo module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -17,7 +17,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Helper\DisplayManager;
|
|||
|
||||
return array(
|
||||
|
||||
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
|
||||
'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array {
|
||||
|
||||
$insert_after = function( array $array, string $key, array $new ): array {
|
||||
$keys = array_keys( $array );
|
||||
|
@ -120,7 +120,6 @@ return array(
|
|||
'',
|
||||
array(
|
||||
$container->get( 'axo.settings-conflict-notice' ),
|
||||
$container->get( 'axo.shipping-config-notice' ),
|
||||
$container->get( 'axo.checkout-config-notice' ),
|
||||
$container->get( 'axo.incompatible-plugins-notice' ),
|
||||
)
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Axo;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): AxoModule {
|
||||
return new AxoModule();
|
||||
};
|
||||
|
|
|
@ -11,17 +11,54 @@ import {
|
|||
} from '../../../ppcp-button/resources/js/modules/Helper/ButtonDisabler';
|
||||
import { getCurrentPaymentMethod } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
||||
|
||||
/**
|
||||
* Internal customer details.
|
||||
*
|
||||
* @typedef {Object} CustomerDetails
|
||||
* @property {null|string} email - Customer email.
|
||||
* @property {null|string} phone - Fastlane phone number.
|
||||
* @property {null|Object} billing - Billing details object.
|
||||
* @property {null|Object} shipping - Shipping details object.
|
||||
* @property {null|Object} card - Payment details object.
|
||||
*/
|
||||
|
||||
class AxoManager {
|
||||
axoConfig = null;
|
||||
ppcpConfig = null;
|
||||
$ = null;
|
||||
|
||||
fastlane = null;
|
||||
/**
|
||||
* @type {FastlaneCardComponent}
|
||||
*/
|
||||
cardComponent = null;
|
||||
|
||||
initialized = false;
|
||||
hideGatewaySelection = false;
|
||||
phoneNumber = null;
|
||||
|
||||
/**
|
||||
* @type {CustomerDetails}
|
||||
*/
|
||||
data = {};
|
||||
status = {};
|
||||
styles = {};
|
||||
locale = 'en_us';
|
||||
|
||||
el = null;
|
||||
emailInput = null;
|
||||
phoneInput = null;
|
||||
shippingView = null;
|
||||
billingView = null;
|
||||
cardView = null;
|
||||
|
||||
constructor( axoConfig, ppcpConfig ) {
|
||||
this.axoConfig = axoConfig;
|
||||
this.ppcpConfig = ppcpConfig;
|
||||
|
||||
this.initialized = false;
|
||||
this.fastlane = new Fastlane();
|
||||
this.$ = jQuery;
|
||||
|
||||
this.hideGatewaySelection = false;
|
||||
|
||||
this.status = {
|
||||
active: false,
|
||||
validEmail: false,
|
||||
|
@ -30,13 +67,9 @@ class AxoManager {
|
|||
hasCard: false,
|
||||
};
|
||||
|
||||
this.data = {
|
||||
email: null,
|
||||
billing: null,
|
||||
shipping: null,
|
||||
card: null,
|
||||
};
|
||||
this.clearData();
|
||||
|
||||
// TODO - Do we need a public `states` property for this?
|
||||
this.states = this.axoConfig.woocommerce.states;
|
||||
|
||||
this.el = new DomElementCollection();
|
||||
|
@ -51,8 +84,6 @@ class AxoManager {
|
|||
},
|
||||
};
|
||||
|
||||
this.locale = 'en_us';
|
||||
|
||||
this.registerEventHandlers();
|
||||
|
||||
this.shippingView = new ShippingView(
|
||||
|
@ -101,9 +132,31 @@ class AxoManager {
|
|||
}
|
||||
}
|
||||
|
||||
this.onChangePhone = this.onChangePhone.bind( this );
|
||||
this.initPhoneSyncWooToFastlane();
|
||||
|
||||
this.triggerGatewayChange();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the current flow is the "Ryan flow": Ryan is a known customer who created a
|
||||
* Fastlane profile before. Ryan can leverage all benefits of the accelerated 1-click checkout.
|
||||
*
|
||||
* @return {boolean} True means, Fastlane could link the customer's email to an existing account.
|
||||
*/
|
||||
get isRyanFlow() {
|
||||
return !! this.data.card;
|
||||
}
|
||||
|
||||
/**
|
||||
* CSS selector to target the Fastlane Card Component wrapper.
|
||||
*
|
||||
* @return {string} CSS selector.
|
||||
*/
|
||||
get cardFormSelector() {
|
||||
return this.el.paymentContainer.selector + '-form';
|
||||
}
|
||||
|
||||
registerEventHandlers() {
|
||||
this.$( document ).on(
|
||||
'change',
|
||||
|
@ -458,6 +511,7 @@ class AxoManager {
|
|||
this.initPlacements();
|
||||
this.initFastlane();
|
||||
this.setStatus( 'active', true );
|
||||
this.readPhoneFromWoo();
|
||||
|
||||
log( `Attempt on activation - emailInput: ${ this.emailInput.value }` );
|
||||
log(
|
||||
|
@ -468,6 +522,8 @@ class AxoManager {
|
|||
this.lastEmailCheckedIdentity !== this.emailInput.value
|
||||
) {
|
||||
this.onChangeEmail();
|
||||
} else {
|
||||
this.refreshFastlanePrefills();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -690,6 +746,51 @@ class AxoManager {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Locates the WooCommerce checkout "billing phone" field and adds event listeners to it.
|
||||
*/
|
||||
initPhoneSyncWooToFastlane() {
|
||||
this.phoneInput = document.querySelector( '#billing_phone' );
|
||||
this.phoneInput?.addEventListener( 'change', this.onChangePhone );
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the country prefix and non-numeric characters from the phone number. If the remaining
|
||||
* phone number is valid, it's returned. Otherwise, the function returns null.
|
||||
*
|
||||
* @param {string} number - Phone number to sanitize.
|
||||
* @return {string|null} A valid US phone number, or null if the number is invalid.
|
||||
*/
|
||||
sanitizePhoneNumber( number ) {
|
||||
const localNumber = number.replace( /^\+1/, '' );
|
||||
const cleanNumber = localNumber.replace( /\D/g, '' );
|
||||
|
||||
// All valid US mobile numbers have exactly 10 digits.
|
||||
return cleanNumber.length === 10 ? cleanNumber : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the phone number from the WooCommerce checkout form, sanitizes it, and (if valid)
|
||||
* stores it in the internal customer details object.
|
||||
*
|
||||
* @return {boolean} True, if the internal phone number was updated.
|
||||
*/
|
||||
readPhoneFromWoo() {
|
||||
if ( ! this.phoneInput ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const phoneNumber = this.phoneInput.value;
|
||||
const validPhoneNumber = this.sanitizePhoneNumber( phoneNumber );
|
||||
|
||||
if ( ! validPhoneNumber ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.data.phone = validPhoneNumber;
|
||||
return true;
|
||||
}
|
||||
|
||||
async onChangeEmail() {
|
||||
this.clearData();
|
||||
|
||||
|
@ -711,8 +812,8 @@ class AxoManager {
|
|||
|
||||
this.emailInput.value = this.stripSpaces( this.emailInput.value );
|
||||
|
||||
this.$( this.el.paymentContainer.selector + '-detail' ).html( '' );
|
||||
this.$( this.el.paymentContainer.selector + '-form' ).html( '' );
|
||||
this.$( this.el.paymentContainer.selector + '-details' ).html( '' );
|
||||
this.removeFastlaneComponent();
|
||||
|
||||
this.setStatus( 'validEmail', false );
|
||||
this.setStatus( 'hasProfile', false );
|
||||
|
@ -733,6 +834,8 @@ class AxoManager {
|
|||
this.data.email = this.emailInput.value;
|
||||
this.billingView.setData( this.data );
|
||||
|
||||
this.readPhoneFromWoo();
|
||||
|
||||
if ( ! this.fastlane.identity ) {
|
||||
log( 'Not initialized.' );
|
||||
return;
|
||||
|
@ -757,6 +860,22 @@ class AxoManager {
|
|||
this.enableGatewaySelection();
|
||||
}
|
||||
|
||||
/**
|
||||
* Event handler that fires when the customer changed the phone number in the WooCommerce
|
||||
* checkout form. If Fastlane is active, the component is refreshed.
|
||||
*
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
async onChangePhone() {
|
||||
const hasChanged = this.readPhoneFromWoo();
|
||||
|
||||
if ( hasChanged && this.status.active ) {
|
||||
await this.refreshFastlanePrefills();
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
async lookupCustomerByEmail() {
|
||||
const lookupResponse =
|
||||
await this.fastlane.identity.lookupCustomerByEmail(
|
||||
|
@ -792,11 +911,7 @@ class AxoManager {
|
|||
if ( authResponse.profileData.card ) {
|
||||
this.setStatus( 'hasCard', true );
|
||||
} else {
|
||||
this.cardComponent = (
|
||||
await this.fastlane.FastlaneCardComponent(
|
||||
this.cardComponentData()
|
||||
)
|
||||
).render( this.el.paymentContainer.selector + '-form' );
|
||||
await this.initializeFastlaneComponent();
|
||||
}
|
||||
|
||||
const cardBillingAddress =
|
||||
|
@ -837,12 +952,7 @@ class AxoManager {
|
|||
this.setStatus( 'hasProfile', false );
|
||||
|
||||
await this.renderWatermark( true );
|
||||
|
||||
this.cardComponent = (
|
||||
await this.fastlane.FastlaneCardComponent(
|
||||
this.cardComponentData()
|
||||
)
|
||||
).render( this.el.paymentContainer.selector + '-form' );
|
||||
await this.initializeFastlaneComponent();
|
||||
}
|
||||
} else {
|
||||
// No profile found with this email address.
|
||||
|
@ -853,12 +963,7 @@ class AxoManager {
|
|||
this.setStatus( 'hasProfile', false );
|
||||
|
||||
await this.renderWatermark( true );
|
||||
|
||||
this.cardComponent = (
|
||||
await this.fastlane.FastlaneCardComponent(
|
||||
this.cardComponentData()
|
||||
)
|
||||
).render( this.el.paymentContainer.selector + '-form' );
|
||||
await this.initializeFastlaneComponent();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -873,6 +978,7 @@ class AxoManager {
|
|||
clearData() {
|
||||
this.data = {
|
||||
email: null,
|
||||
phone: null,
|
||||
billing: null,
|
||||
shipping: null,
|
||||
card: null,
|
||||
|
@ -897,7 +1003,7 @@ class AxoManager {
|
|||
onClickSubmitButton() {
|
||||
// TODO: validate data.
|
||||
|
||||
if ( this.data.card ) {
|
||||
if ( this.isRyanFlow ) {
|
||||
// Ryan flow
|
||||
log( 'Starting Ryan flow.' );
|
||||
|
||||
|
@ -935,7 +1041,7 @@ class AxoManager {
|
|||
}
|
||||
|
||||
cardComponentData() {
|
||||
return {
|
||||
const config = {
|
||||
fields: {
|
||||
cardholderName: {
|
||||
enabled: this.axoConfig.name_on_card === '1',
|
||||
|
@ -945,6 +1051,68 @@ class AxoManager {
|
|||
this.axoConfig.style_options
|
||||
),
|
||||
};
|
||||
|
||||
// Ryan is a known customer, we do not need his phone number.
|
||||
if ( this.data.phone && ! this.isRyanFlow ) {
|
||||
config.fields.phoneNumber = {
|
||||
prefill: this.data.phone,
|
||||
};
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the Fastlane UI component, using configuration provided by the
|
||||
* `cardComponentData()` method. If the UI component was already initialized, nothing happens.
|
||||
*
|
||||
* @return {Promise<*>} Resolves when the component was rendered.
|
||||
*/
|
||||
async initializeFastlaneComponent() {
|
||||
if ( ! this.status.active || this.cardComponent ) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const elem = this.cardFormSelector;
|
||||
const config = this.cardComponentData();
|
||||
|
||||
this.cardComponent =
|
||||
await this.fastlane.FastlaneCardComponent( config );
|
||||
|
||||
return this.cardComponent.render( elem );
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverts the changes made by `initializeFastlaneComponent()`.
|
||||
*
|
||||
* Calling this method will lose any input that the user made inside the
|
||||
* Fastlane Card Component.
|
||||
*/
|
||||
removeFastlaneComponent() {
|
||||
document.querySelector( this.cardFormSelector ).innerHTML = '';
|
||||
|
||||
this.cardComponent = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the prefill-values in the UI component. This method only updates empty fields.
|
||||
*
|
||||
* @return {Promise<*>} Resolves when the component was refreshed.
|
||||
*/
|
||||
async refreshFastlanePrefills() {
|
||||
if ( ! this.cardComponent ) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const { fields } = this.cardComponentData();
|
||||
const prefills = Object.keys( fields ).reduce( ( result, key ) => {
|
||||
if ( fields[ key ].hasOwnProperty( 'prefill' ) ) {
|
||||
result[ key ] = fields[ key ].prefill;
|
||||
}
|
||||
return result;
|
||||
}, {} );
|
||||
|
||||
return this.cardComponent.updatePrefills( prefills );
|
||||
}
|
||||
|
||||
tokenizeData() {
|
||||
|
@ -1081,8 +1249,8 @@ class AxoManager {
|
|||
ensureBillingPhoneNumber( data ) {
|
||||
if ( data.billing_phone === '' ) {
|
||||
let phone = '';
|
||||
const cc = this.data?.shipping?.phoneNumber?.countryCode;
|
||||
const number = this.data?.shipping?.phoneNumber?.nationalNumber;
|
||||
const cc = this.data.shipping?.phoneNumber?.countryCode;
|
||||
const number = this.data.shipping?.phoneNumber?.nationalNumber;
|
||||
|
||||
if ( cc ) {
|
||||
phone = `+${ cc } `;
|
||||
|
|
|
@ -21,14 +21,14 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
return array(
|
||||
|
||||
// If AXO can be configured.
|
||||
'axo.eligible' => static function ( ContainerInterface $container ): bool {
|
||||
'axo.eligible' => static function ( ContainerInterface $container ): bool {
|
||||
$apm_applies = $container->get( 'axo.helpers.apm-applies' );
|
||||
assert( $apm_applies instanceof ApmApplies );
|
||||
|
||||
return $apm_applies->for_country_currency();
|
||||
},
|
||||
|
||||
'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
|
||||
'axo.helpers.apm-applies' => static function ( ContainerInterface $container ) : ApmApplies {
|
||||
return new ApmApplies(
|
||||
$container->get( 'axo.supported-country-currency-matrix' ),
|
||||
$container->get( 'api.shop.currency' ),
|
||||
|
@ -36,16 +36,16 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator {
|
||||
return new SettingsNoticeGenerator();
|
||||
'axo.helpers.settings-notice-generator' => static function ( ContainerInterface $container ) : SettingsNoticeGenerator {
|
||||
return new SettingsNoticeGenerator( $container->get( 'axo.fastlane-incompatible-plugin-names' ) );
|
||||
},
|
||||
|
||||
// If AXO is configured and onboarded.
|
||||
'axo.available' => static function ( ContainerInterface $container ): bool {
|
||||
'axo.available' => static function ( ContainerInterface $container ): bool {
|
||||
return true;
|
||||
},
|
||||
|
||||
'axo.url' => static function ( ContainerInterface $container ): string {
|
||||
'axo.url' => static function ( ContainerInterface $container ): string {
|
||||
$path = realpath( __FILE__ );
|
||||
if ( false === $path ) {
|
||||
return '';
|
||||
|
@ -56,7 +56,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.manager' => static function ( ContainerInterface $container ): AxoManager {
|
||||
'axo.manager' => static function ( ContainerInterface $container ): AxoManager {
|
||||
return new AxoManager(
|
||||
$container->get( 'axo.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
|
@ -70,7 +70,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.gateway' => static function ( ContainerInterface $container ): AxoGateway {
|
||||
'axo.gateway' => static function ( ContainerInterface $container ): AxoGateway {
|
||||
return new AxoGateway(
|
||||
$container->get( 'wcgateway.settings.render' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
|
@ -87,7 +87,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.card_icons' => static function ( ContainerInterface $container ): array {
|
||||
'axo.card_icons' => static function ( ContainerInterface $container ): array {
|
||||
return array(
|
||||
array(
|
||||
'title' => 'Visa',
|
||||
|
@ -108,7 +108,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.card_icons.axo' => static function ( ContainerInterface $container ): array {
|
||||
'axo.card_icons.axo' => static function ( ContainerInterface $container ): array {
|
||||
return array(
|
||||
array(
|
||||
'title' => 'Visa',
|
||||
|
@ -144,7 +144,7 @@ return array(
|
|||
/**
|
||||
* The matrix which countries and currency combinations can be used for AXO.
|
||||
*/
|
||||
'axo.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
|
||||
'axo.supported-country-currency-matrix' => static function ( ContainerInterface $container ) : array {
|
||||
/**
|
||||
* Returns which countries and currency combinations can be used for AXO.
|
||||
*/
|
||||
|
@ -163,7 +163,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string {
|
||||
'axo.settings-conflict-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
|
||||
|
@ -173,28 +173,21 @@ return array(
|
|||
return $settings_notice_generator->generate_settings_conflict_notice( $settings );
|
||||
},
|
||||
|
||||
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||
'axo.checkout-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
|
||||
return $settings_notice_generator->generate_checkout_notice();
|
||||
},
|
||||
|
||||
'axo.shipping-config-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
|
||||
return $settings_notice_generator->generate_shipping_notice();
|
||||
},
|
||||
|
||||
'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string {
|
||||
'axo.incompatible-plugins-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings_notice_generator = $container->get( 'axo.helpers.settings-notice-generator' );
|
||||
assert( $settings_notice_generator instanceof SettingsNoticeGenerator );
|
||||
|
||||
return $settings_notice_generator->generate_incompatible_plugins_notice();
|
||||
},
|
||||
|
||||
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
|
||||
'axo.smart-button-location-notice' => static function ( ContainerInterface $container ) : string {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
|
||||
|
@ -222,10 +215,92 @@ return array(
|
|||
return '<div class="ppcp-notice ppcp-notice-warning"><p>' . $notice_content . '</p></div>';
|
||||
},
|
||||
|
||||
'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint {
|
||||
'axo.endpoint.frontend-logger' => static function ( ContainerInterface $container ): FrontendLoggerEndpoint {
|
||||
return new FrontendLoggerEndpoint(
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* The list of Fastlane incompatible plugins.
|
||||
*
|
||||
* @returns array<array{name: string, is_active: bool}>
|
||||
*/
|
||||
'axo.fastlane-incompatible-plugins' => static function () : array {
|
||||
/**
|
||||
* Filters the list of Fastlane incompatible plugins.
|
||||
*/
|
||||
return apply_filters(
|
||||
'woocommerce_paypal_payments_fastlane_incompatible_plugins',
|
||||
array(
|
||||
array(
|
||||
'name' => 'Elementor',
|
||||
'is_active' => did_action( 'elementor/loaded' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'CheckoutWC',
|
||||
'is_active' => defined( 'CFW_NAME' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'Direct Checkout for WooCommerce',
|
||||
'is_active' => defined( 'QLWCDC_PLUGIN_NAME' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'Multi-Step Checkout for WooCommerce',
|
||||
'is_active' => class_exists( 'WPMultiStepCheckout' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'Fluid Checkout for WooCommerce',
|
||||
'is_active' => class_exists( 'FluidCheckout' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'MultiStep Checkout for WooCommerce',
|
||||
'is_active' => class_exists( 'THWMSCF_Multistep_Checkout' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'WooCommerce Subscriptions',
|
||||
'is_active' => class_exists( 'WC_Subscriptions' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'CartFlows',
|
||||
'is_active' => class_exists( 'Cartflows_Loader' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'FunnelKit Funnel Builder',
|
||||
'is_active' => class_exists( 'WFFN_Core' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'WooCommerce One Page Checkout',
|
||||
'is_active' => class_exists( 'PP_One_Page_Checkout' ),
|
||||
),
|
||||
array(
|
||||
'name' => 'All Products for Woo Subscriptions',
|
||||
'is_active' => class_exists( 'WCS_ATT' ),
|
||||
),
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
'axo.fastlane-incompatible-plugin-names' => static function ( ContainerInterface $container ) : array {
|
||||
$incompatible_plugins = $container->get( 'axo.fastlane-incompatible-plugins' );
|
||||
|
||||
$active_plugins_list = array_filter(
|
||||
$incompatible_plugins,
|
||||
function( array $plugin ): bool {
|
||||
return (bool) $plugin['is_active'];
|
||||
}
|
||||
);
|
||||
|
||||
if ( empty( $active_plugins_list ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( array $plugin ): string {
|
||||
return "<li>{$plugin['name']}</li>";
|
||||
},
|
||||
$active_plugins_list
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
@ -16,10 +16,12 @@ use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
|||
use WooCommerce\PayPalCommerce\Axo\Assets\AxoManager;
|
||||
use WooCommerce\PayPalCommerce\Axo\Gateway\AxoGateway;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\ContextTrait;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\Render\OnboardingOptionsRenderer;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
@ -29,21 +31,28 @@ use WooCommerce\PayPalCommerce\WcSubscriptions\Helper\SubscriptionHelper;
|
|||
/**
|
||||
* Class AxoModule
|
||||
*/
|
||||
class AxoModule implements ModuleInterface {
|
||||
class AxoModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
use ContextTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
|
||||
add_filter(
|
||||
'woocommerce_payment_gateways',
|
||||
|
@ -66,7 +75,9 @@ class AxoModule implements ModuleInterface {
|
|||
|
||||
// Add the gateway in admin area.
|
||||
if ( is_admin() ) {
|
||||
// $methods[] = $gateway; - Temporarily remove Fastlane from the payment gateway list in admin area.
|
||||
if ( ! $this->is_wc_settings_payments_tab() ) {
|
||||
$methods[] = $gateway;
|
||||
}
|
||||
return $methods;
|
||||
}
|
||||
|
||||
|
@ -88,10 +99,6 @@ class AxoModule implements ModuleInterface {
|
|||
return $methods;
|
||||
}
|
||||
|
||||
if ( ! $this->is_compatible_shipping_config() ) {
|
||||
return $methods;
|
||||
}
|
||||
|
||||
$methods[] = $gateway;
|
||||
return $methods;
|
||||
},
|
||||
|
@ -162,7 +169,7 @@ class AxoModule implements ModuleInterface {
|
|||
|| ! $c->get( 'axo.eligible' )
|
||||
|| 'continuation' === $c->get( 'button.context' )
|
||||
|| $subscription_helper->cart_contains_subscription()
|
||||
|| ! $this->is_compatible_shipping_config() ) {
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -284,6 +291,7 @@ class AxoModule implements ModuleInterface {
|
|||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -317,14 +325,6 @@ class AxoModule implements ModuleInterface {
|
|||
return $localized_script_data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition to evaluate if Credit Card gateway should be hidden.
|
||||
*
|
||||
|
@ -353,7 +353,6 @@ class AxoModule implements ModuleInterface {
|
|||
|
||||
return ! is_user_logged_in()
|
||||
&& CartCheckoutDetector::has_classic_checkout()
|
||||
&& $this->is_compatible_shipping_config()
|
||||
&& $is_axo_enabled
|
||||
&& $is_dcc_enabled
|
||||
&& ! $this->is_excluded_endpoint();
|
||||
|
@ -410,15 +409,6 @@ class AxoModule implements ModuleInterface {
|
|||
return is_wc_endpoint_url( 'order-pay' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Condition to evaluate if the shipping configuration is compatible.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_compatible_shipping_config(): bool {
|
||||
return ! wc_shipping_enabled() || ( wc_shipping_enabled() && ! wc_ship_to_billing_address_only() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Outputs a meta tag to allow feature detection on certain pages.
|
||||
*
|
||||
|
|
|
@ -18,6 +18,22 @@ use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
|||
* Class SettingsNoticeGenerator
|
||||
*/
|
||||
class SettingsNoticeGenerator {
|
||||
/**
|
||||
* The list of Fastlane incompatible plugin names.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
protected $incompatible_plugin_names;
|
||||
|
||||
/**
|
||||
* SettingsNoticeGenerator constructor.
|
||||
*
|
||||
* @param string[] $incompatible_plugin_names The list of Fastlane incompatible plugin names.
|
||||
*/
|
||||
public function __construct( array $incompatible_plugin_names ) {
|
||||
$this->incompatible_plugin_names = $incompatible_plugin_names;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the full HTML of the notification.
|
||||
*
|
||||
|
@ -87,54 +103,16 @@ class SettingsNoticeGenerator {
|
|||
return $notice_content ? '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the shipping notice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_shipping_notice(): string {
|
||||
$shipping_settings_link = admin_url( 'admin.php?page=wc-settings&tab=shipping§ion=options' );
|
||||
|
||||
$notice_content = '';
|
||||
|
||||
if ( wc_shipping_enabled() && wc_ship_to_billing_address_only() ) {
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the Shipping destination settings page. */
|
||||
__(
|
||||
'<span class="highlight">Warning:</span> The <a href="%1$s">Shipping destination</a> of your store is currently configured to <code>Force shipping to the customer billing address</code>. To enable Fastlane and accelerate payments, the shipping destination must be configured either to <code>Default to customer shipping address</code> or <code>Default to customer billing address</code> so buyers can set separate billing and shipping details.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
esc_url( $shipping_settings_link )
|
||||
);
|
||||
}
|
||||
|
||||
return $notice_content ? '<div class="ppcp-notice ppcp-notice-error"><p>' . $notice_content . '</p></div>' : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the incompatible plugins notice.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generate_incompatible_plugins_notice(): string {
|
||||
$incompatible_plugins = array(
|
||||
'Elementor' => did_action( 'elementor/loaded' ),
|
||||
'CheckoutWC' => defined( 'CFW_NAME' ),
|
||||
);
|
||||
|
||||
$active_plugins_list = array_filter( $incompatible_plugins );
|
||||
|
||||
if ( empty( $active_plugins_list ) ) {
|
||||
if ( empty( $this->incompatible_plugin_names ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$incompatible_plugin_items = array_map(
|
||||
function ( $plugin ) {
|
||||
return "<li>{$plugin}</li>";
|
||||
},
|
||||
array_keys( $active_plugins_list )
|
||||
);
|
||||
|
||||
$plugins_settings_link = esc_url( admin_url( 'plugins.php' ) );
|
||||
$notice_content = sprintf(
|
||||
/* translators: %1$s: URL to the plugins settings page. %2$s: List of incompatible plugins. */
|
||||
|
@ -143,7 +121,7 @@ class SettingsNoticeGenerator {
|
|||
'woocommerce-paypal-payments'
|
||||
),
|
||||
$plugins_settings_link,
|
||||
implode( '', $incompatible_plugin_items )
|
||||
implode( '', $this->incompatible_plugin_names )
|
||||
);
|
||||
|
||||
return '<div class="ppcp-notice"><p>' . $notice_content . '</p></div>';
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Blocks module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -13,7 +13,7 @@ use WooCommerce\PayPalCommerce\Onboarding\State;
|
|||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'wcgateway.button.locations' => function ( ContainerInterface $container, array $locations ): array {
|
||||
'wcgateway.button.locations' => function ( array $locations, ContainerInterface $container ): array {
|
||||
return array_merge(
|
||||
$locations,
|
||||
array(
|
||||
|
@ -22,13 +22,13 @@ return array(
|
|||
)
|
||||
);
|
||||
},
|
||||
'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array {
|
||||
'wcgateway.settings.pay-later.messaging-locations' => function ( array $locations, ContainerInterface $container ): array {
|
||||
unset( $locations['checkout-block-express'] );
|
||||
unset( $locations['cart-block'] );
|
||||
return $locations;
|
||||
},
|
||||
|
||||
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
|
||||
'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array {
|
||||
$insert_after = function( array $array, string $key, array $new ): array {
|
||||
$keys = array_keys( $array );
|
||||
$index = array_search( $key, $keys, true );
|
||||
|
@ -72,7 +72,7 @@ return array(
|
|||
);
|
||||
},
|
||||
|
||||
'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array {
|
||||
'button.pay-now-contexts' => function ( array $contexts, ContainerInterface $container ): array {
|
||||
if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) {
|
||||
$contexts[] = 'checkout-block';
|
||||
$contexts[] = 'cart-block';
|
||||
|
@ -81,7 +81,7 @@ return array(
|
|||
return $contexts;
|
||||
},
|
||||
|
||||
'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool {
|
||||
'button.handle-shipping-in-paypal' => function ( bool $previous, ContainerInterface $container ): bool {
|
||||
return ! $container->get( 'blocks.settings.final_review_enabled' );
|
||||
},
|
||||
);
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): BlocksModule {
|
||||
return new BlocksModule();
|
||||
};
|
||||
|
|
|
@ -12,29 +12,36 @@ namespace WooCommerce\PayPalCommerce\Blocks;
|
|||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class BlocksModule
|
||||
*/
|
||||
class BlocksModule implements ModuleInterface {
|
||||
class BlocksModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
if (
|
||||
! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' )
|
||||
|| ! function_exists( 'woocommerce_store_api_register_payment_requirements' )
|
||||
|
@ -54,7 +61,7 @@ class BlocksModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
add_action(
|
||||
|
@ -118,13 +125,6 @@ class BlocksModule implements ModuleInterface {
|
|||
return $components;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Button module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Button;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): ButtonModule {
|
||||
return new ButtonModule();
|
||||
};
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
#payment ul.payment_methods li img.ppcp-card-icon {
|
||||
padding: 0 0 3px 3px;
|
||||
max-height: 25px;
|
||||
display: inline-block;
|
||||
#payment ul.payment_methods [class*="payment_method_ppcp-"] label img {
|
||||
max-height: 25px;
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.payments-sdk-contingency-handler {
|
||||
z-index: 1000 !important;
|
||||
z-index: 1000 !important;
|
||||
}
|
||||
|
||||
.ppcp-credit-card-gateway-form-field-disabled {
|
||||
|
|
179
modules/ppcp-button/resources/js/modules/Helper/LocalStorage.js
Normal file
179
modules/ppcp-button/resources/js/modules/Helper/LocalStorage.js
Normal file
|
@ -0,0 +1,179 @@
|
|||
/* global localStorage */
|
||||
|
||||
function checkLocalStorageAvailability() {
|
||||
try {
|
||||
const testKey = '__ppcp_test__';
|
||||
localStorage.setItem( testKey, 'test' );
|
||||
localStorage.removeItem( testKey );
|
||||
return true;
|
||||
} catch ( e ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function sanitizeKey( name ) {
|
||||
return name
|
||||
.toLowerCase()
|
||||
.trim()
|
||||
.replace( /[^a-z0-9_-]/g, '_' );
|
||||
}
|
||||
|
||||
function deserializeEntry( serialized ) {
|
||||
try {
|
||||
const payload = JSON.parse( serialized );
|
||||
|
||||
return {
|
||||
data: payload.data,
|
||||
expires: payload.expires || 0,
|
||||
};
|
||||
} catch ( e ) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function serializeEntry( data, timeToLive ) {
|
||||
const payload = {
|
||||
data,
|
||||
expires: calculateExpiration( timeToLive ),
|
||||
};
|
||||
|
||||
return JSON.stringify( payload );
|
||||
}
|
||||
|
||||
function calculateExpiration( timeToLive ) {
|
||||
return timeToLive ? Date.now() + timeToLive * 1000 : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* A reusable class for handling data storage in the browser's local storage,
|
||||
* with optional expiration.
|
||||
*
|
||||
* Can be extended for module specific logic.
|
||||
*
|
||||
* @see GooglePaySession
|
||||
*/
|
||||
export class LocalStorage {
|
||||
/**
|
||||
* @type {string}
|
||||
*/
|
||||
#group = '';
|
||||
|
||||
/**
|
||||
* @type {null|boolean}
|
||||
*/
|
||||
#canUseLocalStorage = null;
|
||||
|
||||
/**
|
||||
* @param {string} group - Group name for all storage keys managed by this instance.
|
||||
*/
|
||||
constructor( group ) {
|
||||
this.#group = sanitizeKey( group ) + ':';
|
||||
this.#removeExpired();
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all items in the current group that have reached the expiry date.
|
||||
*/
|
||||
#removeExpired() {
|
||||
if ( ! this.canUseLocalStorage ) {
|
||||
return;
|
||||
}
|
||||
|
||||
Object.keys( localStorage ).forEach( ( key ) => {
|
||||
if ( ! key.startsWith( this.#group ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const entry = deserializeEntry( localStorage.getItem( key ) );
|
||||
if ( entry && entry.expires > 0 && entry.expires < Date.now() ) {
|
||||
localStorage.removeItem( key );
|
||||
}
|
||||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sanitizes the given entry name and adds the group prefix.
|
||||
*
|
||||
* @throws {Error} If the name is empty after sanitization.
|
||||
* @param {string} name - Entry name.
|
||||
* @return {string} Prefixed and sanitized entry name.
|
||||
*/
|
||||
#entryKey( name ) {
|
||||
const sanitizedName = sanitizeKey( name );
|
||||
|
||||
if ( sanitizedName.length === 0 ) {
|
||||
throw new Error( 'Name cannot be empty after sanitization' );
|
||||
}
|
||||
|
||||
return `${ this.#group }${ sanitizedName }`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates, whether localStorage is available.
|
||||
*
|
||||
* @return {boolean} True means the localStorage API is available.
|
||||
*/
|
||||
get canUseLocalStorage() {
|
||||
if ( null === this.#canUseLocalStorage ) {
|
||||
this.#canUseLocalStorage = checkLocalStorageAvailability();
|
||||
}
|
||||
|
||||
return this.#canUseLocalStorage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores data in the browser's local storage, with an optional timeout.
|
||||
*
|
||||
* @param {string} name - Name of the item in the storage.
|
||||
* @param {any} data - The data to store.
|
||||
* @param {number} [timeToLive=0] - Lifespan in seconds. 0 means the data won't expire.
|
||||
* @throws {Error} If local storage is not available.
|
||||
*/
|
||||
set( name, data, timeToLive = 0 ) {
|
||||
if ( ! this.canUseLocalStorage ) {
|
||||
throw new Error( 'Local storage is not available' );
|
||||
}
|
||||
|
||||
const entry = serializeEntry( data, timeToLive );
|
||||
const entryKey = this.#entryKey( name );
|
||||
|
||||
localStorage.setItem( entryKey, entry );
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves previously stored data from the browser's local storage.
|
||||
*
|
||||
* @param {string} name - Name of the stored item.
|
||||
* @return {any|null} The stored data, or null when no valid entry is found or it has expired.
|
||||
* @throws {Error} If local storage is not available.
|
||||
*/
|
||||
get( name ) {
|
||||
if ( ! this.canUseLocalStorage ) {
|
||||
throw new Error( 'Local storage is not available' );
|
||||
}
|
||||
|
||||
const itemKey = this.#entryKey( name );
|
||||
const entry = deserializeEntry( localStorage.getItem( itemKey ) );
|
||||
|
||||
if ( ! entry ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return entry.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the specified entry from the browser's local storage.
|
||||
*
|
||||
* @param {string} name - Name of the stored item.
|
||||
* @throws {Error} If local storage is not available.
|
||||
*/
|
||||
clear( name ) {
|
||||
if ( ! this.canUseLocalStorage ) {
|
||||
throw new Error( 'Local storage is not available' );
|
||||
}
|
||||
|
||||
const itemKey = this.#entryKey( name );
|
||||
localStorage.removeItem( itemKey );
|
||||
}
|
||||
}
|
|
@ -1,59 +1,196 @@
|
|||
export const payerData = () => {
|
||||
const payer = PayPalCommerceGateway.payer;
|
||||
/**
|
||||
* Name details.
|
||||
*
|
||||
* @typedef {Object} NameDetails
|
||||
* @property {string} [given_name] - First name, e.g. "John".
|
||||
* @property {string} [surname] - Last name, e.g. "Doe".
|
||||
*/
|
||||
|
||||
/**
|
||||
* Postal address details.
|
||||
*
|
||||
* @typedef {Object} AddressDetails
|
||||
* @property {string} [country_code] - Country code (2-letter).
|
||||
* @property {string} [address_line_1] - Address details, line 1 (street, house number).
|
||||
* @property {string} [address_line_2] - Address details, line 2.
|
||||
* @property {string} [admin_area_1] - State or region.
|
||||
* @property {string} [admin_area_2] - State or region.
|
||||
* @property {string} [postal_code] - Zip code.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Phone details.
|
||||
*
|
||||
* @typedef {Object} PhoneDetails
|
||||
* @property {string} [phone_type] - Type, usually 'HOME'
|
||||
* @property {{national_number: string}} [phone_number] - Phone number details.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Payer details.
|
||||
*
|
||||
* @typedef {Object} PayerDetails
|
||||
* @property {string} [email_address] - Email address for billing communication.
|
||||
* @property {PhoneDetails} [phone] - Phone number for billing communication.
|
||||
* @property {NameDetails} [name] - Payer's name.
|
||||
* @property {AddressDetails} [address] - Postal billing address.
|
||||
*/
|
||||
|
||||
// Map checkout fields to PayerData object properties.
|
||||
const FIELD_MAP = {
|
||||
'#billing_email': [ 'email_address' ],
|
||||
'#billing_last_name': [ 'name', 'surname' ],
|
||||
'#billing_first_name': [ 'name', 'given_name' ],
|
||||
'#billing_country': [ 'address', 'country_code' ],
|
||||
'#billing_address_1': [ 'address', 'address_line_1' ],
|
||||
'#billing_address_2': [ 'address', 'address_line_2' ],
|
||||
'#billing_state': [ 'address', 'admin_area_1' ],
|
||||
'#billing_city': [ 'address', 'admin_area_2' ],
|
||||
'#billing_postcode': [ 'address', 'postal_code' ],
|
||||
'#billing_phone': [ 'phone' ],
|
||||
};
|
||||
|
||||
function normalizePayerDetails( details ) {
|
||||
return {
|
||||
email_address: details.email_address,
|
||||
phone: details.phone,
|
||||
name: {
|
||||
surname: details.name?.surname,
|
||||
given_name: details.name?.given_name,
|
||||
},
|
||||
address: {
|
||||
country_code: details.address?.country_code,
|
||||
address_line_1: details.address?.address_line_1,
|
||||
address_line_2: details.address?.address_line_2,
|
||||
admin_area_1: details.address?.admin_area_1,
|
||||
admin_area_2: details.address?.admin_area_2,
|
||||
postal_code: details.address?.postal_code,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function mergePayerDetails( firstPayer, secondPayer ) {
|
||||
const mergeNestedObjects = ( target, source ) => {
|
||||
for ( const [ key, value ] of Object.entries( source ) ) {
|
||||
if ( null !== value && undefined !== value ) {
|
||||
if ( 'object' === typeof value ) {
|
||||
target[ key ] = mergeNestedObjects(
|
||||
target[ key ] || {},
|
||||
value
|
||||
);
|
||||
} else {
|
||||
target[ key ] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return target;
|
||||
};
|
||||
|
||||
return mergeNestedObjects(
|
||||
normalizePayerDetails( firstPayer ),
|
||||
normalizePayerDetails( secondPayer )
|
||||
);
|
||||
}
|
||||
|
||||
function getCheckoutBillingDetails() {
|
||||
const getElementValue = ( selector ) =>
|
||||
document.querySelector( selector )?.value;
|
||||
|
||||
const setNestedValue = ( obj, path, value ) => {
|
||||
let current = obj;
|
||||
for ( let i = 0; i < path.length - 1; i++ ) {
|
||||
current = current[ path[ i ] ] = current[ path[ i ] ] || {};
|
||||
}
|
||||
current[ path[ path.length - 1 ] ] = value;
|
||||
};
|
||||
|
||||
const data = {};
|
||||
|
||||
Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => {
|
||||
const value = getElementValue( selector );
|
||||
if ( value ) {
|
||||
setNestedValue( data, path, value );
|
||||
}
|
||||
} );
|
||||
|
||||
if ( data.phone && 'string' === typeof data.phone ) {
|
||||
data.phone = {
|
||||
phone_type: 'HOME',
|
||||
phone_number: { national_number: data.phone },
|
||||
};
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
function setCheckoutBillingDetails( payer ) {
|
||||
const setValue = ( path, field, value ) => {
|
||||
if ( null === value || undefined === value || ! field ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'phone' === path[ 0 ] && 'object' === typeof value ) {
|
||||
value = value.phone_number?.national_number;
|
||||
}
|
||||
|
||||
field.value = value;
|
||||
};
|
||||
|
||||
const getNestedValue = ( obj, path ) =>
|
||||
path.reduce( ( current, key ) => current?.[ key ], obj );
|
||||
|
||||
Object.entries( FIELD_MAP ).forEach( ( [ selector, path ] ) => {
|
||||
const value = getNestedValue( payer, path );
|
||||
const element = document.querySelector( selector );
|
||||
|
||||
setValue( path, element, value );
|
||||
} );
|
||||
}
|
||||
|
||||
export function getWooCommerceCustomerDetails() {
|
||||
// Populated on server-side with details about the current WooCommerce customer.
|
||||
return window?.PayPalCommerceGateway?.payer;
|
||||
}
|
||||
|
||||
export function getSessionBillingDetails() {
|
||||
// Populated by JS via `setSessionBillingDetails()`
|
||||
return window._PpcpPayerSessionDetails;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores customer details in the current JS context for use in the same request.
|
||||
* Details that are set are not persisted during navigation.
|
||||
*
|
||||
* @param {unknown} details - New payer details
|
||||
*/
|
||||
export function setSessionBillingDetails( details ) {
|
||||
if ( ! details || 'object' !== typeof details ) {
|
||||
return;
|
||||
}
|
||||
|
||||
window._PpcpPayerSessionDetails = normalizePayerDetails( details );
|
||||
}
|
||||
|
||||
export function payerData() {
|
||||
const payer = getWooCommerceCustomerDetails() ?? getSessionBillingDetails();
|
||||
|
||||
if ( ! payer ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const phone =
|
||||
document.querySelector( '#billing_phone' ) ||
|
||||
typeof payer.phone !== 'undefined'
|
||||
? {
|
||||
phone_type: 'HOME',
|
||||
phone_number: {
|
||||
national_number: document.querySelector(
|
||||
'#billing_phone'
|
||||
)
|
||||
? document.querySelector( '#billing_phone' ).value
|
||||
: payer.phone.phone_number.national_number,
|
||||
},
|
||||
}
|
||||
: null;
|
||||
const payerData = {
|
||||
email_address: document.querySelector( '#billing_email' )
|
||||
? document.querySelector( '#billing_email' ).value
|
||||
: payer.email_address,
|
||||
name: {
|
||||
surname: document.querySelector( '#billing_last_name' )
|
||||
? document.querySelector( '#billing_last_name' ).value
|
||||
: payer.name.surname,
|
||||
given_name: document.querySelector( '#billing_first_name' )
|
||||
? document.querySelector( '#billing_first_name' ).value
|
||||
: payer.name.given_name,
|
||||
},
|
||||
address: {
|
||||
country_code: document.querySelector( '#billing_country' )
|
||||
? document.querySelector( '#billing_country' ).value
|
||||
: payer.address.country_code,
|
||||
address_line_1: document.querySelector( '#billing_address_1' )
|
||||
? document.querySelector( '#billing_address_1' ).value
|
||||
: payer.address.address_line_1,
|
||||
address_line_2: document.querySelector( '#billing_address_2' )
|
||||
? document.querySelector( '#billing_address_2' ).value
|
||||
: payer.address.address_line_2,
|
||||
admin_area_1: document.querySelector( '#billing_state' )
|
||||
? document.querySelector( '#billing_state' ).value
|
||||
: payer.address.admin_area_1,
|
||||
admin_area_2: document.querySelector( '#billing_city' )
|
||||
? document.querySelector( '#billing_city' ).value
|
||||
: payer.address.admin_area_2,
|
||||
postal_code: document.querySelector( '#billing_postcode' )
|
||||
? document.querySelector( '#billing_postcode' ).value
|
||||
: payer.address.postal_code,
|
||||
},
|
||||
};
|
||||
const formData = getCheckoutBillingDetails();
|
||||
|
||||
if ( phone ) {
|
||||
payerData.phone = phone;
|
||||
if ( formData ) {
|
||||
return mergePayerDetails( payer, formData );
|
||||
}
|
||||
return payerData;
|
||||
};
|
||||
|
||||
return normalizePayerDetails( payer );
|
||||
}
|
||||
|
||||
export function setPayerData( payerDetails, updateCheckoutForm = false ) {
|
||||
setSessionBillingDetails( payerDetails );
|
||||
|
||||
if ( updateCheckoutForm ) {
|
||||
setCheckoutBillingDetails( payerDetails );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,31 +1,49 @@
|
|||
const onApprove = ( context, errorHandler ) => {
|
||||
return ( data, actions ) => {
|
||||
const canCreateOrder =
|
||||
! context.config.vaultingEnabled || data.paymentSource !== 'venmo';
|
||||
|
||||
const payload = {
|
||||
nonce: context.config.ajax.approve_order.nonce,
|
||||
order_id: data.orderID,
|
||||
funding_source: window.ppcpFundingSource,
|
||||
should_create_wc_order: canCreateOrder,
|
||||
};
|
||||
|
||||
if ( canCreateOrder && data.payer ) {
|
||||
payload.payer = data.payer;
|
||||
}
|
||||
|
||||
return fetch( context.config.ajax.approve_order.endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify( {
|
||||
nonce: context.config.ajax.approve_order.nonce,
|
||||
order_id: data.orderID,
|
||||
funding_source: window.ppcpFundingSource,
|
||||
should_create_wc_order:
|
||||
! context.config.vaultingEnabled ||
|
||||
data.paymentSource !== 'venmo',
|
||||
} ),
|
||||
body: JSON.stringify( payload ),
|
||||
} )
|
||||
.then( ( res ) => {
|
||||
return res.json();
|
||||
} )
|
||||
.then( ( data ) => {
|
||||
if ( ! data.success ) {
|
||||
location.href = context.config.redirect;
|
||||
.then( ( approveData ) => {
|
||||
if ( ! approveData.success ) {
|
||||
errorHandler.genericError();
|
||||
return actions.restart().catch( ( err ) => {
|
||||
errorHandler.genericError();
|
||||
} );
|
||||
}
|
||||
|
||||
const orderReceivedUrl = data.data?.order_received_url;
|
||||
const orderReceivedUrl = approveData.data?.order_received_url;
|
||||
|
||||
location.href = orderReceivedUrl
|
||||
/**
|
||||
* Notice how this step initiates a redirect to a new page using a plain
|
||||
* URL as new location. This process does not send any details about the
|
||||
* approved order or billed customer.
|
||||
* Also, due to the redirect starting _instantly_ there should be no other
|
||||
* logic scheduled after calling `await onApprove()`;
|
||||
*/
|
||||
|
||||
window.location.href = orderReceivedUrl
|
||||
? orderReceivedUrl
|
||||
: context.config.redirect;
|
||||
} );
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
import PreviewButton from './PreviewButton';
|
||||
|
||||
/**
|
||||
* Dummy preview button, to use in case an APM button cannot be rendered
|
||||
*/
|
||||
export default class DummyPreviewButton extends PreviewButton {
|
||||
#innerEl;
|
||||
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
this.selector = `${ args.selector }Dummy`;
|
||||
this.label = args.label || 'Not Available';
|
||||
}
|
||||
|
||||
createNewWrapper() {
|
||||
const wrapper = super.createNewWrapper();
|
||||
wrapper.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
|
||||
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
this.#innerEl?.remove();
|
||||
|
||||
this.#innerEl = document.createElement( 'div' );
|
||||
this.#innerEl.innerHTML = `<div class="reason">${ this.label }</div>`;
|
||||
|
||||
this._applyStyles( this.ppcpConfig?.button?.style );
|
||||
|
||||
this.domWrapper.appendChild( this.#innerEl );
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the button shape (rect/pill) to the dummy button
|
||||
*
|
||||
* @param {{shape: string, height: number|null}} style
|
||||
* @private
|
||||
*/
|
||||
_applyStyles( style ) {
|
||||
this.domWrapper.classList.remove(
|
||||
'ppcp-button-pill',
|
||||
'ppcp-button-rect'
|
||||
);
|
||||
|
||||
this.domWrapper.classList.add( `ppcp-button-${ style.shape }` );
|
||||
|
||||
if ( style.height ) {
|
||||
this.domWrapper.style.height = `${ style.height }px`;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,16 +5,21 @@ import merge from 'deepmerge';
|
|||
*/
|
||||
class PreviewButton {
|
||||
/**
|
||||
* @param {string} selector - CSS ID of the wrapper, including the `#`
|
||||
* @param {Object} apiConfig - PayPal configuration object; retrieved via a
|
||||
* widgetBuilder API method
|
||||
* @param {string} selector - CSS ID of the wrapper, including the `#`
|
||||
* @param {Object} apiConfig - PayPal configuration object; retrieved via a
|
||||
* widgetBuilder API method
|
||||
* @param {string} methodName - Name of the payment method, e.g. "Google Pay"
|
||||
*/
|
||||
constructor( { selector, apiConfig } ) {
|
||||
constructor( { selector, apiConfig, methodName = '' } ) {
|
||||
this.apiConfig = apiConfig;
|
||||
this.defaultAttributes = {};
|
||||
this.buttonConfig = {};
|
||||
this.ppcpConfig = {};
|
||||
this.isDynamic = true;
|
||||
this.methodName = methodName;
|
||||
this.methodSlug = this.methodName
|
||||
.toLowerCase()
|
||||
.replace( /[^a-z]+/g, '' );
|
||||
|
||||
// The selector is usually overwritten in constructor of derived class.
|
||||
this.selector = selector;
|
||||
|
@ -26,13 +31,16 @@ class PreviewButton {
|
|||
/**
|
||||
* Creates a new DOM node to contain the preview button.
|
||||
*
|
||||
* @return {jQuery} Always a single jQuery element with the new DOM node.
|
||||
* @return {HTMLElement} Always a single jQuery element with the new DOM node.
|
||||
*/
|
||||
createNewWrapper() {
|
||||
const wrapper = document.createElement( 'div' );
|
||||
const previewId = this.selector.replace( '#', '' );
|
||||
const previewClass = 'ppcp-button-apm';
|
||||
const previewClass = `ppcp-preview-button ppcp-button-apm ppcp-button-${ this.methodSlug }`;
|
||||
|
||||
return jQuery( `<div id='${ previewId }' class='${ previewClass }'>` );
|
||||
wrapper.setAttribute( 'id', previewId );
|
||||
wrapper.setAttribute( 'class', previewClass );
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -109,10 +117,12 @@ class PreviewButton {
|
|||
console.error( 'Skip render, button is not configured yet' );
|
||||
return;
|
||||
}
|
||||
|
||||
this.domWrapper = this.createNewWrapper();
|
||||
this.domWrapper.insertAfter( this.wrapper );
|
||||
this._insertWrapper();
|
||||
} else {
|
||||
this.domWrapper.empty().show();
|
||||
this._emptyWrapper();
|
||||
this._showWrapper();
|
||||
}
|
||||
|
||||
this.isVisible = true;
|
||||
|
@ -151,16 +161,38 @@ class PreviewButton {
|
|||
* Using a timeout here will make the button visible again at the end of the current
|
||||
* event queue.
|
||||
*/
|
||||
setTimeout( () => this.domWrapper.show() );
|
||||
setTimeout( () => this._showWrapper() );
|
||||
}
|
||||
|
||||
remove() {
|
||||
this.isVisible = false;
|
||||
|
||||
if ( this.domWrapper ) {
|
||||
this.domWrapper.hide().empty();
|
||||
this._hideWrapper();
|
||||
this._emptyWrapper();
|
||||
}
|
||||
}
|
||||
|
||||
_showWrapper() {
|
||||
this.domWrapper.style.display = '';
|
||||
}
|
||||
|
||||
_hideWrapper() {
|
||||
this.domWrapper.style.display = 'none';
|
||||
}
|
||||
|
||||
_emptyWrapper() {
|
||||
this.domWrapper.innerHTML = '';
|
||||
}
|
||||
|
||||
_insertWrapper() {
|
||||
const wrapperElement = document.querySelector( this.wrapper );
|
||||
|
||||
wrapperElement.parentNode.insertBefore(
|
||||
this.domWrapper,
|
||||
wrapperElement.nextSibling
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default PreviewButton;
|
|
@ -1,7 +1,8 @@
|
|||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
import widgetBuilder from './WidgetBuilder';
|
||||
import widgetBuilder from '../Renderer/WidgetBuilder';
|
||||
import { debounce } from '../../../../../ppcp-blocks/resources/js/Helper/debounce';
|
||||
import ConsoleLogger from '../../../../../ppcp-wc-gateway/resources/js/helper/ConsoleLogger';
|
||||
import DummyPreviewButton from './DummyPreviewButton';
|
||||
|
||||
/**
|
||||
* Manages all PreviewButton instances of a certain payment method on the page.
|
||||
|
@ -26,6 +27,13 @@ class PreviewButtonManager {
|
|||
*/
|
||||
#onInit;
|
||||
|
||||
/**
|
||||
* Initialize the new PreviewButtonManager.
|
||||
*
|
||||
* @param {string} methodName - Name of the payment method, e.g. "Google Pay"
|
||||
* @param {Object} buttonConfig
|
||||
* @param {Object} defaultAttributes
|
||||
*/
|
||||
constructor( { methodName, buttonConfig, defaultAttributes } ) {
|
||||
// Define the payment method name in the derived class.
|
||||
this.methodName = methodName;
|
||||
|
@ -102,27 +110,15 @@ class PreviewButtonManager {
|
|||
*
|
||||
* This dummy is only visible on the admin side, and not rendered on the front-end.
|
||||
*
|
||||
* @todo Consider refactoring this into a new class that extends the PreviewButton class.
|
||||
* @param {string} wrapperId
|
||||
* @return {any}
|
||||
*/
|
||||
createDummy( wrapperId ) {
|
||||
const elButton = document.createElement( 'div' );
|
||||
elButton.classList.add( 'ppcp-button-apm', 'ppcp-button-dummy' );
|
||||
elButton.innerHTML = `<span>${
|
||||
this.apiError ?? 'Not Available'
|
||||
}</span>`;
|
||||
|
||||
document.querySelector( wrapperId ).appendChild( elButton );
|
||||
|
||||
const instDummy = {
|
||||
setDynamic: () => instDummy,
|
||||
setPpcpConfig: () => instDummy,
|
||||
render: () => {},
|
||||
remove: () => {},
|
||||
};
|
||||
|
||||
return instDummy;
|
||||
createDummyButtonInstance( wrapperId ) {
|
||||
return new DummyPreviewButton( {
|
||||
selector: wrapperId,
|
||||
label: this.apiError,
|
||||
methodName: this.methodName,
|
||||
} );
|
||||
}
|
||||
|
||||
registerEventListeners() {
|
||||
|
@ -319,6 +315,17 @@ class PreviewButtonManager {
|
|||
this.log( 'configureAllButtons', ppcpConfig );
|
||||
|
||||
Object.entries( this.buttons ).forEach( ( [ id, button ] ) => {
|
||||
const limitWrapper = ppcpConfig.button?.wrapper;
|
||||
|
||||
/**
|
||||
* When the ppcpConfig object specifies a button wrapper, then ensure to limit preview
|
||||
* changes to this individual wrapper. If no button wrapper is defined, the
|
||||
* configuration is relevant for all buttons on the page.
|
||||
*/
|
||||
if ( limitWrapper && button.wrapper !== limitWrapper ) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._configureButton( id, {
|
||||
...ppcpConfig,
|
||||
button: {
|
||||
|
@ -349,12 +356,11 @@ class PreviewButtonManager {
|
|||
let newInst;
|
||||
|
||||
if ( this.apiConfig && 'object' === typeof this.apiConfig ) {
|
||||
newInst = this.createButtonInstance( id ).setButtonConfig(
|
||||
this.buttonConfig
|
||||
);
|
||||
newInst = this.createButtonInstance( id );
|
||||
} else {
|
||||
newInst = this.createDummy( id );
|
||||
newInst = this.createDummyButtonInstance( id );
|
||||
}
|
||||
newInst.setButtonConfig( this.buttonConfig );
|
||||
|
||||
this.buttons[ id ] = newInst;
|
||||
}
|
|
@ -625,6 +625,15 @@ export default class PaymentButton {
|
|||
this.#logger.error( ...args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Open or close a log-group
|
||||
*
|
||||
* @param {?string} [label=null] Group label.
|
||||
*/
|
||||
logGroup( label = null ) {
|
||||
this.#logger.group( label );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the current button instance has valid and complete configuration details.
|
||||
* Used during initialization to decide if the button can be initialized or should be skipped.
|
||||
|
|
|
@ -228,7 +228,6 @@ class Renderer {
|
|||
};
|
||||
|
||||
shouldEnableShippingCallback = () => {
|
||||
console.log(this.defaultSettings.context, this.defaultSettings)
|
||||
let needShipping = this.defaultSettings.needShipping || this.defaultSettings.context === 'product'
|
||||
return this.defaultSettings.should_handle_shipping_in_paypal && needShipping
|
||||
};
|
||||
|
|
|
@ -320,6 +320,7 @@ return array(
|
|||
},
|
||||
'button.helper.messages-apply' => static function ( ContainerInterface $container ): MessagesApply {
|
||||
return new MessagesApply(
|
||||
$container->get( 'api.paylater-countries' ),
|
||||
$container->get( 'api.shop.country' )
|
||||
);
|
||||
},
|
||||
|
|
|
@ -14,8 +14,6 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
|
||||
|
@ -23,29 +21,36 @@ use WooCommerce\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
|
|||
use WooCommerce\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\StartPayPalVaultingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class ButtonModule
|
||||
*/
|
||||
class ButtonModule implements ModuleInterface {
|
||||
|
||||
class ButtonModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
|
||||
add_action(
|
||||
'wp',
|
||||
|
@ -91,6 +96,8 @@ class ButtonModule implements ModuleInterface {
|
|||
);
|
||||
|
||||
$this->register_ajax_endpoints( $c );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -211,12 +218,4 @@ class ButtonModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
}
|
||||
}
|
||||
|
|
|
@ -85,7 +85,8 @@ class SimulateCartEndpoint extends AbstractCartEndpoint {
|
|||
$this->add_products( $products );
|
||||
|
||||
$this->cart->calculate_totals();
|
||||
$total = (float) $this->cart->get_total( 'numeric' );
|
||||
$total = (float) $this->cart->get_total( 'numeric' );
|
||||
$shipping_fee = (float) $this->cart->get_shipping_total();
|
||||
|
||||
$this->restore_real_cart();
|
||||
|
||||
|
@ -113,7 +114,7 @@ class SimulateCartEndpoint extends AbstractCartEndpoint {
|
|||
wp_send_json_success(
|
||||
array(
|
||||
'total' => $total,
|
||||
'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
|
||||
'shipping_fee' => $shipping_fee,
|
||||
'currency_code' => $currency_code,
|
||||
'country_code' => $shop_country_code,
|
||||
'funding' => array(
|
||||
|
|
|
@ -243,4 +243,20 @@ trait ContextTrait {
|
|||
$screen = get_current_screen();
|
||||
return $screen && $screen->is_block_editor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if is WooCommerce Settings Payments tab screen (/wp-admin/admin.php?page=wc-settings&tab=checkout).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_wc_settings_payments_tab(): bool {
|
||||
if ( ! is_admin() || isset( $_GET['section'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
|
||||
return false;
|
||||
}
|
||||
|
||||
$page = wc_clean( wp_unslash( $_GET['page'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
|
||||
$tab = wc_clean( wp_unslash( $_GET['tab'] ?? '' ) ); // phpcs:ignore WordPress.Security.NonceVerification
|
||||
|
||||
return $page === 'wc-settings' && $tab === 'checkout';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,17 +18,9 @@ class MessagesApply {
|
|||
/**
|
||||
* In which countries credit messaging is available.
|
||||
*
|
||||
* @var array
|
||||
* @var string[]
|
||||
*/
|
||||
private $countries = array(
|
||||
'US',
|
||||
'DE',
|
||||
'GB',
|
||||
'FR',
|
||||
'AU',
|
||||
'IT',
|
||||
'ES',
|
||||
);
|
||||
private $allowed_countries;
|
||||
|
||||
/**
|
||||
* 2-letter country code of the shop.
|
||||
|
@ -40,10 +32,12 @@ class MessagesApply {
|
|||
/**
|
||||
* MessagesApply constructor.
|
||||
*
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
* @param string[] $allowed_countries In which countries credit messaging is available.
|
||||
* @param string $country 2-letter country code of the shop.
|
||||
*/
|
||||
public function __construct( string $country ) {
|
||||
$this->country = $country;
|
||||
public function __construct( array $allowed_countries, string $country ) {
|
||||
$this->allowed_countries = $allowed_countries;
|
||||
$this->country = $country;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -52,6 +46,6 @@ class MessagesApply {
|
|||
* @return bool
|
||||
*/
|
||||
public function for_country(): bool {
|
||||
return in_array( $this->country, $this->countries, true );
|
||||
return in_array( $this->country, $this->allowed_countries, true );
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Advanced Checkout Card Fields module",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\CardFields;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): CardFieldsModule {
|
||||
return new CardFieldsModule();
|
||||
};
|
||||
|
|
|
@ -1,47 +1,35 @@
|
|||
import { cardFieldStyles } from './CardFieldsHelper';
|
||||
import { hide } from '../../../ppcp-button/resources/js/modules/Helper/Hiding';
|
||||
|
||||
function renderField( cardField, inputField ) {
|
||||
if ( ! inputField || inputField.hidden || ! cardField ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Insert the PayPal card field after the original input field.
|
||||
const styles = cardFieldStyles( inputField );
|
||||
cardField( { style: { input: styles } } ).render( inputField.parentNode );
|
||||
|
||||
// Hide the original input field.
|
||||
hide( inputField, true );
|
||||
inputField.hidden = true;
|
||||
}
|
||||
|
||||
export function renderFields( cardFields ) {
|
||||
const nameField = document.getElementById(
|
||||
'ppcp-credit-card-gateway-card-name'
|
||||
renderField(
|
||||
cardFields.NameField,
|
||||
document.getElementById( 'ppcp-credit-card-gateway-card-name' )
|
||||
);
|
||||
if ( nameField && nameField.hidden !== true ) {
|
||||
const styles = cardFieldStyles( nameField );
|
||||
cardFields
|
||||
.NameField( { style: { input: styles } } )
|
||||
.render( nameField.parentNode );
|
||||
nameField.hidden = true;
|
||||
}
|
||||
|
||||
const numberField = document.getElementById(
|
||||
'ppcp-credit-card-gateway-card-number'
|
||||
renderField(
|
||||
cardFields.NumberField,
|
||||
document.getElementById( 'ppcp-credit-card-gateway-card-number' )
|
||||
);
|
||||
if ( numberField && numberField.hidden !== true ) {
|
||||
const styles = cardFieldStyles( numberField );
|
||||
cardFields
|
||||
.NumberField( { style: { input: styles } } )
|
||||
.render( numberField.parentNode );
|
||||
numberField.hidden = true;
|
||||
}
|
||||
|
||||
const expiryField = document.getElementById(
|
||||
'ppcp-credit-card-gateway-card-expiry'
|
||||
renderField(
|
||||
cardFields.ExpiryField,
|
||||
document.getElementById( 'ppcp-credit-card-gateway-card-expiry' )
|
||||
);
|
||||
if ( expiryField && expiryField.hidden !== true ) {
|
||||
const styles = cardFieldStyles( expiryField );
|
||||
cardFields
|
||||
.ExpiryField( { style: { input: styles } } )
|
||||
.render( expiryField.parentNode );
|
||||
expiryField.hidden = true;
|
||||
}
|
||||
|
||||
const cvvField = document.getElementById(
|
||||
'ppcp-credit-card-gateway-card-cvc'
|
||||
renderField(
|
||||
cardFields.CVVField,
|
||||
document.getElementById( 'ppcp-credit-card-gateway-card-cvc' )
|
||||
);
|
||||
if ( cvvField && cvvField.hidden !== true ) {
|
||||
const styles = cardFieldStyles( cvvField );
|
||||
cardFields
|
||||
.CVVField( { style: { input: styles } } )
|
||||
.render( cvvField.parentNode );
|
||||
cvvField.hidden = true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,9 +9,10 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\CardFields;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
@ -19,30 +20,35 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
/**
|
||||
* Class CardFieldsModule
|
||||
*/
|
||||
class CardFieldsModule implements ModuleInterface {
|
||||
class CardFieldsModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
if ( ! $c->get( 'card-fields.eligible' ) ) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
$settings = $c->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof Settings );
|
||||
if ( ! $settings->has( 'dcc_enabled' ) || ! $settings->get( 'dcc_enabled' ) ) {
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -137,5 +143,7 @@ class CardFieldsModule implements ModuleInterface {
|
|||
10,
|
||||
2
|
||||
);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
"description": "Compatibility module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Compat;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): CompatModule {
|
||||
return new CompatModule();
|
||||
};
|
||||
|
|
|
@ -14,9 +14,10 @@ use Psr\Log\LoggerInterface;
|
|||
use WC_Cart;
|
||||
use WC_Order;
|
||||
use WC_Order_Item_Product;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExecutableModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ExtendingModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ModuleClassNameIdTrait;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Inpsyde\Modularity\Module\ServiceModule;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\Compat\Assets\CompatAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
|
@ -26,17 +27,21 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
/**
|
||||
* Class CompatModule
|
||||
*/
|
||||
class CompatModule implements ModuleInterface {
|
||||
class CompatModule implements ServiceModule, ExtendingModule, ExecutableModule {
|
||||
use ModuleClassNameIdTrait;
|
||||
|
||||
/**
|
||||
* Setup the compatibility module.
|
||||
*
|
||||
* @return ServiceProviderInterface
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
public function services(): array {
|
||||
return require __DIR__ . '/../services.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function extensions(): array {
|
||||
return require __DIR__ . '/../extensions.php';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,7 +49,7 @@ class CompatModule implements ModuleInterface {
|
|||
*
|
||||
* @throws NotFoundException
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
public function run( ContainerInterface $c ): bool {
|
||||
|
||||
$this->initialize_ppec_compat_layer( $c );
|
||||
$this->initialize_tracking_compat_layer( $c );
|
||||
|
@ -73,14 +78,8 @@ class CompatModule implements ModuleInterface {
|
|||
if ( $is_wc_bookings_active ) {
|
||||
$this->initialize_wc_bookings_compat_layer( $logger );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 19 KiB |
36
modules/ppcp-googlepay/assets/images/googlepay.svg
Normal file
36
modules/ppcp-googlepay/assets/images/googlepay.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<svg version="1.1" width="92px" height="50px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
||||
x="0px" y="0px" viewBox="160 160 774 422" enable-background="new 0 0 1094 742" xml:space="preserve">
|
||||
<path id="Base_1_" fill="#FFFFFF" d="M722.7,170h-352c-110,0-200,90-200,200l0,0c0,110,90,200,200,200h352c110,0,200-90,200-200l0,0
|
||||
C922.7,260,832.7,170,722.7,170z"/>
|
||||
<path id="Outline" fill="#3C4043" d="M722.7,186.2c24.7,0,48.7,4.9,71.3,14.5c21.9,9.3,41.5,22.6,58.5,39.5
|
||||
c16.9,16.9,30.2,36.6,39.5,58.5c9.6,22.6,14.5,46.6,14.5,71.3s-4.9,48.7-14.5,71.3c-9.3,21.9-22.6,41.5-39.5,58.5
|
||||
c-16.9,16.9-36.6,30.2-58.5,39.5c-22.6,9.6-46.6,14.5-71.3,14.5h-352c-24.7,0-48.7-4.9-71.3-14.5c-21.9-9.3-41.5-22.6-58.5-39.5
|
||||
c-16.9-16.9-30.2-36.6-39.5-58.5c-9.6-22.6-14.5-46.6-14.5-71.3s4.9-48.7,14.5-71.3c9.3-21.9,22.6-41.5,39.5-58.5
|
||||
c16.9-16.9,36.6-30.2,58.5-39.5c22.6-9.6,46.6-14.5,71.3-14.5L722.7,186.2 M722.7,170h-352c-110,0-200,90-200,200l0,0
|
||||
c0,110,90,200,200,200h352c110,0,200-90,200-200l0,0C922.7,260,832.7,170,722.7,170L722.7,170z"/>
|
||||
<g id="G_Pay_Lockup_1_">
|
||||
<g id="Pay_Typeface_3_">
|
||||
<path id="Letter_p_3_" fill="#3C4043" d="M529.3,384.2v60.5h-19.2V295.3H561c12.9,0,23.9,4.3,32.9,12.9
|
||||
c9.2,8.6,13.8,19.1,13.8,31.5c0,12.7-4.6,23.2-13.8,31.7c-8.9,8.5-19.9,12.7-32.9,12.7h-31.7V384.2z M529.3,313.7v52.1h32.1
|
||||
c7.6,0,14-2.6,19-7.7c5.1-5.1,7.7-11.3,7.7-18.3c0-6.9-2.6-13-7.7-18.1c-5-5.3-11.3-7.9-19-7.9h-32.1V313.7z"/>
|
||||
<path id="Letter_a_3_" fill="#3C4043" d="M657.9,339.1c14.2,0,25.4,3.8,33.6,11.4c8.2,7.6,12.3,18,12.3,31.2v63h-18.3v-14.2h-0.8
|
||||
c-7.9,11.7-18.5,17.5-31.7,17.5c-11.3,0-20.7-3.3-28.3-10s-11.4-15-11.4-25c0-10.6,4-19,12-25.2c8-6.3,18.7-9.4,32-9.4
|
||||
c11.4,0,20.8,2.1,28.1,6.3v-4.4c0-6.7-2.6-12.3-7.9-17c-5.3-4.7-11.5-7-18.6-7c-10.7,0-19.2,4.5-25.4,13.6l-16.9-10.6
|
||||
C625.9,345.8,639.7,339.1,657.9,339.1z M633.1,413.3c0,5,2.1,9.2,6.4,12.5c4.2,3.3,9.2,5,14.9,5c8.1,0,15.3-3,21.6-9
|
||||
s9.5-13,9.5-21.1c-6-4.7-14.3-7.1-25-7.1c-7.8,0-14.3,1.9-19.5,5.6C635.7,403.1,633.1,407.8,633.1,413.3z"/>
|
||||
<path id="Letter_y_3_" fill="#3C4043" d="M808.2,342.4l-64,147.2h-19.8l23.8-51.5L706,342.4h20.9l30.4,73.4h0.4l29.6-73.4H808.2z"
|
||||
/>
|
||||
</g>
|
||||
<g id="G_Mark_1_">
|
||||
<path id="Blue_500" fill="#4285F4" d="M452.93,372c0-6.26-0.56-12.25-1.6-18.01h-80.48v33L417.2,387
|
||||
c-1.88,10.98-7.93,20.34-17.2,26.58v21.41h27.59C443.7,420.08,452.93,398.04,452.93,372z"/>
|
||||
<path id="Green_500_1_" fill="#34A853" d="M400.01,413.58c-7.68,5.18-17.57,8.21-29.14,8.21c-22.35,0-41.31-15.06-48.1-35.36
|
||||
h-28.46v22.08c14.1,27.98,43.08,47.18,76.56,47.18c23.14,0,42.58-7.61,56.73-20.71L400.01,413.58z"/>
|
||||
<path id="Yellow_500_1_" fill="#FABB05" d="M320.09,370.05c0-5.7,0.95-11.21,2.68-16.39v-22.08h-28.46
|
||||
c-5.83,11.57-9.11,24.63-9.11,38.47s3.29,26.9,9.11,38.47l28.46-22.08C321.04,381.26,320.09,375.75,320.09,370.05z"/>
|
||||
<path id="Red_500" fill="#E94235" d="M370.87,318.3c12.63,0,23.94,4.35,32.87,12.85l24.45-24.43
|
||||
c-14.85-13.83-34.21-22.32-57.32-22.32c-33.47,0-62.46,19.2-76.56,47.18l28.46,22.08C329.56,333.36,348.52,318.3,370.87,318.3z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 3 KiB |
|
@ -4,7 +4,7 @@
|
|||
"description": "Googlepay module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"php": "^7.4 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
@ -18,7 +18,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
|||
|
||||
return array(
|
||||
|
||||
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
|
||||
'wcgateway.settings.fields' => function ( array $fields, ContainerInterface $container ): array {
|
||||
|
||||
// Used in various places to mark fields for the preview button.
|
||||
$apm_name = 'GooglePay';
|
||||
|
@ -72,7 +72,7 @@ return array(
|
|||
'googlepay_button_enabled' => array(
|
||||
'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
|
||||
'title_html' => sprintf(
|
||||
'<img src="%sassets/images/googlepay.png" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
'<img src="%sassets/images/googlepay.svg" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
$module_url,
|
||||
__( 'Google Pay', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
|
@ -117,7 +117,7 @@ return array(
|
|||
'googlepay_button_enabled' => array(
|
||||
'title' => __( 'Google Pay Button', 'woocommerce-paypal-payments' ),
|
||||
'title_html' => sprintf(
|
||||
'<img src="%sassets/images/googlepay.png" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
'<img src="%sassets/images/googlepay.svg" alt="%s" style="max-width: 150px; max-height: 45px;" />',
|
||||
$module_url,
|
||||
__( 'Google Pay', 'woocommerce-paypal-payments' )
|
||||
),
|
||||
|
|
|
@ -9,8 +9,6 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Googlepay;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return static function (): GooglepayModule {
|
||||
return new GooglepayModule();
|
||||
};
|
||||
|
|
|
@ -13,6 +13,11 @@
|
|||
outline-offset: -1px;
|
||||
border-radius: var(--apm-button-border-radius);
|
||||
}
|
||||
|
||||
&.ppcp-preview-button.ppcp-button-dummy {
|
||||
/* URL must specify the correct module-folder! */
|
||||
--apm-button-dummy-background: url(../../../ppcp-googlepay/assets/images/googlepay.png);
|
||||
}
|
||||
}
|
||||
|
||||
.wp-block-woocommerce-checkout, .wp-block-woocommerce-cart {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
|
||||
import CartActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CartActionHandler';
|
||||
import TransactionInfo from '../Helper/TransactionInfo';
|
||||
|
||||
class BaseHandler {
|
||||
constructor( buttonConfig, ppcpConfig, externalHandler ) {
|
||||
|
@ -34,13 +35,14 @@ class BaseHandler {
|
|||
|
||||
// handle script reload
|
||||
const data = result.data;
|
||||
const transaction = new TransactionInfo(
|
||||
data.total,
|
||||
data.shipping_fee,
|
||||
data.currency_code,
|
||||
data.country_code
|
||||
);
|
||||
|
||||
resolve( {
|
||||
countryCode: data.country_code,
|
||||
currencyCode: data.currency_code,
|
||||
totalPriceStatus: 'FINAL',
|
||||
totalPrice: data.total_str,
|
||||
} );
|
||||
resolve( transaction );
|
||||
} );
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import Spinner from '../../../../ppcp-button/resources/js/modules/Helper/Spinner';
|
||||
import BaseHandler from './BaseHandler';
|
||||
import CheckoutActionHandler from '../../../../ppcp-button/resources/js/modules/ActionHandler/CheckoutActionHandler';
|
||||
import TransactionInfo from '../Helper/TransactionInfo';
|
||||
|
||||
class PayNowHandler extends BaseHandler {
|
||||
validateContext() {
|
||||
|
@ -14,12 +15,14 @@ class PayNowHandler extends BaseHandler {
|
|||
return new Promise( async ( resolve, reject ) => {
|
||||
const data = this.ppcpConfig.pay_now;
|
||||
|
||||
resolve( {
|
||||
countryCode: data.country_code,
|
||||
currencyCode: data.currency_code,
|
||||
totalPriceStatus: 'FINAL',
|
||||
totalPrice: data.total_str,
|
||||
} );
|
||||
const transaction = new TransactionInfo(
|
||||
data.total,
|
||||
data.shipping_fee,
|
||||
data.currency_code,
|
||||
data.country_code
|
||||
);
|
||||
|
||||
resolve( transaction );
|
||||
} );
|
||||
}
|
||||
|
||||
|
|
|
@ -3,6 +3,7 @@ import SimulateCart from '../../../../ppcp-button/resources/js/modules/Helper/Si
|
|||
import ErrorHandler from '../../../../ppcp-button/resources/js/modules/ErrorHandler';
|
||||
import UpdateCart from '../../../../ppcp-button/resources/js/modules/Helper/UpdateCart';
|
||||
import BaseHandler from './BaseHandler';
|
||||
import TransactionInfo from '../Helper/TransactionInfo';
|
||||
|
||||
class SingleProductHandler extends BaseHandler {
|
||||
validateContext() {
|
||||
|
@ -42,12 +43,14 @@ class SingleProductHandler extends BaseHandler {
|
|||
this.ppcpConfig.ajax.simulate_cart.endpoint,
|
||||
this.ppcpConfig.ajax.simulate_cart.nonce
|
||||
).simulate( ( data ) => {
|
||||
resolve( {
|
||||
countryCode: data.country_code,
|
||||
currencyCode: data.currency_code,
|
||||
totalPriceStatus: 'FINAL',
|
||||
totalPrice: data.total_str,
|
||||
} );
|
||||
const transaction = new TransactionInfo(
|
||||
data.total,
|
||||
data.shipping_fee,
|
||||
data.currency_code,
|
||||
data.country_code
|
||||
);
|
||||
|
||||
resolve( transaction );
|
||||
}, products );
|
||||
} );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,102 @@
|
|||
import { GooglePayStorage } from '../Helper/GooglePayStorage';
|
||||
import {
|
||||
getWooCommerceCustomerDetails,
|
||||
setPayerData,
|
||||
} from '../../../../ppcp-button/resources/js/modules/Helper/PayerData';
|
||||
|
||||
const CHECKOUT_FORM_SELECTOR = 'form.woocommerce-checkout';
|
||||
|
||||
export class CheckoutBootstrap {
|
||||
/**
|
||||
* @type {GooglePayStorage}
|
||||
*/
|
||||
#storage;
|
||||
|
||||
/**
|
||||
* @type {HTMLFormElement|null}
|
||||
*/
|
||||
#checkoutForm;
|
||||
|
||||
/**
|
||||
* @param {GooglePayStorage} storage
|
||||
*/
|
||||
constructor( storage ) {
|
||||
this.#storage = storage;
|
||||
this.#checkoutForm = CheckoutBootstrap.getCheckoutForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Indicates if the current page contains a checkout form.
|
||||
*
|
||||
* @return {boolean} True if a checkout form is present.
|
||||
*/
|
||||
static isPageWithCheckoutForm() {
|
||||
return null !== CheckoutBootstrap.getCheckoutForm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the WooCommerce checkout form element.
|
||||
*
|
||||
* @return {HTMLFormElement|null} The form, or null if not a checkout page.
|
||||
*/
|
||||
static getCheckoutForm() {
|
||||
return document.querySelector( CHECKOUT_FORM_SELECTOR );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the WooCommerce checkout form element.
|
||||
*
|
||||
* @return {HTMLFormElement|null} The form, or null if not a checkout page.
|
||||
*/
|
||||
get checkoutForm() {
|
||||
return this.#checkoutForm;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the checkout process.
|
||||
*
|
||||
* @throws {Error} If called on a page without a checkout form.
|
||||
*/
|
||||
init() {
|
||||
if ( ! this.#checkoutForm ) {
|
||||
throw new Error(
|
||||
'Checkout form not found. Cannot initialize CheckoutBootstrap.'
|
||||
);
|
||||
}
|
||||
|
||||
this.#populateCheckoutFields();
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates checkout fields with stored or customer data.
|
||||
*/
|
||||
#populateCheckoutFields() {
|
||||
const loggedInData = getWooCommerceCustomerDetails();
|
||||
|
||||
if ( loggedInData ) {
|
||||
// If customer is logged in, we use the details from the customer profile.
|
||||
return;
|
||||
}
|
||||
|
||||
const billingData = this.#storage.getPayer();
|
||||
|
||||
if ( ! billingData ) {
|
||||
return;
|
||||
}
|
||||
|
||||
setPayerData( billingData, true );
|
||||
this.checkoutForm.addEventListener(
|
||||
'submit',
|
||||
this.#onFormSubmit.bind( this )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean-up when checkout form is submitted.
|
||||
*
|
||||
* Immediately removes the payer details from the localStorage.
|
||||
*/
|
||||
#onFormSubmit() {
|
||||
this.#storage.clearPayer();
|
||||
}
|
||||
}
|
|
@ -5,7 +5,10 @@ import {
|
|||
import PaymentButton from '../../../ppcp-button/resources/js/modules/Renderer/PaymentButton';
|
||||
import widgetBuilder from '../../../ppcp-button/resources/js/modules/Renderer/WidgetBuilder';
|
||||
import UpdatePaymentData from './Helper/UpdatePaymentData';
|
||||
import TransactionInfo from './Helper/TransactionInfo';
|
||||
import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper/CheckoutMethodState';
|
||||
import { setPayerData } from '../../../ppcp-button/resources/js/modules/Helper/PayerData';
|
||||
import moduleStorage from './Helper/GooglePayStorage';
|
||||
|
||||
/**
|
||||
* Plugin-specific styling.
|
||||
|
@ -39,11 +42,17 @@ import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper
|
|||
*
|
||||
* @see https://developers.google.com/pay/api/web/reference/client
|
||||
* @typedef {Object} PaymentsClient
|
||||
* @property {Function} createButton - The convenience method is used to generate a Google Pay payment button styled with the latest Google Pay branding for insertion into a webpage.
|
||||
* @property {Function} isReadyToPay - Use the isReadyToPay(isReadyToPayRequest) method to determine a user's ability to return a form of payment from the Google Pay API.
|
||||
* @property {Function} loadPaymentData - This method presents a Google Pay payment sheet that allows selection of a payment method and optionally configured parameters
|
||||
* @property {Function} onPaymentAuthorized - This method is called when a payment is authorized in the payment sheet.
|
||||
* @property {Function} onPaymentDataChanged - This method handles payment data changes in the payment sheet such as shipping address and shipping options.
|
||||
* @property {Function} createButton - The convenience method is used to
|
||||
* generate a Google Pay payment button styled with the latest Google Pay branding for
|
||||
* insertion into a webpage.
|
||||
* @property {Function} isReadyToPay - Use the isReadyToPay(isReadyToPayRequest)
|
||||
* method to determine a user's ability to return a form of payment from the Google Pay API.
|
||||
* @property {(Object) => Promise} loadPaymentData - This method presents a Google Pay payment
|
||||
* sheet that allows selection of a payment method and optionally configured parameters
|
||||
* @property {Function} onPaymentAuthorized - This method is called when a payment is
|
||||
* authorized in the payment sheet.
|
||||
* @property {Function} onPaymentDataChanged - This method handles payment data changes
|
||||
* in the payment sheet such as shipping address and shipping options.
|
||||
*/
|
||||
|
||||
/**
|
||||
|
@ -53,14 +62,40 @@ import { PaymentMethods } from '../../../ppcp-button/resources/js/modules/Helper
|
|||
* @typedef {Object} TransactionInfo
|
||||
* @property {string} currencyCode - Required. The ISO 4217 alphabetic currency code.
|
||||
* @property {string} countryCode - Optional. required for EEA countries,
|
||||
* @property {string} transactionId - Optional. A unique ID that identifies a facilitation attempt. Highly encouraged for troubleshooting.
|
||||
* @property {string} totalPriceStatus - Required. [ESTIMATED|FINAL] The status of the total price used:
|
||||
* @property {string} totalPrice - Required. Total monetary value of the transaction with an optional decimal precision of two decimal places.
|
||||
* @property {Array} displayItems - Optional. A list of cart items shown in the payment sheet (e.g. subtotals, sales taxes, shipping charges, discounts etc.).
|
||||
* @property {string} totalPriceLabel - Optional. Custom label for the total price within the display items.
|
||||
* @property {string} checkoutOption - Optional. Affects the submit button text displayed in the Google Pay payment sheet.
|
||||
* @property {string} transactionId - Optional. A unique ID that identifies a facilitation
|
||||
* attempt. Highly encouraged for troubleshooting.
|
||||
* @property {string} totalPriceStatus - Required. [ESTIMATED|FINAL] The status of the total price
|
||||
* used:
|
||||
* @property {string} totalPrice - Required. Total monetary value of the transaction with an
|
||||
* optional decimal precision of two decimal places.
|
||||
* @property {Array} displayItems - Optional. A list of cart items shown in the payment sheet
|
||||
* (e.g. subtotals, sales taxes, shipping charges, discounts etc.).
|
||||
* @property {string} totalPriceLabel - Optional. Custom label for the total price within the
|
||||
* display items.
|
||||
* @property {string} checkoutOption - Optional. Affects the submit button text displayed in the
|
||||
* Google Pay payment sheet.
|
||||
*/
|
||||
|
||||
function payerDataFromPaymentResponse( response ) {
|
||||
const raw = response?.paymentMethodData?.info?.billingAddress;
|
||||
|
||||
return {
|
||||
email_address: response?.email,
|
||||
name: {
|
||||
given_name: raw.name.split( ' ' )[ 0 ], // Assuming first name is the first part
|
||||
surname: raw.name.split( ' ' ).slice( 1 ).join( ' ' ), // Assuming last name is the rest
|
||||
},
|
||||
address: {
|
||||
country_code: raw.countryCode,
|
||||
address_line_1: raw.address1,
|
||||
address_line_2: raw.address2,
|
||||
admin_area_1: raw.administrativeArea,
|
||||
admin_area_2: raw.locality,
|
||||
postal_code: raw.postalCode,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
class GooglepayButton extends PaymentButton {
|
||||
/**
|
||||
* @inheritDoc
|
||||
|
@ -78,7 +113,7 @@ class GooglepayButton extends PaymentButton {
|
|||
#paymentsClient = null;
|
||||
|
||||
/**
|
||||
* Details about the processed transaction.
|
||||
* Details about the processed transaction, provided to the Google SDK.
|
||||
*
|
||||
* @type {?TransactionInfo}
|
||||
*/
|
||||
|
@ -388,12 +423,14 @@ class GooglepayButton extends PaymentButton {
|
|||
const initiatePaymentRequest = () => {
|
||||
window.ppcpFundingSource = 'googlepay';
|
||||
const paymentDataRequest = this.paymentDataRequest();
|
||||
|
||||
this.log(
|
||||
'onButtonClick: paymentDataRequest',
|
||||
paymentDataRequest,
|
||||
this.context
|
||||
);
|
||||
this.paymentsClient.loadPaymentData( paymentDataRequest );
|
||||
|
||||
return this.paymentsClient.loadPaymentData( paymentDataRequest );
|
||||
};
|
||||
|
||||
const validateForm = () => {
|
||||
|
@ -434,28 +471,24 @@ class GooglepayButton extends PaymentButton {
|
|||
apiVersionMinor: 0,
|
||||
};
|
||||
|
||||
const googlePayConfig = this.googlePayConfig;
|
||||
const paymentDataRequest = Object.assign( {}, baseRequest );
|
||||
paymentDataRequest.allowedPaymentMethods =
|
||||
googlePayConfig.allowedPaymentMethods;
|
||||
paymentDataRequest.transactionInfo = this.transactionInfo;
|
||||
paymentDataRequest.merchantInfo = googlePayConfig.merchantInfo;
|
||||
const useShippingCallback = this.requiresShipping;
|
||||
const callbackIntents = [ 'PAYMENT_AUTHORIZATION' ];
|
||||
|
||||
if ( this.requiresShipping ) {
|
||||
paymentDataRequest.callbackIntents = [
|
||||
'SHIPPING_ADDRESS',
|
||||
'SHIPPING_OPTION',
|
||||
'PAYMENT_AUTHORIZATION',
|
||||
];
|
||||
paymentDataRequest.shippingAddressRequired = true;
|
||||
paymentDataRequest.shippingAddressParameters =
|
||||
this.shippingAddressParameters();
|
||||
paymentDataRequest.shippingOptionRequired = true;
|
||||
} else {
|
||||
paymentDataRequest.callbackIntents = [ 'PAYMENT_AUTHORIZATION' ];
|
||||
if ( useShippingCallback ) {
|
||||
callbackIntents.push( 'SHIPPING_ADDRESS', 'SHIPPING_OPTION' );
|
||||
}
|
||||
|
||||
return paymentDataRequest;
|
||||
return {
|
||||
...baseRequest,
|
||||
allowedPaymentMethods: this.googlePayConfig.allowedPaymentMethods,
|
||||
transactionInfo: this.transactionInfo.finalObject,
|
||||
merchantInfo: this.googlePayConfig.merchantInfo,
|
||||
callbackIntents,
|
||||
emailRequired: true,
|
||||
shippingAddressRequired: useShippingCallback,
|
||||
shippingOptionRequired: useShippingCallback,
|
||||
shippingAddressParameters: this.shippingAddressParameters(),
|
||||
};
|
||||
}
|
||||
|
||||
//------------------------
|
||||
|
@ -481,6 +514,16 @@ class GooglepayButton extends PaymentButton {
|
|||
).update( paymentData );
|
||||
const transactionInfo = this.transactionInfo;
|
||||
|
||||
// Check, if the current context uses the WC cart.
|
||||
const hasRealCart = [
|
||||
'checkout-block',
|
||||
'checkout',
|
||||
'cart-block',
|
||||
'cart',
|
||||
'mini-cart',
|
||||
'pay-now',
|
||||
].includes( this.context );
|
||||
|
||||
this.log( 'onPaymentDataChanged:updatedData', updatedData );
|
||||
this.log(
|
||||
'onPaymentDataChanged:transactionInfo',
|
||||
|
@ -489,7 +532,6 @@ class GooglepayButton extends PaymentButton {
|
|||
|
||||
updatedData.country_code = transactionInfo.countryCode;
|
||||
updatedData.currency_code = transactionInfo.currencyCode;
|
||||
updatedData.total_str = transactionInfo.totalPrice;
|
||||
|
||||
// Handle unserviceable address.
|
||||
if ( ! updatedData.shipping_options?.shippingOptions?.length ) {
|
||||
|
@ -499,20 +541,37 @@ class GooglepayButton extends PaymentButton {
|
|||
return;
|
||||
}
|
||||
|
||||
switch ( paymentData.callbackTrigger ) {
|
||||
case 'INITIALIZE':
|
||||
case 'SHIPPING_ADDRESS':
|
||||
paymentDataRequestUpdate.newShippingOptionParameters =
|
||||
updatedData.shipping_options;
|
||||
paymentDataRequestUpdate.newTransactionInfo =
|
||||
this.calculateNewTransactionInfo( updatedData );
|
||||
break;
|
||||
case 'SHIPPING_OPTION':
|
||||
paymentDataRequestUpdate.newTransactionInfo =
|
||||
this.calculateNewTransactionInfo( updatedData );
|
||||
break;
|
||||
if (
|
||||
[ 'INITIALIZE', 'SHIPPING_ADDRESS' ].includes(
|
||||
paymentData.callbackTrigger
|
||||
)
|
||||
) {
|
||||
paymentDataRequestUpdate.newShippingOptionParameters =
|
||||
this.sanitizeShippingOptions(
|
||||
updatedData.shipping_options
|
||||
);
|
||||
}
|
||||
|
||||
if ( updatedData.total && hasRealCart ) {
|
||||
transactionInfo.setTotal(
|
||||
updatedData.total,
|
||||
updatedData.shipping_fee
|
||||
);
|
||||
|
||||
// This page contains a real cart and potentially a form for shipping options.
|
||||
this.syncShippingOptionWithForm(
|
||||
paymentData?.shippingOptionData?.id
|
||||
);
|
||||
} else {
|
||||
transactionInfo.shippingFee = this.getShippingCosts(
|
||||
paymentData?.shippingOptionData?.id,
|
||||
updatedData.shipping_options
|
||||
);
|
||||
}
|
||||
|
||||
paymentDataRequestUpdate.newTransactionInfo =
|
||||
this.calculateNewTransactionInfo( transactionInfo );
|
||||
|
||||
resolve( paymentDataRequestUpdate );
|
||||
} catch ( error ) {
|
||||
this.error( 'Error during onPaymentDataChanged:', error );
|
||||
|
@ -521,6 +580,76 @@ class GooglepayButton extends PaymentButton {
|
|||
} );
|
||||
}
|
||||
|
||||
/**
|
||||
* Google Pay throws an error, when the shippingOptions entries contain
|
||||
* custom properties. This function strips unsupported properties from the
|
||||
* provided ajax response.
|
||||
*
|
||||
* @param {Object} responseData Data returned from the ajax endpoint.
|
||||
* @return {Object} Sanitized object.
|
||||
*/
|
||||
sanitizeShippingOptions( responseData ) {
|
||||
// Sanitize the shipping options.
|
||||
const cleanOptions = responseData.shippingOptions.map( ( item ) => ( {
|
||||
id: item.id,
|
||||
label: item.label,
|
||||
description: item.description,
|
||||
} ) );
|
||||
|
||||
// Ensure that the default option is valid.
|
||||
let defaultOptionId = responseData.defaultSelectedOptionId;
|
||||
if ( ! cleanOptions.some( ( item ) => item.id === defaultOptionId ) ) {
|
||||
defaultOptionId = cleanOptions[ 0 ].id;
|
||||
}
|
||||
|
||||
return {
|
||||
defaultSelectedOptionId: defaultOptionId,
|
||||
shippingOptions: cleanOptions,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shipping costs as numeric value.
|
||||
*
|
||||
* TODO - Move this to the PaymentButton base class
|
||||
*
|
||||
* @param {string} shippingId - The shipping method ID.
|
||||
* @param {Object} shippingData - The PaymentDataRequest object that
|
||||
* contains shipping options.
|
||||
* @param {Array} shippingData.shippingOptions
|
||||
* @param {string} shippingData.defaultSelectedOptionId
|
||||
*
|
||||
* @return {number} The shipping costs.
|
||||
*/
|
||||
getShippingCosts(
|
||||
shippingId,
|
||||
{ shippingOptions = [], defaultSelectedOptionId = '' } = {}
|
||||
) {
|
||||
if ( ! shippingOptions?.length ) {
|
||||
this.log( 'Cannot calculate shipping cost: No Shipping Options' );
|
||||
return 0;
|
||||
}
|
||||
|
||||
const findOptionById = ( id ) =>
|
||||
shippingOptions.find( ( option ) => option.id === id );
|
||||
|
||||
const getValidShippingId = () => {
|
||||
if (
|
||||
'shipping_option_unselected' === shippingId ||
|
||||
! findOptionById( shippingId )
|
||||
) {
|
||||
// Entered on initial call, and when changing the shipping country.
|
||||
return defaultSelectedOptionId;
|
||||
}
|
||||
|
||||
return shippingId;
|
||||
};
|
||||
|
||||
const currentOption = findOptionById( getValidShippingId() );
|
||||
|
||||
return Number( currentOption?.cost ) || 0;
|
||||
}
|
||||
|
||||
unserviceableShippingAddressError() {
|
||||
return {
|
||||
reason: 'SHIPPING_ADDRESS_UNSERVICEABLE',
|
||||
|
@ -529,13 +658,14 @@ class GooglepayButton extends PaymentButton {
|
|||
};
|
||||
}
|
||||
|
||||
calculateNewTransactionInfo( updatedData ) {
|
||||
return {
|
||||
countryCode: updatedData.country_code,
|
||||
currencyCode: updatedData.currency_code,
|
||||
totalPriceStatus: 'FINAL',
|
||||
totalPrice: updatedData.total_str,
|
||||
};
|
||||
/**
|
||||
* Recalculates and returns the plain transaction info object.
|
||||
*
|
||||
* @param {TransactionInfo} transactionInfo - Internal transactionInfo instance.
|
||||
* @return {{totalPrice: string, countryCode: string, totalPriceStatus: string, currencyCode: string}} Updated details.
|
||||
*/
|
||||
calculateNewTransactionInfo( transactionInfo ) {
|
||||
return transactionInfo.finalObject;
|
||||
}
|
||||
|
||||
//------------------------
|
||||
|
@ -543,83 +673,111 @@ class GooglepayButton extends PaymentButton {
|
|||
//------------------------
|
||||
|
||||
onPaymentAuthorized( paymentData ) {
|
||||
this.log( 'onPaymentAuthorized' );
|
||||
this.log( 'onPaymentAuthorized', paymentData );
|
||||
|
||||
return this.processPayment( paymentData );
|
||||
}
|
||||
|
||||
async processPayment( paymentData ) {
|
||||
this.log( 'processPayment' );
|
||||
this.logGroup( 'processPayment' );
|
||||
|
||||
return new Promise( async ( resolve, reject ) => {
|
||||
try {
|
||||
const id = await this.contextHandler.createOrder();
|
||||
const payer = payerDataFromPaymentResponse( paymentData );
|
||||
|
||||
this.log( 'processPayment: createOrder', id );
|
||||
const paymentError = ( reason ) => {
|
||||
this.error( reason );
|
||||
|
||||
const confirmOrderResponse = await widgetBuilder.paypal
|
||||
.Googlepay()
|
||||
.confirmOrder( {
|
||||
orderId: id,
|
||||
paymentMethodData: paymentData.paymentMethodData,
|
||||
} );
|
||||
return this.processPaymentResponse(
|
||||
'ERROR',
|
||||
'PAYMENT_AUTHORIZATION',
|
||||
reason
|
||||
);
|
||||
};
|
||||
|
||||
this.log(
|
||||
'processPayment: confirmOrder',
|
||||
confirmOrderResponse
|
||||
);
|
||||
const checkPayPalApproval = async ( orderId ) => {
|
||||
const confirmationData = {
|
||||
orderId,
|
||||
paymentMethodData: paymentData.paymentMethodData,
|
||||
};
|
||||
|
||||
/** Capture the Order on the Server */
|
||||
if ( confirmOrderResponse.status === 'APPROVED' ) {
|
||||
let approveFailed = false;
|
||||
await this.contextHandler.approveOrder(
|
||||
{
|
||||
orderID: id,
|
||||
},
|
||||
{
|
||||
// actions mock object.
|
||||
restart: () =>
|
||||
new Promise( ( resolve, reject ) => {
|
||||
approveFailed = true;
|
||||
resolve();
|
||||
} ),
|
||||
order: {
|
||||
get: () =>
|
||||
new Promise( ( resolve, reject ) => {
|
||||
resolve( null );
|
||||
} ),
|
||||
},
|
||||
}
|
||||
);
|
||||
const confirmOrderResponse = await widgetBuilder.paypal
|
||||
.Googlepay()
|
||||
.confirmOrder( confirmationData );
|
||||
|
||||
if ( ! approveFailed ) {
|
||||
resolve( this.processPaymentResponse( 'SUCCESS' ) );
|
||||
} else {
|
||||
resolve(
|
||||
this.processPaymentResponse(
|
||||
'ERROR',
|
||||
'PAYMENT_AUTHORIZATION',
|
||||
'FAILED TO APPROVE'
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
resolve(
|
||||
this.processPaymentResponse(
|
||||
'ERROR',
|
||||
'PAYMENT_AUTHORIZATION',
|
||||
'TRANSACTION FAILED'
|
||||
)
|
||||
);
|
||||
this.log( 'confirmOrder', confirmOrderResponse );
|
||||
|
||||
return 'APPROVED' === confirmOrderResponse?.status;
|
||||
};
|
||||
|
||||
/**
|
||||
* This approval mainly confirms that the orderID is valid.
|
||||
*
|
||||
* It's still needed because this handler redirects to the checkout page if the server-side
|
||||
* approval was successful.
|
||||
*
|
||||
* @param {string} orderID
|
||||
*/
|
||||
const approveOrderServerSide = async ( orderID ) => {
|
||||
let isApproved = true;
|
||||
|
||||
this.log( 'approveOrder', orderID );
|
||||
|
||||
await this.contextHandler.approveOrder(
|
||||
{ orderID, payer },
|
||||
{
|
||||
restart: () =>
|
||||
new Promise( ( resolve ) => {
|
||||
isApproved = false;
|
||||
resolve();
|
||||
} ),
|
||||
order: {
|
||||
get: () =>
|
||||
new Promise( ( resolve ) => {
|
||||
resolve( null );
|
||||
} ),
|
||||
},
|
||||
}
|
||||
} catch ( err ) {
|
||||
resolve(
|
||||
this.processPaymentResponse(
|
||||
'ERROR',
|
||||
'PAYMENT_AUTHORIZATION',
|
||||
err.message
|
||||
)
|
||||
);
|
||||
);
|
||||
|
||||
return isApproved;
|
||||
};
|
||||
|
||||
const processPaymentPromise = async ( resolve ) => {
|
||||
const id = await this.contextHandler.createOrder();
|
||||
|
||||
this.log( 'createOrder', id );
|
||||
|
||||
const isApprovedByPayPal = await checkPayPalApproval( id );
|
||||
|
||||
if ( ! isApprovedByPayPal ) {
|
||||
resolve( paymentError( 'TRANSACTION FAILED' ) );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This must be the last step in the process, as it initiates a redirect.
|
||||
const success = await approveOrderServerSide( id );
|
||||
|
||||
if ( success ) {
|
||||
resolve( this.processPaymentResponse( 'SUCCESS' ) );
|
||||
} else {
|
||||
resolve( paymentError( 'FAILED TO APPROVE' ) );
|
||||
}
|
||||
};
|
||||
|
||||
const addBillingDataToSession = () => {
|
||||
moduleStorage.setPayer( payer );
|
||||
setPayerData( payer );
|
||||
};
|
||||
|
||||
return new Promise( async ( resolve ) => {
|
||||
try {
|
||||
addBillingDataToSession();
|
||||
await processPaymentPromise( resolve );
|
||||
} catch ( err ) {
|
||||
resolve( paymentError( err.message ) );
|
||||
}
|
||||
|
||||
this.logGroup();
|
||||
} );
|
||||
}
|
||||
|
||||
|
@ -639,6 +797,55 @@ class GooglepayButton extends PaymentButton {
|
|||
|
||||
return response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the shipping option in the checkout form, if a form with shipping options is
|
||||
* detected.
|
||||
*
|
||||
* @param {string} shippingOption - The shipping option ID, e.g. "flat_rate:4".
|
||||
* @return {boolean} - True if a shipping option was found and selected, false otherwise.
|
||||
*/
|
||||
syncShippingOptionWithForm( shippingOption ) {
|
||||
const wrappers = [
|
||||
// Classic checkout, Classic cart.
|
||||
'.woocommerce-shipping-methods',
|
||||
// Block checkout.
|
||||
'.wc-block-components-shipping-rates-control',
|
||||
// Block cart.
|
||||
'.wc-block-components-totals-shipping',
|
||||
];
|
||||
|
||||
const sanitizedShippingOption = shippingOption.replace( /"/g, '' );
|
||||
|
||||
// Check for radio buttons with shipping options.
|
||||
for ( const wrapper of wrappers ) {
|
||||
const selector = `${ wrapper } input[type="radio"][value="${ sanitizedShippingOption }"]`;
|
||||
const radioInput = document.querySelector( selector );
|
||||
|
||||
if ( radioInput ) {
|
||||
radioInput.click();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for select list with shipping options.
|
||||
for ( const wrapper of wrappers ) {
|
||||
const selector = `${ wrapper } select option[value="${ sanitizedShippingOption }"]`;
|
||||
const selectOption = document.querySelector( selector );
|
||||
|
||||
if ( selectOption ) {
|
||||
const selectElement = selectOption.closest( 'select' );
|
||||
|
||||
if ( selectElement ) {
|
||||
selectElement.value = sanitizedShippingOption;
|
||||
selectElement.dispatchEvent( new Event( 'change' ) );
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export default GooglepayButton;
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
import { LocalStorage } from '../../../../ppcp-button/resources/js/modules/Helper/LocalStorage';
|
||||
|
||||
export class GooglePayStorage extends LocalStorage {
|
||||
static PAYER = 'payer';
|
||||
static PAYER_TTL = 900; // 15 minutes in seconds
|
||||
|
||||
constructor() {
|
||||
super( 'ppcp-googlepay' );
|
||||
}
|
||||
|
||||
getPayer() {
|
||||
return this.get( GooglePayStorage.PAYER );
|
||||
}
|
||||
|
||||
setPayer( data ) {
|
||||
/*
|
||||
* The payer details are deleted on successful checkout, or after the TTL is reached.
|
||||
* This helps to remove stale data from the browser, in case the customer chooses to
|
||||
* use a different method to complete the purchase.
|
||||
*/
|
||||
this.set( GooglePayStorage.PAYER, data, GooglePayStorage.PAYER_TTL );
|
||||
}
|
||||
|
||||
clearPayer() {
|
||||
this.clear( GooglePayStorage.PAYER );
|
||||
}
|
||||
}
|
||||
|
||||
const moduleStorage = new GooglePayStorage();
|
||||
|
||||
export default moduleStorage;
|
|
@ -0,0 +1,73 @@
|
|||
export default class TransactionInfo {
|
||||
#country = '';
|
||||
#currency = '';
|
||||
#amount = 0;
|
||||
#shippingFee = 0;
|
||||
|
||||
constructor( total, shippingFee, currency, country ) {
|
||||
this.#country = country;
|
||||
this.#currency = currency;
|
||||
|
||||
this.shippingFee = shippingFee;
|
||||
this.amount = total - shippingFee;
|
||||
}
|
||||
|
||||
set amount( newAmount ) {
|
||||
this.#amount = this.toAmount( newAmount );
|
||||
}
|
||||
|
||||
get amount() {
|
||||
return this.#amount;
|
||||
}
|
||||
|
||||
set shippingFee( newCost ) {
|
||||
this.#shippingFee = this.toAmount( newCost );
|
||||
}
|
||||
|
||||
get shippingFee() {
|
||||
return this.#shippingFee;
|
||||
}
|
||||
|
||||
get currencyCode() {
|
||||
return this.#currency;
|
||||
}
|
||||
|
||||
get countryCode() {
|
||||
return this.#country;
|
||||
}
|
||||
|
||||
get totalPrice() {
|
||||
const total = this.#amount + this.#shippingFee;
|
||||
|
||||
return total.toFixed( 2 );
|
||||
}
|
||||
|
||||
get finalObject() {
|
||||
return {
|
||||
countryCode: this.countryCode,
|
||||
currencyCode: this.currencyCode,
|
||||
totalPriceStatus: 'FINAL',
|
||||
totalPrice: this.totalPrice,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the value to a number and rounds to a precision of 2 digits.
|
||||
*
|
||||
* @param {any} value - The value to sanitize.
|
||||
* @return {number} Numeric value.
|
||||
*/
|
||||
toAmount( value ) {
|
||||
value = Number( value ) || 0;
|
||||
return Math.round( value * 100 ) / 100;
|
||||
}
|
||||
|
||||
setTotal( totalPrice, shippingFee ) {
|
||||
totalPrice = this.toAmount( totalPrice );
|
||||
|
||||
if ( totalPrice ) {
|
||||
this.shippingFee = shippingFee;
|
||||
this.amount = totalPrice - this.shippingFee;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
import GooglepayButton from '../GooglepayButton';
|
||||
import PreviewButton from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButton';
|
||||
|
||||
/**
|
||||
* A single GooglePay preview button instance.
|
||||
*/
|
||||
export default class GooglePayPreviewButton extends PreviewButton {
|
||||
constructor( args ) {
|
||||
super( args );
|
||||
|
||||
this.selector = `${ args.selector }GooglePay`;
|
||||
this.defaultAttributes = {
|
||||
button: {
|
||||
style: {
|
||||
type: 'pay',
|
||||
color: 'black',
|
||||
language: 'en',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
createButton( buttonConfig ) {
|
||||
const button = new GooglepayButton(
|
||||
'preview',
|
||||
null,
|
||||
buttonConfig,
|
||||
this.ppcpConfig
|
||||
);
|
||||
|
||||
button.init( this.apiConfig );
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge form details into the config object for preview.
|
||||
* Mutates the previewConfig object; no return value.
|
||||
* @param buttonConfig
|
||||
* @param ppcpConfig
|
||||
*/
|
||||
dynamicPreviewConfig( buttonConfig, ppcpConfig ) {
|
||||
// Merge the current form-values into the preview-button configuration.
|
||||
if ( ppcpConfig.button && buttonConfig.button ) {
|
||||
Object.assign( buttonConfig.button.style, ppcpConfig.button.style );
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,57 @@
|
|||
import PreviewButtonManager from '../../../../ppcp-button/resources/js/modules/Preview/PreviewButtonManager';
|
||||
import GooglePayPreviewButton from './GooglePayPreviewButton';
|
||||
|
||||
/**
|
||||
* Manages all GooglePay preview buttons on this page.
|
||||
*/
|
||||
export default class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||
constructor() {
|
||||
const args = {
|
||||
methodName: 'GooglePay',
|
||||
buttonConfig: window.wc_ppcp_googlepay_admin,
|
||||
};
|
||||
|
||||
super( args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||
* method.
|
||||
*
|
||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||
* @return {Promise<{}>}
|
||||
*/
|
||||
async fetchConfig( payPal ) {
|
||||
const apiMethod = payPal?.Googlepay()?.config;
|
||||
|
||||
if ( ! apiMethod ) {
|
||||
this.error(
|
||||
'configuration object cannot be retrieved from PayPal'
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return await apiMethod();
|
||||
} catch ( error ) {
|
||||
if ( error.message.includes( 'Not Eligible' ) ) {
|
||||
this.apiError = 'Not Eligible';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||
*
|
||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||
* @return {GooglePayPreviewButton}
|
||||
*/
|
||||
createButtonInstance( wrapperId ) {
|
||||
return new GooglePayPreviewButton( {
|
||||
selector: wrapperId,
|
||||
apiConfig: this.apiConfig,
|
||||
methodName: this.methodName,
|
||||
} );
|
||||
}
|
||||
}
|
|
@ -1,5 +1,4 @@
|
|||
import PreviewButtonManager from '../../../ppcp-button/resources/js/modules/Renderer/PreviewButtonManager';
|
||||
import GooglePayPreviewButton from './GooglepayPreviewButton';
|
||||
import GooglePayPreviewButtonManager from './Preview/GooglePayPreviewButtonManager';
|
||||
|
||||
/**
|
||||
* Accessor that creates and returns a single PreviewButtonManager instance.
|
||||
|
@ -13,59 +12,5 @@ const buttonManager = () => {
|
|||
return GooglePayPreviewButtonManager.instance;
|
||||
};
|
||||
|
||||
/**
|
||||
* Manages all GooglePay preview buttons on this page.
|
||||
*/
|
||||
class GooglePayPreviewButtonManager extends PreviewButtonManager {
|
||||
constructor() {
|
||||
const args = {
|
||||
methodName: 'GooglePay',
|
||||
buttonConfig: window.wc_ppcp_googlepay_admin,
|
||||
};
|
||||
|
||||
super( args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Responsible for fetching and returning the PayPal configuration object for this payment
|
||||
* method.
|
||||
*
|
||||
* @param {{}} payPal - The PayPal SDK object provided by WidgetBuilder.
|
||||
* @return {Promise<{}>} Promise that resolves when API configuration is available.
|
||||
*/
|
||||
async fetchConfig( payPal ) {
|
||||
const apiMethod = payPal?.Googlepay()?.config;
|
||||
|
||||
if ( ! apiMethod ) {
|
||||
this.error(
|
||||
'configuration object cannot be retrieved from PayPal'
|
||||
);
|
||||
return {};
|
||||
}
|
||||
|
||||
try {
|
||||
return await apiMethod();
|
||||
} catch ( error ) {
|
||||
if ( error.message.includes( 'Not Eligible' ) ) {
|
||||
this.apiError = 'Not Eligible';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is responsible for creating a new PreviewButton instance and returning it.
|
||||
*
|
||||
* @param {string} wrapperId - CSS ID of the wrapper element.
|
||||
* @return {GooglePayPreviewButton} The new preview button instance.
|
||||
*/
|
||||
createButtonInstance( wrapperId ) {
|
||||
return new GooglePayPreviewButton( {
|
||||
selector: wrapperId,
|
||||
apiConfig: this.apiConfig,
|
||||
} );
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize the preview button manager.
|
||||
buttonManager();
|
||||
|
|
|
@ -1,28 +1,62 @@
|
|||
/**
|
||||
* Initialize the GooglePay module in the front end.
|
||||
* In some cases, this module is loaded when the `window.PayPalCommerceGateway` object is not
|
||||
* present. In that case, the page does not contain a Google Pay button, but some other logic
|
||||
* that is related to Google Pay (e.g., the CheckoutBootstrap module)
|
||||
*
|
||||
* @file
|
||||
*/
|
||||
|
||||
import { loadCustomScript } from '@paypal/paypal-js';
|
||||
import { loadPaypalScript } from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading';
|
||||
import GooglepayManager from './GooglepayManager';
|
||||
import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Helper/ButtonRefreshHelper';
|
||||
import { CheckoutBootstrap } from './ContextBootstrap/CheckoutBootstrap';
|
||||
import moduleStorage from './Helper/GooglePayStorage';
|
||||
|
||||
( function ( { buttonConfig, ppcpConfig, jQuery } ) {
|
||||
let manager;
|
||||
( function ( { buttonConfig, ppcpConfig = {} } ) {
|
||||
const context = ppcpConfig.context;
|
||||
|
||||
const bootstrap = function () {
|
||||
manager = new GooglepayManager( buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
};
|
||||
|
||||
setupButtonEvents( function () {
|
||||
if ( manager ) {
|
||||
manager.reinit();
|
||||
function bootstrapPayButton() {
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
return;
|
||||
}
|
||||
} );
|
||||
|
||||
const manager = new GooglepayManager( buttonConfig, ppcpConfig );
|
||||
manager.init();
|
||||
|
||||
setupButtonEvents( function () {
|
||||
manager.reinit();
|
||||
} );
|
||||
}
|
||||
|
||||
function bootstrapCheckout() {
|
||||
if ( context && ! [ 'continuation', 'checkout' ].includes( context ) ) {
|
||||
// Context must be missing/empty, or "continuation"/"checkout" to proceed.
|
||||
return;
|
||||
}
|
||||
if ( ! CheckoutBootstrap.isPageWithCheckoutForm() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const checkoutBootstrap = new CheckoutBootstrap( moduleStorage );
|
||||
checkoutBootstrap.init();
|
||||
}
|
||||
|
||||
function bootstrap() {
|
||||
bootstrapPayButton();
|
||||
bootstrapCheckout();
|
||||
}
|
||||
|
||||
document.addEventListener( 'DOMContentLoaded', () => {
|
||||
if (
|
||||
typeof buttonConfig === 'undefined' ||
|
||||
typeof ppcpConfig === 'undefined'
|
||||
) {
|
||||
// No PayPal buttons present on this page.
|
||||
if ( ! buttonConfig || ! ppcpConfig ) {
|
||||
/*
|
||||
* No PayPal buttons present on this page, but maybe a bootstrap module needs to be
|
||||
* initialized. Skip loading the SDK or gateway configuration, and directly initialize
|
||||
* the module.
|
||||
*/
|
||||
bootstrap();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -52,5 +86,4 @@ import { setupButtonEvents } from '../../../ppcp-button/resources/js/modules/Hel
|
|||
} )( {
|
||||
buttonConfig: window.wc_ppcp_googlepay,
|
||||
ppcpConfig: window.PayPalCommerceGateway,
|
||||
jQuery: window.jQuery,
|
||||
} );
|
||||
|
|
|
@ -90,7 +90,8 @@ class UpdatePaymentDataEndpoint {
|
|||
WC()->cart->calculate_fees();
|
||||
WC()->cart->calculate_totals();
|
||||
|
||||
$total = (float) WC()->cart->get_total( 'numeric' );
|
||||
$total = (float) WC()->cart->get_total( 'numeric' );
|
||||
$shipping_fee = (float) WC()->cart->get_shipping_total();
|
||||
|
||||
// Shop settings.
|
||||
$base_location = wc_get_base_location();
|
||||
|
@ -100,7 +101,7 @@ class UpdatePaymentDataEndpoint {
|
|||
wp_send_json_success(
|
||||
array(
|
||||
'total' => $total,
|
||||
'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
|
||||
'shipping_fee' => $shipping_fee,
|
||||
'currency_code' => $currency_code,
|
||||
'country_code' => $shop_country_code,
|
||||
'shipping_options' => $this->get_shipping_options(),
|
||||
|
@ -146,6 +147,7 @@ class UpdatePaymentDataEndpoint {
|
|||
wc_price( (float) $rate->get_cost(), array( 'currency' => get_woocommerce_currency() ) )
|
||||
)
|
||||
),
|
||||
'cost' => $rate->get_cost(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
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