mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-01 07:02:48 +08:00
Fix merge conflicts
This commit is contained in:
commit
3c0e807758
55 changed files with 5170 additions and 202 deletions
|
@ -27,6 +27,7 @@ web_environment:
|
|||
- ADMIN_PASS=admin
|
||||
- ADMIN_EMAIL=admin@example.com
|
||||
- WC_VERSION=6.1.0
|
||||
- PCP_BLOCKS_ENABLED=1
|
||||
|
||||
# Key features of ddev's config.yaml:
|
||||
|
||||
|
|
|
@ -2,7 +2,16 @@ PPCP_E2E_WP_DIR=${ROOT_DIR}/.ddev/wordpress
|
|||
|
||||
BASEURL="https://woocommerce-paypal-payments.ddev.site"
|
||||
AUTHORIZATION="Bearer ABC123"
|
||||
|
||||
CHECKOUT_URL="/checkout"
|
||||
CHECKOUT_PAGE_ID=7
|
||||
CART_URL="/cart"
|
||||
BLOCK_CHECKOUT_URL="/checkout-block"
|
||||
BLOCK_CHECKOUT_PAGE_ID=22
|
||||
BLOCK_CART_URL="/cart-block"
|
||||
|
||||
PRODUCT_URL="/product/prod"
|
||||
PRODUCT_ID=123
|
||||
|
||||
WP_MERCHANT_USER="admin"
|
||||
WP_MERCHANT_PASSWORD="admin"
|
||||
|
|
381
.psalm/wcblocks.php
Normal file
381
.psalm/wcblocks.php
Normal file
|
@ -0,0 +1,381 @@
|
|||
<?php
|
||||
namespace Automattic\WooCommerce\Blocks\Integrations {
|
||||
/**
|
||||
* Integration.Interface
|
||||
*
|
||||
* Integrations must use this interface when registering themselves with blocks,
|
||||
*/
|
||||
interface IntegrationInterface
|
||||
{
|
||||
/**
|
||||
* The name of the integration.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name();
|
||||
|
||||
/**
|
||||
* When called invokes any initialization/setup for the integration.
|
||||
*/
|
||||
public function initialize();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the frontend context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_script_handles();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the editor context.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_editor_script_handles();
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to the block on the client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_script_data();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Payments {
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;
|
||||
|
||||
interface PaymentMethodTypeInterface extends IntegrationInterface
|
||||
{
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the frontend context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles();
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the admin context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles_for_admin();
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to payment methods
|
||||
* client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data();
|
||||
|
||||
/**
|
||||
* Get array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features();
|
||||
}
|
||||
}
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Payments\Integrations
|
||||
{
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodTypeInterface;
|
||||
|
||||
/**
|
||||
* AbstractPaymentMethodType class.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
abstract class AbstractPaymentMethodType implements PaymentMethodTypeInterface
|
||||
{
|
||||
/**
|
||||
* Payment method name defined by payment methods extending this class.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $name = '';
|
||||
|
||||
/**
|
||||
* Settings from the WP options table
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $settings = [];
|
||||
|
||||
/**
|
||||
* Get a setting from the settings array if set.
|
||||
*
|
||||
* @param string $name Setting name.
|
||||
* @param mixed $default Value that is returned if the setting does not exist.
|
||||
* @return mixed
|
||||
*/
|
||||
protected function get_setting($name, $default = '')
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the payment method.
|
||||
*/
|
||||
public function get_name()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns if this payment method should be active. If false, the scripts will not be enqueued.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_active()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the frontend context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue for this payment method in
|
||||
* the admin context
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_payment_method_script_handles_for_admin()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of supported features.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_supported_features()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to payment methods
|
||||
* client side.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_payment_method_data()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the frontend context.
|
||||
*
|
||||
* Alias of get_payment_method_script_handles. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_script_handles()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array of script handles to enqueue in the admin context.
|
||||
*
|
||||
* Alias of get_payment_method_script_handles_for_admin. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_editor_script_handles()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* An array of key, value pairs of data made available to the block on the client side.
|
||||
*
|
||||
* Alias of get_payment_method_data. Defined by IntegrationInterface.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_script_data()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Integrations {
|
||||
/**
|
||||
* Class used for tracking registered integrations with various Block types.
|
||||
*/
|
||||
class IntegrationRegistry
|
||||
{
|
||||
/**
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $registry_identifier = '';
|
||||
|
||||
/**
|
||||
* Registered integrations, as `$name => $instance` pairs.
|
||||
*
|
||||
* @var IntegrationInterface[]
|
||||
*/
|
||||
protected $registered_integrations = [];
|
||||
|
||||
/**
|
||||
* Initializes all registered integrations.
|
||||
*
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @param string $registry_identifier Identifier for this registry.
|
||||
*/
|
||||
public function initialize($registry_identifier = '')
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers an integration.
|
||||
*
|
||||
* @param IntegrationInterface $integration An instance of IntegrationInterface.
|
||||
*
|
||||
* @return boolean True means registered successfully.
|
||||
*/
|
||||
public function register(IntegrationInterface $integration)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if an integration is already registered.
|
||||
*
|
||||
* @param string $name Integration name.
|
||||
* @return bool True if the integration is registered, false otherwise.
|
||||
*/
|
||||
public function is_registered($name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-register an integration.
|
||||
*
|
||||
* @param string|IntegrationInterface $name Integration name, or alternatively a IntegrationInterface instance.
|
||||
* @return boolean|IntegrationInterface Returns the unregistered integration instance if unregistered successfully.
|
||||
*/
|
||||
public function unregister($name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a registered Integration by name.
|
||||
*
|
||||
* @param string $name Integration name.
|
||||
* @return IntegrationInterface|null The registered integration, or null if it is not registered.
|
||||
*/
|
||||
public function get_registered($name)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all registered integrations.
|
||||
*
|
||||
* @return IntegrationInterface[]
|
||||
*/
|
||||
public function get_all_registered()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script handles for the editor.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_registered_editor_script_handles()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script handles.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_registered_script_handles()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered integration's script data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_registered_script_data()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
namespace Automattic\WooCommerce\Blocks\Payments {
|
||||
use Automattic\WooCommerce\Blocks\Integrations\IntegrationRegistry;
|
||||
|
||||
/**
|
||||
* Class used for interacting with payment method types.
|
||||
*
|
||||
* @since 2.6.0
|
||||
*/
|
||||
final class PaymentMethodRegistry extends IntegrationRegistry
|
||||
{
|
||||
/**
|
||||
* Integration identifier is used to construct hook names and is given when the integration registry is initialized.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $registry_identifier = 'payment_method_type';
|
||||
|
||||
/**
|
||||
* Retrieves all registered payment methods that are also active.
|
||||
*
|
||||
* @return PaymentMethodTypeInterface[]
|
||||
*/
|
||||
public function get_all_active_registered()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered payment method script handles, but only for active payment methods.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
public function get_all_active_payment_method_script_dependencies()
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an array of all registered payment method script data, but only for active payment methods.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_all_registered_script_data()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers and validates payment requirements callbacks.
|
||||
*
|
||||
* @see Automattic\WooCommerce\StoreApi\Schemas\ExtendSchema::register_payment_requirements()
|
||||
*
|
||||
* @param array $args Args to pass to register_payment_requirements.
|
||||
* @returns boolean|\WP_Error True on success, WP_Error on fail.
|
||||
*/
|
||||
function woocommerce_store_api_register_payment_requirements( $args ) {
|
||||
}
|
651
.psalm/wpcli.php
Normal file
651
.psalm/wpcli.php
Normal file
|
@ -0,0 +1,651 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Various utilities for WP-CLI commands.
|
||||
*/
|
||||
class WP_CLI {
|
||||
/**
|
||||
* Set the logger instance.
|
||||
*
|
||||
* @param object $logger Logger instance to set.
|
||||
*/
|
||||
public static function set_logger( $logger ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the logger instance.
|
||||
*
|
||||
* @return object $logger Logger instance.
|
||||
*/
|
||||
public static function get_logger() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Configurator instance
|
||||
*
|
||||
* @return Configurator
|
||||
*/
|
||||
public static function get_configurator() {
|
||||
}
|
||||
|
||||
public static function get_root_command() {
|
||||
}
|
||||
|
||||
public static function get_runner() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return FileCache
|
||||
*/
|
||||
public static function get_cache() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the context in which WP-CLI should be run
|
||||
*/
|
||||
public static function set_url( $url ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @return WpHttpCacheManager
|
||||
*/
|
||||
public static function get_http_cache_manager() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Colorize a string for output.
|
||||
*
|
||||
* Yes, you can change the color of command line text too. For instance,
|
||||
* here's how `WP_CLI::success()` colorizes "Success: "
|
||||
*
|
||||
* ```
|
||||
* WP_CLI::colorize( "%GSuccess:%n " )
|
||||
* ```
|
||||
*
|
||||
* Uses `\cli\Colors::colorize()` to transform color tokens to display
|
||||
* settings. Choose from the following tokens (and note 'reset'):
|
||||
*
|
||||
* * %y => ['color' => 'yellow'],
|
||||
* * %g => ['color' => 'green'],
|
||||
* * %b => ['color' => 'blue'],
|
||||
* * %r => ['color' => 'red'],
|
||||
* * %p => ['color' => 'magenta'],
|
||||
* * %m => ['color' => 'magenta'],
|
||||
* * %c => ['color' => 'cyan'],
|
||||
* * %w => ['color' => 'grey'],
|
||||
* * %k => ['color' => 'black'],
|
||||
* * %n => ['color' => 'reset'],
|
||||
* * %Y => ['color' => 'yellow', 'style' => 'bright'],
|
||||
* * %G => ['color' => 'green', 'style' => 'bright'],
|
||||
* * %B => ['color' => 'blue', 'style' => 'bright'],
|
||||
* * %R => ['color' => 'red', 'style' => 'bright'],
|
||||
* * %P => ['color' => 'magenta', 'style' => 'bright'],
|
||||
* * %M => ['color' => 'magenta', 'style' => 'bright'],
|
||||
* * %C => ['color' => 'cyan', 'style' => 'bright'],
|
||||
* * %W => ['color' => 'grey', 'style' => 'bright'],
|
||||
* * %K => ['color' => 'black', 'style' => 'bright'],
|
||||
* * %N => ['color' => 'reset', 'style' => 'bright'],
|
||||
* * %3 => ['background' => 'yellow'],
|
||||
* * %2 => ['background' => 'green'],
|
||||
* * %4 => ['background' => 'blue'],
|
||||
* * %1 => ['background' => 'red'],
|
||||
* * %5 => ['background' => 'magenta'],
|
||||
* * %6 => ['background' => 'cyan'],
|
||||
* * %7 => ['background' => 'grey'],
|
||||
* * %0 => ['background' => 'black'],
|
||||
* * %F => ['style' => 'blink'],
|
||||
* * %U => ['style' => 'underline'],
|
||||
* * %8 => ['style' => 'inverse'],
|
||||
* * %9 => ['style' => 'bright'],
|
||||
* * %_ => ['style' => 'bright']
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string $string String to colorize for output, with color tokens.
|
||||
* @return string Colorized string.
|
||||
*/
|
||||
public static function colorize( $string ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule a callback to be executed at a certain point.
|
||||
*
|
||||
* Hooks conceptually are very similar to WordPress actions. WP-CLI hooks
|
||||
* are typically called before WordPress is loaded.
|
||||
*
|
||||
* WP-CLI hooks include:
|
||||
*
|
||||
* * `before_add_command:<command>` - Before the command is added.
|
||||
* * `after_add_command:<command>` - After the command was added.
|
||||
* * `before_invoke:<command>` (1) - Just before a command is invoked.
|
||||
* * `after_invoke:<command>` (1) - Just after a command is invoked.
|
||||
* * `find_command_to_run_pre` - Just before WP-CLI finds the command to run.
|
||||
* * `before_registering_contexts` (1) - Before the contexts are registered.
|
||||
* * `before_wp_load` - Just before the WP load process begins.
|
||||
* * `before_wp_config_load` - After wp-config.php has been located.
|
||||
* * `after_wp_config_load` - After wp-config.php has been loaded into scope.
|
||||
* * `after_wp_load` - Just after the WP load process has completed.
|
||||
* * `before_run_command` (3) - Just before the command is executed.
|
||||
*
|
||||
* The parentheses behind the hook name denote the number of arguments
|
||||
* being passed into the hook. For such hooks, the callback should return
|
||||
* the first argument again, making them work like a WP filter.
|
||||
*
|
||||
* WP-CLI commands can create their own hooks with `WP_CLI::do_hook()`.
|
||||
*
|
||||
* If additional arguments are passed through the `WP_CLI::do_hook()` call,
|
||||
* these will be passed on to the callback provided by `WP_CLI::add_hook()`.
|
||||
*
|
||||
* ```
|
||||
* # `wp network meta` confirms command is executing in multisite context.
|
||||
* WP_CLI::add_command( 'network meta', 'Network_Meta_Command', array(
|
||||
* 'before_invoke' => function ( $name ) {
|
||||
* if ( !is_multisite() ) {
|
||||
* WP_CLI::error( 'This is not a multisite installation.' );
|
||||
* }
|
||||
* }
|
||||
* ) );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Registration
|
||||
*
|
||||
* @param string $when Identifier for the hook.
|
||||
* @param mixed $callback Callback to execute when hook is called.
|
||||
* @return null
|
||||
*/
|
||||
public static function add_hook( $when, $callback ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callbacks registered to a given hook.
|
||||
*
|
||||
* See `WP_CLI::add_hook()` for details on WP-CLI's internal hook system.
|
||||
* Commands can provide and call their own hooks.
|
||||
*
|
||||
* @access public
|
||||
* @category Registration
|
||||
*
|
||||
* @param string $when Identifier for the hook.
|
||||
* @param mixed ...$args Optional. Arguments that will be passed onto the
|
||||
* callback provided by `WP_CLI::add_hook()`.
|
||||
* @return null|mixed Returns the first optional argument if optional
|
||||
* arguments were passed, otherwise returns null.
|
||||
*/
|
||||
public static function do_hook( $when, ...$args ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a callback to a WordPress action or filter.
|
||||
*
|
||||
* `add_action()` without needing access to `add_action()`. If WordPress is
|
||||
* already loaded though, you should use `add_action()` (and `add_filter()`)
|
||||
* instead.
|
||||
*
|
||||
* @access public
|
||||
* @category Registration
|
||||
*
|
||||
* @param string $tag Named WordPress action or filter.
|
||||
* @param mixed $function_to_add Callable to execute when the action or filter is evaluated.
|
||||
* @param integer $priority Priority to add the callback as.
|
||||
* @param integer $accepted_args Number of arguments to pass to callback.
|
||||
* @return true
|
||||
*/
|
||||
public static function add_wp_hook( $tag, $function_to_add, $priority = 10, $accepted_args = 1 ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a command to WP-CLI.
|
||||
*
|
||||
* WP-CLI supports using any callable class, function, or closure as a
|
||||
* command. `WP_CLI::add_command()` is used for both internal and
|
||||
* third-party command registration.
|
||||
*
|
||||
* Command arguments are parsed from PHPDoc by default, but also can be
|
||||
* supplied as an optional third argument during registration.
|
||||
*
|
||||
* ```
|
||||
* # Register a custom 'foo' command to output a supplied positional param.
|
||||
* #
|
||||
* # $ wp foo bar --append=qux
|
||||
* # Success: bar qux
|
||||
*
|
||||
* /**
|
||||
* * My awesome closure command
|
||||
* *
|
||||
* * <message>
|
||||
* * : An awesome message to display
|
||||
* *
|
||||
* * --append=<message>
|
||||
* * : An awesome message to append to the original message.
|
||||
* *
|
||||
* * @when before_wp_load
|
||||
* *\/
|
||||
* $foo = function( $args, $assoc_args ) {
|
||||
* WP_CLI::success( $args[0] . ' ' . $assoc_args['append'] );
|
||||
* };
|
||||
* WP_CLI::add_command( 'foo', $foo );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Registration
|
||||
*
|
||||
* @param string $name Name for the command (e.g. "post list" or "site empty").
|
||||
* @param callable $callable Command implementation as a class, function or closure.
|
||||
* @param array $args {
|
||||
* Optional. An associative array with additional registration parameters.
|
||||
*
|
||||
* @type callable $before_invoke Callback to execute before invoking the command.
|
||||
* @type callable $after_invoke Callback to execute after invoking the command.
|
||||
* @type string $shortdesc Short description (80 char or less) for the command.
|
||||
* @type string $longdesc Description of arbitrary length for examples, etc.
|
||||
* @type string $synopsis The synopsis for the command (string or array).
|
||||
* @type string $when Execute callback on a named WP-CLI hook (e.g. before_wp_load).
|
||||
* @type bool $is_deferred Whether the command addition had already been deferred.
|
||||
* }
|
||||
* @return bool True on success, false if deferred, hard error if registration failed.
|
||||
*/
|
||||
public static function add_command( $name, $callable, $args = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the list of outstanding deferred command additions.
|
||||
*
|
||||
* @return array Array of outstanding command additions.
|
||||
*/
|
||||
public static function get_deferred_additions() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a command addition from the list of outstanding deferred additions.
|
||||
*/
|
||||
public static function remove_deferred_addition( $name ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display informational message without prefix, and ignore `--quiet`.
|
||||
*
|
||||
* Message is written to STDOUT. `WP_CLI::log()` is typically recommended;
|
||||
* `WP_CLI::line()` is included for historical compat.
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string $message Message to display to the end user.
|
||||
* @return null
|
||||
*/
|
||||
public static function line( $message = '' ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display informational message without prefix.
|
||||
*
|
||||
* Message is written to STDOUT, or discarded when `--quiet` flag is supplied.
|
||||
*
|
||||
* ```
|
||||
* # `wp cli update` lets user know of each step in the update process.
|
||||
* WP_CLI::log( sprintf( 'Downloading from %s...', $download_url ) );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string $message Message to write to STDOUT.
|
||||
*/
|
||||
public static function log( $message ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display success message prefixed with "Success: ".
|
||||
*
|
||||
* Success message is written to STDOUT.
|
||||
*
|
||||
* Typically recommended to inform user of successful script conclusion.
|
||||
*
|
||||
* ```
|
||||
* # wp rewrite flush expects 'rewrite_rules' option to be set after flush.
|
||||
* flush_rewrite_rules( \WP_CLI\Utils\get_flag_value( $assoc_args, 'hard' ) );
|
||||
* if ( ! get_option( 'rewrite_rules' ) ) {
|
||||
* WP_CLI::warning( "Rewrite rules are empty." );
|
||||
* } else {
|
||||
* WP_CLI::success( 'Rewrite rules flushed.' );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string $message Message to write to STDOUT.
|
||||
* @return null
|
||||
*/
|
||||
public static function success( $message ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display debug message prefixed with "Debug: " when `--debug` is used.
|
||||
*
|
||||
* Debug message is written to STDERR, and includes script execution time.
|
||||
*
|
||||
* Helpful for optionally showing greater detail when needed. Used throughout
|
||||
* WP-CLI bootstrap process for easier debugging and profiling.
|
||||
*
|
||||
* ```
|
||||
* # Called in `WP_CLI\Runner::set_wp_root()`.
|
||||
* private static function set_wp_root( $path ) {
|
||||
* define( 'ABSPATH', Utils\trailingslashit( $path ) );
|
||||
* WP_CLI::debug( 'ABSPATH defined: ' . ABSPATH );
|
||||
* $_SERVER['DOCUMENT_ROOT'] = realpath( $path );
|
||||
* }
|
||||
*
|
||||
* # Debug details only appear when `--debug` is used.
|
||||
* # $ wp --debug
|
||||
* # [...]
|
||||
* # Debug: ABSPATH defined: /srv/www/wordpress-develop.dev/src/ (0.225s)
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
|
||||
* @param string|bool $group Organize debug message to a specific group.
|
||||
* Use `false` to not group the message.
|
||||
* @return null
|
||||
*/
|
||||
public static function debug( $message, $group = false ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display warning message prefixed with "Warning: ".
|
||||
*
|
||||
* Warning message is written to STDERR.
|
||||
*
|
||||
* Use instead of `WP_CLI::debug()` when script execution should be permitted
|
||||
* to continue.
|
||||
*
|
||||
* ```
|
||||
* # `wp plugin activate` skips activation when plugin is network active.
|
||||
* $status = $this->get_status( $plugin->file );
|
||||
* // Network-active is the highest level of activation status
|
||||
* if ( 'active-network' === $status ) {
|
||||
* WP_CLI::warning( "Plugin '{$plugin->name}' is already network active." );
|
||||
* continue;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
|
||||
* @return null
|
||||
*/
|
||||
public static function warning( $message ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display error message prefixed with "Error: " and exit script.
|
||||
*
|
||||
* Error message is written to STDERR. Defaults to halting script execution
|
||||
* with return code 1.
|
||||
*
|
||||
* Use `WP_CLI::warning()` instead when script execution should be permitted
|
||||
* to continue.
|
||||
*
|
||||
* ```
|
||||
* # `wp cache flush` considers flush failure to be a fatal error.
|
||||
* if ( false === wp_cache_flush() ) {
|
||||
* WP_CLI::error( 'The object cache could not be flushed.' );
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param string|WP_Error|Exception|Throwable $message Message to write to STDERR.
|
||||
* @param boolean|integer $exit True defaults to exit(1).
|
||||
* @return null
|
||||
*/
|
||||
public static function error( $message, $exit = true ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Halt script execution with a specific return code.
|
||||
*
|
||||
* Permits script execution to be overloaded by `WP_CLI::runcommand()`
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param integer $return_code
|
||||
* @return never
|
||||
*/
|
||||
public static function halt( $return_code ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a multi-line error message in a red box. Doesn't exit script.
|
||||
*
|
||||
* Error message is written to STDERR.
|
||||
*
|
||||
* @access public
|
||||
* @category Output
|
||||
*
|
||||
* @param array $message_lines Multi-line error message to be displayed.
|
||||
*/
|
||||
public static function error_multi_line( $message_lines ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask for confirmation before running a destructive operation.
|
||||
*
|
||||
* If 'y' is provided to the question, the script execution continues. If
|
||||
* 'n' or any other response is provided to the question, script exits.
|
||||
*
|
||||
* ```
|
||||
* # `wp db drop` asks for confirmation before dropping the database.
|
||||
*
|
||||
* WP_CLI::confirm( "Are you sure you want to drop the database?", $assoc_args );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Input
|
||||
*
|
||||
* @param string $question Question to display before the prompt.
|
||||
* @param array $assoc_args Skips prompt if 'yes' is provided.
|
||||
*/
|
||||
public static function confirm( $question, $assoc_args = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read value from a positional argument or from STDIN.
|
||||
*
|
||||
* @param array $args The list of positional arguments.
|
||||
* @param int $index At which position to check for the value.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_value_from_arg_or_stdin( $args, $index ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a value, from various formats.
|
||||
*
|
||||
* @access public
|
||||
* @category Input
|
||||
*
|
||||
* @param mixed $raw_value
|
||||
* @param array $assoc_args
|
||||
*/
|
||||
public static function read_value( $raw_value, $assoc_args = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a value, in various formats
|
||||
*
|
||||
* @param mixed $value Value to display.
|
||||
* @param array $assoc_args Arguments passed to the command, determining format.
|
||||
*/
|
||||
public static function print_value( $value, $assoc_args = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a WP_Error or Exception into a string
|
||||
*
|
||||
* @param string|WP_Error|Exception|Throwable $errors
|
||||
* @throws InvalidArgumentException
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function error_to_string( $errors ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch an arbitrary external process that takes over I/O.
|
||||
*
|
||||
* ```
|
||||
* # `wp core download` falls back to the `tar` binary when PharData isn't available
|
||||
* if ( ! class_exists( 'PharData' ) ) {
|
||||
* $cmd = "tar xz --strip-components=1 --directory=%s -f $tarball";
|
||||
* WP_CLI::launch( Utils\esc_cmd( $cmd, $dest ) );
|
||||
* return;
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Execution
|
||||
*
|
||||
* @param string $command External process to launch.
|
||||
* @param boolean $exit_on_error Whether to exit if the command returns an elevated return code.
|
||||
* @param boolean $return_detailed Whether to return an exit status (default) or detailed execution results.
|
||||
* @return int|ProcessRun The command exit status, or a ProcessRun object for full details.
|
||||
*/
|
||||
public static function launch( $command, $exit_on_error = true, $return_detailed = false ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a WP-CLI command in a new process reusing the current runtime arguments.
|
||||
*
|
||||
* Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
|
||||
*
|
||||
* Note: While this command does persist a limited set of runtime arguments,
|
||||
* it *does not* persist environment variables. Practically speaking, WP-CLI
|
||||
* packages won't be loaded when using WP_CLI::launch_self() because the
|
||||
* launched process doesn't have access to the current process $HOME.
|
||||
*
|
||||
* @access public
|
||||
* @category Execution
|
||||
*
|
||||
* @param string $command WP-CLI command to call.
|
||||
* @param array $args Positional arguments to include when calling the command.
|
||||
* @param array $assoc_args Associative arguments to include when calling the command.
|
||||
* @param bool $exit_on_error Whether to exit if the command returns an elevated return code.
|
||||
* @param bool $return_detailed Whether to return an exit status (default) or detailed execution results.
|
||||
* @param array $runtime_args Override one or more global args (path,url,user,allow-root)
|
||||
* @return int|ProcessRun The command exit status, or a ProcessRun instance
|
||||
*/
|
||||
public static function launch_self( $command, $args = [], $assoc_args = [], $exit_on_error = true, $return_detailed = false, $runtime_args = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the PHP binary used when executing WP-CLI.
|
||||
*
|
||||
* Environment values permit specific binaries to be indicated.
|
||||
*
|
||||
* Note: moved to Utils, left for BC.
|
||||
*
|
||||
* @access public
|
||||
* @category System
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_php_binary() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Confirm that a global configuration parameter does exist.
|
||||
*
|
||||
* @access public
|
||||
* @category Input
|
||||
*
|
||||
* @param string $key Config parameter key to check.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function has_config( $key ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get values of global configuration parameters.
|
||||
*
|
||||
* Provides access to `--path=<path>`, `--url=<url>`, and other values of
|
||||
* the [global configuration parameters](https://wp-cli.org/config/).
|
||||
*
|
||||
* ```
|
||||
* WP_CLI::log( 'The --url=<url> value is: ' . WP_CLI::get_config( 'url' ) );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Input
|
||||
*
|
||||
* @param string $key Get value for a specific global configuration parameter.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function get_config( $key = null ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a WP-CLI command.
|
||||
*
|
||||
* Launches a new child process to run a specified WP-CLI command.
|
||||
* Optionally:
|
||||
*
|
||||
* * Run the command in an existing process.
|
||||
* * Prevent halting script execution on error.
|
||||
* * Capture and return STDOUT, or full details about command execution.
|
||||
* * Parse JSON output if the command rendered it.
|
||||
*
|
||||
* ```
|
||||
* $options = array(
|
||||
* 'return' => true, // Return 'STDOUT'; use 'all' for full object.
|
||||
* 'parse' => 'json', // Parse captured STDOUT to JSON array.
|
||||
* 'launch' => false, // Reuse the current process.
|
||||
* 'exit_error' => true, // Halt script execution on error.
|
||||
* );
|
||||
* $plugins = WP_CLI::runcommand( 'plugin list --format=json', $options );
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Execution
|
||||
*
|
||||
* @param string $command WP-CLI command to run, including arguments.
|
||||
* @param array $options Configuration options for command execution.
|
||||
* @return mixed
|
||||
*/
|
||||
public static function runcommand( $command, $options = [] ) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a given command within the current process using the same global
|
||||
* parameters.
|
||||
*
|
||||
* Use `WP_CLI::runcommand()` instead, which is easier to use and works better.
|
||||
*
|
||||
* To run a command using a new process with the same global parameters,
|
||||
* use WP_CLI::launch_self(). To run a command using a new process with
|
||||
* different global parameters, use WP_CLI::launch().
|
||||
*
|
||||
* ```
|
||||
* ob_start();
|
||||
* WP_CLI::run_command( array( 'cli', 'cmd-dump' ) );
|
||||
* $ret = ob_get_clean();
|
||||
* ```
|
||||
*
|
||||
* @access public
|
||||
* @category Execution
|
||||
*
|
||||
* @param array $args Positional arguments including command name.
|
||||
* @param array $assoc_args
|
||||
*/
|
||||
public static function run_command( $args, $assoc_args = [] ) {
|
||||
}
|
||||
}
|
|
@ -28,5 +28,12 @@ return function ( string $root_dir ): iterable {
|
|||
( require "$modules_dir/ppcp-uninstall/module.php" )(),
|
||||
);
|
||||
|
||||
if ( apply_filters(
|
||||
'woocommerce_paypal_payments_blocks_enabled',
|
||||
getenv( 'PCP_BLOCKS_ENABLED' ) === '1'
|
||||
) ) {
|
||||
$modules[] = ( require "$modules_dir/ppcp-blocks/module.php" )();
|
||||
}
|
||||
|
||||
return $modules;
|
||||
};
|
||||
|
|
|
@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Factory\BillingCycleFactory;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PaymentPreferencesFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PlanFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ProductFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingOptionFactory;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Authentication\Bearer;
|
||||
|
@ -320,12 +321,19 @@ return array(
|
|||
);
|
||||
},
|
||||
'api.factory.shipping' => static function ( ContainerInterface $container ): ShippingFactory {
|
||||
$address_factory = $container->get( 'api.factory.address' );
|
||||
return new ShippingFactory( $address_factory );
|
||||
return new ShippingFactory(
|
||||
$container->get( 'api.factory.address' ),
|
||||
$container->get( 'api.factory.shipping-option' )
|
||||
);
|
||||
},
|
||||
'api.factory.shipping-preference' => static function ( ContainerInterface $container ): ShippingPreferenceFactory {
|
||||
return new ShippingPreferenceFactory();
|
||||
},
|
||||
'api.factory.shipping-option' => static function ( ContainerInterface $container ): ShippingOptionFactory {
|
||||
return new ShippingOptionFactory(
|
||||
$container->get( 'api.factory.money' )
|
||||
);
|
||||
},
|
||||
'api.factory.amount' => static function ( ContainerInterface $container ): AmountFactory {
|
||||
$item_factory = $container->get( 'api.factory.item' );
|
||||
return new AmountFactory(
|
||||
|
|
|
@ -16,6 +16,7 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\CaptureStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentToken;
|
||||
|
@ -180,6 +181,8 @@ class OrderEndpoint {
|
|||
* @param Payer|null $payer The payer off the order.
|
||||
* @param PaymentToken|null $payment_token The payment token.
|
||||
* @param PaymentMethod|null $payment_method The payment method.
|
||||
* @param string $paypal_request_id The paypal request id.
|
||||
* @param string $user_action The user action.
|
||||
*
|
||||
* @return Order
|
||||
* @throws RuntimeException If the request fails.
|
||||
|
@ -189,19 +192,28 @@ class OrderEndpoint {
|
|||
string $shipping_preference,
|
||||
Payer $payer = null,
|
||||
PaymentToken $payment_token = null,
|
||||
PaymentMethod $payment_method = null
|
||||
PaymentMethod $payment_method = null,
|
||||
string $paypal_request_id = '',
|
||||
string $user_action = ApplicationContext::USER_ACTION_CONTINUE
|
||||
): Order {
|
||||
$bearer = $this->bearer->bearer();
|
||||
$data = array(
|
||||
'intent' => ( $this->subscription_helper->cart_contains_subscription() || $this->subscription_helper->current_product_is_subscription() ) ? 'AUTHORIZE' : $this->intent,
|
||||
'purchase_units' => array_map(
|
||||
static function ( PurchaseUnit $item ): array {
|
||||
return $item->to_array();
|
||||
static function ( PurchaseUnit $item ) use ( $shipping_preference ): array {
|
||||
$data = $item->to_array();
|
||||
|
||||
if ( $shipping_preference !== ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE ) {
|
||||
// Shipping options are not allowed to be sent when not getting the address from PayPal.
|
||||
unset( $data['shipping']['options'] );
|
||||
}
|
||||
|
||||
return $data;
|
||||
},
|
||||
$items
|
||||
),
|
||||
'application_context' => $this->application_context_repository
|
||||
->current_context( $shipping_preference )->to_array(),
|
||||
->current_context( $shipping_preference, $user_action )->to_array(),
|
||||
);
|
||||
if ( $payer && ! empty( $payer->email_address() ) ) {
|
||||
$data['payer'] = $payer->to_array();
|
||||
|
@ -510,13 +522,22 @@ class OrderEndpoint {
|
|||
return $order_to_update;
|
||||
}
|
||||
|
||||
$this->patch( $order_to_update->id(), $patches );
|
||||
|
||||
$new_order = $this->order( $order_to_update->id() );
|
||||
return $new_order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches an order.
|
||||
*
|
||||
* @param string $order_id The PayPal order ID.
|
||||
* @param PatchCollection $patches The patches.
|
||||
*
|
||||
* @throws RuntimeException If the request fails.
|
||||
*/
|
||||
public function patch( string $order_id, PatchCollection $patches ): void {
|
||||
$patches_array = $patches->to_array();
|
||||
if ( ! isset( $patches_array[0]['value']['shipping'] ) ) {
|
||||
$shipping = isset( $order_to_update->purchase_units()[0] ) && null !== $order_to_update->purchase_units()[0]->shipping() ? $order_to_update->purchase_units()[0]->shipping() : null;
|
||||
if ( $shipping ) {
|
||||
$patches_array[0]['value']['shipping'] = $shipping->to_array();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter can be used to modify the order patching request body data (the final prices, items).
|
||||
|
@ -524,7 +545,7 @@ class OrderEndpoint {
|
|||
$patches_array = apply_filters( 'ppcp_patch_order_request_body_data', $patches_array );
|
||||
|
||||
$bearer = $this->bearer->bearer();
|
||||
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_to_update->id();
|
||||
$url = trailingslashit( $this->host ) . 'v2/checkout/orders/' . $order_id;
|
||||
$args = array(
|
||||
'method' => 'PATCH',
|
||||
'headers' => array(
|
||||
|
@ -540,11 +561,8 @@ class OrderEndpoint {
|
|||
$response = $this->request( $url, $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
$error = new RuntimeException(
|
||||
__( 'Could not retrieve order.', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$error = new RuntimeException( 'Could not patch order.' );
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
@ -560,8 +578,7 @@ class OrderEndpoint {
|
|||
$json,
|
||||
$status_code
|
||||
);
|
||||
$this->logger->log(
|
||||
'warning',
|
||||
$this->logger->warning(
|
||||
$error->getMessage(),
|
||||
array(
|
||||
'args' => $args,
|
||||
|
@ -570,9 +587,6 @@ class OrderEndpoint {
|
|||
);
|
||||
throw $error;
|
||||
}
|
||||
|
||||
$new_order = $this->order( $order_to_update->id() );
|
||||
return $new_order;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -83,8 +83,8 @@ class Patch {
|
|||
public function to_array(): array {
|
||||
return array(
|
||||
'op' => $this->op(),
|
||||
'value' => $this->value(),
|
||||
'path' => $this->path(),
|
||||
'value' => $this->value(),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -28,15 +28,24 @@ class Shipping {
|
|||
*/
|
||||
private $address;
|
||||
|
||||
/**
|
||||
* Shipping methods.
|
||||
*
|
||||
* @var ShippingOption[]
|
||||
*/
|
||||
private $options;
|
||||
|
||||
/**
|
||||
* Shipping constructor.
|
||||
*
|
||||
* @param string $name The name.
|
||||
* @param Address $address The address.
|
||||
* @param ShippingOption[] $options Shipping methods.
|
||||
*/
|
||||
public function __construct( string $name, Address $address ) {
|
||||
public function __construct( string $name, Address $address, array $options = array() ) {
|
||||
$this->name = $name;
|
||||
$this->address = $address;
|
||||
$this->options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -57,17 +66,35 @@ class Shipping {
|
|||
return $this->address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the shipping methods.
|
||||
*
|
||||
* @return ShippingOption[]
|
||||
*/
|
||||
public function options(): array {
|
||||
return $this->options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
$result = array(
|
||||
'name' => array(
|
||||
'full_name' => $this->name(),
|
||||
),
|
||||
'address' => $this->address()->to_array(),
|
||||
);
|
||||
if ( $this->options ) {
|
||||
$result['options'] = array_map(
|
||||
function ( ShippingOption $opt ): array {
|
||||
return $opt->to_array();
|
||||
},
|
||||
$this->options
|
||||
);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
|
139
modules/ppcp-api-client/src/Entity/ShippingOption.php
Normal file
139
modules/ppcp-api-client/src/Entity/ShippingOption.php
Normal file
|
@ -0,0 +1,139 @@
|
|||
<?php
|
||||
/**
|
||||
* The ShippingOption object.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Entity
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Entity;
|
||||
|
||||
/**
|
||||
* Class ShippingOption
|
||||
*/
|
||||
class ShippingOption {
|
||||
const TYPE_SHIPPING = 'SHIPPING';
|
||||
const TYPE_PICKUP = 'PICKUP';
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $id;
|
||||
|
||||
/**
|
||||
* The label.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $label;
|
||||
|
||||
/**
|
||||
* Whether the method is selected by default.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $selected;
|
||||
|
||||
/**
|
||||
* The price.
|
||||
*
|
||||
* @var Money
|
||||
*/
|
||||
private $amount;
|
||||
|
||||
/**
|
||||
* SHIPPING or PICKUP.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* ShippingOption constructor.
|
||||
*
|
||||
* @param string $id The name.
|
||||
* @param string $label The label.
|
||||
* @param bool $selected Whether the method is selected by default.
|
||||
* @param Money $amount The price.
|
||||
* @param string $type SHIPPING or PICKUP.
|
||||
*/
|
||||
public function __construct( string $id, string $label, bool $selected, Money $amount, string $type ) {
|
||||
$this->id = $id;
|
||||
$this->label = $label;
|
||||
$this->selected = $selected;
|
||||
$this->amount = $amount;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function id(): string {
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
/**
|
||||
* The label.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function label(): string {
|
||||
return $this->label;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the method is selected by default.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function selected(): bool {
|
||||
return $this->selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the method is selected by default.
|
||||
*
|
||||
* @param bool $selected The value to be set.
|
||||
*/
|
||||
public function set_selected( bool $selected ): void {
|
||||
$this->selected = $selected;
|
||||
}
|
||||
|
||||
/**
|
||||
* The price.
|
||||
*
|
||||
* @return Money
|
||||
*/
|
||||
public function amount(): Money {
|
||||
return $this->amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* SHIPPING or PICKUP.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function type(): string {
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the object as array.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function to_array(): array {
|
||||
return array(
|
||||
'id' => $this->id,
|
||||
'label' => $this->label,
|
||||
'selected' => $this->selected,
|
||||
'amount' => $this->amount->to_array(),
|
||||
'type' => $this->type,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -71,6 +71,14 @@ class PatchCollectionFactory {
|
|||
);
|
||||
$operation = $purchase_unit_from ? 'replace' : 'add';
|
||||
$value = $purchase_unit_to->to_array();
|
||||
|
||||
if ( ! isset( $value['shipping'] ) ) {
|
||||
$shipping = $purchase_unit_from && null !== $purchase_unit_from->shipping() ? $purchase_unit_from->shipping() : null;
|
||||
if ( $shipping ) {
|
||||
$value['shipping'] = $shipping->to_array();
|
||||
}
|
||||
}
|
||||
|
||||
$patches[] = new Patch(
|
||||
$operation,
|
||||
$path . "/@reference_id=='" . $purchase_unit_to->reference_id() . "'",
|
||||
|
|
|
@ -153,10 +153,11 @@ class PurchaseUnitFactory {
|
|||
* Creates a PurchaseUnit based off a WooCommerce cart.
|
||||
*
|
||||
* @param \WC_Cart|null $cart The cart.
|
||||
* @param bool $with_shipping_options Include WC shipping methods.
|
||||
*
|
||||
* @return PurchaseUnit
|
||||
*/
|
||||
public function from_wc_cart( ?\WC_Cart $cart = null ): PurchaseUnit {
|
||||
public function from_wc_cart( ?\WC_Cart $cart = null, bool $with_shipping_options = false ): PurchaseUnit {
|
||||
if ( ! $cart ) {
|
||||
$cart = WC()->cart ?? new \WC_Cart();
|
||||
}
|
||||
|
@ -172,7 +173,7 @@ class PurchaseUnitFactory {
|
|||
$shipping = null;
|
||||
$customer = \WC()->customer;
|
||||
if ( $this->shipping_needed( ... array_values( $items ) ) && is_a( $customer, \WC_Customer::class ) ) {
|
||||
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer );
|
||||
$shipping = $this->shipping_factory->from_wc_customer( \WC()->customer, $with_shipping_options );
|
||||
if (
|
||||
2 !== strlen( $shipping->address()->country_code() ) ||
|
||||
( ! $shipping->address()->postal_code() && ! $this->country_without_postal_code( $shipping->address()->country_code() ) )
|
||||
|
|
|
@ -10,6 +10,7 @@ declare(strict_types=1);
|
|||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Shipping;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
|
@ -24,23 +25,33 @@ class ShippingFactory {
|
|||
*/
|
||||
private $address_factory;
|
||||
|
||||
/**
|
||||
* The shipping option factory.
|
||||
*
|
||||
* @var ShippingOptionFactory
|
||||
*/
|
||||
private $shipping_option_factory;
|
||||
|
||||
/**
|
||||
* ShippingFactory constructor.
|
||||
*
|
||||
* @param AddressFactory $address_factory The address factory.
|
||||
* @param ShippingOptionFactory $shipping_option_factory The shipping option factory.
|
||||
*/
|
||||
public function __construct( AddressFactory $address_factory ) {
|
||||
public function __construct( AddressFactory $address_factory, ShippingOptionFactory $shipping_option_factory ) {
|
||||
$this->address_factory = $address_factory;
|
||||
$this->shipping_option_factory = $shipping_option_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a shipping object based off a WooCommerce customer.
|
||||
*
|
||||
* @param \WC_Customer $customer The WooCommerce customer.
|
||||
* @param bool $with_shipping_options Include WC shipping methods.
|
||||
*
|
||||
* @return Shipping
|
||||
*/
|
||||
public function from_wc_customer( \WC_Customer $customer ): Shipping {
|
||||
public function from_wc_customer( \WC_Customer $customer, bool $with_shipping_options = false ): Shipping {
|
||||
// Replicates the Behavior of \WC_Order::get_formatted_shipping_full_name().
|
||||
$full_name = sprintf(
|
||||
// translators: %1$s is the first name and %2$s is the second name. wc translation.
|
||||
|
@ -51,7 +62,8 @@ class ShippingFactory {
|
|||
$address = $this->address_factory->from_wc_customer( $customer );
|
||||
return new Shipping(
|
||||
$full_name,
|
||||
$address
|
||||
$address,
|
||||
$with_shipping_options ? $this->shipping_option_factory->from_wc_cart() : array()
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -91,9 +103,14 @@ class ShippingFactory {
|
|||
);
|
||||
}
|
||||
$address = $this->address_factory->from_paypal_response( $data->address );
|
||||
$options = array_map(
|
||||
array( $this->shipping_option_factory, 'from_paypal_response' ),
|
||||
$data->options ?? array()
|
||||
);
|
||||
return new Shipping(
|
||||
$data->name->full_name,
|
||||
$address
|
||||
$address,
|
||||
$options
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
111
modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php
Normal file
111
modules/ppcp-api-client/src/Factory/ShippingOptionFactory.php
Normal file
|
@ -0,0 +1,111 @@
|
|||
<?php
|
||||
/**
|
||||
* The shipping options factory.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\ApiClient\Factory
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\ApiClient\Factory;
|
||||
|
||||
use stdClass;
|
||||
use WC_Cart;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\ShippingOption;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
|
||||
/**
|
||||
* Class ShippingOptionFactory
|
||||
*/
|
||||
class ShippingOptionFactory {
|
||||
|
||||
/**
|
||||
* The Money factory.
|
||||
*
|
||||
* @var MoneyFactory
|
||||
*/
|
||||
private $money_factory;
|
||||
|
||||
/**
|
||||
* ShippingOptionFactory constructor.
|
||||
*
|
||||
* @param MoneyFactory $money_factory The Money factory.
|
||||
*/
|
||||
public function __construct( MoneyFactory $money_factory ) {
|
||||
$this->money_factory = $money_factory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an array of ShippingOption objects for the shipping methods available in the cart.
|
||||
*
|
||||
* @param WC_Cart|null $cart The cart.
|
||||
* @return ShippingOption[]
|
||||
*/
|
||||
public function from_wc_cart( ?WC_Cart $cart = null ): array {
|
||||
if ( ! $cart ) {
|
||||
$cart = WC()->cart ?? new WC_Cart();
|
||||
}
|
||||
|
||||
$cart->calculate_shipping();
|
||||
|
||||
$chosen_shipping_methods = WC()->session->get( 'chosen_shipping_methods', array() );
|
||||
if ( ! is_array( $chosen_shipping_methods ) ) {
|
||||
$chosen_shipping_methods = array();
|
||||
}
|
||||
|
||||
$packages = WC()->shipping()->get_packages();
|
||||
$options = array();
|
||||
foreach ( $packages as $package ) {
|
||||
$rates = $package['rates'] ?? array();
|
||||
foreach ( $rates as $rate ) {
|
||||
if ( ! $rate instanceof \WC_Shipping_Rate ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = new ShippingOption(
|
||||
$rate->get_id(),
|
||||
$rate->get_label(),
|
||||
in_array( $rate->get_id(), $chosen_shipping_methods, true ),
|
||||
new Money(
|
||||
(float) $rate->get_cost(),
|
||||
get_woocommerce_currency()
|
||||
),
|
||||
ShippingOption::TYPE_SHIPPING
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $chosen_shipping_methods && $options ) {
|
||||
$options[0]->set_selected( true );
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a ShippingOption object from the PayPal JSON object.
|
||||
*
|
||||
* @param stdClass $data The JSON object.
|
||||
*
|
||||
* @return ShippingOption
|
||||
* @throws RuntimeException When JSON object is malformed.
|
||||
*/
|
||||
public function from_paypal_response( stdClass $data ): ShippingOption {
|
||||
if ( ! isset( $data->id ) ) {
|
||||
throw new RuntimeException( 'No id was given for shipping option.' );
|
||||
}
|
||||
if ( ! isset( $data->amount ) ) {
|
||||
throw new RuntimeException( 'Shipping option amount not found' );
|
||||
}
|
||||
|
||||
$amount = $this->money_factory->from_paypal_response( $data->amount );
|
||||
return new ShippingOption(
|
||||
$data->id,
|
||||
$data->label ?? '',
|
||||
isset( $data->selected ) ? (bool) $data->selected : false,
|
||||
$amount,
|
||||
$data->type ?? ShippingOption::TYPE_SHIPPING
|
||||
);
|
||||
}
|
||||
}
|
|
@ -38,11 +38,13 @@ class ApplicationContextRepository {
|
|||
* Returns the current application context.
|
||||
*
|
||||
* @param string $shipping_preferences The shipping preferences.
|
||||
* @param string $user_action The user action.
|
||||
*
|
||||
* @return ApplicationContext
|
||||
*/
|
||||
public function current_context(
|
||||
string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING
|
||||
string $shipping_preferences = ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING,
|
||||
string $user_action = ApplicationContext::USER_ACTION_CONTINUE
|
||||
): ApplicationContext {
|
||||
|
||||
$brand_name = $this->settings->has( 'brand_name' ) ? $this->settings->get( 'brand_name' ) : '';
|
||||
|
@ -55,7 +57,8 @@ class ApplicationContextRepository {
|
|||
(string) $brand_name,
|
||||
$locale,
|
||||
(string) $landingpage,
|
||||
$shipping_preferences
|
||||
$shipping_preferences,
|
||||
$user_action
|
||||
);
|
||||
return $context;
|
||||
}
|
||||
|
|
14
modules/ppcp-blocks/.babelrc
Normal file
14
modules/ppcp-blocks/.babelrc
Normal file
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"presets": [
|
||||
[
|
||||
"@babel/preset-env",
|
||||
{
|
||||
"useBuiltIns": "usage",
|
||||
"corejs": "3.25.0"
|
||||
}
|
||||
],
|
||||
[
|
||||
"@babel/preset-react"
|
||||
]
|
||||
]
|
||||
}
|
3
modules/ppcp-blocks/.gitignore
vendored
Normal file
3
modules/ppcp-blocks/.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
node_modules
|
||||
assets/js
|
||||
assets/css
|
17
modules/ppcp-blocks/composer.json
Normal file
17
modules/ppcp-blocks/composer.json
Normal file
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"name": "woocommerce/ppcp-blocks",
|
||||
"type": "dhii-mod",
|
||||
"description": "Blocks module for PPCP",
|
||||
"license": "GPL-2.0",
|
||||
"require": {
|
||||
"php": "^7.2 | ^8.0",
|
||||
"dhii/module-interface": "^0.3.0-alpha1"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"WooCommerce\\PayPalCommerce\\Blocks\\": "src"
|
||||
}
|
||||
},
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true
|
||||
}
|
74
modules/ppcp-blocks/extensions.php
Normal file
74
modules/ppcp-blocks/extensions.php
Normal file
|
@ -0,0 +1,74 @@
|
|||
<?php
|
||||
/**
|
||||
* The blocks module extensions.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Blocks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'wcgateway.button.locations' => function ( ContainerInterface $container, array $locations ): array {
|
||||
return array_merge(
|
||||
$locations,
|
||||
array(
|
||||
'checkout-block-express' => _x( 'Block Express Checkout', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
|
||||
'cart-block' => _x( 'Block Cart', 'Name of Buttons Location', 'woocommerce-paypal-payments' ),
|
||||
)
|
||||
);
|
||||
},
|
||||
'wcgateway.settings.pay-later.messaging-locations' => function ( ContainerInterface $container, array $locations ): array {
|
||||
unset( $locations['checkout-block-express'] );
|
||||
unset( $locations['cart-block'] );
|
||||
return $locations;
|
||||
},
|
||||
|
||||
'wcgateway.settings.fields' => function ( ContainerInterface $container, array $fields ): array {
|
||||
$insert_after = function( array $array, string $key, array $new ): array {
|
||||
$keys = array_keys( $array );
|
||||
$index = array_search( $key, $keys, true );
|
||||
$pos = false === $index ? count( $array ) : $index + 1;
|
||||
|
||||
return array_merge( array_slice( $array, 0, $pos ), $new, array_slice( $array, $pos ) );
|
||||
};
|
||||
|
||||
return $insert_after(
|
||||
$fields,
|
||||
'smart_button_locations',
|
||||
array(
|
||||
'blocks_final_review_enabled' => array(
|
||||
'title' => __( 'Require final confirmation on checkout', 'woocommerce-paypal-payments' ),
|
||||
'type' => 'checkbox',
|
||||
'label' => __(
|
||||
'Require customers to confirm express payments from the Block Cart and Block Express Checkout on the checkout page.
|
||||
<p class="description">If this setting is not enabled, <a href="https://woocommerce.com/document/woocommerce-paypal-payments/#blocks-faq" target="_blank">payment confirmation on the checkout will be skipped</a>.
|
||||
Skipping the final confirmation on the checkout page may impact the buyer experience during the PayPal payment process.</p>',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'default' => true,
|
||||
'screens' => array( State::STATE_START, State::STATE_ONBOARDED ),
|
||||
'requirements' => array(),
|
||||
'gateway' => 'paypal',
|
||||
),
|
||||
)
|
||||
);
|
||||
},
|
||||
|
||||
'button.pay-now-contexts' => function ( ContainerInterface $container, array $contexts ): array {
|
||||
if ( ! $container->get( 'blocks.settings.final_review_enabled' ) ) {
|
||||
$contexts[] = 'checkout-block';
|
||||
$contexts[] = 'cart-block';
|
||||
}
|
||||
|
||||
return $contexts;
|
||||
},
|
||||
|
||||
'button.handle-shipping-in-paypal' => function ( ContainerInterface $container ): bool {
|
||||
return ! $container->get( 'blocks.settings.final_review_enabled' );
|
||||
},
|
||||
);
|
16
modules/ppcp-blocks/module.php
Normal file
16
modules/ppcp-blocks/module.php
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
/**
|
||||
* The blocks module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Blocks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
|
||||
return static function (): ModuleInterface {
|
||||
return new BlocksModule();
|
||||
};
|
33
modules/ppcp-blocks/package.json
Normal file
33
modules/ppcp-blocks/package.json
Normal file
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "ppcp-blocks",
|
||||
"version": "1.0.0",
|
||||
"license": "GPL-3.0-or-later",
|
||||
"browserslist": [
|
||||
"> 0.5%",
|
||||
"Safari >= 8",
|
||||
"Chrome >= 41",
|
||||
"Firefox >= 43",
|
||||
"Edge >= 14"
|
||||
],
|
||||
"dependencies": {
|
||||
"core-js": "^3.25.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.19",
|
||||
"@babel/preset-env": "^7.19",
|
||||
"@babel/preset-react": "^7.18.6",
|
||||
"@woocommerce/dependency-extraction-webpack-plugin": "^2.2.0",
|
||||
"babel-loader": "^8.2",
|
||||
"cross-env": "^7.0.3",
|
||||
"file-loader": "^6.2.0",
|
||||
"sass": "^1.42.1",
|
||||
"sass-loader": "^12.1.0",
|
||||
"webpack": "^5.74",
|
||||
"webpack-cli": "^4.10"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "cross-env BABEL_ENV=default NODE_ENV=production webpack",
|
||||
"watch": "cross-env BABEL_ENV=default NODE_ENV=production webpack --watch",
|
||||
"dev": "cross-env BABEL_ENV=default webpack --watch"
|
||||
}
|
||||
}
|
126
modules/ppcp-blocks/resources/js/Helper/Address.js
Normal file
126
modules/ppcp-blocks/resources/js/Helper/Address.js
Normal file
|
@ -0,0 +1,126 @@
|
|||
/**
|
||||
* @param {String} fullName
|
||||
* @returns {Array}
|
||||
*/
|
||||
export const splitFullName = (fullName) => {
|
||||
fullName = fullName.trim()
|
||||
if (!fullName.includes(' ')) {
|
||||
return [fullName, ''];
|
||||
}
|
||||
const parts = fullName.split(' ');
|
||||
const firstName = parts[0];
|
||||
parts.shift();
|
||||
const lastName = parts.join(' ');
|
||||
return [firstName, lastName];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} address
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const paypalAddressToWc = (address) => {
|
||||
let map = {
|
||||
country_code: 'country',
|
||||
address_line_1: 'address_1',
|
||||
address_line_2: 'address_2',
|
||||
admin_area_1: 'state',
|
||||
admin_area_2: 'city',
|
||||
postal_code: 'postcode',
|
||||
};
|
||||
if (address.city) { // address not from API, such as onShippingChange
|
||||
map = {
|
||||
country_code: 'country',
|
||||
state: 'state',
|
||||
city: 'city',
|
||||
postal_code: 'postcode',
|
||||
};
|
||||
}
|
||||
const result = {};
|
||||
Object.entries(map).forEach(([paypalKey, wcKey]) => {
|
||||
if (address[paypalKey]) {
|
||||
result[wcKey] = address[paypalKey];
|
||||
}
|
||||
});
|
||||
|
||||
const defaultAddress = {
|
||||
first_name: '',
|
||||
last_name: '',
|
||||
company: '',
|
||||
address_1: '',
|
||||
address_2: '',
|
||||
city: '',
|
||||
state: '',
|
||||
postcode: '',
|
||||
country: '',
|
||||
phone: '',
|
||||
};
|
||||
|
||||
return {...defaultAddress, ...result};
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} shipping
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const paypalShippingToWc = (shipping) => {
|
||||
const [firstName, lastName] = splitFullName(shipping.name.full_name);
|
||||
return {
|
||||
...paypalAddressToWc(shipping.address),
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} payer
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const paypalPayerToWc = (payer) => {
|
||||
const firstName = payer.name.given_name;
|
||||
const lastName = payer.name.surname;
|
||||
const address = payer.address ? paypalAddressToWc(payer.address) : {};
|
||||
return {
|
||||
...address,
|
||||
first_name: firstName,
|
||||
last_name: lastName,
|
||||
email: payer.email_address,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object} order
|
||||
* @returns {Object}
|
||||
*/
|
||||
export const paypalOrderToWcShippingAddress = (order) => {
|
||||
const shipping = order.purchase_units[0].shipping;
|
||||
if (!shipping) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const res = paypalShippingToWc(shipping);
|
||||
|
||||
// use the name from billing if the same, to avoid possible mistakes when splitting full_name
|
||||
const billingAddress = paypalPayerToWc(order.payer);
|
||||
if (`${res.first_name} ${res.last_name}` === `${billingAddress.first_name} ${billingAddress.last_name}`) {
|
||||
res.first_name = billingAddress.first_name;
|
||||
res.last_name = billingAddress.last_name;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param order
|
||||
* @returns {{shippingAddress: Object, billingAddress: Object}}
|
||||
*/
|
||||
export const paypalOrderToWcAddresses = (order) => {
|
||||
const shippingAddress = paypalOrderToWcShippingAddress(order);
|
||||
let billingAddress = paypalPayerToWc(order.payer);
|
||||
// no billing address, such as if billing address retrieval is not allowed in the merchant account
|
||||
if (!billingAddress.address_line_1) {
|
||||
billingAddress = {...shippingAddress, ...paypalPayerToWc(order.payer)};
|
||||
}
|
||||
|
||||
return {billingAddress, shippingAddress};
|
||||
}
|
252
modules/ppcp-blocks/resources/js/checkout-block.js
Normal file
252
modules/ppcp-blocks/resources/js/checkout-block.js
Normal file
|
@ -0,0 +1,252 @@
|
|||
import {useEffect, useState} from '@wordpress/element';
|
||||
import {registerExpressPaymentMethod, registerPaymentMethod} from '@woocommerce/blocks-registry';
|
||||
import {paypalAddressToWc, paypalOrderToWcAddresses} from "./Helper/Address";
|
||||
import {loadPaypalScript} from '../../../ppcp-button/resources/js/modules/Helper/ScriptLoading'
|
||||
|
||||
const config = wc.wcSettings.getSetting('ppcp-gateway_data');
|
||||
|
||||
window.ppcpFundingSource = config.fundingSource;
|
||||
|
||||
const PayPalComponent = ({
|
||||
onClick,
|
||||
onClose,
|
||||
onSubmit,
|
||||
onError,
|
||||
eventRegistration,
|
||||
emitResponse,
|
||||
activePaymentMethod,
|
||||
shippingData,
|
||||
}) => {
|
||||
const {onPaymentSetup} = eventRegistration;
|
||||
const {responseTypes} = emitResponse;
|
||||
|
||||
const [paypalOrder, setPaypalOrder] = useState(null);
|
||||
|
||||
const [loaded, setLoaded] = useState(false);
|
||||
useEffect(() => {
|
||||
if (!loaded) {
|
||||
loadPaypalScript(config.scriptData, () => {
|
||||
setLoaded(true);
|
||||
});
|
||||
}
|
||||
}, [loaded]);
|
||||
|
||||
const createOrder = async () => {
|
||||
try {
|
||||
const res = await fetch(config.scriptData.ajax.create_order.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: config.scriptData.ajax.create_order.nonce,
|
||||
bn_code: '',
|
||||
context: config.scriptData.context,
|
||||
payment_method: 'ppcp-gateway',
|
||||
createaccount: false
|
||||
}),
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success) {
|
||||
if (json.data?.details?.length > 0) {
|
||||
throw new Error(json.data.details.map(d => `${d.issue} ${d.description}`).join('<br/>'));
|
||||
} else if (json.data?.message) {
|
||||
throw new Error(json.data.message);
|
||||
}
|
||||
|
||||
throw new Error(config.scriptData.labels.error.generic);
|
||||
}
|
||||
return json.data.id;
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
onError(err.message);
|
||||
|
||||
onClose();
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleApprove = async (data, actions) => {
|
||||
try {
|
||||
const res = await fetch(config.scriptData.ajax.approve_order.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: config.scriptData.ajax.approve_order.nonce,
|
||||
order_id: data.orderID,
|
||||
funding_source: window.ppcpFundingSource ?? 'paypal',
|
||||
})
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success) {
|
||||
if (typeof actions !== 'undefined' && typeof actions.restart !== 'undefined') {
|
||||
return actions.restart();
|
||||
}
|
||||
if (json.data?.message) {
|
||||
throw new Error(json.data.message);
|
||||
}
|
||||
|
||||
throw new Error(config.scriptData.labels.error.generic)
|
||||
}
|
||||
|
||||
const order = json.data;
|
||||
|
||||
setPaypalOrder(order);
|
||||
|
||||
if (config.finalReviewEnabled) {
|
||||
const addresses = paypalOrderToWcAddresses(order);
|
||||
|
||||
await wp.data.dispatch('wc/store/cart').updateCustomerData({
|
||||
billing_address: addresses.billingAddress,
|
||||
shipping_address: addresses.shippingAddress,
|
||||
});
|
||||
const checkoutUrl = new URL(config.scriptData.redirect);
|
||||
// sometimes some browsers may load some kind of cached version of the page,
|
||||
// so adding a parameter to avoid that
|
||||
checkoutUrl.searchParams.append('ppcp-continuation-redirect', (new Date()).getTime().toString());
|
||||
|
||||
location.href = checkoutUrl.toString();
|
||||
} else {
|
||||
onSubmit();
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
|
||||
onError(err.message);
|
||||
|
||||
onClose();
|
||||
|
||||
throw err;
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (data, actions) => {
|
||||
window.ppcpFundingSource = data.fundingSource;
|
||||
|
||||
onClick();
|
||||
};
|
||||
|
||||
let handleShippingChange = null;
|
||||
if (shippingData.needsShipping && !config.finalReviewEnabled) {
|
||||
handleShippingChange = async (data, actions) => {
|
||||
try {
|
||||
const shippingOptionId = data.selected_shipping_option?.id;
|
||||
if (shippingOptionId) {
|
||||
await shippingData.setSelectedRates(shippingOptionId);
|
||||
}
|
||||
|
||||
const address = paypalAddressToWc(data.shipping_address);
|
||||
|
||||
await wp.data.dispatch('wc/store/cart').updateCustomerData({
|
||||
shipping_address: address,
|
||||
});
|
||||
|
||||
await shippingData.setShippingAddress(address);
|
||||
|
||||
const res = await fetch(config.ajax.update_shipping.endpoint, {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({
|
||||
nonce: config.ajax.update_shipping.nonce,
|
||||
order_id: data.orderID,
|
||||
})
|
||||
});
|
||||
|
||||
const json = await res.json();
|
||||
|
||||
if (!json.success) {
|
||||
throw new Error(json.data.message);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
actions.reject();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (activePaymentMethod !== config.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
const unsubscribeProcessing = onPaymentSetup(() => {
|
||||
if (config.scriptData.continuation) {
|
||||
return {
|
||||
type: responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
'paypal_order_id': config.scriptData.continuation.order_id,
|
||||
'funding_source': window.ppcpFundingSource ?? 'paypal',
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const addresses = paypalOrderToWcAddresses(paypalOrder);
|
||||
|
||||
return {
|
||||
type: responseTypes.SUCCESS,
|
||||
meta: {
|
||||
paymentMethodData: {
|
||||
'paypal_order_id': paypalOrder.id,
|
||||
'funding_source': window.ppcpFundingSource ?? 'paypal',
|
||||
},
|
||||
...addresses,
|
||||
},
|
||||
};
|
||||
});
|
||||
return () => {
|
||||
unsubscribeProcessing();
|
||||
};
|
||||
}, [onPaymentSetup, paypalOrder, activePaymentMethod]);
|
||||
|
||||
if (config.scriptData.continuation) {
|
||||
return (
|
||||
<div dangerouslySetInnerHTML={{__html: config.scriptData.continuation.cancel.html}}>
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const PayPalButton = window.paypal.Buttons.driver("react", { React, ReactDOM });
|
||||
|
||||
return (
|
||||
<PayPalButton
|
||||
style={config.scriptData.button.style}
|
||||
onClick={handleClick}
|
||||
onCancel={onClose}
|
||||
onError={onClose}
|
||||
createOrder={createOrder}
|
||||
onApprove={handleApprove}
|
||||
onShippingChange={handleShippingChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
const features = ['products'];
|
||||
let registerMethod = registerExpressPaymentMethod;
|
||||
if (config.scriptData.continuation) {
|
||||
features.push('ppcp_continuation');
|
||||
registerMethod = registerPaymentMethod;
|
||||
}
|
||||
|
||||
registerMethod({
|
||||
name: config.id,
|
||||
label: <div dangerouslySetInnerHTML={{__html: config.title}}/>,
|
||||
content: <PayPalComponent/>,
|
||||
edit: <b>TODO: editing</b>,
|
||||
ariaLabel: config.title,
|
||||
canMakePayment: () => config.enabled,
|
||||
supports: {
|
||||
features: features,
|
||||
},
|
||||
});
|
57
modules/ppcp-blocks/services.php
Normal file
57
modules/ppcp-blocks/services.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
/**
|
||||
* The blocks module services.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Blocks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
return array(
|
||||
'blocks.url' => static function ( ContainerInterface $container ): string {
|
||||
/**
|
||||
* The path cannot be false.
|
||||
*
|
||||
* @psalm-suppress PossiblyFalseArgument
|
||||
*/
|
||||
return plugins_url(
|
||||
'/modules/ppcp-blocks/',
|
||||
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
'blocks.method' => static function ( ContainerInterface $container ): PayPalPaymentMethod {
|
||||
return new PayPalPaymentMethod(
|
||||
$container->get( 'blocks.url' ),
|
||||
$container->get( 'ppcp.asset-version' ),
|
||||
$container->get( 'button.smart-button' ),
|
||||
$container->get( 'wcgateway.settings' ),
|
||||
$container->get( 'wcgateway.settings.status' ),
|
||||
$container->get( 'wcgateway.paypal-gateway' ),
|
||||
$container->get( 'blocks.settings.final_review_enabled' ),
|
||||
$container->get( 'session.cancellation.view' ),
|
||||
$container->get( 'session.handler' )
|
||||
);
|
||||
},
|
||||
'blocks.settings.final_review_enabled' => static function ( ContainerInterface $container ): bool {
|
||||
$settings = $container->get( 'wcgateway.settings' );
|
||||
assert( $settings instanceof ContainerInterface );
|
||||
|
||||
return $settings->has( 'blocks_final_review_enabled' ) ?
|
||||
(bool) $settings->get( 'blocks_final_review_enabled' ) :
|
||||
true;
|
||||
},
|
||||
|
||||
'blocks.endpoint.update-shipping' => static function ( ContainerInterface $container ): UpdateShippingEndpoint {
|
||||
return new UpdateShippingEndpoint(
|
||||
$container->get( 'button.request-data' ),
|
||||
$container->get( 'api.endpoint.order' ),
|
||||
$container->get( 'api.factory.purchase-unit' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
);
|
100
modules/ppcp-blocks/src/BlocksModule.php
Normal file
100
modules/ppcp-blocks/src/BlocksModule.php
Normal file
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
/**
|
||||
* The blocks module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Blocks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry;
|
||||
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Interop\Container\ServiceProviderInterface;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* Class BlocksModule
|
||||
*/
|
||||
class BlocksModule implements ModuleInterface {
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function setup(): ServiceProviderInterface {
|
||||
return new ServiceProvider(
|
||||
require __DIR__ . '/../services.php',
|
||||
require __DIR__ . '/../extensions.php'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function run( ContainerInterface $c ): void {
|
||||
if (
|
||||
! class_exists( 'Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType' )
|
||||
|| ! function_exists( 'woocommerce_store_api_register_payment_requirements' )
|
||||
) {
|
||||
add_action(
|
||||
'admin_notices',
|
||||
function () {
|
||||
printf(
|
||||
'<div class="notice notice-error"><p>%1$s</p></div>',
|
||||
wp_kses_post(
|
||||
__(
|
||||
'PayPal checkout block initialization failed, possibly old WooCommerce version or disabled WooCommerce Blocks plugin.',
|
||||
'woocommerce-paypal-payments'
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
add_action(
|
||||
'woocommerce_blocks_payment_method_type_registration',
|
||||
function( PaymentMethodRegistry $payment_method_registry ) use ( $c ): void {
|
||||
$payment_method_registry->register( $c->get( 'blocks.method' ) );
|
||||
}
|
||||
);
|
||||
|
||||
woocommerce_store_api_register_payment_requirements(
|
||||
array(
|
||||
'data_callback' => function() use ( $c ): array {
|
||||
$smart_button = $c->get( 'button.smart-button' );
|
||||
assert( $smart_button instanceof SmartButton );
|
||||
|
||||
if ( isset( $smart_button->script_data()['continuation'] ) ) {
|
||||
return array( 'ppcp_continuation' );
|
||||
}
|
||||
|
||||
return array();
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . UpdateShippingEndpoint::ENDPOINT,
|
||||
static function () use ( $c ) {
|
||||
$endpoint = $c->get( 'blocks.endpoint.update-shipping' );
|
||||
assert( $endpoint instanceof UpdateShippingEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key for the module.
|
||||
*
|
||||
* @return string|void
|
||||
*/
|
||||
public function getKey() {
|
||||
}
|
||||
}
|
133
modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php
Normal file
133
modules/ppcp-blocks/src/Endpoint/UpdateShippingEndpoint.php
Normal file
|
@ -0,0 +1,133 @@
|
|||
<?php
|
||||
/**
|
||||
* Updates PayPal order with the current shipping methods.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Patch;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PatchCollection;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\EndpointInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\RequestData;
|
||||
|
||||
/**
|
||||
* Class UpdateShippingEndpoint
|
||||
*/
|
||||
class UpdateShippingEndpoint implements EndpointInterface {
|
||||
const ENDPOINT = 'ppc-update-shipping';
|
||||
|
||||
/**
|
||||
* The Request Data Helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The order endpoint.
|
||||
*
|
||||
* @var OrderEndpoint
|
||||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The purchase unit factory.
|
||||
*
|
||||
* @var PurchaseUnitFactory
|
||||
*/
|
||||
private $purchase_unit_factory;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* UpdateShippingEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The Request Data Helper.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param PurchaseUnitFactory $purchase_unit_factory The purchase unit factory.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestData $request_data,
|
||||
OrderEndpoint $order_endpoint,
|
||||
PurchaseUnitFactory $purchase_unit_factory,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->request_data = $request_data;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
|
||||
$order_id = $data['order_id'];
|
||||
|
||||
$pu = $this->purchase_unit_factory->from_wc_cart( null, true );
|
||||
$pu_data = $pu->to_array();
|
||||
|
||||
if ( ! isset( $pu_data['shipping']['options'] ) ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => 'No shipping methods.',
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: maybe should patch only if methods changed.
|
||||
// But it seems a bit difficult to detect,
|
||||
// e.g. ->order($id) may not have Shipping because we drop it when address or name are missing.
|
||||
// Also may consider patching only amount and options instead of the whole PU, though not sure if it makes any difference.
|
||||
$patches = new PatchCollection(
|
||||
new Patch(
|
||||
'replace',
|
||||
"/purchase_units/@reference_id=='{$pu->reference_id()}'",
|
||||
$pu_data
|
||||
)
|
||||
);
|
||||
|
||||
$this->order_endpoint->patch( $order_id, $patches );
|
||||
|
||||
wp_send_json_success();
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $error->getMessage(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
188
modules/ppcp-blocks/src/PayPalPaymentMethod.php
Normal file
188
modules/ppcp-blocks/src/PayPalPaymentMethod.php
Normal file
|
@ -0,0 +1,188 @@
|
|||
<?php
|
||||
/**
|
||||
* The blocks module.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Blocks
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Blocks;
|
||||
|
||||
use Automattic\WooCommerce\Blocks\Payments\Integrations\AbstractPaymentMethodType;
|
||||
use WC_AJAX;
|
||||
use WooCommerce\PayPalCommerce\Blocks\Endpoint\UpdateShippingEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
use WooCommerce\PayPalCommerce\Session\Cancellation\CancelController;
|
||||
use WooCommerce\PayPalCommerce\Session\Cancellation\CancelView;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Helper\SettingsStatus;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class PayPalPaymentMethod
|
||||
*/
|
||||
class PayPalPaymentMethod extends AbstractPaymentMethodType {
|
||||
/**
|
||||
* The URL of this module.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $module_url;
|
||||
|
||||
/**
|
||||
* The assets version.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $version;
|
||||
|
||||
/**
|
||||
* The smart button script loading handler.
|
||||
*
|
||||
* @var SmartButtonInterface
|
||||
*/
|
||||
private $smart_button;
|
||||
|
||||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $plugin_settings;
|
||||
|
||||
/**
|
||||
* The Settings status helper.
|
||||
*
|
||||
* @var SettingsStatus
|
||||
*/
|
||||
protected $settings_status;
|
||||
|
||||
/**
|
||||
* The WC gateway.
|
||||
*
|
||||
* @var PayPalGateway
|
||||
*/
|
||||
private $gateway;
|
||||
|
||||
/**
|
||||
* Whether the final review is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $final_review_enabled;
|
||||
|
||||
/**
|
||||
* The cancellation view.
|
||||
*
|
||||
* @var CancelView
|
||||
*/
|
||||
private $cancellation_view;
|
||||
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* Assets constructor.
|
||||
*
|
||||
* @param string $module_url The url of this module.
|
||||
* @param string $version The assets version.
|
||||
* @param SmartButtonInterface $smart_button The smart button script loading handler.
|
||||
* @param Settings $plugin_settings The settings.
|
||||
* @param SettingsStatus $settings_status The Settings status helper.
|
||||
* @param PayPalGateway $gateway The WC gateway.
|
||||
* @param bool $final_review_enabled Whether the final review is enabled.
|
||||
* @param CancelView $cancellation_view The cancellation view.
|
||||
* @param SessionHandler $session_handler The Session handler.
|
||||
*/
|
||||
public function __construct(
|
||||
string $module_url,
|
||||
string $version,
|
||||
SmartButtonInterface $smart_button,
|
||||
Settings $plugin_settings,
|
||||
SettingsStatus $settings_status,
|
||||
PayPalGateway $gateway,
|
||||
bool $final_review_enabled,
|
||||
CancelView $cancellation_view,
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
$this->name = PayPalGateway::ID;
|
||||
$this->module_url = $module_url;
|
||||
$this->version = $version;
|
||||
$this->smart_button = $smart_button;
|
||||
$this->plugin_settings = $plugin_settings;
|
||||
$this->settings_status = $settings_status;
|
||||
$this->gateway = $gateway;
|
||||
$this->final_review_enabled = $final_review_enabled;
|
||||
$this->cancellation_view = $cancellation_view;
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function initialize() { }
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function is_active() {
|
||||
// Do not load when definitely not needed,
|
||||
// but we still need to check the locations later and handle in JS
|
||||
// because has_block cannot be called here (too early).
|
||||
return $this->plugin_settings->has( 'enabled' ) && $this->plugin_settings->get( 'enabled' )
|
||||
&& ( $this->settings_status->is_smart_button_enabled_for_location( 'checkout-block-express' ) ||
|
||||
$this->settings_status->is_smart_button_enabled_for_location( 'cart-block' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_payment_method_script_handles() {
|
||||
wp_register_script(
|
||||
'ppcp-checkout-block',
|
||||
trailingslashit( $this->module_url ) . 'assets/js/checkout-block.js',
|
||||
array(),
|
||||
$this->version,
|
||||
true
|
||||
);
|
||||
|
||||
return array( 'ppcp-checkout-block' );
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_payment_method_data() {
|
||||
$script_data = $this->smart_button->script_data();
|
||||
|
||||
if ( isset( $script_data['continuation'] ) ) {
|
||||
$url = add_query_arg( array( CancelController::NONCE => wp_create_nonce( CancelController::NONCE ) ), wc_get_checkout_url() );
|
||||
|
||||
$script_data['continuation']['cancel'] = array(
|
||||
'html' => $this->cancellation_view->render_session_cancellation( $url, $this->session_handler->funding_source() ),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'id' => $this->gateway->id,
|
||||
'title' => $this->gateway->title,
|
||||
'description' => $this->gateway->description,
|
||||
'enabled' => $this->settings_status->is_smart_button_enabled_for_location( $script_data['context'] ),
|
||||
'fundingSource' => $this->session_handler->funding_source(),
|
||||
'finalReviewEnabled' => $this->final_review_enabled,
|
||||
'ajax' => array(
|
||||
'update_shipping' => array(
|
||||
'endpoint' => WC_AJAX::get_endpoint( UpdateShippingEndpoint::ENDPOINT ),
|
||||
'nonce' => wp_create_nonce( UpdateShippingEndpoint::nonce() ),
|
||||
),
|
||||
),
|
||||
'scriptData' => $script_data,
|
||||
);
|
||||
}
|
||||
}
|
38
modules/ppcp-blocks/webpack.config.js
Normal file
38
modules/ppcp-blocks/webpack.config.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
const path = require('path');
|
||||
const isProduction = process.env.NODE_ENV === 'production';
|
||||
|
||||
const DependencyExtractionWebpackPlugin = require( '@woocommerce/dependency-extraction-webpack-plugin' );
|
||||
|
||||
module.exports = {
|
||||
devtool: isProduction ? 'source-map' : 'eval-source-map',
|
||||
mode: isProduction ? 'production' : 'development',
|
||||
target: 'web',
|
||||
plugins: [ new DependencyExtractionWebpackPlugin() ],
|
||||
entry: {
|
||||
'checkout-block': path.resolve('./resources/js/checkout-block.js')
|
||||
},
|
||||
output: {
|
||||
path: path.resolve(__dirname, 'assets/'),
|
||||
filename: 'js/[name].js',
|
||||
},
|
||||
module: {
|
||||
rules: [{
|
||||
test: /\.js?$/,
|
||||
exclude: /node_modules/,
|
||||
loader: 'babel-loader',
|
||||
},
|
||||
{
|
||||
test: /\.scss$/,
|
||||
exclude: /node_modules/,
|
||||
use: [
|
||||
{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: 'css/[name].css',
|
||||
}
|
||||
},
|
||||
{loader:'sass-loader'}
|
||||
]
|
||||
}]
|
||||
}
|
||||
};
|
2201
modules/ppcp-blocks/yarn.lock
Normal file
2201
modules/ppcp-blocks/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
|
@ -177,6 +177,7 @@ return array(
|
|||
$container->get( 'button.context' ),
|
||||
$container->get( 'button.can_save_vault_token' ),
|
||||
$container->get( 'button.vault' ),
|
||||
$container->get( 'button.pay-now-contexts' ),
|
||||
$container->get( 'woocommerce.logger.woocommerce' )
|
||||
);
|
||||
},
|
||||
|
@ -186,6 +187,9 @@ return array(
|
|||
dirname( realpath( __FILE__ ), 3 ) . '/woocommerce-paypal-payments.php'
|
||||
);
|
||||
},
|
||||
'button.pay-now-contexts' => static function ( ContainerInterface $container ): array {
|
||||
return array( 'checkout', 'pay-now' );
|
||||
},
|
||||
'button.request-data' => static function ( ContainerInterface $container ): RequestData {
|
||||
return new RequestData();
|
||||
},
|
||||
|
@ -223,6 +227,8 @@ return array(
|
|||
$registration_needed,
|
||||
$container->get( 'wcgateway.settings.card_billing_data_mode' ),
|
||||
$container->get( 'button.early-wc-checkout-validation-enabled' ),
|
||||
$container->get( 'button.pay-now-contexts' ),
|
||||
$container->get( 'button.handle-shipping-in-paypal' ),
|
||||
$logger
|
||||
);
|
||||
},
|
||||
|
@ -340,4 +346,12 @@ return array(
|
|||
'button.validation.wc-checkout-validator' => static function ( ContainerInterface $container ): CheckoutFormValidator {
|
||||
return new CheckoutFormValidator();
|
||||
},
|
||||
|
||||
/**
|
||||
* If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
|
||||
* May result in slower popup performance, additional loading.
|
||||
*/
|
||||
'button.handle-shipping-in-paypal' => static function ( ContainerInterface $container ): bool {
|
||||
return false;
|
||||
},
|
||||
);
|
||||
|
|
|
@ -26,7 +26,7 @@ class DisabledSmartButton implements SmartButtonInterface {
|
|||
/**
|
||||
* Whether the scripts should be loaded.
|
||||
*/
|
||||
public function should_load(): bool {
|
||||
public function should_load_ppcp_script(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -194,6 +194,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
*/
|
||||
private $vault;
|
||||
|
||||
/**
|
||||
* The contexts that should have the Pay Now button.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $pay_now_contexts;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -232,6 +239,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @param string $context The current context.
|
||||
* @param bool $can_save_vault_token Whether vault tokens could be saved.
|
||||
* @param string $vault Whether vault could be enabled or not.
|
||||
* @param string[] $pay_now_contexts The contexts that should have the Pay Now button.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -252,6 +260,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
array $all_funding_sources,
|
||||
bool $basic_checkout_validation_enabled,
|
||||
bool $early_validation_enabled,
|
||||
array $pay_now_contexts,
|
||||
string $intent,
|
||||
string $context,
|
||||
bool $can_save_vault_token,
|
||||
|
@ -276,6 +285,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
$this->all_funding_sources = $all_funding_sources;
|
||||
$this->basic_checkout_validation_enabled = $basic_checkout_validation_enabled;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->intent = $intent;
|
||||
$this->logger = $logger;
|
||||
$this->context = $context;
|
||||
|
@ -541,31 +551,70 @@ class SmartButton implements SmartButtonInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Whether the scripts should be loaded.
|
||||
* Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
|
||||
*/
|
||||
public function should_load(): bool {
|
||||
public function should_load_ppcp_script(): bool {
|
||||
$buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
|
||||
if ( ! is_checkout() && ! $buttons_enabled ) {
|
||||
if ( ! $buttons_enabled ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
if ( in_array( $this->context(), array( 'checkout-block', 'cart-block' ), true ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->should_load_buttons() || $this->can_render_dcc();
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues the scripts.
|
||||
* Determines whether the button component should be loaded.
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
if ( ! $this->should_load() ) {
|
||||
return;
|
||||
public function should_load_buttons() : bool {
|
||||
$buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
|
||||
if ( ! $buttons_enabled ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$load_script = false;
|
||||
if ( is_checkout() && $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) ) {
|
||||
$load_script = true;
|
||||
$smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context() );
|
||||
$smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
|
||||
$messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() );
|
||||
|
||||
switch ( $this->context() ) {
|
||||
case 'checkout':
|
||||
case 'cart':
|
||||
case 'pay-now':
|
||||
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location;
|
||||
case 'checkout-block':
|
||||
case 'cart-block':
|
||||
return $smart_button_enabled_for_current_location;
|
||||
case 'product':
|
||||
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
|
||||
default:
|
||||
return $smart_button_enabled_for_mini_cart;
|
||||
}
|
||||
if ( $this->load_button_component() ) {
|
||||
$load_script = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether DCC fields can be rendered.
|
||||
*/
|
||||
public function can_render_dcc() : bool {
|
||||
return $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' )
|
||||
&& $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' )
|
||||
&& $this->dcc_applies->for_country_currency()
|
||||
&& in_array( $this->context(), array( 'checkout', 'pay-now' ), true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
if ( $this->can_render_dcc() ) {
|
||||
wp_enqueue_style(
|
||||
'ppcp-hosted-fields',
|
||||
untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css',
|
||||
array(),
|
||||
$this->version
|
||||
);
|
||||
}
|
||||
|
||||
if ( in_array( $this->context, array( 'pay-now', 'checkout' ), true ) ) {
|
||||
|
@ -575,17 +624,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
array(),
|
||||
$this->version
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->can_render_dcc() ) {
|
||||
wp_enqueue_style(
|
||||
'ppcp-hosted-fields',
|
||||
untrailingslashit( $this->module_url ) . '/assets/css/hosted-fields.css',
|
||||
array(),
|
||||
$this->version
|
||||
);
|
||||
}
|
||||
}
|
||||
if ( $load_script ) {
|
||||
wp_enqueue_script(
|
||||
'ppcp-smart-button',
|
||||
untrailingslashit( $this->module_url ) . '/assets/js/button.js',
|
||||
|
@ -600,7 +640,6 @@ class SmartButton implements SmartButtonInterface {
|
|||
$this->script_data()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML for the buttons.
|
||||
|
@ -691,18 +730,6 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether DCC fields can be rendered.
|
||||
*
|
||||
* @return bool
|
||||
* @throws NotFoundException When a setting was not found.
|
||||
*/
|
||||
private function can_render_dcc() : bool {
|
||||
|
||||
$can_render = $this->settings->has( 'dcc_enabled' ) && $this->settings->get( 'dcc_enabled' ) && $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) && $this->dcc_applies->for_country_currency();
|
||||
return $can_render;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders the HTML for the DCC fields.
|
||||
*/
|
||||
|
@ -731,6 +758,24 @@ class SmartButton implements SmartButtonInterface {
|
|||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we can store vault tokens or not.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function can_save_vault_token(): bool {
|
||||
|
||||
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->settings->has( 'vault_enabled' ) || ! $this->settings->get( 'vault_enabled' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether we need to initialize the script to enable tokenization for subscriptions or not.
|
||||
*
|
||||
|
@ -789,7 +834,6 @@ class SmartButton implements SmartButtonInterface {
|
|||
* The configuration for the smart buttons.
|
||||
*
|
||||
* @return array
|
||||
* @throws NotFoundException If a setting hasn't been found.
|
||||
*/
|
||||
public function script_data(): array {
|
||||
global $wp;
|
||||
|
@ -944,6 +988,15 @@ class SmartButton implements SmartButtonInterface {
|
|||
$localize['button']['style']['tagline'] = false;
|
||||
}
|
||||
|
||||
if ( $this->is_paypal_continuation() ) {
|
||||
$order = $this->session_handler->order();
|
||||
assert( $order !== null );
|
||||
|
||||
$localize['continuation'] = array(
|
||||
'order_id' => $order->id(),
|
||||
);
|
||||
}
|
||||
|
||||
$this->request_data->dequeue_nonce_fix();
|
||||
return $localize;
|
||||
}
|
||||
|
@ -966,9 +1019,9 @@ class SmartButton implements SmartButtonInterface {
|
|||
* The JavaScript SDK url parameters.
|
||||
*
|
||||
* @return array
|
||||
* @throws NotFoundException If a setting was not found.
|
||||
*/
|
||||
private function url_params(): array {
|
||||
$context = $this->context();
|
||||
$intent = ( $this->settings->has( 'intent' ) ) ? $this->settings->get( 'intent' ) : 'capture';
|
||||
$product_intent = $this->subscription_helper->current_product_is_subscription() ? 'authorize' : $intent;
|
||||
$other_context_intent = $this->subscription_helper->cart_contains_subscription() ? 'authorize' : $intent;
|
||||
|
@ -978,9 +1031,9 @@ class SmartButton implements SmartButtonInterface {
|
|||
'currency' => $this->currency,
|
||||
'integration-date' => PAYPAL_INTEGRATION_DATE,
|
||||
'components' => implode( ',', $this->components() ),
|
||||
'vault' => $this->vault,
|
||||
'commit' => is_checkout() ? 'true' : 'false',
|
||||
'intent' => $this->intent,
|
||||
'vault' => $this->can_save_vault_token() ? 'true' : 'false',
|
||||
'commit' => in_array( $context, $this->pay_now_contexts, true ) ? 'true' : 'false',
|
||||
'intent' => $context === 'product' ? $product_intent : $other_context_intent,
|
||||
);
|
||||
if (
|
||||
$this->environment->current_environment_is( Environment::SANDBOX )
|
||||
|
@ -1011,6 +1064,13 @@ class SmartButton implements SmartButtonInterface {
|
|||
}
|
||||
}
|
||||
|
||||
if ( in_array( $context, array( 'checkout-block', 'cart-block' ), true ) ) {
|
||||
$disable_funding = array_diff(
|
||||
array_keys( $this->all_funding_sources ),
|
||||
array( 'venmo', 'paylater' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->is_free_trial_cart() ) {
|
||||
$all_sources = array_keys( $this->all_funding_sources );
|
||||
if ( $is_dcc_enabled || $is_separate_card_enabled ) {
|
||||
|
@ -1021,8 +1081,8 @@ class SmartButton implements SmartButtonInterface {
|
|||
|
||||
$enable_funding = array( 'venmo' );
|
||||
|
||||
if ( $this->settings_status->is_pay_later_button_enabled_for_location( $this->context() ) ||
|
||||
$this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context() )
|
||||
if ( $this->settings_status->is_pay_later_button_enabled_for_location( $context ) ||
|
||||
$this->settings_status->is_pay_later_messaging_enabled_for_location( $context )
|
||||
) {
|
||||
$enable_funding[] = 'paylater';
|
||||
} else {
|
||||
|
@ -1091,7 +1151,7 @@ class SmartButton implements SmartButtonInterface {
|
|||
private function components(): array {
|
||||
$components = array();
|
||||
|
||||
if ( $this->load_button_component() ) {
|
||||
if ( $this->should_load_buttons() ) {
|
||||
$components[] = 'buttons';
|
||||
$components[] = 'funding-eligibility';
|
||||
}
|
||||
|
@ -1107,34 +1167,10 @@ class SmartButton implements SmartButtonInterface {
|
|||
return $components;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the button component should be loaded.
|
||||
*
|
||||
* @return bool
|
||||
* @throws NotFoundException If a setting has not been found.
|
||||
*/
|
||||
private function load_button_component() : bool {
|
||||
$smart_button_enabled_for_current_location = $this->settings_status->is_smart_button_enabled_for_location( $this->context );
|
||||
$smart_button_enabled_for_mini_cart = $this->settings_status->is_smart_button_enabled_for_location( 'mini-cart' );
|
||||
$messaging_enabled_for_current_location = $this->settings_status->is_pay_later_messaging_enabled_for_location( $this->context );
|
||||
|
||||
switch ( $this->context ) {
|
||||
case 'checkout':
|
||||
case 'cart':
|
||||
case 'pay-now':
|
||||
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location;
|
||||
case 'product':
|
||||
return $smart_button_enabled_for_current_location || $messaging_enabled_for_current_location || $smart_button_enabled_for_mini_cart;
|
||||
default:
|
||||
return $smart_button_enabled_for_mini_cart;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether DCC is enabled or not.
|
||||
*
|
||||
* @return bool
|
||||
* @throws NotFoundException If a setting has not been found.
|
||||
*/
|
||||
private function dcc_is_enabled(): bool {
|
||||
if ( ! is_checkout() ) {
|
||||
|
@ -1163,9 +1199,11 @@ class SmartButton implements SmartButtonInterface {
|
|||
* @param string $context The context.
|
||||
*
|
||||
* @return string
|
||||
* @throws NotFoundException When a setting hasn't been found.
|
||||
*/
|
||||
private function style_for_context( string $style, string $context ): string {
|
||||
// Use the cart/checkout styles for blocks.
|
||||
$context = str_replace( '-block', '', $context );
|
||||
|
||||
$defaults = array(
|
||||
'layout' => 'vertical',
|
||||
'size' => 'responsive',
|
||||
|
|
|
@ -22,12 +22,12 @@ interface SmartButtonInterface {
|
|||
public function render_wrapper(): bool;
|
||||
|
||||
/**
|
||||
* Whether the scripts should be loaded.
|
||||
* Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
|
||||
*/
|
||||
public function should_load(): bool;
|
||||
public function should_load_ppcp_script(): bool;
|
||||
|
||||
/**
|
||||
* Enqueues the necessary scripts.
|
||||
* Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
|
||||
*/
|
||||
public function enqueue(): void;
|
||||
|
||||
|
|
|
@ -64,15 +64,13 @@ class ButtonModule implements ModuleInterface {
|
|||
add_action(
|
||||
'wp_enqueue_scripts',
|
||||
static function () use ( $c ) {
|
||||
|
||||
$smart_button = $c->get( 'button.smart-button' );
|
||||
/**
|
||||
* The Smart Button.
|
||||
*
|
||||
* @var SmartButtonInterface $smart_button
|
||||
*/
|
||||
assert( $smart_button instanceof SmartButtonInterface );
|
||||
|
||||
if ( $smart_button->should_load_ppcp_script() ) {
|
||||
$smart_button->enqueue();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
add_filter(
|
||||
|
|
|
@ -180,7 +180,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
|
|||
);
|
||||
}
|
||||
$this->session_handler->replace_order( $order );
|
||||
wp_send_json_success( $order );
|
||||
wp_send_json_success( $order->to_array() );
|
||||
}
|
||||
|
||||
if ( $this->order_helper->contains_physical_goods( $order ) && ! $order->status()->is( OrderStatus::APPROVED ) && ! $order->status()->is( OrderStatus::CREATED ) ) {
|
||||
|
@ -198,7 +198,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
|
|||
$this->session_handler->replace_funding_source( $funding_source );
|
||||
|
||||
$this->session_handler->replace_order( $order );
|
||||
wp_send_json_success( $order );
|
||||
wp_send_json_success( $order->to_array() );
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Order approve failed: ' . $error->getMessage() );
|
||||
|
|
|
@ -138,6 +138,20 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
*/
|
||||
protected $early_validation_enabled;
|
||||
|
||||
/**
|
||||
* The contexts that should have the Pay Now button.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $pay_now_contexts;
|
||||
|
||||
/**
|
||||
* If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $handle_shipping_in_paypal;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -159,6 +173,8 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
* @param bool $registration_needed Whether a new user must be registered during checkout.
|
||||
* @param string $card_billing_data_mode The value of card_billing_data_mode from the settings.
|
||||
* @param bool $early_validation_enabled Whether to execute WC validation of the checkout form.
|
||||
* @param string[] $pay_now_contexts The contexts that should have the Pay Now button.
|
||||
* @param bool $handle_shipping_in_paypal If true, the shipping methods are sent to PayPal allowing the customer to select it inside the popup.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -173,6 +189,8 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
bool $registration_needed,
|
||||
string $card_billing_data_mode,
|
||||
bool $early_validation_enabled,
|
||||
array $pay_now_contexts,
|
||||
bool $handle_shipping_in_paypal,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
|
@ -187,6 +205,8 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->pay_now_contexts = $pay_now_contexts;
|
||||
$this->handle_shipping_in_paypal = $handle_shipping_in_paypal;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
|
@ -226,7 +246,7 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
}
|
||||
$this->purchase_unit = $this->purchase_unit_factory->from_wc_order( $wc_order );
|
||||
} else {
|
||||
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart();
|
||||
$this->purchase_unit = $this->purchase_unit_factory->from_wc_cart( null, $this->handle_shipping_in_paypal );
|
||||
|
||||
// The cart does not have any info about payment method, so we must handle free trial here.
|
||||
if ( (
|
||||
|
@ -385,6 +405,9 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$funding_source
|
||||
);
|
||||
|
||||
$action = in_array( $this->parsed_request_data['context'], $this->pay_now_contexts, true ) ?
|
||||
ApplicationContext::USER_ACTION_PAY_NOW : ApplicationContext::USER_ACTION_CONTINUE;
|
||||
|
||||
if ( 'card' === $funding_source ) {
|
||||
if ( CardBillingMode::MINIMAL_INPUT === $this->card_billing_data_mode ) {
|
||||
if ( ApplicationContext::SHIPPING_PREFERENCE_SET_PROVIDED_ADDRESS === $shipping_preference ) {
|
||||
|
@ -410,7 +433,9 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$shipping_preference,
|
||||
$payer,
|
||||
null,
|
||||
$this->payment_method()
|
||||
$this->payment_method(),
|
||||
'',
|
||||
$action
|
||||
);
|
||||
} catch ( PayPalApiException $exception ) {
|
||||
// Looks like currently there is no proper way to validate the shipping address for PayPal,
|
||||
|
|
|
@ -17,24 +17,32 @@ trait ContextTrait {
|
|||
* @return string
|
||||
*/
|
||||
protected function context(): string {
|
||||
$context = 'mini-cart';
|
||||
if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
|
||||
$context = 'product';
|
||||
return 'product';
|
||||
}
|
||||
|
||||
// has_block may not work if called too early, such as during the block registration.
|
||||
if ( has_block( 'woocommerce/cart' ) ) {
|
||||
return 'cart-block';
|
||||
}
|
||||
|
||||
if ( is_cart() ) {
|
||||
$context = 'cart';
|
||||
}
|
||||
|
||||
if ( is_checkout() && ! $this->is_paypal_continuation() ) {
|
||||
$context = 'checkout';
|
||||
return 'cart';
|
||||
}
|
||||
|
||||
if ( is_checkout_pay_page() ) {
|
||||
$context = 'pay-now';
|
||||
return 'pay-now';
|
||||
}
|
||||
|
||||
return $context;
|
||||
if ( has_block( 'woocommerce/checkout' ) ) {
|
||||
return 'checkout-block';
|
||||
}
|
||||
|
||||
if ( ( is_checkout() ) && ! $this->is_paypal_continuation() ) {
|
||||
return 'checkout';
|
||||
}
|
||||
|
||||
return 'mini-cart';
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -16,6 +16,8 @@ use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
|||
*/
|
||||
class CancelController {
|
||||
|
||||
public const NONCE = 'ppcp-cancel';
|
||||
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
|
@ -49,12 +51,11 @@ class CancelController {
|
|||
* Runs the controller.
|
||||
*/
|
||||
public function run() {
|
||||
$param_name = 'ppcp-cancel';
|
||||
$nonce = 'ppcp-cancel-' . get_current_user_id();
|
||||
$param_name = self::NONCE;
|
||||
if ( isset( $_GET[ $param_name ] ) && // Input var ok.
|
||||
wp_verify_nonce(
|
||||
sanitize_text_field( wp_unslash( $_GET[ $param_name ] ) ), // Input var ok.
|
||||
$nonce
|
||||
self::NONCE
|
||||
)
|
||||
) { // Input var ok.
|
||||
$this->session_handler->destroy_session_data();
|
||||
|
@ -74,11 +75,12 @@ class CancelController {
|
|||
return; // Ignore for card buttons.
|
||||
}
|
||||
|
||||
$url = add_query_arg( array( $param_name => wp_create_nonce( $nonce ) ), wc_get_checkout_url() );
|
||||
$url = add_query_arg( array( $param_name => wp_create_nonce( self::NONCE ) ), wc_get_checkout_url() );
|
||||
add_action(
|
||||
'woocommerce_review_order_after_submit',
|
||||
function () use ( $url ) {
|
||||
$this->view->render_session_cancellation( $url, $this->session_handler->funding_source() );
|
||||
// phpcs:ignore WordPress.Security.EscapeOutput
|
||||
echo $this->view->render_session_cancellation( $url, $this->session_handler->funding_source() );
|
||||
}
|
||||
);
|
||||
}
|
||||
|
|
|
@ -50,7 +50,8 @@ class CancelView {
|
|||
* @param string $url The URL.
|
||||
* @param string|null $funding_source The ID of the funding source, such as 'venmo'.
|
||||
*/
|
||||
public function render_session_cancellation( string $url, ?string $funding_source ) {
|
||||
public function render_session_cancellation( string $url, ?string $funding_source ): string {
|
||||
ob_start();
|
||||
?>
|
||||
<p id="ppcp-cancel"
|
||||
class="has-text-align-center ppcp-cancel"
|
||||
|
@ -73,5 +74,6 @@ class CancelView {
|
|||
?>
|
||||
</p>
|
||||
<?php
|
||||
return (string) ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,6 +33,7 @@ use WooCommerce\PayPalCommerce\WcGateway\Admin\RenderAuthorizeAction;
|
|||
use WooCommerce\PayPalCommerce\WcGateway\Assets\FraudNetAssets;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Checkout\DisableGateways;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Cli\SettingsCommand;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Endpoint\ReturnUrlEndpoint;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\FundingSource\FundingSourceRenderer;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||
|
@ -1413,4 +1414,9 @@ return array(
|
|||
$container->get( 'wcgateway.is-fraudnet-enabled' )
|
||||
);
|
||||
},
|
||||
'wcgateway.cli.settings.command' => function( ContainerInterface $container ) : SettingsCommand {
|
||||
return new SettingsCommand(
|
||||
$container->get( 'wcgateway.settings' )
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
70
modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php
Normal file
70
modules/ppcp-wc-gateway/src/Cli/SettingsCommand.php
Normal file
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
/**
|
||||
* WP-CLI commands for managing plugin settings.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\WcGateway\Cli
|
||||
*/
|
||||
|
||||
declare( strict_types=1 );
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\WcGateway\Cli;
|
||||
|
||||
use WP_CLI;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Settings\Settings;
|
||||
|
||||
/**
|
||||
* Class SettingsCommand.
|
||||
*/
|
||||
class SettingsCommand {
|
||||
/**
|
||||
* The settings.
|
||||
*
|
||||
* @var Settings
|
||||
*/
|
||||
private $settings;
|
||||
|
||||
/**
|
||||
* SettingsCommand constructor.
|
||||
*
|
||||
* @param Settings $settings The settings.
|
||||
*/
|
||||
public function __construct( Settings $settings ) {
|
||||
$this->settings = $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the specified settings.
|
||||
*
|
||||
* ## OPTIONS
|
||||
*
|
||||
* <id>
|
||||
* : The setting key.
|
||||
*
|
||||
* <value>
|
||||
* : The setting value.
|
||||
*
|
||||
* ## EXAMPLES
|
||||
*
|
||||
* wp pcp settings update description "Pay via PayPal."
|
||||
* wp pcp settings update vault_enabled true
|
||||
* wp pcp settings update vault_enabled false
|
||||
*
|
||||
* @param array $args Positional args.
|
||||
* @param array $assoc_args Option args.
|
||||
*/
|
||||
public function update( array $args, array $assoc_args ): void {
|
||||
$key = (string) $args[0];
|
||||
$value = $args[1];
|
||||
|
||||
if ( 'true' === strtolower( $value ) ) {
|
||||
$value = true;
|
||||
} elseif ( 'false' === strtolower( $value ) ) {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
$this->settings->set( $key, $value );
|
||||
$this->settings->persist();
|
||||
|
||||
WP_CLI::success( "Updated '{$key}' to '{$value}'." );
|
||||
}
|
||||
}
|
|
@ -444,7 +444,13 @@ class PayPalGateway extends \WC_Payment_Gateway {
|
|||
}
|
||||
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
|
||||
$funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? '' ) );
|
||||
$funding_source = wc_clean( wp_unslash( $_POST['ppcp-funding-source'] ?? ( $_POST['funding_source'] ?? '' ) ) );
|
||||
|
||||
if ( $funding_source ) {
|
||||
$wc_order->set_payment_method_title( $this->funding_source_renderer->render_name( $funding_source ) );
|
||||
$wc_order->save();
|
||||
}
|
||||
|
||||
if ( 'card' !== $funding_source && $this->is_free_trial_order( $wc_order ) ) {
|
||||
$user_id = (int) $wc_order->get_customer_id();
|
||||
$tokens = $this->payment_token_repository->all_for_user_id( $user_id );
|
||||
|
|
|
@ -440,6 +440,7 @@ class PayUponInvoice {
|
|||
}
|
||||
|
||||
if (
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
isset( $_GET['pay_for_order'] ) && $_GET['pay_for_order'] === 'true'
|
||||
&& ! $this->pui_helper->is_pay_for_order_ready_for_pui()
|
||||
) {
|
||||
|
|
|
@ -98,6 +98,9 @@ class SettingsStatus {
|
|||
if ( 'pay-now' === $location ) {
|
||||
$location = 'checkout';
|
||||
}
|
||||
if ( 'checkout-block' === $location ) {
|
||||
$location = 'checkout-block-express';
|
||||
}
|
||||
return $location;
|
||||
}
|
||||
|
||||
|
|
|
@ -162,8 +162,13 @@ class OrderProcessor {
|
|||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function process( WC_Order $wc_order ): bool {
|
||||
public function process( \WC_Order $wc_order ): bool {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification
|
||||
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY ) ?: wc_clean( wp_unslash( $_POST['paypal_order_id'] ?? '' ) );
|
||||
$order = $this->session_handler->order();
|
||||
if ( ! $order && is_string( $order_id ) ) {
|
||||
$order = $this->order_endpoint->order( $order_id );
|
||||
}
|
||||
if ( ! $order ) {
|
||||
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
|
||||
if ( ! $order_id ) {
|
||||
|
|
|
@ -381,6 +381,13 @@ class WCGatewayModule implements ModuleInterface {
|
|||
10,
|
||||
3
|
||||
);
|
||||
|
||||
if ( defined( 'WP_CLI' ) && WP_CLI ) {
|
||||
\WP_CLI::add_command(
|
||||
'pcp settings',
|
||||
$c->get( 'wcgateway.cli.settings.command' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
"author": "WooCommerce",
|
||||
"scripts": {
|
||||
"postinstall": "run-s install:modules:* && run-s build:modules",
|
||||
"install:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn install",
|
||||
"install:modules:ppcp-button": "cd modules/ppcp-button && yarn install",
|
||||
"install:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn install",
|
||||
"install:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn install",
|
||||
|
@ -14,6 +15,7 @@
|
|||
"install:modules:ppcp-onboarding": "cd modules/ppcp-onboarding && yarn install",
|
||||
"install:modules:ppcp-compat": "cd modules/ppcp-compat && yarn install",
|
||||
"install:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn install",
|
||||
"build:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run build",
|
||||
"build:modules:ppcp-button": "cd modules/ppcp-button && yarn run build",
|
||||
"build:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run build",
|
||||
"build:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run build",
|
||||
|
@ -22,6 +24,7 @@
|
|||
"build:modules:ppcp-compat": "cd modules/ppcp-compat && yarn run build",
|
||||
"build:modules:ppcp-uninstall": "cd modules/ppcp-uninstall && yarn run build",
|
||||
"build:modules": "run-p build:modules:*",
|
||||
"watch:modules:ppcp-blocks": "cd modules/ppcp-blocks && yarn run watch",
|
||||
"watch:modules:ppcp-button": "cd modules/ppcp-button && yarn run watch",
|
||||
"watch:modules:ppcp-wc-gateway": "cd modules/ppcp-wc-gateway && yarn run watch",
|
||||
"watch:modules:ppcp-webhooks": "cd modules/ppcp-webhooks && yarn run watch",
|
||||
|
@ -39,7 +42,7 @@
|
|||
"ddev:watch-js": "ddev yarn watch:modules",
|
||||
"ddev:composer-update": "ddev composer update && ddev composer update --lock",
|
||||
"ddev:unit-tests": "ddev exec phpunit",
|
||||
"ddev:e2e-tests": "cp -n .env.e2e.example .env.e2e && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist",
|
||||
"ddev:e2e-tests": "(cp -n .env.e2e.example .env.e2e || true) && ddev php tests/e2e/PHPUnit/setup.php && ddev exec phpunit -c tests/e2e/phpunit.xml.dist",
|
||||
"ddev:pw-install": "ddev yarn playwright install --with-deps",
|
||||
"ddev:pw-tests": "ddev yarn playwright test",
|
||||
"ddev:test": "yarn run ddev:unit-tests && yarn run ddev:e2e-tests && yarn run ddev:pw-tests",
|
||||
|
@ -58,7 +61,7 @@
|
|||
"prearchive": "rm -rf $npm_package_name.zip",
|
||||
"archive": "zip -r $npm_package_name.zip . -x **.git/\\* **node_modules/\\*",
|
||||
"postarchive": "yarn run archive:cleanup && rm -rf $npm_package_name && unzip $npm_package_name.zip -d $npm_package_name && rm $npm_package_name.zip && zip -r $npm_package_name.zip $npm_package_name && rm -rf $npm_package_name",
|
||||
"archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\"
|
||||
"archive:cleanup": "zip -d $npm_package_name.zip .env* .ddev/\\* \\*.idea/\\* .editorconfig tests/\\* .github/\\* .psalm/\\* wordpress_org_assets/\\* \\*.DS_Store \\*README.md \\*.gitattributes \\*.gitignore \\*composer.json \\*composer.lock patchwork.json phpunit.xml.dist .phpunit.result.cache phpcs.xml* psalm*.xml* playwright.config.js wp-cli.yml \\*.babelrc \\*package.json \\*webpack.config.js \\*yarn.lock \\*.travis.yml\\"
|
||||
},
|
||||
"config": {
|
||||
"wp_org_slug": "woocommerce-paypal-payments"
|
||||
|
|
|
@ -53,4 +53,5 @@
|
|||
<exclude-pattern>*/vendor/*</exclude-pattern>
|
||||
<exclude-pattern>./tests/*</exclude-pattern>
|
||||
<exclude-pattern>*/resources/*</exclude-pattern>
|
||||
<exclude-pattern>*.asset.php</exclude-pattern>
|
||||
</ruleset>
|
||||
|
|
|
@ -32,8 +32,10 @@
|
|||
|
||||
<stubs>
|
||||
<file name=".psalm/stubs.php"/>
|
||||
<file name=".psalm/wcblocks.php"/>
|
||||
<file name=".psalm/wcs.php"/>
|
||||
<file name=".psalm/gzd.php"/>
|
||||
<file name=".psalm/wpcli.php"/>
|
||||
<file name="vendor/php-stubs/wordpress-stubs/wordpress-stubs.php"/>
|
||||
<file name="vendor/php-stubs/woocommerce-stubs/woocommerce-stubs.php"/>
|
||||
</stubs>
|
||||
|
|
|
@ -647,7 +647,7 @@ class OrderEndpointTest extends TestCase
|
|||
$intent = 'CAPTURE';
|
||||
|
||||
$logger = Mockery::mock(LoggerInterface::class);
|
||||
$logger->shouldReceive('log');
|
||||
$logger->shouldReceive('warning');
|
||||
$logger->shouldReceive('debug');
|
||||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
|
@ -742,7 +742,7 @@ class OrderEndpointTest extends TestCase
|
|||
$intent = 'CAPTURE';
|
||||
|
||||
$logger = Mockery::mock(LoggerInterface::class);
|
||||
$logger->shouldReceive('log');
|
||||
$logger->shouldReceive('warning');
|
||||
$logger->shouldReceive('debug');
|
||||
|
||||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
|
@ -882,7 +882,7 @@ class OrderEndpointTest extends TestCase
|
|||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
$applicationContextRepository
|
||||
->expects('current_context')
|
||||
->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING))
|
||||
->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE)
|
||||
->andReturn($applicationContext);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
|
||||
|
@ -985,7 +985,7 @@ class OrderEndpointTest extends TestCase
|
|||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
$applicationContextRepository
|
||||
->expects('current_context')
|
||||
->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE))
|
||||
->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE)
|
||||
->andReturn($applicationContext);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
|
||||
|
@ -1065,7 +1065,7 @@ class OrderEndpointTest extends TestCase
|
|||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
$applicationContextRepository
|
||||
->expects('current_context')
|
||||
->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING))
|
||||
->with(ApplicationContext::SHIPPING_PREFERENCE_NO_SHIPPING, ApplicationContext::USER_ACTION_CONTINUE)
|
||||
->andReturn($applicationContext);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
|
||||
|
@ -1156,7 +1156,7 @@ class OrderEndpointTest extends TestCase
|
|||
$applicationContextRepository = Mockery::mock(ApplicationContextRepository::class);
|
||||
$applicationContextRepository
|
||||
->expects('current_context')
|
||||
->with(Matchers::identicalTo(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE))
|
||||
->with(ApplicationContext::SHIPPING_PREFERENCE_GET_FROM_FILE, ApplicationContext::USER_ACTION_CONTINUE)
|
||||
->andReturn($applicationContext);
|
||||
$subscription_helper = Mockery::mock(SubscriptionHelper::class);
|
||||
$subscription_helper->shouldReceive('cart_contains_subscription')->andReturn(true);
|
||||
|
|
|
@ -294,7 +294,7 @@ class PurchaseUnitFactoryTest extends TestCase
|
|||
$shippingFactory = Mockery::mock(ShippingFactory::class);
|
||||
$shippingFactory
|
||||
->expects('from_wc_customer')
|
||||
->with($wcCustomer)
|
||||
->with($wcCustomer, false)
|
||||
->andReturn($shipping);
|
||||
$paymentsFacory = Mockery::mock(PaymentsFactory::class);
|
||||
$testee = new PurchaseUnitFactory(
|
||||
|
|
|
@ -167,6 +167,8 @@ class CreateOrderEndpointTest extends TestCase
|
|||
false,
|
||||
CardBillingMode::MINIMAL_INPUT,
|
||||
false,
|
||||
['checkout'],
|
||||
false,
|
||||
new NullLogger()
|
||||
);
|
||||
return array($payer_factory, $testee);
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const {test, expect} = require('@playwright/test');
|
||||
const {serverExec} = require("./utils/server");
|
||||
|
||||
const {
|
||||
CUSTOMER_EMAIL,
|
||||
|
@ -7,6 +8,13 @@ const {
|
|||
CREDIT_CARD_EXPIRATION,
|
||||
CREDIT_CARD_CVV,
|
||||
PRODUCT_URL,
|
||||
PRODUCT_ID,
|
||||
CHECKOUT_URL,
|
||||
CHECKOUT_PAGE_ID,
|
||||
CART_URL,
|
||||
BLOCK_CHECKOUT_URL,
|
||||
BLOCK_CHECKOUT_PAGE_ID,
|
||||
BLOCK_CART_URL,
|
||||
} = process.env;
|
||||
|
||||
async function fillCheckoutForm(page) {
|
||||
|
@ -30,36 +38,79 @@ async function fillCheckoutForm(page) {
|
|||
}
|
||||
}
|
||||
|
||||
async function openPaypalPopup(page) {
|
||||
async function openPaypalPopup(page, retry = true) {
|
||||
try {
|
||||
await page.locator('.component-frame').scrollIntoViewIfNeeded();
|
||||
|
||||
const [popup] = await Promise.all([
|
||||
page.waitForEvent('popup'),
|
||||
page.waitForEvent('popup', {timeout: 5000}),
|
||||
page.frameLocator('.component-frame').locator('[data-funding-source="paypal"]').click(),
|
||||
]);
|
||||
|
||||
await popup.waitForLoadState();
|
||||
|
||||
return popup;
|
||||
} catch (err) {
|
||||
if (retry) {
|
||||
return openPaypalPopup(page, false);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIntoPaypal(popup) {
|
||||
await popup.click("text=Log in");
|
||||
await Promise.any([
|
||||
popup.locator('[name="login_email"]'),
|
||||
popup.click("text=Log in"),
|
||||
]);
|
||||
|
||||
await popup.fill('[name="login_email"]', CUSTOMER_EMAIL);
|
||||
await popup.locator('#btnNext').click();
|
||||
await popup.fill('[name="login_password"]', CUSTOMER_PASSWORD);
|
||||
await popup.locator('#btnLogin').click();
|
||||
}
|
||||
|
||||
test('PayPal button place order from Product page', async ({page}) => {
|
||||
async function waitForPaypalShippingList(popup) {
|
||||
await expect(popup.locator('#shippingMethodsDropdown')).toBeVisible({timeout: 15000});
|
||||
}
|
||||
|
||||
async function completePaypalPayment(popup) {
|
||||
await Promise.all([
|
||||
popup.waitForEvent('close', {timeout: 20000}),
|
||||
popup.click('#payment-submit-btn'),
|
||||
]);
|
||||
}
|
||||
|
||||
async function expectOrderReceivedPage(page) {
|
||||
const title = await page.locator('.entry-title');
|
||||
await expect(title).toHaveText('Order received');
|
||||
}
|
||||
|
||||
async function completeBlockContinuation(page) {
|
||||
await expect(page.locator('#radio-control-wc-payment-method-options-ppcp-gateway')).toBeChecked();
|
||||
|
||||
await expect(page.locator('.component-frame')).toHaveCount(0);
|
||||
|
||||
await page.locator('.wc-block-components-checkout-place-order-button').click();
|
||||
|
||||
await page.waitForNavigation();
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
}
|
||||
|
||||
test.describe('Classic checkout', () => {
|
||||
test.beforeAll(async ({ browser }) => {
|
||||
await serverExec('wp option update woocommerce_checkout_page_id ' + CHECKOUT_PAGE_ID);
|
||||
});
|
||||
|
||||
test('PayPal button place order from Product page', async ({page}) => {
|
||||
await page.goto(PRODUCT_URL);
|
||||
|
||||
const popup = await openPaypalPopup(page);
|
||||
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await popup.locator('#payment-submit-btn').click();
|
||||
await completePaypalPayment(popup);
|
||||
|
||||
await fillCheckoutForm(page);
|
||||
|
||||
|
@ -68,16 +119,14 @@ test('PayPal button place order from Product page', async ({page}) => {
|
|||
page.locator('#place_order').click(),
|
||||
]);
|
||||
|
||||
const title = await page.locator('.entry-title');
|
||||
await expect(title).toHaveText('Order received');
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', async ({page}) => {
|
||||
|
||||
await page.goto(PRODUCT_URL);
|
||||
await page.locator('.single_add_to_cart_button').click();
|
||||
|
||||
await page.goto('/checkout/');
|
||||
await page.goto(CHECKOUT_URL);
|
||||
await fillCheckoutForm(page);
|
||||
|
||||
await page.click("text=Credit Cards");
|
||||
|
@ -96,6 +145,75 @@ test('Advanced Credit and Debit Card (ACDC) place order from Checkout page', asy
|
|||
page.locator('.ppcp-dcc-order-button').click(),
|
||||
]);
|
||||
|
||||
const title = await page.locator('.entry-title');
|
||||
await expect(title).toHaveText('Order received');
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
});
|
||||
|
||||
test.describe('Block checkout', () => {
|
||||
test.beforeAll(async ({browser}) => {
|
||||
await serverExec('wp option update woocommerce_checkout_page_id ' + BLOCK_CHECKOUT_PAGE_ID);
|
||||
await serverExec('wp pcp settings update blocks_final_review_enabled true');
|
||||
});
|
||||
|
||||
test('PayPal express block checkout', async ({page}) => {
|
||||
await page.goto('?add-to-cart=' + PRODUCT_ID);
|
||||
|
||||
await page.goto(BLOCK_CHECKOUT_URL)
|
||||
|
||||
const popup = await openPaypalPopup(page);
|
||||
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await completePaypalPayment(popup);
|
||||
|
||||
await completeBlockContinuation(page);
|
||||
});
|
||||
|
||||
test('PayPal express block cart', async ({page}) => {
|
||||
await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID)
|
||||
|
||||
const popup = await openPaypalPopup(page);
|
||||
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await completePaypalPayment(popup);
|
||||
|
||||
await completeBlockContinuation(page);
|
||||
});
|
||||
|
||||
test.describe('Without review', () => {
|
||||
test.beforeAll(async ({browser}) => {
|
||||
await serverExec('wp pcp settings update blocks_final_review_enabled false');
|
||||
});
|
||||
|
||||
test('PayPal express block checkout', async ({page}) => {
|
||||
await page.goto('?add-to-cart=' + PRODUCT_ID);
|
||||
|
||||
await page.goto(BLOCK_CHECKOUT_URL)
|
||||
|
||||
const popup = await openPaypalPopup(page);
|
||||
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await waitForPaypalShippingList(popup);
|
||||
|
||||
await completePaypalPayment(popup);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
|
||||
test('PayPal express block cart', async ({page}) => {
|
||||
await page.goto(BLOCK_CART_URL + '?add-to-cart=' + PRODUCT_ID)
|
||||
|
||||
const popup = await openPaypalPopup(page);
|
||||
|
||||
await loginIntoPaypal(popup);
|
||||
|
||||
await waitForPaypalShippingList(popup);
|
||||
|
||||
await completePaypalPayment(popup);
|
||||
|
||||
await expectOrderReceivedPage(page);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
27
tests/playwright/utils/server.js
Normal file
27
tests/playwright/utils/server.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
const { exec } = require('node:child_process');
|
||||
|
||||
/**
|
||||
* Executes the command on the server (inside DDEV). Can be called inside and outside DDEV.
|
||||
*/
|
||||
export const serverExec = async (cmd) => {
|
||||
const isDdev = process.env.IS_DDEV_PROJECT === 'true';
|
||||
if (!isDdev) {
|
||||
cmd = 'ddev exec ' + cmd;
|
||||
}
|
||||
|
||||
console.log(cmd);
|
||||
|
||||
return new Promise((resolve) => exec(cmd, (error, stdout, stderr) => {
|
||||
if (stderr) {
|
||||
console.error(stderr);
|
||||
}
|
||||
if (stdout) {
|
||||
console.log(stdout);
|
||||
}
|
||||
if (error) {
|
||||
throw error;
|
||||
} else {
|
||||
resolve(stdout);
|
||||
}
|
||||
}))
|
||||
}
|
1
wp-cli.yml
Normal file
1
wp-cli.yml
Normal file
|
@ -0,0 +1 @@
|
|||
path: .ddev/wordpress/
|
Loading…
Add table
Add a link
Reference in a new issue