mirror of
https://github.com/woocommerce/woocommerce-paypal-payments.git
synced 2025-09-06 14:57:26 +08:00
Merge branch 'trunk' into PCP-190-override-language-used-to-display-PayPal-buttons
# Conflicts: # modules/ppcp-button/services.php # modules/ppcp-button/src/Assets/SmartButton.php # modules/ppcp-wc-gateway/services.php
This commit is contained in:
commit
e3fbadf419
465 changed files with 41582 additions and 6679 deletions
47
modules/ppcp-button/src/Assets/ButtonInterface.php
Normal file
47
modules/ppcp-button/src/Assets/ButtonInterface.php
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
/**
|
||||
* The interface for the button asset renderer.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Assets
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Assets;
|
||||
|
||||
/**
|
||||
* Interface ButtonInterface
|
||||
*/
|
||||
interface ButtonInterface {
|
||||
|
||||
/**
|
||||
* Initializes the button.
|
||||
*/
|
||||
public function initialize(): void;
|
||||
|
||||
/**
|
||||
* Indicates if the button is enabled.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled(): bool;
|
||||
|
||||
/**
|
||||
* Renders the necessary HTML.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function render(): bool;
|
||||
|
||||
/**
|
||||
* Enqueues scripts/styles.
|
||||
*/
|
||||
public function enqueue(): void;
|
||||
|
||||
/**
|
||||
* The configuration for the smart buttons.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function script_data(): array;
|
||||
}
|
|
@ -24,12 +24,16 @@ class DisabledSmartButton implements SmartButtonInterface {
|
|||
}
|
||||
|
||||
/**
|
||||
* Enqueues necessary scripts.
|
||||
*
|
||||
* @return bool
|
||||
* Whether the scripts should be loaded.
|
||||
*/
|
||||
public function enqueue(): bool {
|
||||
return true;
|
||||
public function should_load_ppcp_script(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueues necessary scripts.
|
||||
*/
|
||||
public function enqueue(): void {
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -41,4 +45,13 @@ class DisabledSmartButton implements SmartButtonInterface {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The configuration for the smart buttons.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function script_data(): array {
|
||||
return array();
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -22,16 +22,19 @@ interface SmartButtonInterface {
|
|||
public function render_wrapper(): bool;
|
||||
|
||||
/**
|
||||
* Enqueues the necessary scripts.
|
||||
*
|
||||
* @return bool
|
||||
* Whether any of our scripts (for DCC or product, mini-cart, non-block cart/checkout) should be loaded.
|
||||
*/
|
||||
public function enqueue(): bool;
|
||||
public function should_load_ppcp_script(): bool;
|
||||
|
||||
/**
|
||||
* Whether the running installation could save vault tokens or not.
|
||||
*
|
||||
* @return bool
|
||||
* Enqueues our scripts/styles (for DCC and product, mini-cart and non-block cart/checkout)
|
||||
*/
|
||||
public function can_save_vault_token(): bool;
|
||||
public function enqueue(): void;
|
||||
|
||||
/**
|
||||
* The configuration for the smart buttons.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function script_data(): array;
|
||||
}
|
||||
|
|
|
@ -9,6 +9,11 @@ declare(strict_types=1);
|
|||
|
||||
namespace WooCommerce\PayPalCommerce\Button;
|
||||
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ApproveSubscriptionEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\CartScriptParamsEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SaveCheckoutFormEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\SimulateCartEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Endpoint\ValidateCheckoutEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Container\ServiceProvider;
|
||||
use WooCommerce\PayPalCommerce\Vendor\Dhii\Modular\Module\ModuleInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButtonInterface;
|
||||
|
@ -60,14 +65,12 @@ 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
|
||||
*/
|
||||
$smart_button->enqueue();
|
||||
assert( $smart_button instanceof SmartButtonInterface );
|
||||
|
||||
if ( $smart_button->should_load_ppcp_script() ) {
|
||||
$smart_button->enqueue();
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
|
@ -95,7 +98,7 @@ class ButtonModule implements ModuleInterface {
|
|||
*
|
||||
* @param ContainerInterface $container The Container.
|
||||
*/
|
||||
private function register_ajax_endpoints( ContainerInterface $container ) {
|
||||
private function register_ajax_endpoints( ContainerInterface $container ): void {
|
||||
add_action(
|
||||
'wc_ajax_' . DataClientIdEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
|
@ -118,6 +121,19 @@ class ButtonModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . SimulateCartEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.simulate-cart' );
|
||||
/**
|
||||
* The Simulate Cart Endpoint.
|
||||
*
|
||||
* @var SimulateCartEndpoint $endpoint
|
||||
*/
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . ChangeCartEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
|
@ -144,6 +160,16 @@ class ButtonModule implements ModuleInterface {
|
|||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . ApproveSubscriptionEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.approve-subscription' );
|
||||
assert( $endpoint instanceof ApproveSubscriptionEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . CreateOrderEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
|
@ -156,6 +182,34 @@ class ButtonModule implements ModuleInterface {
|
|||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . SaveCheckoutFormEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.save-checkout-form' );
|
||||
assert( $endpoint instanceof SaveCheckoutFormEndpoint );
|
||||
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . ValidateCheckoutEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.validate-checkout' );
|
||||
assert( $endpoint instanceof ValidateCheckoutEndpoint );
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
|
||||
add_action(
|
||||
'wc_ajax_' . CartScriptParamsEndpoint::ENDPOINT,
|
||||
static function () use ( $container ) {
|
||||
$endpoint = $container->get( 'button.endpoint.cart-script-params' );
|
||||
assert( $endpoint instanceof CartScriptParamsEndpoint );
|
||||
$endpoint->handle_request();
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
186
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
186
modules/ppcp-button/src/Endpoint/AbstractCartEndpoint.php
Normal file
|
@ -0,0 +1,186 @@
|
|||
<?php
|
||||
/**
|
||||
* Abstract class for cart Endpoints.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
|
||||
|
||||
/**
|
||||
* Abstract Class AbstractCartEndpoint
|
||||
*/
|
||||
abstract class AbstractCartEndpoint implements EndpointInterface {
|
||||
|
||||
const ENDPOINT = '';
|
||||
|
||||
/**
|
||||
* The current cart object.
|
||||
*
|
||||
* @var \WC_Cart
|
||||
*/
|
||||
protected $cart;
|
||||
|
||||
/**
|
||||
* The cart products helper.
|
||||
*
|
||||
* @var CartProductsHelper
|
||||
*/
|
||||
protected $cart_products;
|
||||
|
||||
/**
|
||||
* The request data helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
protected $request_data;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The tag to be added to logs.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $logger_tag = '';
|
||||
|
||||
/**
|
||||
* The nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return static::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
return $this->handle_data();
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Cart ' . $this->logger_tag . ' failed: ' . $error->getMessage() );
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request data.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
abstract protected function handle_data(): bool;
|
||||
|
||||
/**
|
||||
* Adds products to cart.
|
||||
*
|
||||
* @param array $products Array of products to be added to cart.
|
||||
* @return bool
|
||||
* @throws Exception Add to cart methods throw an exception on fail.
|
||||
*/
|
||||
protected function add_products( array $products ): bool {
|
||||
$this->cart->empty_cart( false );
|
||||
|
||||
try {
|
||||
$this->cart_products->add_products( $products );
|
||||
} catch ( Exception $e ) {
|
||||
$this->handle_error();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors.
|
||||
*
|
||||
* @param bool $send_response If this error handling should return the response.
|
||||
* @return void
|
||||
*/
|
||||
protected function handle_error( bool $send_response = true ): void {
|
||||
|
||||
$message = __(
|
||||
'Something went wrong. Action aborted',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
$errors = wc_get_notices( 'error' );
|
||||
if ( count( $errors ) ) {
|
||||
$message = array_reduce(
|
||||
$errors,
|
||||
static function ( string $add, array $error ): string {
|
||||
return $add . $error['notice'] . ' ';
|
||||
},
|
||||
''
|
||||
);
|
||||
wc_clear_notices();
|
||||
}
|
||||
|
||||
if ( $send_response ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => '',
|
||||
'message' => $message,
|
||||
'code' => 0,
|
||||
'details' => array(),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product information from request data.
|
||||
*
|
||||
* @return array|false
|
||||
*/
|
||||
protected function products_from_request() {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
$products = $this->cart_products->products_from_data( $data );
|
||||
if ( ! $products ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => '',
|
||||
'message' => __(
|
||||
'Necessary fields not defined. Action aborted.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'code' => 0,
|
||||
'details' => array(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes stored cart items from WooCommerce cart.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function remove_cart_items(): void {
|
||||
$this->cart_products->remove_cart_items();
|
||||
}
|
||||
}
|
|
@ -180,7 +180,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
|
|||
);
|
||||
}
|
||||
$this->session_handler->replace_order( $order );
|
||||
wp_send_json_success( $order );
|
||||
wp_send_json_success();
|
||||
}
|
||||
|
||||
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();
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Order approve failed: ' . $error->getMessage() );
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* Endpoint to handle PayPal Subscription created.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
||||
/**
|
||||
* Class ApproveSubscriptionEndpoint
|
||||
*/
|
||||
class ApproveSubscriptionEndpoint implements EndpointInterface {
|
||||
|
||||
const ENDPOINT = 'ppc-approve-subscription';
|
||||
|
||||
/**
|
||||
* The request data helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The order endpoint.
|
||||
*
|
||||
* @var OrderEndpoint
|
||||
*/
|
||||
private $order_endpoint;
|
||||
|
||||
/**
|
||||
* The session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* ApproveSubscriptionEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The request data helper.
|
||||
* @param OrderEndpoint $order_endpoint The order endpoint.
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestData $request_data,
|
||||
OrderEndpoint $order_endpoint,
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
$this->request_data = $request_data;
|
||||
$this->order_endpoint = $order_endpoint;
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* The nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
* @throws RuntimeException When order not found or handling failed.
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
if ( ! isset( $data['order_id'] ) ) {
|
||||
throw new RuntimeException(
|
||||
__( 'No order id given', 'woocommerce-paypal-payments' )
|
||||
);
|
||||
}
|
||||
|
||||
$order = $this->order_endpoint->order( $data['order_id'] );
|
||||
$this->session_handler->replace_order( $order );
|
||||
|
||||
if ( isset( $data['subscription_id'] ) ) {
|
||||
WC()->session->set( 'ppcp_subscription_id', $data['subscription_id'] );
|
||||
}
|
||||
|
||||
wp_send_json_success();
|
||||
return true;
|
||||
}
|
||||
}
|
104
modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php
Normal file
104
modules/ppcp-button/src/Endpoint/CartScriptParamsEndpoint.php
Normal file
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for returning the PayPal SDK Script parameters for the current cart.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
|
||||
/**
|
||||
* Class CartScriptParamsEndpoint.
|
||||
*/
|
||||
class CartScriptParamsEndpoint implements EndpointInterface {
|
||||
|
||||
|
||||
const ENDPOINT = 'ppc-cart-script-params';
|
||||
|
||||
/**
|
||||
* The SmartButton.
|
||||
*
|
||||
* @var SmartButton
|
||||
*/
|
||||
private $smart_button;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* CartScriptParamsEndpoint constructor.
|
||||
*
|
||||
* @param SmartButton $smart_button he SmartButton.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
SmartButton $smart_button,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->smart_button = $smart_button;
|
||||
$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 {
|
||||
if ( is_callable( 'wc_maybe_define_constant' ) ) {
|
||||
wc_maybe_define_constant( 'WOOCOMMERCE_CART', true );
|
||||
}
|
||||
|
||||
$script_data = $this->smart_button->script_data();
|
||||
|
||||
$total = (float) WC()->cart->get_total( 'numeric' );
|
||||
|
||||
// Shop settings.
|
||||
$base_location = wc_get_base_location();
|
||||
$shop_country_code = $base_location['country'] ?? '';
|
||||
$currency_code = get_woocommerce_currency();
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'url_params' => $script_data['url_params'],
|
||||
'button' => $script_data['button'],
|
||||
'messages' => $script_data['messages'],
|
||||
'amount' => WC()->cart->get_total( 'raw' ),
|
||||
|
||||
'total' => $total,
|
||||
'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
|
||||
'currency_code' => $currency_code,
|
||||
'country_code' => $shop_country_code,
|
||||
)
|
||||
);
|
||||
|
||||
return true;
|
||||
} catch ( Throwable $error ) {
|
||||
$this->logger->error( "CartScriptParamsEndpoint execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
|
||||
|
||||
wp_send_json_error();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,26 +11,16 @@ namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
|||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
|
||||
|
||||
/**
|
||||
* Class ChangeCartEndpoint
|
||||
*/
|
||||
class ChangeCartEndpoint implements EndpointInterface {
|
||||
|
||||
class ChangeCartEndpoint extends AbstractCartEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-change-cart';
|
||||
|
||||
/**
|
||||
* The current cart object.
|
||||
*
|
||||
* @var \WC_Cart
|
||||
*/
|
||||
private $cart;
|
||||
|
||||
/**
|
||||
* The current shipping object.
|
||||
*
|
||||
|
@ -38,13 +28,6 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
*/
|
||||
private $shipping;
|
||||
|
||||
/**
|
||||
* The request data helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The PurchaseUnit factory.
|
||||
*
|
||||
|
@ -52,20 +35,6 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
*/
|
||||
private $purchase_unit_factory;
|
||||
|
||||
/**
|
||||
* The product data store.
|
||||
*
|
||||
* @var \WC_Data_Store
|
||||
*/
|
||||
private $product_data_store;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* ChangeCartEndpoint constructor.
|
||||
*
|
||||
|
@ -73,7 +42,7 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
* @param \WC_Shipping $shipping The current WC shipping object.
|
||||
* @param RequestData $request_data The request data helper.
|
||||
* @param PurchaseUnitFactory $purchase_unit_factory The PurchaseUnit factory.
|
||||
* @param \WC_Data_Store $product_data_store The data store for products.
|
||||
* @param CartProductsHelper $cart_products The cart products helper.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -81,7 +50,7 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
\WC_Shipping $shipping,
|
||||
RequestData $request_data,
|
||||
PurchaseUnitFactory $purchase_unit_factory,
|
||||
\WC_Data_Store $product_data_store,
|
||||
CartProductsHelper $cart_products,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
|
@ -89,40 +58,10 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
$this->shipping = $shipping;
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->product_data_store = $product_data_store;
|
||||
$this->cart_products = $cart_products;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
* The nonce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function nonce(): string {
|
||||
return self::ENDPOINT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function handle_request(): bool {
|
||||
try {
|
||||
return $this->handle_data();
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Cart updating failed: ' . $error->getMessage() );
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => is_a( $error, PayPalApiException::class ) ? $error->name() : '',
|
||||
'message' => $error->getMessage(),
|
||||
'code' => $error->getCode(),
|
||||
'details' => is_a( $error, PayPalApiException::class ) ? $error->details() : array(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
$this->logger_tag = 'updating';
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -131,161 +70,25 @@ class ChangeCartEndpoint implements EndpointInterface {
|
|||
* @return bool
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
private function handle_data(): bool {
|
||||
$data = $this->request_data->read_request( $this->nonce() );
|
||||
$products = $this->products_from_data( $data );
|
||||
protected function handle_data(): bool {
|
||||
$this->cart_products->set_cart( $this->cart );
|
||||
|
||||
$products = $this->products_from_request();
|
||||
|
||||
if ( ! $products ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => '',
|
||||
'message' => __(
|
||||
'Necessary fields not defined. Action aborted.',
|
||||
'woocommerce-paypal-payments'
|
||||
),
|
||||
'code' => 0,
|
||||
'details' => array(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->shipping->reset_shipping();
|
||||
$this->cart->empty_cart( false );
|
||||
$success = true;
|
||||
foreach ( $products as $product ) {
|
||||
$success = $success && ( ! $product['product']->is_type( 'variable' ) ) ?
|
||||
$this->add_product( $product['product'], $product['quantity'] )
|
||||
: $this->add_variable_product(
|
||||
$product['product'],
|
||||
$product['quantity'],
|
||||
$product['variations']
|
||||
);
|
||||
}
|
||||
if ( ! $success ) {
|
||||
$this->handle_error();
|
||||
return $success;
|
||||
|
||||
if ( ! $this->add_products( $products ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wp_send_json_success( $this->generate_purchase_units() );
|
||||
return $success;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function handle_error(): bool {
|
||||
|
||||
$message = __(
|
||||
'Something went wrong. Action aborted',
|
||||
'woocommerce-paypal-payments'
|
||||
);
|
||||
$errors = wc_get_notices( 'error' );
|
||||
if ( count( $errors ) ) {
|
||||
$message = array_reduce(
|
||||
$errors,
|
||||
static function ( string $add, array $error ): string {
|
||||
return $add . $error['notice'] . ' ';
|
||||
},
|
||||
''
|
||||
);
|
||||
wc_clear_notices();
|
||||
}
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'name' => '',
|
||||
'message' => $message,
|
||||
'code' => 0,
|
||||
'details' => array(),
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product information from an data array.
|
||||
*
|
||||
* @param array $data The data array.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
private function products_from_data( array $data ) {
|
||||
|
||||
$products = array();
|
||||
|
||||
if (
|
||||
! isset( $data['products'] )
|
||||
|| ! is_array( $data['products'] )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
foreach ( $data['products'] as $product ) {
|
||||
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wc_product = wc_get_product( (int) $product['id'] );
|
||||
|
||||
if ( ! $wc_product ) {
|
||||
return null;
|
||||
}
|
||||
$products[] = array(
|
||||
'product' => $wc_product,
|
||||
'quantity' => (int) $product['quantity'],
|
||||
'variations' => isset( $product['variations'] ) ? $product['variations'] : null,
|
||||
);
|
||||
}
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a product to the cart.
|
||||
*
|
||||
* @param \WC_Product $product The Product.
|
||||
* @param int $quantity The Quantity.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception When product could not be added.
|
||||
*/
|
||||
private function add_product( \WC_Product $product, int $quantity ): bool {
|
||||
return false !== $this->cart->add_to_cart( $product->get_id(), $quantity );
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Adds variations to the cart.
|
||||
*
|
||||
* @param \WC_Product $product The Product.
|
||||
* @param int $quantity The Quantity.
|
||||
* @param array $post_variations The variations.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception When product could not be added.
|
||||
*/
|
||||
private function add_variable_product(
|
||||
\WC_Product $product,
|
||||
int $quantity,
|
||||
array $post_variations
|
||||
): bool {
|
||||
|
||||
$variations = array();
|
||||
foreach ( $post_variations as $key => $value ) {
|
||||
$variations[ $value['name'] ] = $value['value'];
|
||||
}
|
||||
|
||||
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
|
||||
|
||||
// ToDo: Check stock status for variation.
|
||||
return false !== $this->cart->add_to_cart(
|
||||
$product->get_id(),
|
||||
$quantity,
|
||||
$variation_id,
|
||||
$variations
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Based on the cart contents, the purchase units are created.
|
||||
*
|
||||
|
|
|
@ -19,20 +19,19 @@ use WooCommerce\PayPalCommerce\ApiClient\Entity\ApplicationContext;
|
|||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Payer;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PaymentMethod;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\RuntimeException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PayerFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\PurchaseUnitFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Factory\ShippingPreferenceFactory;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\EarlyOrderHandler;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\Subscription\FreeTrialHandlerTrait;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\CardBillingMode;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Exception\NotFoundException;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CardButtonGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
|
@ -138,6 +137,27 @@ 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 sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
*
|
||||
* @var string[]
|
||||
*/
|
||||
private $funding_sources_without_redirect;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
|
@ -145,6 +165,13 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* The form data, or empty if not available.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $form = array();
|
||||
|
||||
/**
|
||||
* CreateOrderEndpoint constructor.
|
||||
*
|
||||
|
@ -159,6 +186,9 @@ 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 string[] $funding_sources_without_redirect The sources that do not cause issues about redirecting (on mobile, ...) and sometimes not returning back.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
|
@ -173,21 +203,27 @@ 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,
|
||||
array $funding_sources_without_redirect,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$this->registration_needed = $registration_needed;
|
||||
$this->card_billing_data_mode = $card_billing_data_mode;
|
||||
$this->early_validation_enabled = $early_validation_enabled;
|
||||
$this->logger = $logger;
|
||||
$this->request_data = $request_data;
|
||||
$this->purchase_unit_factory = $purchase_unit_factory;
|
||||
$this->shipping_preference_factory = $shipping_preference_factory;
|
||||
$this->api_endpoint = $order_endpoint;
|
||||
$this->payer_factory = $payer_factory;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->settings = $settings;
|
||||
$this->early_order_handler = $early_order_handler;
|
||||
$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->funding_sources_without_redirect = $funding_sources_without_redirect;
|
||||
$this->logger = $logger;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -226,7 +262,13 @@ 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 );
|
||||
|
||||
// Do not allow completion by webhooks when started via non-checkout buttons,
|
||||
// it is needed only for some APMs in checkout.
|
||||
if ( in_array( $data['context'], array( 'product', 'cart', 'cart-block' ), true ) ) {
|
||||
$this->purchase_unit->set_custom_id( '' );
|
||||
}
|
||||
|
||||
// The cart does not have any info about payment method, so we must handle free trial here.
|
||||
if ( (
|
||||
|
@ -246,18 +288,20 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
|
||||
$this->set_bn_code( $data );
|
||||
|
||||
$form_fields = $data['form'] ?? null;
|
||||
if ( isset( $data['form'] ) ) {
|
||||
$this->form = $data['form'];
|
||||
}
|
||||
|
||||
if ( $this->early_validation_enabled
|
||||
&& is_array( $form_fields )
|
||||
&& $this->form
|
||||
&& 'checkout' === $data['context']
|
||||
&& in_array( $payment_method, array( PayPalGateway::ID, CardButtonGateway::ID ), true )
|
||||
) {
|
||||
$this->validate_form( $form_fields );
|
||||
$this->validate_form( $this->form );
|
||||
}
|
||||
|
||||
if ( 'pay-now' === $data['context'] && is_array( $form_fields ) && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
|
||||
$this->validate_paynow_form( $form_fields );
|
||||
if ( 'pay-now' === $data['context'] && $this->form && get_option( 'woocommerce_terms_page_id', '' ) !== '' ) {
|
||||
$this->validate_paynow_form( $this->form );
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -268,11 +312,16 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
}
|
||||
|
||||
if ( 'checkout' === $data['context'] ) {
|
||||
if ( $payment_method === PayPalGateway::ID && ! in_array( $funding_source, $this->funding_sources_without_redirect, true ) ) {
|
||||
$this->session_handler->replace_order( $order );
|
||||
$this->session_handler->replace_funding_source( $funding_source );
|
||||
}
|
||||
|
||||
if (
|
||||
! $this->early_order_handler->should_create_early_order()
|
||||
|| $this->registration_needed
|
||||
|| isset( $data['createaccount'] ) && '1' === $data['createaccount'] ) {
|
||||
wp_send_json_success( $order->to_array() );
|
||||
wp_send_json_success( $this->make_response( $order ) );
|
||||
}
|
||||
|
||||
$this->early_order_handler->register_for_order( $order );
|
||||
|
@ -282,18 +331,23 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
|
||||
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
|
||||
$wc_order->save_meta_data();
|
||||
|
||||
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
|
||||
}
|
||||
|
||||
wp_send_json_success( $order->to_array() );
|
||||
wp_send_json_success( $this->make_response( $order ) );
|
||||
return true;
|
||||
|
||||
} catch ( ValidationException $error ) {
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $error->getMessage(),
|
||||
'errors' => $error->errors(),
|
||||
)
|
||||
$response = array(
|
||||
'message' => $error->getMessage(),
|
||||
'errors' => $error->errors(),
|
||||
'refresh' => isset( WC()->session->refresh_totals ),
|
||||
);
|
||||
|
||||
unset( WC()->session->refresh_totals );
|
||||
|
||||
wp_send_json_error( $response );
|
||||
} catch ( \RuntimeException $error ) {
|
||||
$this->logger->error( 'Order creation failed: ' . $error->getMessage() );
|
||||
|
||||
|
@ -339,7 +393,7 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
* during the "onApprove"-JS callback or the webhook listener.
|
||||
*/
|
||||
if ( ! $this->early_order_handler->should_create_early_order() ) {
|
||||
wp_send_json_success( $order->to_array() );
|
||||
wp_send_json_success( $this->make_response( $order ) );
|
||||
}
|
||||
$this->early_order_handler->register_for_order( $order );
|
||||
return $data;
|
||||
|
@ -382,6 +436,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 ) {
|
||||
|
@ -407,7 +464,8 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$shipping_preference,
|
||||
$payer,
|
||||
null,
|
||||
$this->payment_method()
|
||||
'',
|
||||
$action
|
||||
);
|
||||
} catch ( PayPalApiException $exception ) {
|
||||
// Looks like currently there is no proper way to validate the shipping address for PayPal,
|
||||
|
@ -428,8 +486,7 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
array( $this->purchase_unit ),
|
||||
$shipping_preference,
|
||||
$payer,
|
||||
null,
|
||||
$this->payment_method()
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -467,11 +524,9 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$payer = $this->payer_factory->from_paypal_response( json_decode( wp_json_encode( $data['payer'] ) ) );
|
||||
}
|
||||
|
||||
if ( ! $payer && isset( $data['form'] ) ) {
|
||||
$form_fields = $data['form'];
|
||||
|
||||
if ( is_array( $form_fields ) && isset( $form_fields['billing_email'] ) && '' !== $form_fields['billing_email'] ) {
|
||||
return $this->payer_factory->from_checkout_form( $form_fields );
|
||||
if ( ! $payer && $this->form ) {
|
||||
if ( isset( $this->form['billing_email'] ) && '' !== $this->form['billing_email'] ) {
|
||||
return $this->payer_factory->from_checkout_form( $this->form );
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -493,24 +548,6 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
$this->api_endpoint->with_bn_code( $bn_code );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the PaymentMethod object for the order.
|
||||
*
|
||||
* @return PaymentMethod
|
||||
*/
|
||||
private function payment_method() : PaymentMethod {
|
||||
try {
|
||||
$payee_preferred = $this->settings->has( 'payee_preferred' ) && $this->settings->get( 'payee_preferred' ) ?
|
||||
PaymentMethod::PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED
|
||||
: PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
|
||||
} catch ( NotFoundException $exception ) {
|
||||
$payee_preferred = PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
|
||||
}
|
||||
|
||||
$payment_method = new PaymentMethod( $payee_preferred );
|
||||
return $payment_method;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether the form fields are valid.
|
||||
*
|
||||
|
@ -541,4 +578,17 @@ class CreateOrderEndpoint implements EndpointInterface {
|
|||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the response data for success response.
|
||||
*
|
||||
* @param Order $order The PayPal order.
|
||||
* @return array
|
||||
*/
|
||||
private function make_response( Order $order ): array {
|
||||
return array(
|
||||
'id' => $order->id(),
|
||||
'custom_id' => $order->purchase_units()[0]->custom_id(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -53,6 +53,11 @@ class RequestData {
|
|||
}
|
||||
$this->dequeue_nonce_fix();
|
||||
|
||||
if ( isset( $json['form_encoded'] ) ) {
|
||||
$json['form'] = array();
|
||||
parse_str( $json['form_encoded'], $json['form'] );
|
||||
}
|
||||
|
||||
$sanitized = $this->sanitize( $json );
|
||||
return $sanitized;
|
||||
}
|
||||
|
@ -80,6 +85,10 @@ class RequestData {
|
|||
private function sanitize( array $assoc_array ): array {
|
||||
$data = array();
|
||||
foreach ( (array) $assoc_array as $raw_key => $raw_value ) {
|
||||
if ( $raw_key === 'form_encoded' ) {
|
||||
$data[ $raw_key ] = $raw_value;
|
||||
continue;
|
||||
}
|
||||
if ( ! is_array( $raw_value ) ) {
|
||||
// Not sure if it is a good idea to sanitize everything at this level,
|
||||
// but should be fine for now since we do not send any HTML or multi-line texts via ajax.
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
/**
|
||||
* Saves the form data to the WC customer and session.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\CheckoutFormSaver;
|
||||
|
||||
/**
|
||||
* Class SaveCheckoutFormEndpoint
|
||||
*/
|
||||
class SaveCheckoutFormEndpoint implements EndpointInterface {
|
||||
const ENDPOINT = 'ppc-save-checkout-form';
|
||||
|
||||
/**
|
||||
* The Request Data Helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The checkout form saver.
|
||||
*
|
||||
* @var CheckoutFormSaver
|
||||
*/
|
||||
private $checkout_form_saver;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
protected $logger;
|
||||
|
||||
/**
|
||||
* SaveCheckoutFormEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The Request Data Helper.
|
||||
* @param CheckoutFormSaver $checkout_form_saver The checkout form saver.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestData $request_data,
|
||||
CheckoutFormSaver $checkout_form_saver,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
|
||||
$this->request_data = $request_data;
|
||||
$this->checkout_form_saver = $checkout_form_saver;
|
||||
$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() );
|
||||
|
||||
$this->checkout_form_saver->save( $data['form'] );
|
||||
|
||||
wp_send_json_success();
|
||||
return true;
|
||||
} catch ( Exception $error ) {
|
||||
$this->logger->error( 'Checkout form saving failed: ' . $error->getMessage() );
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $error->getMessage(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
178
modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
Normal file
178
modules/ppcp-button/src/Endpoint/SimulateCartEndpoint.php
Normal file
|
@ -0,0 +1,178 @@
|
|||
<?php
|
||||
/**
|
||||
* Endpoint to simulate adding products to the cart.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Exception;
|
||||
use Psr\Log\LoggerInterface;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Money;
|
||||
use WooCommerce\PayPalCommerce\Button\Assets\SmartButton;
|
||||
use WooCommerce\PayPalCommerce\Button\Helper\CartProductsHelper;
|
||||
|
||||
/**
|
||||
* Class SimulateCartEndpoint
|
||||
*/
|
||||
class SimulateCartEndpoint extends AbstractCartEndpoint {
|
||||
|
||||
const ENDPOINT = 'ppc-simulate-cart';
|
||||
|
||||
/**
|
||||
* The SmartButton.
|
||||
*
|
||||
* @var SmartButton
|
||||
*/
|
||||
private $smart_button;
|
||||
|
||||
/**
|
||||
* The WooCommerce real active cart.
|
||||
*
|
||||
* @var \WC_Cart|null
|
||||
*/
|
||||
private $real_cart = null;
|
||||
|
||||
/**
|
||||
* ChangeCartEndpoint constructor.
|
||||
*
|
||||
* @param SmartButton $smart_button The SmartButton.
|
||||
* @param \WC_Cart $cart The current WC cart object.
|
||||
* @param RequestData $request_data The request data helper.
|
||||
* @param CartProductsHelper $cart_products The cart products helper.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
SmartButton $smart_button,
|
||||
\WC_Cart $cart,
|
||||
RequestData $request_data,
|
||||
CartProductsHelper $cart_products,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->smart_button = $smart_button;
|
||||
$this->cart = clone $cart;
|
||||
$this->request_data = $request_data;
|
||||
$this->cart_products = $cart_products;
|
||||
$this->logger = $logger;
|
||||
|
||||
$this->logger_tag = 'simulation';
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the request data.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception On error.
|
||||
*/
|
||||
protected function handle_data(): bool {
|
||||
$products = $this->products_from_request();
|
||||
|
||||
if ( ! $products ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->replace_real_cart();
|
||||
|
||||
$this->add_products( $products );
|
||||
|
||||
$this->cart->calculate_totals();
|
||||
$total = (float) $this->cart->get_total( 'numeric' );
|
||||
|
||||
$this->restore_real_cart();
|
||||
|
||||
// Process filters.
|
||||
$pay_later_enabled = true;
|
||||
$pay_later_messaging_enabled = true;
|
||||
$button_enabled = true;
|
||||
|
||||
foreach ( $products as $product ) {
|
||||
$context_data = array(
|
||||
'product' => $product['product'],
|
||||
'order_total' => $total,
|
||||
);
|
||||
|
||||
$pay_later_enabled = $pay_later_enabled && $this->smart_button->is_pay_later_button_enabled_for_location( 'product', $context_data );
|
||||
$pay_later_messaging_enabled = $pay_later_messaging_enabled && $this->smart_button->is_pay_later_messaging_enabled_for_location( 'product', $context_data );
|
||||
$button_enabled = $button_enabled && ! $this->smart_button->is_button_disabled( 'product', $context_data );
|
||||
}
|
||||
|
||||
// Shop settings.
|
||||
$base_location = wc_get_base_location();
|
||||
$shop_country_code = $base_location['country'];
|
||||
$currency_code = get_woocommerce_currency();
|
||||
|
||||
wp_send_json_success(
|
||||
array(
|
||||
'total' => $total,
|
||||
'total_str' => ( new Money( $total, $currency_code ) )->value_str(),
|
||||
'currency_code' => $currency_code,
|
||||
'country_code' => $shop_country_code,
|
||||
'funding' => array(
|
||||
'paylater' => array(
|
||||
'enabled' => $pay_later_enabled,
|
||||
),
|
||||
),
|
||||
'button' => array(
|
||||
'is_disabled' => ! $button_enabled,
|
||||
),
|
||||
'messages' => array(
|
||||
'is_hidden' => ! $pay_later_messaging_enabled,
|
||||
),
|
||||
)
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles errors.
|
||||
*
|
||||
* @param bool $send_response If this error handling should return the response.
|
||||
* @return void
|
||||
*
|
||||
* phpcs:disable Generic.CodeAnalysis.UselessOverridingMethod.Found
|
||||
*/
|
||||
protected function handle_error( bool $send_response = false ): void {
|
||||
parent::handle_error( $send_response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces the real cart with the clone.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function replace_real_cart() {
|
||||
// Set WC default cart as the clone.
|
||||
// Store a reference to the real cart.
|
||||
$this->real_cart = WC()->cart;
|
||||
WC()->cart = $this->cart;
|
||||
$this->cart_products->set_cart( $this->cart );
|
||||
}
|
||||
|
||||
/**
|
||||
* Restores the real cart.
|
||||
* Currently, unsets the WC cart to prevent race conditions arising from it being persisted.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function restore_real_cart() {
|
||||
// Remove from cart because some plugins reserve resources internally when adding to cart.
|
||||
$this->remove_cart_items();
|
||||
|
||||
if ( apply_filters( 'woocommerce_paypal_payments_simulate_cart_prevent_updates', true ) ) {
|
||||
// Removes shutdown actions to prevent persisting session, transients and save cookies.
|
||||
remove_all_actions( 'shutdown' );
|
||||
unset( WC()->cart );
|
||||
} else {
|
||||
// Restores cart, may lead to race conditions.
|
||||
if ( null !== $this->real_cart ) {
|
||||
WC()->cart = $this->real_cart;
|
||||
}
|
||||
}
|
||||
|
||||
unset( $this->cart );
|
||||
}
|
||||
|
||||
}
|
110
modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
Normal file
110
modules/ppcp-button/src/Endpoint/ValidateCheckoutEndpoint.php
Normal file
|
@ -0,0 +1,110 @@
|
|||
<?php
|
||||
/**
|
||||
* The endpoint for validating the checkout form.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Endpoint
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Endpoint;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Throwable;
|
||||
use WooCommerce\PayPalCommerce\Button\Exception\ValidationException;
|
||||
use WooCommerce\PayPalCommerce\Button\Validation\CheckoutFormValidator;
|
||||
|
||||
/**
|
||||
* Class ValidateCheckoutEndpoint.
|
||||
*/
|
||||
class ValidateCheckoutEndpoint implements EndpointInterface {
|
||||
|
||||
|
||||
const ENDPOINT = 'ppc-validate-checkout';
|
||||
|
||||
/**
|
||||
* The Request Data Helper.
|
||||
*
|
||||
* @var RequestData
|
||||
*/
|
||||
private $request_data;
|
||||
|
||||
/**
|
||||
* The CheckoutFormValidator.
|
||||
*
|
||||
* @var CheckoutFormValidator
|
||||
*/
|
||||
private $checkout_form_validator;
|
||||
|
||||
/**
|
||||
* The logger.
|
||||
*
|
||||
* @var LoggerInterface
|
||||
*/
|
||||
private $logger;
|
||||
|
||||
/**
|
||||
* ValidateCheckoutEndpoint constructor.
|
||||
*
|
||||
* @param RequestData $request_data The Request Data Helper.
|
||||
* @param CheckoutFormValidator $checkout_form_validator The CheckoutFormValidator.
|
||||
* @param LoggerInterface $logger The logger.
|
||||
*/
|
||||
public function __construct(
|
||||
RequestData $request_data,
|
||||
CheckoutFormValidator $checkout_form_validator,
|
||||
LoggerInterface $logger
|
||||
) {
|
||||
$this->request_data = $request_data;
|
||||
$this->checkout_form_validator = $checkout_form_validator;
|
||||
$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() );
|
||||
|
||||
$form_fields = $data['form'];
|
||||
|
||||
$this->checkout_form_validator->validate( $form_fields );
|
||||
|
||||
wp_send_json_success();
|
||||
|
||||
return true;
|
||||
} catch ( ValidationException $exception ) {
|
||||
$response = array(
|
||||
'message' => $exception->getMessage(),
|
||||
'errors' => $exception->errors(),
|
||||
'refresh' => isset( WC()->session->refresh_totals ),
|
||||
);
|
||||
|
||||
unset( WC()->session->refresh_totals );
|
||||
|
||||
wp_send_json_error( $response );
|
||||
return false;
|
||||
} catch ( Throwable $error ) {
|
||||
$this->logger->error( "Form validation execution failed. {$error->getMessage()} {$error->getFile()}:{$error->getLine()}" );
|
||||
|
||||
wp_send_json_error(
|
||||
array(
|
||||
'message' => $error->getMessage(),
|
||||
)
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
285
modules/ppcp-button/src/Helper/CartProductsHelper.php
Normal file
285
modules/ppcp-button/src/Helper/CartProductsHelper.php
Normal file
|
@ -0,0 +1,285 @@
|
|||
<?php
|
||||
/**
|
||||
* Handles the adding of products to WooCommerce cart.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use Exception;
|
||||
use WC_Cart;
|
||||
use WC_Data_Store;
|
||||
|
||||
/**
|
||||
* Class CartProductsHelper
|
||||
*/
|
||||
class CartProductsHelper {
|
||||
|
||||
/**
|
||||
* The cart
|
||||
*
|
||||
* @var ?WC_Cart
|
||||
*/
|
||||
private $cart;
|
||||
|
||||
/**
|
||||
* The product data store.
|
||||
*
|
||||
* @var WC_Data_Store
|
||||
*/
|
||||
protected $product_data_store;
|
||||
|
||||
/**
|
||||
* The added cart item IDs
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $cart_item_keys = array();
|
||||
|
||||
/**
|
||||
* CheckoutFormSaver constructor.
|
||||
*
|
||||
* @param WC_Data_Store $product_data_store The data store for products.
|
||||
*/
|
||||
public function __construct(
|
||||
WC_Data_Store $product_data_store
|
||||
) {
|
||||
$this->product_data_store = $product_data_store;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a new cart instance.
|
||||
*
|
||||
* @param WC_Cart $cart The cart.
|
||||
* @return void
|
||||
*/
|
||||
public function set_cart( WC_Cart $cart ): void {
|
||||
$this->cart = $cart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns products information from a data array.
|
||||
*
|
||||
* @param array $data The data array.
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function products_from_data( array $data ): ?array {
|
||||
|
||||
$products = array();
|
||||
|
||||
if (
|
||||
! isset( $data['products'] )
|
||||
|| ! is_array( $data['products'] )
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
foreach ( $data['products'] as $product ) {
|
||||
$product = $this->product_from_data( $product );
|
||||
if ( $product ) {
|
||||
$products[] = $product;
|
||||
}
|
||||
}
|
||||
return $products;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns product information from a data array.
|
||||
*
|
||||
* @param array $product The product data array, usually provided by the product page form.
|
||||
* @return array|null
|
||||
*/
|
||||
public function product_from_data( array $product ): ?array {
|
||||
if ( ! isset( $product['quantity'] ) || ! isset( $product['id'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$wc_product = wc_get_product( (int) $product['id'] );
|
||||
|
||||
if ( ! $wc_product ) {
|
||||
return null;
|
||||
}
|
||||
return array(
|
||||
'product' => $wc_product,
|
||||
'quantity' => (int) $product['quantity'],
|
||||
'variations' => $product['variations'] ?? null,
|
||||
'booking' => $product['booking'] ?? null,
|
||||
'extra' => $product['extra'] ?? null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds products to cart.
|
||||
*
|
||||
* @param array $products Array of products to be added to cart.
|
||||
* @return bool
|
||||
* @throws Exception Add to cart methods throw an exception on fail.
|
||||
*/
|
||||
public function add_products( array $products ): bool {
|
||||
$success = true;
|
||||
foreach ( $products as $product ) {
|
||||
|
||||
// Add extras to POST, they are usually added by custom plugins.
|
||||
if ( $product['extra'] && is_array( $product['extra'] ) ) {
|
||||
// Handle cases like field[].
|
||||
$query = http_build_query( $product['extra'] );
|
||||
parse_str( $query, $extra );
|
||||
|
||||
foreach ( $extra as $key => $value ) {
|
||||
$_POST[ $key ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $product['product']->is_type( 'booking' ) ) {
|
||||
$success = $success && $this->add_booking_product(
|
||||
$product['product'],
|
||||
$product['booking']
|
||||
);
|
||||
} elseif ( $product['product']->is_type( 'variable' ) ) {
|
||||
$success = $success && $this->add_variable_product(
|
||||
$product['product'],
|
||||
$product['quantity'],
|
||||
$product['variations']
|
||||
);
|
||||
} else {
|
||||
$success = $success && $this->add_product(
|
||||
$product['product'],
|
||||
$product['quantity']
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $success ) {
|
||||
throw new Exception( 'Error adding products to cart.' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a product to the cart.
|
||||
*
|
||||
* @param \WC_Product $product The Product.
|
||||
* @param int $quantity The Quantity.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception When product could not be added.
|
||||
*/
|
||||
public function add_product( \WC_Product $product, int $quantity ): bool {
|
||||
if ( ! $this->cart ) {
|
||||
throw new Exception( 'Cart not set.' );
|
||||
}
|
||||
|
||||
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), $quantity );
|
||||
|
||||
if ( $cart_item_key ) {
|
||||
$this->cart_item_keys[] = $cart_item_key;
|
||||
}
|
||||
return false !== $cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds variations to the cart.
|
||||
*
|
||||
* @param \WC_Product $product The Product.
|
||||
* @param int $quantity The Quantity.
|
||||
* @param array $post_variations The variations.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception When product could not be added.
|
||||
*/
|
||||
public function add_variable_product(
|
||||
\WC_Product $product,
|
||||
int $quantity,
|
||||
array $post_variations
|
||||
): bool {
|
||||
if ( ! $this->cart ) {
|
||||
throw new Exception( 'Cart not set.' );
|
||||
}
|
||||
|
||||
$variations = array();
|
||||
foreach ( $post_variations as $key => $value ) {
|
||||
$variations[ $value['name'] ] = $value['value'];
|
||||
}
|
||||
|
||||
$variation_id = $this->product_data_store->find_matching_product_variation( $product, $variations );
|
||||
|
||||
// ToDo: Check stock status for variation.
|
||||
$cart_item_key = $this->cart->add_to_cart(
|
||||
$product->get_id(),
|
||||
$quantity,
|
||||
$variation_id,
|
||||
$variations
|
||||
);
|
||||
|
||||
if ( $cart_item_key ) {
|
||||
$this->cart_item_keys[] = $cart_item_key;
|
||||
}
|
||||
return false !== $cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds booking to the cart.
|
||||
*
|
||||
* @param \WC_Product $product The Product.
|
||||
* @param array $data Data used by the booking plugin.
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception When product could not be added.
|
||||
*/
|
||||
public function add_booking_product(
|
||||
\WC_Product $product,
|
||||
array $data
|
||||
): bool {
|
||||
if ( ! $this->cart ) {
|
||||
throw new Exception( 'Cart not set.' );
|
||||
}
|
||||
|
||||
if ( ! is_callable( 'wc_bookings_get_posted_data' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$cart_item_data = array(
|
||||
'booking' => wc_bookings_get_posted_data( $data, $product ),
|
||||
);
|
||||
|
||||
$cart_item_key = $this->cart->add_to_cart( $product->get_id(), 1, 0, array(), $cart_item_data );
|
||||
|
||||
if ( $cart_item_key ) {
|
||||
$this->cart_item_keys[] = $cart_item_key;
|
||||
}
|
||||
return false !== $cart_item_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes stored cart items from WooCommerce cart.
|
||||
*
|
||||
* @return void
|
||||
* @throws Exception Throws if there's a failure removing the cart items.
|
||||
*/
|
||||
public function remove_cart_items(): void {
|
||||
if ( ! $this->cart ) {
|
||||
throw new Exception( 'Cart not set.' );
|
||||
}
|
||||
|
||||
foreach ( $this->cart_item_keys as $cart_item_key ) {
|
||||
if ( ! $cart_item_key ) {
|
||||
continue;
|
||||
}
|
||||
$this->cart->remove_cart_item( $cart_item_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cart item keys of the items added to cart.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function cart_item_keys(): array {
|
||||
return $this->cart_item_keys;
|
||||
}
|
||||
|
||||
}
|
53
modules/ppcp-button/src/Helper/CheckoutFormSaver.php
Normal file
53
modules/ppcp-button/src/Helper/CheckoutFormSaver.php
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
/**
|
||||
* Saves the form data to the WC customer and session.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use WC_Checkout;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
|
||||
/**
|
||||
* Class CheckoutFormSaver
|
||||
*/
|
||||
class CheckoutFormSaver extends WC_Checkout {
|
||||
/**
|
||||
* The Session handler.
|
||||
*
|
||||
* @var SessionHandler
|
||||
*/
|
||||
private $session_handler;
|
||||
|
||||
/**
|
||||
* CheckoutFormSaver constructor.
|
||||
*
|
||||
* @param SessionHandler $session_handler The session handler.
|
||||
*/
|
||||
public function __construct(
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
$this->session_handler = $session_handler;
|
||||
}
|
||||
|
||||
/**
|
||||
* Saves the form data to the WC customer and session.
|
||||
*
|
||||
* @param array $data The form data.
|
||||
* @return void
|
||||
*/
|
||||
public function save( array $data ) {
|
||||
foreach ( $data as $key => $value ) {
|
||||
$_POST[ $key ] = $value;
|
||||
}
|
||||
$data = $this->get_posted_data();
|
||||
|
||||
$this->update_session( $data );
|
||||
|
||||
$this->session_handler->replace_checkout_form( $data );
|
||||
}
|
||||
}
|
128
modules/ppcp-button/src/Helper/ContextTrait.php
Normal file
128
modules/ppcp-button/src/Helper/ContextTrait.php
Normal file
|
@ -0,0 +1,128 @@
|
|||
<?php
|
||||
/**
|
||||
* Helper trait for context.
|
||||
*
|
||||
* @package WooCommerce\PayPalCommerce\Button\Helper
|
||||
*/
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace WooCommerce\PayPalCommerce\Button\Helper;
|
||||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\OrderStatus;
|
||||
|
||||
trait ContextTrait {
|
||||
/**
|
||||
* Checks WC is_checkout() + WC checkout ajax requests.
|
||||
*/
|
||||
private function is_checkout(): bool {
|
||||
if ( is_checkout() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* The filter returning whether to detect WC checkout ajax requests.
|
||||
*/
|
||||
if ( apply_filters( 'ppcp_check_ajax_checkout', true ) ) {
|
||||
// phpcs:ignore WordPress.Security
|
||||
$wc_ajax = $_GET['wc-ajax'] ?? '';
|
||||
if ( in_array( $wc_ajax, array( 'update_order_review' ), true ) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The current context.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function context(): string {
|
||||
if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
|
||||
|
||||
// Do this check here instead of reordering outside conditions.
|
||||
// In order to have more control over the context.
|
||||
if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
|
||||
return 'checkout';
|
||||
}
|
||||
|
||||
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() ) {
|
||||
return 'cart';
|
||||
}
|
||||
|
||||
if ( is_checkout_pay_page() ) {
|
||||
return 'pay-now';
|
||||
}
|
||||
|
||||
if ( has_block( 'woocommerce/checkout' ) ) {
|
||||
return 'checkout-block';
|
||||
}
|
||||
|
||||
if ( $this->is_checkout() && ! $this->is_paypal_continuation() ) {
|
||||
return 'checkout';
|
||||
}
|
||||
|
||||
return 'mini-cart';
|
||||
}
|
||||
|
||||
/**
|
||||
* The current location.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function location(): string {
|
||||
$context = $this->context();
|
||||
if ( $context !== 'mini-cart' ) {
|
||||
return $context;
|
||||
}
|
||||
|
||||
if ( is_shop() || is_product_category() ) {
|
||||
return 'shop';
|
||||
}
|
||||
|
||||
if ( is_front_page() ) {
|
||||
return 'home';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if PayPal payment was already initiated (on the product or cart pages).
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_paypal_continuation(): bool {
|
||||
$order = $this->session_handler->order();
|
||||
if ( ! $order ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $order->status()->is( OrderStatus::APPROVED )
|
||||
&& ! $order->status()->is( OrderStatus::COMPLETED )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$source = $order->payment_source();
|
||||
if ( $source && $source->card() ) {
|
||||
return false; // Ignore for DCC.
|
||||
}
|
||||
|
||||
if ( 'card' === $this->session_handler->funding_source() ) {
|
||||
return false; // Ignore for card buttons.
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -11,19 +11,17 @@ namespace WooCommerce\PayPalCommerce\Button\Helper;
|
|||
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Entity\Order;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Exception\PayPalApiException;
|
||||
use WooCommerce\PayPalCommerce\ApiClient\Helper\OrderTransient;
|
||||
use WooCommerce\PayPalCommerce\Onboarding\State;
|
||||
use WooCommerce\PayPalCommerce\Session\SessionHandler;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
|
||||
use WooCommerce\PayPalCommerce\WcGateway\Processor\OrderProcessor;
|
||||
use WooCommerce\PayPalCommerce\Webhooks\Handler\PrefixTrait;
|
||||
|
||||
/**
|
||||
* Class EarlyOrderHandler
|
||||
*/
|
||||
class EarlyOrderHandler {
|
||||
|
||||
use PrefixTrait;
|
||||
|
||||
/**
|
||||
* The State.
|
||||
*
|
||||
|
@ -51,19 +49,16 @@ class EarlyOrderHandler {
|
|||
* @param State $state The State.
|
||||
* @param OrderProcessor $order_processor The Order Processor.
|
||||
* @param SessionHandler $session_handler The Session Handler.
|
||||
* @param string $prefix The Prefix.
|
||||
*/
|
||||
public function __construct(
|
||||
State $state,
|
||||
OrderProcessor $order_processor,
|
||||
SessionHandler $session_handler,
|
||||
string $prefix
|
||||
SessionHandler $session_handler
|
||||
) {
|
||||
|
||||
$this->state = $state;
|
||||
$this->order_processor = $order_processor;
|
||||
$this->session_handler = $session_handler;
|
||||
$this->prefix = $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -101,7 +96,7 @@ class EarlyOrderHandler {
|
|||
$order_id = false;
|
||||
foreach ( $order->purchase_units() as $purchase_unit ) {
|
||||
if ( $purchase_unit->custom_id() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
|
||||
$order_id = (int) $this->sanitize_custom_id( $purchase_unit->custom_id() );
|
||||
$order_id = (int) $purchase_unit->custom_id();
|
||||
}
|
||||
}
|
||||
if ( $order_id === $resume_order_id ) {
|
||||
|
@ -169,6 +164,10 @@ class EarlyOrderHandler {
|
|||
/**
|
||||
* Patch Order so we have the \WC_Order id added.
|
||||
*/
|
||||
return $this->order_processor->patch_order( $wc_order, $order );
|
||||
$order = $this->order_processor->patch_order( $wc_order, $order );
|
||||
|
||||
do_action( 'woocommerce_paypal_payments_woocommerce_order_created', $wc_order, $order );
|
||||
|
||||
return $order;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,20 +27,91 @@ class CheckoutFormValidator extends WC_Checkout {
|
|||
public function validate( array $data ) {
|
||||
$errors = new WP_Error();
|
||||
|
||||
// Some plugins check their fields using $_POST,
|
||||
// Some plugins check their fields using $_POST or $_REQUEST,
|
||||
// also WC terms checkbox https://github.com/woocommerce/woocommerce/issues/35328 .
|
||||
foreach ( $data as $key => $value ) {
|
||||
$_POST[ $key ] = $value;
|
||||
$_POST[ $key ] = $value;
|
||||
$_REQUEST[ $key ] = $value;
|
||||
}
|
||||
// And we must call get_posted_data because it handles the shipping address.
|
||||
$data = $this->get_posted_data();
|
||||
|
||||
// It throws some notices when checking fields etc., also from other plugins via hooks.
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
@$this->validate_checkout( $data, $errors );
|
||||
// Looks like without this WC()->shipping->get_packages() is empty which is used by some plugins.
|
||||
WC()->cart->calculate_shipping();
|
||||
|
||||
if ( $errors->has_errors() ) {
|
||||
throw new ValidationException( $errors->get_error_messages() );
|
||||
// Some plugins/filters check is_checkout().
|
||||
$is_checkout = function () {
|
||||
return true;
|
||||
};
|
||||
add_filter( 'woocommerce_is_checkout', $is_checkout );
|
||||
try {
|
||||
// And we must call get_posted_data because it handles the shipping address.
|
||||
$data = $this->get_posted_data();
|
||||
|
||||
// It throws some notices when checking fields etc., also from other plugins via hooks.
|
||||
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
|
||||
@$this->validate_checkout( $data, $errors );
|
||||
} finally {
|
||||
remove_filter( 'woocommerce_is_checkout', $is_checkout );
|
||||
}
|
||||
|
||||
if (
|
||||
apply_filters( 'woocommerce_paypal_payments_early_wc_checkout_account_creation_validation_enabled', true ) &&
|
||||
! is_user_logged_in() && ( $this->is_registration_required() || ! empty( $data['createaccount'] ) )
|
||||
) {
|
||||
$username = ! empty( $data['account_username'] ) ? $data['account_username'] : '';
|
||||
$email = $data['billing_email'] ?? '';
|
||||
|
||||
if ( email_exists( $email ) ) {
|
||||
$errors->add(
|
||||
'registration-error-email-exists',
|
||||
apply_filters(
|
||||
'woocommerce_registration_error_email_exists',
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
|
||||
__( 'An account is already registered with your email address. <a href="#" class="showlogin">Please log in.</a>', 'woocommerce' ),
|
||||
$email
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $username ) { // Empty username is already checked in validate_checkout, and it can be generated.
|
||||
$username = sanitize_user( $username );
|
||||
if ( empty( $username ) || ! validate_username( $username ) ) {
|
||||
$errors->add(
|
||||
'registration-error-invalid-username',
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
|
||||
__( 'Please enter a valid account username.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( username_exists( $username ) ) {
|
||||
$errors->add(
|
||||
'registration-error-username-exists',
|
||||
// phpcs:ignore WordPress.WP.I18n.TextDomainMismatch
|
||||
__( 'An account is already registered with that username. Please choose another.', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Some plugins call wc_add_notice directly.
|
||||
// We should retrieve such notices, and also clear them to avoid duplicates later.
|
||||
// TODO: Normally WC converts the messages from validate_checkout into notices,
|
||||
// maybe we should do the same for consistency, but it requires lots of changes in the way we handle/output errors.
|
||||
$messages = array_merge(
|
||||
$errors->get_error_messages(),
|
||||
array_map(
|
||||
function ( array $notice ): string {
|
||||
return $notice['notice'];
|
||||
},
|
||||
wc_get_notices( 'error' )
|
||||
)
|
||||
);
|
||||
|
||||
if ( wc_notice_count( 'error' ) > 0 ) {
|
||||
wc_clear_notices();
|
||||
}
|
||||
|
||||
if ( $messages ) {
|
||||
throw new ValidationException( $messages );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue