coding standards for button module

This commit is contained in:
David Remer 2020-08-31 11:12:46 +03:00
parent 3c916138ce
commit 5e39de1247
29 changed files with 1633 additions and 934 deletions

View file

@ -1,6 +1,10 @@
<?php
/**
* The button module extensions.
*
* @package Inpsyde\PayPalCommerce\Button
*/
declare(strict_types=1);
return [
];
return array();

View file

@ -1,4 +1,9 @@
<?php
/**
* The button module.
*
* @package Inpsyde\PayPalCommerce\Button
*/
declare(strict_types=1);

View file

@ -1,4 +1,9 @@
<?php
/**
* The button module services.
*
* @package Inpsyde\PayPalCommerce\Button
*/
declare(strict_types=1);
@ -20,129 +25,133 @@ use Inpsyde\PayPalCommerce\Button\Helper\ThreeDSecure;
use Inpsyde\PayPalCommerce\Onboarding\Environment;
use Inpsyde\PayPalCommerce\Onboarding\State;
return [
'button.client_id' => static function (ContainerInterface $container): string {
return array(
'button.client_id' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings');
$clientId = $settings->has('client_id') ? $settings->get('client_id') : '';
if ($clientId) {
return $clientId;
$settings = $container->get( 'wcgateway.settings' );
$client_id = $settings->has( 'client_id' ) ? $settings->get( 'client_id' ) : '';
if ( $client_id ) {
return $client_id;
}
$env = $container->get('onboarding.environment');
$env = $container->get( 'onboarding.environment' );
/**
* The environment.
*
* @var Environment $env
*/
/**
* ToDo: Add production platform client Id.
*/
return $env->current_environment_is(Environment::SANDBOX) ?
return $env->current_environment_is( Environment::SANDBOX ) ?
'AQB97CzMsd58-It1vxbcDAGvMuXNCXRD9le_XUaMlHB_U7XsU9IiItBwGQOtZv9sEeD6xs2vlIrL4NiD' : '';
},
'button.smart-button' => static function (ContainerInterface $container): SmartButtonInterface {
'button.smart-button' => static function ( ContainerInterface $container ): SmartButtonInterface {
$state = $container->get('onboarding.state');
$state = $container->get( 'onboarding.state' );
/**
* The state.
*
* @var State $state
*/
if ( $state->current_state() < State::STATE_PROGRESSIVE) {
if ( $state->current_state() < State::STATE_PROGRESSIVE ) {
return new DisabledSmartButton();
}
$settings = $container->get('wcgateway.settings');
$paypalDisabled = !$settings->has('enabled') || ! $settings->get('enabled');
$creditCardDisabled = !$settings->has('dcc_gateway_enabled') || ! $settings->get('dcc_gateway_enabled');
if ($paypalDisabled && $creditCardDisabled) {
$settings = $container->get( 'wcgateway.settings' );
$paypal_disabled = ! $settings->has( 'enabled' ) || ! $settings->get( 'enabled' );
$credit_card_disabled = ! $settings->has( 'dcc_gateway_enabled' ) || ! $settings->get( 'dcc_gateway_enabled' );
if ( $paypal_disabled && $credit_card_disabled ) {
return new DisabledSmartButton();
}
$payeeRepository = $container->get('api.repository.payee');
$identityToken = $container->get('api.endpoint.identity-token');
$payerFactory = $container->get('api.factory.payer');
$requestData = $container->get('button.request-data');
$payee_repository = $container->get( 'api.repository.payee' );
$identity_token = $container->get( 'api.endpoint.identity-token' );
$payer_factory = $container->get( 'api.factory.payer' );
$request_data = $container->get( 'button.request-data' );
$clientId = $container->get('button.client_id');
$dccApplies = $container->get('api.helpers.dccapplies');
$subscriptionHelper = $container->get('subscription.helper');
$messagesApply = $container->get('button.helper.messages-apply');
$client_id = $container->get( 'button.client_id' );
$dcc_applies = $container->get( 'api.helpers.dccapplies' );
$subscription_helper = $container->get( 'subscription.helper' );
$messages_apply = $container->get( 'button.helper.messages-apply' );
return new SmartButton(
$container->get('button.url'),
$container->get('session.handler'),
$container->get( 'button.url' ),
$container->get( 'session.handler' ),
$settings,
$payeeRepository,
$identityToken,
$payerFactory,
$clientId,
$requestData,
$dccApplies,
$subscriptionHelper,
$messagesApply
$payee_repository,
$identity_token,
$payer_factory,
$client_id,
$request_data,
$dcc_applies,
$subscription_helper,
$messages_apply
);
},
'button.url' => static function (ContainerInterface $container): string {
'button.url' => static function ( ContainerInterface $container ): string {
return plugins_url(
'/modules.local/ppcp-button/',
dirname(__FILE__, 3) . '/woocommerce-paypal-commerce-gateway.php'
dirname( __FILE__, 3 ) . '/woocommerce-paypal-commerce-gateway.php'
);
},
'button.request-data' => static function (ContainerInterface $container): RequestData {
'button.request-data' => static function ( ContainerInterface $container ): RequestData {
return new RequestData();
},
'button.endpoint.change-cart' => static function (ContainerInterface $container): ChangeCartEndpoint {
if (!\WC()->cart) {
throw new RuntimeException('cant initialize endpoint at this moment');
'button.endpoint.change-cart' => static function ( ContainerInterface $container ): ChangeCartEndpoint {
if ( ! \WC()->cart ) {
throw new RuntimeException( 'cant initialize endpoint at this moment' );
}
$cart = WC()->cart;
$shipping = WC()->shipping();
$requestData = $container->get('button.request-data');
$repository = $container->get('api.repository.cart');
$dataStore = \WC_Data_Store::load('product');
return new ChangeCartEndpoint($cart, $shipping, $requestData, $repository, $dataStore);
$request_data = $container->get( 'button.request-data' );
$repository = $container->get( 'api.repository.cart' );
$data_store = \WC_Data_Store::load( 'product' );
return new ChangeCartEndpoint( $cart, $shipping, $request_data, $repository, $data_store );
},
'button.endpoint.create-order' => static function (ContainerInterface $container): CreateOrderEndpoint {
$requestData = $container->get('button.request-data');
$repository = $container->get('api.repository.cart');
$apiClient = $container->get('api.endpoint.order');
$payerFactory = $container->get('api.factory.payer');
$sessionHandler = $container->get('session.handler');
$settings = $container->get('wcgateway.settings');
$earlyOrderHandler = $container->get('button.helper.early-order-handler');
'button.endpoint.create-order' => static function ( ContainerInterface $container ): CreateOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
$repository = $container->get( 'api.repository.cart' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$payer_factory = $container->get( 'api.factory.payer' );
$session_handler = $container->get( 'session.handler' );
$settings = $container->get( 'wcgateway.settings' );
$early_order_handler = $container->get( 'button.helper.early-order-handler' );
return new CreateOrderEndpoint(
$requestData,
$request_data,
$repository,
$apiClient,
$payerFactory,
$sessionHandler,
$order_endpoint,
$payer_factory,
$session_handler,
$settings,
$earlyOrderHandler
$early_order_handler
);
},
'button.helper.early-order-handler' => static function (ContainerInterface $container) : EarlyOrderHandler {
'button.helper.early-order-handler' => static function ( ContainerInterface $container ) : EarlyOrderHandler {
$state = $container->get('onboarding.state');
$orderProcessor = $container->get('wcgateway.order-processor');
$sessionHandler = $container->get('session.handler');
$prefix = $container->get('api.prefix');
return new EarlyOrderHandler($state, $orderProcessor, $sessionHandler, $prefix);
$state = $container->get( 'onboarding.state' );
$order_processor = $container->get( 'wcgateway.order-processor' );
$session_handler = $container->get( 'session.handler' );
$prefix = $container->get( 'api.prefix' );
return new EarlyOrderHandler( $state, $order_processor, $session_handler, $prefix );
},
'button.endpoint.approve-order' => static function (ContainerInterface $container): ApproveOrderEndpoint {
$requestData = $container->get('button.request-data');
$apiClient = $container->get('api.endpoint.order');
$sessionHandler = $container->get('session.handler');
$threeDSecure = $container->get('button.helper.three-d-secure');
return new ApproveOrderEndpoint($requestData, $apiClient, $sessionHandler, $threeDSecure);
'button.endpoint.approve-order' => static function ( ContainerInterface $container ): ApproveOrderEndpoint {
$request_data = $container->get( 'button.request-data' );
$order_endpoint = $container->get( 'api.endpoint.order' );
$session_handler = $container->get( 'session.handler' );
$three_d_secure = $container->get( 'button.helper.three-d-secure' );
return new ApproveOrderEndpoint( $request_data, $order_endpoint, $session_handler, $three_d_secure );
},
'button.endpoint.data-client-id' => static function(ContainerInterface $container) : DataClientIdEndpoint {
$requestData = $container->get('button.request-data');
$tokenEndpoint = $container->get('api.endpoint.identity-token');
'button.endpoint.data-client-id' => static function( ContainerInterface $container ) : DataClientIdEndpoint {
$request_data = $container->get( 'button.request-data' );
$identity_token = $container->get( 'api.endpoint.identity-token' );
return new DataClientIdEndpoint(
$requestData,
$tokenEndpoint
$request_data,
$identity_token
);
},
'button.helper.three-d-secure' => static function (ContainerInterface $container): ThreeDSecure {
'button.helper.three-d-secure' => static function ( ContainerInterface $container ): ThreeDSecure {
return new ThreeDSecure();
},
'button.helper.messages-apply' => static function (ContainerInterface $container): MessagesApply {
'button.helper.messages-apply' => static function ( ContainerInterface $container ): MessagesApply {
return new MessagesApply();
},
];
);

View file

@ -1,22 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets;
class DisabledSmartButton implements SmartButtonInterface {
public function renderWrapper(): bool {
return true;
}
public function enqueue(): bool {
return true;
}
public function canSaveVaultToken(): bool {
return false;
}
}

View file

@ -1,14 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets;
interface SmartButtonInterface {
public function renderWrapper(): bool;
public function enqueue(): bool;
public function canSaveVaultToken(): bool;
}

View file

@ -0,0 +1,44 @@
<?php
/**
* If we can't render our buttons, this Null object will be used.
*
* @package Inpsyde\PayPalCommerce\Button\Assets
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets;
/**
* Class DisabledSmartButton
*/
class DisabledSmartButton implements SmartButtonInterface {
/**
* Renders the necessary HTML.
*
* @return bool
*/
public function render_wrapper(): bool {
return true;
}
/**
* Enqueues necessary scripts.
*
* @return bool
*/
public function enqueue(): bool {
return true;
}
/**
* Whether tokens can be stored or not.
*
* @return bool
*/
public function can_save_vault_token(): bool {
return false;
}
}

View file

@ -1,11 +1,15 @@
<?php
/**
* Registers and configures the necessary Javascript for the button, credit messaging and DCC fields.
*
* @package Inpsyde\PayPalCommerce\Button\Assets
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\IdentityToken;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PayerFactory;
use Inpsyde\PayPalCommerce\ApiClient\Helper\DccApplies;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayeeRepository;
@ -19,72 +23,157 @@ use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\Subscription\Helper\SubscriptionHelper;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
//phpcs:disable Inpsyde.CodeQuality.PropertyPerClassLimit.TooManyProperties
/**
* Class SmartButton
*/
class SmartButton implements SmartButtonInterface {
private $moduleUrl;
private $sessionHandler;
private $settings;
private $payeeRepository;
private $identityToken;
private $payerFactory;
private $clientId;
private $requestData;
private $dccApplies;
private $subscriptionHelper;
private $messagesApply;
/**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* The Session Handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The Payee Repository.
*
* @var PayeeRepository
*/
private $payee_repository;
/**
* The Identity Token.
*
* @var IdentityToken
*/
private $identity_token;
/**
* The Payer Factory.
*
* @var PayerFactory
*/
private $payer_factory;
/**
* The client ID.
*
* @var string
*/
private $client_id;
/**
* The Request Data.
*
* @var RequestData
*/
private $request_data;
/**
* The DCC Applies helper.
*
* @var DccApplies
*/
private $dcc_applies;
/**
* The Subscription Helper.
*
* @var SubscriptionHelper
*/
private $subscription_helper;
/**
* The Messages apply helper.
*
* @var MessagesApply
*/
private $messages_apply;
/**
* SmartButton constructor.
*
* @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler.
* @param Settings $settings The Settings.
* @param PayeeRepository $payee_repository The Payee Repository.
* @param IdentityToken $identity_token The Identity Token.
* @param PayerFactory $payer_factory The Payer factory.
* @param string $client_id The client ID.
* @param RequestData $request_data The Request Data helper.
* @param DccApplies $dcc_applies The DCC applies helper.
* @param SubscriptionHelper $subscription_helper The subscription helper.
* @param MessagesApply $messages_apply The Messages apply helper.
*/
public function __construct(
string $moduleUrl,
SessionHandler $sessionHandler,
string $module_url,
SessionHandler $session_handler,
Settings $settings,
PayeeRepository $payeeRepository,
IdentityToken $identityToken,
PayerFactory $payerFactory,
string $clientId,
RequestData $requestData,
DccApplies $dccApplies,
SubscriptionHelper $subscriptionHelper,
MessagesApply $messagesApply
PayeeRepository $payee_repository,
IdentityToken $identity_token,
PayerFactory $payer_factory,
string $client_id,
RequestData $request_data,
DccApplies $dcc_applies,
SubscriptionHelper $subscription_helper,
MessagesApply $messages_apply
) {
$this->moduleUrl = $moduleUrl;
$this->sessionHandler = $sessionHandler;
$this->module_url = $module_url;
$this->session_handler = $session_handler;
$this->settings = $settings;
$this->payeeRepository = $payeeRepository;
$this->identityToken = $identityToken;
$this->payerFactory = $payerFactory;
$this->clientId = $clientId;
$this->requestData = $requestData;
$this->dccApplies = $dccApplies;
$this->subscriptionHelper = $subscriptionHelper;
$this->messagesApply = $messagesApply;
$this->payee_repository = $payee_repository;
$this->identity_token = $identity_token;
$this->payer_factory = $payer_factory;
$this->client_id = $client_id;
$this->request_data = $request_data;
$this->dcc_applies = $dcc_applies;
$this->subscription_helper = $subscription_helper;
$this->messages_apply = $messages_apply;
}
/**
* Registers the necessary action hooks to render the HTML depending on the settings.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
public function renderWrapper(): bool {
public function render_wrapper(): bool {
if ( ! $this->canSaveVaultToken() && $this->hasSubscription() ) {
if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
return false;
}
if ( $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' ) ) {
$this->renderButtonWrapperRegistrar();
$this->renderMessageWrapperRegistrar();
$this->render_button_wrapper_registrar();
$this->render_message_wrapper_registrar();
}
if (
$this->settings->has( 'dcc_gateway_enabled' )
&& $this->settings->get( 'dcc_gateway_enabled' )
&& ! $this->sessionHandler->order()
&& ! $this->session_handler->order()
) {
add_action(
'woocommerce_review_order_after_submit',
array(
$this,
'dccRenderer',
'dcc_renderer',
),
11
);
@ -92,48 +181,54 @@ class SmartButton implements SmartButtonInterface {
return true;
}
private function renderMessageWrapperRegistrar(): bool {
/**
* Registers the hooks to render the credit messaging HTML depending on the settings.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
private function render_message_wrapper_registrar(): bool {
$notEnabledOnCart = $this->settings->has( 'message_cart_enabled' ) &&
$not_enabled_on_cart = $this->settings->has( 'message_cart_enabled' ) &&
! $this->settings->get( 'message_cart_enabled' );
if (
is_cart()
&& ! $notEnabledOnCart
&& ! $not_enabled_on_cart
) {
add_action(
'woocommerce_proceed_to_checkout',
array(
$this,
'messageRenderer',
'message_renderer',
),
19
);
}
$notEnabledOnProductPage = $this->settings->has( 'message_product_enabled' ) &&
$not_enabled_on_product_page = $this->settings->has( 'message_product_enabled' ) &&
! $this->settings->get( 'message_product_enabled' );
if (
( is_product() || wc_post_content_has_shortcode( 'product_page' ) )
&& ! $notEnabledOnProductPage
&& ! $not_enabled_on_product_page
) {
add_action(
'woocommerce_single_product_summary',
array(
$this,
'messageRenderer',
'message_renderer',
),
30
);
}
$notEnabledOnCheckout = $this->settings->has( 'message_enabled' ) &&
$not_enabled_on_checkout = $this->settings->has( 'message_enabled' ) &&
! $this->settings->get( 'message_enabled' );
if ( ! $notEnabledOnCheckout ) {
if ( ! $not_enabled_on_checkout ) {
add_action(
'woocommerce_review_order_after_submit',
array(
$this,
'messageRenderer',
'message_renderer',
),
11
);
@ -141,44 +236,50 @@ class SmartButton implements SmartButtonInterface {
return true;
}
private function renderButtonWrapperRegistrar(): bool {
/**
* Registers the hooks where to render the button HTML according to the settings.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
private function render_button_wrapper_registrar(): bool {
$notEnabledOnCart = $this->settings->has( 'button_cart_enabled' ) &&
$not_enabled_on_cart = $this->settings->has( 'button_cart_enabled' ) &&
! $this->settings->get( 'button_cart_enabled' );
if (
is_cart()
&& ! $notEnabledOnCart
&& ! $not_enabled_on_cart
) {
add_action(
'woocommerce_proceed_to_checkout',
array(
$this,
'buttonRenderer',
'button_renderer',
),
20
);
}
$notEnabledOnProductPage = $this->settings->has( 'button_single_product_enabled' ) &&
$not_enabled_on_product_page = $this->settings->has( 'button_single_product_enabled' ) &&
! $this->settings->get( 'button_single_product_enabled' );
if (
( is_product() || wc_post_content_has_shortcode( 'product_page' ) )
&& ! $notEnabledOnProductPage
&& ! $not_enabled_on_product_page
) {
add_action(
'woocommerce_single_product_summary',
array(
$this,
'buttonRenderer',
'button_renderer',
),
31
);
}
$notEnabledOnMiniCart = $this->settings->has( 'button_mini_cart_enabled' ) &&
$not_enabled_on_minicart = $this->settings->has( 'button_mini_cart_enabled' ) &&
! $this->settings->get( 'button_mini_cart_enabled' );
if (
! $notEnabledOnMiniCart
! $not_enabled_on_minicart
) {
add_action(
'woocommerce_widget_shopping_cart_after_buttons',
@ -192,28 +293,34 @@ class SmartButton implements SmartButtonInterface {
);
}
add_action( 'woocommerce_review_order_after_submit', array( $this, 'buttonRenderer' ), 10 );
add_action( 'woocommerce_review_order_after_submit', array( $this, 'button_renderer' ), 10 );
return true;
}
/**
* Enqueues the script.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
public function enqueue(): bool {
$buttonsEnabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
if ( ! is_checkout() && ! $buttonsEnabled ) {
$buttons_enabled = $this->settings->has( 'enabled' ) && $this->settings->get( 'enabled' );
if ( ! is_checkout() && ! $buttons_enabled ) {
return false;
}
if ( ! $this->canSaveVaultToken() && $this->hasSubscription() ) {
if ( ! $this->can_save_vault_token() && $this->has_subscriptions() ) {
return false;
}
wp_enqueue_style(
'ppcp-hosted-fields',
$this->moduleUrl . '/assets/css/hosted-fields.css',
$this->module_url . '/assets/css/hosted-fields.css',
array(),
1
);
wp_enqueue_script(
'ppcp-smart-button',
$this->moduleUrl . '/assets/js/button.js',
$this->module_url . '/assets/js/button.js',
array( 'jquery' ),
1,
true
@ -222,12 +329,15 @@ class SmartButton implements SmartButtonInterface {
wp_localize_script(
'ppcp-smart-button',
'PayPalCommerceGateway',
$this->localizeScript()
$this->localize_script()
);
return true;
}
public function buttonRenderer() {
/**
* Renders the HTML for the buttons.
*/
public function button_renderer() {
$product = wc_get_product();
if (
! is_checkout() && is_a( $product, \WC_Product::class )
@ -242,13 +352,21 @@ class SmartButton implements SmartButtonInterface {
echo '<div id="ppc-button"></div>';
}
public function messageRenderer() {
/**
* Renders the HTML for the credit messaging.
*/
public function message_renderer() {
echo '<div id="ppcp-messages"></div>';
}
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
private function messageValues(): array {
/**
* The values for the credit messaging.
*
* @return array
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
*/
private function message_values(): array {
if (
$this->settings->has( 'disable_funding' )
@ -261,34 +379,34 @@ class SmartButton implements SmartButtonInterface {
$amount = ( is_a( $product, \WC_Product::class ) ) ? wc_get_price_including_tax( $product ) : 0;
$layout = $this->settings->has( 'message_product_layout' ) ?
$this->settings->get( 'message_product_layout' ) : 'text';
$logoType = $this->settings->has( 'message_product_logo' ) ?
$logo_type = $this->settings->has( 'message_product_logo' ) ?
$this->settings->get( 'message_product_logo' ) : 'primary';
$logoPosition = $this->settings->has( 'message_product_position' ) ?
$logo_position = $this->settings->has( 'message_product_position' ) ?
$this->settings->get( 'message_product_position' ) : 'left';
$textColor = $this->settings->has( 'message_product_color' ) ?
$text_color = $this->settings->has( 'message_product_color' ) ?
$this->settings->get( 'message_product_color' ) : 'black';
$styleColor = $this->settings->has( 'message_product_flex_color' ) ?
$style_color = $this->settings->has( 'message_product_flex_color' ) ?
$this->settings->get( 'message_product_flex_color' ) : 'blue';
$ratio = $this->settings->has( 'message_product_flex_ratio' ) ?
$this->settings->get( 'message_product_flex_ratio' ) : '1x1';
$shouldShow = $this->settings->has( 'message_product_enabled' )
$should_show = $this->settings->has( 'message_product_enabled' )
&& $this->settings->get( 'message_product_enabled' );
if ( is_checkout() ) {
$placement = 'payment';
$amount = WC()->cart->get_total( 'raw' );
$layout = $this->settings->has( 'message_layout' ) ?
$this->settings->get( 'message_layout' ) : 'text';
$logoType = $this->settings->has( 'message_logo' ) ?
$logo_type = $this->settings->has( 'message_logo' ) ?
$this->settings->get( 'message_logo' ) : 'primary';
$logoPosition = $this->settings->has( 'message_position' ) ?
$logo_position = $this->settings->has( 'message_position' ) ?
$this->settings->get( 'message_position' ) : 'left';
$textColor = $this->settings->has( 'message_color' ) ?
$text_color = $this->settings->has( 'message_color' ) ?
$this->settings->get( 'message_color' ) : 'black';
$styleColor = $this->settings->has( 'message_flex_color' ) ?
$style_color = $this->settings->has( 'message_flex_color' ) ?
$this->settings->get( 'message_flex_color' ) : 'blue';
$ratio = $this->settings->has( 'message_flex_ratio' ) ?
$this->settings->get( 'message_flex_ratio' ) : '1x1';
$shouldShow = $this->settings->has( 'message_enabled' )
$should_show = $this->settings->has( 'message_enabled' )
&& $this->settings->get( 'message_enabled' );
}
if ( is_cart() ) {
@ -296,21 +414,21 @@ class SmartButton implements SmartButtonInterface {
$amount = WC()->cart->get_total( 'raw' );
$layout = $this->settings->has( 'message_cart_layout' ) ?
$this->settings->get( 'message_cart_layout' ) : 'text';
$logoType = $this->settings->has( 'message_cart_logo' ) ?
$logo_type = $this->settings->has( 'message_cart_logo' ) ?
$this->settings->get( 'message_cart_logo' ) : 'primary';
$logoPosition = $this->settings->has( 'message_cart_position' ) ?
$logo_position = $this->settings->has( 'message_cart_position' ) ?
$this->settings->get( 'message_cart_position' ) : 'left';
$textColor = $this->settings->has( 'message_cart_color' ) ?
$text_color = $this->settings->has( 'message_cart_color' ) ?
$this->settings->get( 'message_cart_color' ) : 'black';
$styleColor = $this->settings->has( 'message_cart_flex_color' ) ?
$style_color = $this->settings->has( 'message_cart_flex_color' ) ?
$this->settings->get( 'message_cart_flex_color' ) : 'blue';
$ratio = $this->settings->has( 'message_cart_flex_ratio' ) ?
$this->settings->get( 'message_cart_flex_ratio' ) : '1x1';
$shouldShow = $this->settings->has( 'message_cart_enabled' )
$should_show = $this->settings->has( 'message_cart_enabled' )
&& $this->settings->get( 'message_cart_enabled' );
}
if ( ! $shouldShow ) {
if ( ! $should_show ) {
return array();
}
@ -321,32 +439,36 @@ class SmartButton implements SmartButtonInterface {
'style' => array(
'layout' => $layout,
'logo' => array(
'type' => $logoType,
'position' => $logoPosition,
'type' => $logo_type,
'position' => $logo_position,
),
'text' => array(
'color' => $textColor,
'color' => $text_color,
),
'color' => $styleColor,
'color' => $style_color,
'ratio' => $ratio,
),
);
return $values;
}
//phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
public function dccRenderer() {
/**
* Renders the HTML for the DCC fields.
*
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting hasnt been found.
*/
public function dcc_renderer() {
$id = 'ppcp-hosted-fields';
$canRenderDcc = $this->dccApplies->forCountryCurrency()
$can_render_dcc = $this->dcc_applies->forCountryCurrency()
&& $this->settings->has( 'client_id' )
&& $this->settings->get( 'client_id' );
if ( ! $canRenderDcc ) {
if ( ! $can_render_dcc ) {
return;
}
$saveCard = $this->canSaveVaultToken() ? sprintf(
$save_card = $this->can_save_vault_token() ? sprintf(
'<div>
<label for="ppcp-vault-%1$s">%2$s</label>
@ -382,13 +504,18 @@ class SmartButton implements SmartButtonInterface {
esc_html__( 'Expiration', 'woocommerce-paypal-commerce-gateway' ),
esc_html__( 'CVV', 'woocommerce-paypal-commerce-gateway' ),
//phpcs:ignore
$saveCard,
esc_html__( 'Place order', 'woocommerce' )
$save_card,
esc_html__( 'Place order', 'woocommerce-paypal-commerce-gateway' )
);
}
// phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
public function canSaveVaultToken(): bool {
/**
* Whether we can store vault tokens or not.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting hasnt been found.
*/
public function can_save_vault_token(): bool {
if ( ! $this->settings->has( 'client_id' ) || ! $this->settings->get( 'client_id' ) ) {
return false;
@ -399,24 +526,34 @@ class SmartButton implements SmartButtonInterface {
return is_user_logged_in();
}
private function hasSubscription(): bool {
if ( ! $this->subscriptionHelper->accept_only_automatic_payment_gateways() ) {
/**
* Whether we need to initialize the script to enable tokenization for subscriptions or not.
*
* @return bool
*/
private function has_subscriptions(): bool {
if ( ! $this->subscription_helper->accept_only_automatic_payment_gateways() ) {
return false;
}
if ( is_product() ) {
return $this->subscriptionHelper->current_product_is_subscription();
return $this->subscription_helper->current_product_is_subscription();
}
return $this->subscriptionHelper->cart_contains_subscription();
return $this->subscription_helper->cart_contains_subscription();
}
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
private function localizeScript(): array {
$this->requestData->enqueueNonceFix();
/**
* The localized data for the smart button.
*
* @return array
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting hasn't been found.
*/
private function localize_script(): array {
$this->request_data->enqueue_nonce_fix();
$localize = array(
'script_attributes' => $this->attributes(),
'data_client_id' => array(
'set_attribute' => ( is_checkout() && $this->dccIsEnabled() )
|| $this->canSaveVaultToken(),
'set_attribute' => ( is_checkout() && $this->dcc_is_enabled() )
|| $this->can_save_vault_token(),
'endpoint' => home_url( \WC_AJAX::get_endpoint( DataClientIdEndpoint::ENDPOINT ) ),
'nonce' => wp_create_nonce( DataClientIdEndpoint::nonce() ),
'user' => get_current_user_id(),
@ -437,8 +574,8 @@ class SmartButton implements SmartButtonInterface {
'nonce' => wp_create_nonce( ApproveOrderEndpoint::nonce() ),
),
),
'enforce_vault' => $this->hasSubscription(),
'bn_codes' => $this->bnCodes(),
'enforce_vault' => $this->has_subscriptions(),
'bn_codes' => $this->bn_codes(),
'payer' => $this->payerData(),
'button' => array(
'wrapper' => '#ppc-button',
@ -446,18 +583,18 @@ class SmartButton implements SmartButtonInterface {
'cancel_wrapper' => '#ppcp-cancel',
'url' => $this->url(),
'mini_cart_style' => array(
'layout' => $this->styleForContext( 'layout', 'mini-cart' ),
'color' => $this->styleForContext( 'color', 'mini-cart' ),
'shape' => $this->styleForContext( 'shape', 'mini-cart' ),
'label' => $this->styleForContext( 'label', 'mini-cart' ),
'tagline' => $this->styleForContext( 'tagline', 'mini-cart' ),
'layout' => $this->style_for_context( 'layout', 'mini-cart' ),
'color' => $this->style_for_context( 'color', 'mini-cart' ),
'shape' => $this->style_for_context( 'shape', 'mini-cart' ),
'label' => $this->style_for_context( 'label', 'mini-cart' ),
'tagline' => $this->style_for_context( 'tagline', 'mini-cart' ),
),
'style' => array(
'layout' => $this->styleForContext( 'layout', $this->context() ),
'color' => $this->styleForContext( 'color', $this->context() ),
'shape' => $this->styleForContext( 'shape', $this->context() ),
'label' => $this->styleForContext( 'label', $this->context() ),
'tagline' => $this->styleForContext( 'tagline', $this->context() ),
'layout' => $this->style_for_context( 'layout', $this->context() ),
'color' => $this->style_for_context( 'color', $this->context() ),
'shape' => $this->style_for_context( 'shape', $this->context() ),
'label' => $this->style_for_context( 'label', $this->context() ),
'tagline' => $this->style_for_context( 'tagline', $this->context() ),
),
),
'hosted_fields' => array(
@ -473,7 +610,7 @@ class SmartButton implements SmartButtonInterface {
),
),
),
'messages' => $this->messageValues(),
'messages' => $this->message_values(),
'labels' => array(
'error' => array(
'generic' => __(
@ -484,37 +621,46 @@ class SmartButton implements SmartButtonInterface {
),
);
if ( $this->styleForContext( 'layout', 'mini-cart' ) !== 'horizontal' ) {
if ( $this->style_for_context( 'layout', 'mini-cart' ) !== 'horizontal' ) {
unset( $localize['button']['mini_cart_style']['tagline'] );
}
if ( $this->styleForContext( 'layout', $this->context() ) !== 'horizontal' ) {
if ( $this->style_for_context( 'layout', $this->context() ) !== 'horizontal' ) {
unset( $localize['button']['style']['tagline'] );
}
$this->requestData->dequeueNonceFix();
$this->request_data->dequeue_nonce_fix();
return $localize;
}
//phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
/**
* If we can find the payer data for a current customer, we will return it.
*
* @return array|null
*/
private function payerData(): ?array {
$customer = WC()->customer;
if ( ! is_user_logged_in() || ! is_a( $customer, \WC_Customer::class ) ) {
return null;
}
return $this->payerFactory->fromCustomer( $customer )->toArray();
return $this->payer_factory->fromCustomer( $customer )->toArray();
}
/**
* The JavaScript SDK url to load.
*
* @return string
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting was not found.
*/
private function url(): string {
$params = array(
'client-id' => $this->clientId,
'client-id' => $this->client_id,
'currency' => get_woocommerce_currency(),
'locale' => get_user_locale(),
// 'debug' => (defined('WP_DEBUG') && WP_DEBUG) ? 'true' : 'false',
// ToDo: Update date on releases.
'integration-date' => date( 'Y-m-d' ),
'integration-date' => gmdate( 'Y-m-d' ),
'components' => implode( ',', $this->components() ),
'vault' => ( is_checkout() && $this->dccIsEnabled() ) || $this->canSaveVaultToken() ?
'vault' => ( is_checkout() && $this->dcc_is_enabled() ) || $this->can_save_vault_token() ?
'true' : 'false',
'commit' => is_checkout() ? 'true' : 'false',
'intent' => ( $this->settings->has( 'intent' ) ) ?
@ -526,43 +672,50 @@ class SmartButton implements SmartButtonInterface {
) {
$params['buyer-country'] = WC()->customer->get_billing_country();
}
$payee = $this->payeeRepository->payee();
$payee = $this->payee_repository->payee();
if ( $payee->merchantId() ) {
$params['merchant-id'] = $payee->merchantId();
}
$disableFunding = $this->settings->has( 'disable_funding' ) ?
$disable_funding = $this->settings->has( 'disable_funding' ) ?
$this->settings->get( 'disable_funding' ) : array();
$disableFunding[] = 'venmo';
$disable_funding[] = 'venmo';
if ( ! is_checkout() ) {
$disableFunding[] = 'card';
$disable_funding[] = 'card';
}
$params['disable-funding'] = implode( ',', array_unique( $disableFunding ) );
$smartButtonUrl = add_query_arg( $params, 'https://www.paypal.com/sdk/js' );
return $smartButtonUrl;
$params['disable-funding'] = implode( ',', array_unique( $disable_funding ) );
$smart_button_url = add_query_arg( $params, 'https://www.paypal.com/sdk/js' );
return $smart_button_url;
}
/**
* The attributes we need to load for the JS SDK.
*
* @return array
*/
private function attributes(): array {
return array(
'data-partner-attribution-id' => $this->bnCodeForContext( $this->context() ),
'data-partner-attribution-id' => $this->bn_code_for_context( $this->context() ),
);
}
/**
* @param string $context
* What BN Code to use in a given context.
*
* @param string $context The context.
* @return string
*/
private function bnCodeForContext( string $context ): string {
private function bn_code_for_context( string $context ): string {
$codes = $this->bnCodes();
$codes = $this->bn_codes();
return ( isset( $codes[ $context ] ) ) ? $codes[ $context ] : '';
}
/**
* BN Codes
* BN Codes to use.
*
* @return array
*/
private function bnCodes(): array {
private function bn_codes(): array {
return array(
'checkout' => 'Woo_PPCP',
@ -572,17 +725,28 @@ class SmartButton implements SmartButtonInterface {
);
}
/**
* The JS SKD components we need to load.
*
* @return array
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting was not found.
*/
private function components(): array {
$components = array( 'buttons' );
if ( $this->messagesApply->forCountry() ) {
if ( $this->messages_apply->for_country() ) {
$components[] = 'messages';
}
if ( $this->dccIsEnabled() ) {
if ( $this->dcc_is_enabled() ) {
$components[] = 'hosted-fields';
}
return $components;
}
/**
* The current context.
*
* @return string
*/
private function context(): string {
$context = 'mini-cart';
if ( is_product() || wc_post_content_has_shortcode( 'product_page' ) ) {
@ -591,14 +755,20 @@ class SmartButton implements SmartButtonInterface {
if ( is_cart() ) {
$context = 'cart';
}
if ( is_checkout() && ! $this->sessionHandler->order() ) {
if ( is_checkout() && ! $this->session_handler->order() ) {
$context = 'checkout';
}
return $context;
}
private function dccIsEnabled(): bool {
if ( ! $this->dccApplies->forCountryCurrency() ) {
/**
* Whether DCC is enabled or not.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting has not been found.
*/
private function dcc_is_enabled(): bool {
if ( ! $this->dcc_applies->forCountryCurrency() ) {
return false;
}
$keys = array(
@ -612,7 +782,16 @@ class SmartButton implements SmartButtonInterface {
return false;
}
private function styleForContext( string $style, string $context ): string {
/**
* Determines the style for a given indicator in a given context.
*
* @param string $style The style.
* @param string $context The context.
*
* @return string
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting hasn't been found.
*/
private function style_for_context( string $style, string $context ): string {
$defaults = array(
'layout' => 'vertical',
'size' => 'responsive',

View file

@ -0,0 +1,37 @@
<?php
/**
* The interface for the smart button asset renderer.
*
* @package Inpsyde\PayPalCommerce\Button\Assets
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Assets;
/**
* Interface SmartButtonInterface
*/
interface SmartButtonInterface {
/**
* Renders the necessary HTML.
*
* @return bool
*/
public function render_wrapper(): bool;
/**
* Enqueues the necessary scripts.
*
* @return bool
*/
public function enqueue(): bool;
/**
* Whether the running installation could save vault tokens or not.
*
* @return bool
*/
public function can_save_vault_token(): bool;
}

View file

@ -1,189 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Item;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
class ChangeCartEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-change-cart';
private $cart;
private $shipping;
private $requestData;
private $repository;
private $productDataStore;
public function __construct(
\WC_Cart $cart,
\WC_Shipping $shipping,
RequestData $requestData,
CartRepository $repository,
\WC_Data_Store $productDataStore
) {
$this->cart = $cart;
$this->shipping = $shipping;
$this->requestData = $requestData;
$this->repository = $repository;
$this->productDataStore = $productDataStore;
}
public static function nonce(): string {
return self::ENDPOINT;
}
public function handleRequest(): bool {
try {
return $this->handleData();
} catch ( RuntimeException $error ) {
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;
}
}
private function handleData(): bool {
$data = $this->requestData->readRequest( $this->nonce() );
$products = $this->productsFromData( $data );
if ( ! $products ) {
wp_send_json_error(
array(
'name' => '',
'message' => __(
'Necessary fields not defined. Action aborted.',
'woocommerce-paypal-commerce-gateway'
),
'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->addProduct( $product['product'], $product['quantity'] )
: $this->addVariableProduct(
$product['product'],
$product['quantity'],
$product['variations']
);
}
if ( ! $success ) {
$this->handleError();
return $success;
}
wp_send_json_success( $this->generatePurchaseUnits() );
return $success;
}
private function handleError(): bool {
$message = __(
'Something went wrong. Action aborted',
'woocommerce-paypal-commerce-gateway'
);
$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;
}
private function productsFromData( array $data ): ?array {
$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;
}
$wcProduct = wc_get_product( (int) $product['id'] );
if ( ! $wcProduct ) {
return null;
}
$products[] = array(
'product' => $wcProduct,
'quantity' => (int) $product['quantity'],
'variations' => isset( $product['variations'] ) ? $product['variations'] : null,
);
}
return $products;
}
private function addProduct( \WC_Product $product, int $quantity ): bool {
return false !== $this->cart->add_to_cart( $product->get_id(), $quantity );
}
private function addVariableProduct(
\WC_Product $product,
int $quantity,
array $postVariations
): bool {
$variations = array();
foreach ( $postVariations as $key => $value ) {
$variations[ $value['name'] ] = $value['value'];
}
$variationId = $this->productDataStore->find_matching_product_variation( $product, $variations );
// ToDo: Check stock status for variation.
return false !== $this->cart->add_to_cart(
$product->get_id(),
$quantity,
$variationId,
$variations
);
}
private function generatePurchaseUnits(): array {
return array_map(
static function ( PurchaseUnit $lineItem ): array {
return $lineItem->toArray();
},
$this->repository->all()
);
}
}

View file

@ -1,154 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PayerFactory;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
class CreateOrderEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-create-order';
private $requestData;
private $repository;
private $apiEndpoint;
private $payerFactory;
private $sessionHandler;
private $settings;
private $earlyOrderHandler;
private $order;
public function __construct(
RequestData $requestData,
CartRepository $repository,
OrderEndpoint $apiEndpoint,
PayerFactory $payerFactory,
SessionHandler $sessionHandler,
Settings $settings,
EarlyOrderHandler $earlyOrderHandler
) {
$this->requestData = $requestData;
$this->repository = $repository;
$this->apiEndpoint = $apiEndpoint;
$this->payerFactory = $payerFactory;
$this->sessionHandler = $sessionHandler;
$this->settings = $settings;
$this->earlyOrderHandler = $earlyOrderHandler;
}
public static function nonce(): string {
return self::ENDPOINT;
}
public function handleRequest(): bool {
try {
$data = $this->requestData->readRequest( $this->nonce() );
$purchaseUnits = $this->repository->all();
$payer = null;
if ( isset( $data['payer'] ) && $data['payer'] ) {
if ( isset( $data['payer']['phone']['phone_number']['national_number'] ) ) {
// make sure the phone number contains only numbers and is max 14. chars long.
$number = $data['payer']['phone']['phone_number']['national_number'];
$number = preg_replace( '/[^0-9]/', '', $number );
$number = substr( $number, 0, 14 );
$data['payer']['phone']['phone_number']['national_number'] = $number;
}
$payer = $this->payerFactory->fromPayPalResponse( json_decode( json_encode( $data['payer'] ) ) );
}
$bnCode = isset( $data['bn_code'] ) ? (string) $data['bn_code'] : '';
if ( $bnCode ) {
$this->sessionHandler->replaceBnCode( $bnCode );
$this->apiEndpoint->withBnCode( $bnCode );
}
$payeePreferred = $this->settings->has( 'payee_preferred' )
&& $this->settings->get( 'payee_preferred' ) ?
PaymentMethod::PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED
: PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
$paymentMethod = new PaymentMethod( $payeePreferred );
$order = $this->apiEndpoint->createForPurchaseUnits(
$purchaseUnits,
$payer,
null,
$paymentMethod
);
if ( $data['context'] === 'checkout' ) {
$this->validateForm( $data['form'], $order );
}
wp_send_json_success( $order->toArray() );
return true;
} catch ( \RuntimeException $error ) {
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;
}
}
private function validateForm( string $formValues, Order $order ) {
$this->order = $order;
$parsedValues = wp_parse_args( $formValues );
$_POST = $parsedValues;
$_REQUEST = $parsedValues;
add_filter(
'woocommerce_after_checkout_validation',
array(
$this,
'afterCheckoutValidation',
),
10,
2
);
$checkout = \WC()->checkout();
$checkout->process_checkout();
}
public function afterCheckoutValidation( array $data, \WP_Error $errors ): array {
$order = $this->order;
if ( ! $errors->errors ) {
/**
* In case we are onboarded and everything is fine with the \WC_Order
* we want this order to be created. We will intercept it and leave it
* in the "Pending payment" status though, which than later will change
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->earlyOrderHandler->shouldCreateEarlyOrder() ) {
wp_send_json_success( $order->toArray() );
}
$this->earlyOrderHandler->registerForOrder( $order );
return $data;
}
wp_send_json_error(
array(
'name' => '',
'message' => $errors->get_error_message(),
'code' => (int) $errors->get_error_code(),
'details' => array(),
)
);
return $data;
}
}

View file

@ -1,13 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
interface EndpointInterface {
public static function nonce(): string;
public function handleRequest(): bool;
}

View file

@ -1,63 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
class RequestData {
public function enqueueNonceFix() {
add_filter( 'nonce_user_logged_out', array( $this, 'nonceFix' ), 100 );
}
public function dequeueNonceFix() {
remove_filter( 'nonce_user_logged_out', array( $this, 'nonceFix' ), 100 );
}
public function readRequest( string $nonce ): array {
$stream = file_get_contents( 'php://input' );
$json = json_decode( $stream, true );
$this->enqueueNonceFix();
if (
! isset( $json['nonce'] )
|| ! wp_verify_nonce( $json['nonce'], $nonce )
) {
remove_filter( 'nonce_user_logged_out', array( $this, 'nonceFix' ), 100 );
throw new RuntimeException(
__( 'Could not validate nonce.', 'woocommerce-paypal-commerce-gateway' )
);
}
$this->dequeueNonceFix();
$sanitized = $this->sanitize( $json );
return $sanitized;
}
/**
* woocommerce will give you a customer object on your 2nd request. the first page
* load will not yet have this customer object, but the ajax request will. Therefore
* the nonce validation will fail. this fixes this problem:
*
* @wp-hook nonce_user_logged_out
* @see https://github.com/woocommerce/woocommerce/blob/69e3835041113bee80379c1037e97e26815a699b/includes/class-wc-session-handler.php#L288-L296
* @return int
*/
public function nonceFix(): int {
return 0;
}
private function sanitize( array $assocArray ): array {
$data = array();
foreach ( (array) $assocArray as $rawKey => $rawValue ) {
if ( ! is_array( $rawValue ) ) {
$data[ sanitize_text_field( urldecode( (string) $rawKey ) ) ] = sanitize_text_field( urldecode( (string) $rawValue ) );
continue;
}
$data[ sanitize_text_field( urldecode( (string) $rawKey ) ) ] = $this->sanitize( $rawValue );
}
return $data;
}
}

View file

@ -1,54 +1,104 @@
<?php
/**
* Endpoint to verify if an order has been approved. An approved order
* will be stored in the current session.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
use Inpsyde\PayPalCommerce\Button\Helper\ThreeDSecure;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
/**
* Class ApproveOrderEndpoint
*/
class ApproveOrderEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-approve-order';
private $requestData;
private $sessionHandler;
private $apiEndpoint;
private $threedSecure;
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The session handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $api_endpoint;
/**
* The 3d secure helper object.
*
* @var ThreeDSecure
*/
private $threed_secure;
/**
* ApproveOrderEndpoint constructor.
*
* @param RequestData $request_data The request data helper.
* @param OrderEndpoint $order_endpoint The order endpoint.
* @param SessionHandler $session_handler The session handler.
* @param ThreeDSecure $three_d_secure The 3d secure helper object.
*/
public function __construct(
RequestData $requestData,
OrderEndpoint $apiEndpoint,
SessionHandler $sessionHandler,
ThreeDSecure $threedSecure
RequestData $request_data,
OrderEndpoint $order_endpoint,
SessionHandler $session_handler,
ThreeDSecure $three_d_secure
) {
$this->requestData = $requestData;
$this->apiEndpoint = $apiEndpoint;
$this->sessionHandler = $sessionHandler;
$this->threedSecure = $threedSecure;
$this->request_data = $request_data;
$this->api_endpoint = $order_endpoint;
$this->session_handler = $session_handler;
$this->threed_secure = $three_d_secure;
}
/**
* The nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
public function handleRequest(): bool {
/**
* Handles the request.
*
* @return bool
* @throws RuntimeException When no order was found.
*/
public function handle_request(): bool {
try {
$data = $this->requestData->readRequest( $this->nonce() );
$data = $this->request_data->read_request( $this->nonce() );
if ( ! isset( $data['order_id'] ) ) {
throw new RuntimeException(
__( 'No order id given', 'woocommerce-paypal-commerce-gateway' )
);
}
$order = $this->apiEndpoint->order( $data['order_id'] );
$order = $this->api_endpoint->order( $data['order_id'] );
if ( ! $order ) {
throw new RuntimeException(
sprintf(
@ -60,8 +110,8 @@ class ApproveOrderEndpoint implements EndpointInterface {
}
if ( $order->paymentSource() && $order->paymentSource()->card() ) {
$proceed = $this->threedSecure->proceedWithOrder( $order );
if ( $proceed === ThreeDSecure::RETRY ) {
$proceed = $this->threed_secure->proceed_with_order( $order );
if ( ThreeDSecure::RETRY === $proceed ) {
throw new RuntimeException(
__(
'Something went wrong. Please try again.',
@ -69,7 +119,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
)
);
}
if ( $proceed === ThreeDSecure::REJECT ) {
if ( ThreeDSecure::REJECT === $proceed ) {
throw new RuntimeException(
__(
'Unfortunatly, we can\'t accept your card. Please choose a different payment method.',
@ -77,7 +127,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
)
);
}
$this->sessionHandler->replaceOrder( $order );
$this->session_handler->replaceOrder( $order );
wp_send_json_success( $order );
}
@ -91,7 +141,7 @@ class ApproveOrderEndpoint implements EndpointInterface {
);
}
$this->sessionHandler->replaceOrder( $order );
$this->session_handler->replaceOrder( $order );
wp_send_json_success( $order );
return true;
} catch ( \RuntimeException $error ) {
@ -106,5 +156,4 @@ class ApproveOrderEndpoint implements EndpointInterface {
return false;
}
}
//phpcs:enable Inpsyde.CodeQuality.FunctionLength.TooLong
}

View file

@ -0,0 +1,289 @@
<?php
/**
* Endpoint to update the cart.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PurchaseUnit;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
/**
* Class ChangeCartEndpoint
*/
class ChangeCartEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-change-cart';
/**
* The current cart object.
*
* @var \WC_Cart
*/
private $cart;
/**
* The current shipping object.
*
* @var \WC_Shipping
*/
private $shipping;
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* Contains purchase units based off the current WC cart.
*
* @var CartRepository
*/
private $repository;
/**
* The product data store.
*
* @var \WC_Data_Store
*/
private $product_data_store;
/**
* ChangeCartEndpoint constructor.
*
* @param \WC_Cart $cart The current WC cart object.
* @param \WC_Shipping $shipping The current WC shipping object.
* @param RequestData $request_data The request data helper.
* @param CartRepository $repository The repository for the current purchase items.
* @param \WC_Data_Store $product_data_store The data store for products.
*/
public function __construct(
\WC_Cart $cart,
\WC_Shipping $shipping,
RequestData $request_data,
CartRepository $repository,
\WC_Data_Store $product_data_store
) {
$this->cart = $cart;
$this->shipping = $shipping;
$this->request_data = $request_data;
$this->repository = $repository;
$this->product_data_store = $product_data_store;
}
/**
* The nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws \Exception On error.
*/
public function handle_request(): bool {
try {
return $this->handle_data();
} catch ( RuntimeException $error ) {
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.
*/
private function handle_data(): bool {
$data = $this->request_data->read_request( $this->nonce() );
$products = $this->products_from_data( $data );
if ( ! $products ) {
wp_send_json_error(
array(
'name' => '',
'message' => __(
'Necessary fields not defined. Action aborted.',
'woocommerce-paypal-commerce-gateway'
),
'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;
}
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-commerce-gateway'
);
$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 ): ?array {
$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.
*
* @return array
*/
private function generate_purchase_units(): array {
return array_map(
static function ( PurchaseUnit $line_item ): array {
return $line_item->toArray();
},
$this->repository->all()
);
}
}

View file

@ -0,0 +1,243 @@
<?php
/**
* The endpoint to create an PayPal order.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\PaymentMethod;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\PayerFactory;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class CreateOrderEndpoint
*/
class CreateOrderEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-create-order';
/**
* The request data helper.
*
* @var RequestData
*/
private $request_data;
/**
* The cart repository.
*
* @var CartRepository
*/
private $repository;
/**
* The order endpoint.
*
* @var OrderEndpoint
*/
private $api_endpoint;
/**
* The payer factory.
*
* @var PayerFactory
*/
private $payer_factory;
/**
* The session handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The early order handler.
*
* @var EarlyOrderHandler
*/
private $early_order_handler;
/**
* The current PayPal order in a process.
*
* @var Order|null
*/
private $order;
/**
* CreateOrderEndpoint constructor.
*
* @param RequestData $request_data The RequestData object.
* @param CartRepository $repository The CartRepository object.
* @param OrderEndpoint $order_endpoint The OrderEndpoint object.
* @param PayerFactory $payer_factory The PayerFactory object.
* @param SessionHandler $session_handler The SessionHandler object.
* @param Settings $settings The Settings object.
* @param EarlyOrderHandler $early_order_handler The EarlyOrderHandler object.
*/
public function __construct(
RequestData $request_data,
CartRepository $repository,
OrderEndpoint $order_endpoint,
PayerFactory $payer_factory,
SessionHandler $session_handler,
Settings $settings,
EarlyOrderHandler $early_order_handler
) {
$this->request_data = $request_data;
$this->repository = $repository;
$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;
}
/**
* Returns the nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
/**
* Handles the request.
*
* @return bool
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException In case a setting was not found.
*/
public function handle_request(): bool {
try {
$data = $this->request_data->read_request( $this->nonce() );
$purchase_units = $this->repository->all();
$payer = null;
if ( isset( $data['payer'] ) && $data['payer'] ) {
if ( isset( $data['payer']['phone']['phone_number']['national_number'] ) ) {
// make sure the phone number contains only numbers and is max 14. chars long.
$number = $data['payer']['phone']['phone_number']['national_number'];
$number = preg_replace( '/[^0-9]/', '', $number );
$number = substr( $number, 0, 14 );
$data['payer']['phone']['phone_number']['national_number'] = $number;
}
$payer = $this->payer_factory->fromPayPalResponse( json_decode( wp_json_encode( $data['payer'] ) ) );
}
$bn_code = isset( $data['bn_code'] ) ? (string) $data['bn_code'] : '';
if ( $bn_code ) {
$this->session_handler->replaceBnCode( $bn_code );
$this->api_endpoint->withBnCode( $bn_code );
}
$payee_preferred = $this->settings->has( 'payee_preferred' )
&& $this->settings->get( 'payee_preferred' ) ?
PaymentMethod::PAYEE_PREFERRED_IMMEDIATE_PAYMENT_REQUIRED
: PaymentMethod::PAYEE_PREFERRED_UNRESTRICTED;
$payment_method = new PaymentMethod( $payee_preferred );
$order = $this->api_endpoint->createForPurchaseUnits(
$purchase_units,
$payer,
null,
$payment_method
);
if ( 'checkout' === $data['context'] ) {
$this->validateForm( $data['form'], $order );
}
wp_send_json_success( $order->toArray() );
return true;
} catch ( \RuntimeException $error ) {
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;
}
}
/**
* Prepare the Request parameter and process the checkout form and validate it.
*
* @param string $form_values The values of the form.
* @param Order $order The Order.
*
* @throws \Exception On Error.
*/
private function validateForm( string $form_values, Order $order ) {
$this->order = $order;
$parsed_values = wp_parse_args( $form_values );
$_POST = $parsed_values;
$_REQUEST = $parsed_values;
add_filter(
'woocommerce_after_checkout_validation',
array(
$this,
'after_checkout_validation',
),
10,
2
);
$checkout = \WC()->checkout();
$checkout->process_checkout();
}
/**
* Once the checkout has been validated we execute this method.
*
* @param array $data The data.
* @param \WP_Error $errors The errors, which occurred.
*
* @return array
*/
public function after_checkout_validation( array $data, \WP_Error $errors ): array {
$order = $this->order;
if ( ! $errors->errors ) {
/**
* In case we are onboarded and everything is fine with the \WC_Order
* we want this order to be created. We will intercept it and leave it
* in the "Pending payment" status though, which than later will change
* during the "onApprove"-JS callback or the webhook listener.
*/
if ( ! $this->early_order_handler->should_create_early_order() ) {
wp_send_json_success( $order->toArray() );
}
$this->early_order_handler->register_for_order( $order );
return $data;
}
wp_send_json_error(
array(
'name' => '',
'message' => $errors->get_error_message(),
'code' => (int) $errors->get_error_code(),
'details' => array(),
)
);
return $data;
}
}

View file

@ -1,4 +1,9 @@
<?php
/**
* The Data Client ID endpoint.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
@ -8,31 +13,62 @@ use Inpsyde\PayPalCommerce\ApiClient\Endpoint\IdentityToken;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Exception\RuntimeException;
/**
* Class DataClientIdEndpoint
*/
class DataClientIdEndpoint implements EndpointInterface {
public const ENDPOINT = 'ppc-data-client-id';
private $requestData;
private $identityToken;
/**
* The Request Data Helper.
*
* @var RequestData
*/
private $request_data;
/**
* The Identity Token.
*
* @var IdentityToken
*/
private $identity_token;
/**
* DataClientIdEndpoint constructor.
*
* @param RequestData $request_data The Request Data Helper.
* @param IdentityToken $identity_token The Identity Token.
*/
public function __construct(
RequestData $requestData,
IdentityToken $identityToken
RequestData $request_data,
IdentityToken $identity_token
) {
$this->requestData = $requestData;
$this->identityToken = $identityToken;
$this->request_data = $request_data;
$this->identity_token = $identity_token;
}
/**
* Returns the nonce.
*
* @return string
*/
public static function nonce(): string {
return self::ENDPOINT;
}
public function handleRequest(): bool {
/**
* Handles the request.
*
* @return bool
*/
public function handle_request(): bool {
try {
$this->requestData->readRequest( $this->nonce() );
$userId = get_current_user_id();
$token = $this->identityToken->generateForCustomer( $userId );
$this->request_data->read_request( $this->nonce() );
$user_id = get_current_user_id();
$token = $this->identity_token->generateForCustomer( $user_id );
wp_send_json(
array(
'token' => $token->token(),

View file

@ -0,0 +1,30 @@
<?php
/**
* The Endpoint interface.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
/**
* Interface EndpointInterface
*/
interface EndpointInterface {
/**
* Returns the nonce for an endpoint.
*
* @return string
*/
public static function nonce(): string;
/**
* Handles the request for an endpoint.
*
* @return bool
*/
public function handle_request(): bool;
}

View file

@ -0,0 +1,91 @@
<?php
/**
* Helper to read request data for the endpoints.
*
* @package Inpsyde\PayPalCommerce\Button\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Endpoint;
use Inpsyde\PayPalCommerce\Button\Exception\RuntimeException;
/**
* Class RequestData
*/
class RequestData {
/**
* Enqueues the nonce fix hook.
*/
public function enqueue_nonce_fix() {
add_filter( 'nonce_user_logged_out', array( $this, 'nonce_fix' ), 100 );
}
/**
* Dequeues the nonce fix hook.
*/
public function dequeue_nonce_fix() {
remove_filter( 'nonce_user_logged_out', array( $this, 'nonce_fix' ), 100 );
}
/**
* Reads the current request.
*
* @param string $nonce The nonce.
*
* @return array
* @throws RuntimeException When nonce validation fails.
*/
public function read_request( string $nonce ): array {
$stream = file_get_contents( 'php://input' );
$json = json_decode( $stream, true );
$this->enqueue_nonce_fix();
if (
! isset( $json['nonce'] )
|| ! wp_verify_nonce( $json['nonce'], $nonce )
) {
remove_filter( 'nonce_user_logged_out', array( $this, 'nonce_fix' ), 100 );
throw new RuntimeException(
__( 'Could not validate nonce.', 'woocommerce-paypal-commerce-gateway' )
);
}
$this->dequeue_nonce_fix();
$sanitized = $this->sanitize( $json );
return $sanitized;
}
/**
* Woocommerce will give you a customer object on your 2nd request. the first page
* load will not yet have this customer object, but the ajax request will. Therefore
* the nonce validation will fail. this fixes this problem:
*
* @wp-hook nonce_user_logged_out
* @see https://github.com/woocommerce/woocommerce/blob/69e3835041113bee80379c1037e97e26815a699b/includes/class-wc-session-handler.php#L288-L296
* @return int
*/
public function nonce_fix(): int {
return 0;
}
/**
* Sanitizes the data.
*
* @param array $assoc_array The data array.
*
* @return array
*/
private function sanitize( array $assoc_array ): array {
$data = array();
foreach ( (array) $assoc_array as $raw_key => $raw_value ) {
if ( ! is_array( $raw_value ) ) {
$data[ sanitize_text_field( urldecode( (string) $raw_key ) ) ] = sanitize_text_field( urldecode( (string) $raw_value ) );
continue;
}
$data[ sanitize_text_field( urldecode( (string) $raw_key ) ) ] = $this->sanitize( $raw_value );
}
return $data;
}
}

View file

@ -1,9 +1,17 @@
<?php
/**
* The modules Runtime Exception.
*
* @package Inpsyde\PayPalCommerce\Button\Exception
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Exception;
/**
* Class RuntimeException
*/
class RuntimeException extends \RuntimeException {

View file

@ -1,113 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Helper;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PrefixTrait;
class EarlyOrderHandler {
use PrefixTrait;
private $state;
private $orderProcessor;
private $sessionHandler;
public function __construct(
State $state,
OrderProcessor $orderProcessor,
SessionHandler $sessionHandler,
string $prefix
) {
$this->state = $state;
$this->orderProcessor = $orderProcessor;
$this->sessionHandler = $sessionHandler;
$this->prefix = $prefix;
}
public function shouldCreateEarlyOrder(): bool {
return $this->state->current_state() === State::STATE_ONBOARDED;
}
//phpcs:disable WordPress.Security.NonceVerification.Recommended
public function determineWcOrderId( int $value = null ): ?int {
if ( ! isset( $_REQUEST['ppcp-resume-order'] ) ) {
return $value;
}
$resumeOrderId = (int) WC()->session->get( 'order_awaiting_payment' );
$order = $this->sessionHandler->order();
if ( ! $order ) {
return $value;
}
$orderId = false;
foreach ( $order->purchaseUnits() as $purchaseUnit ) {
if ( $purchaseUnit->customId() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
$orderId = (int) $this->sanitize_custom_id( $purchaseUnit->customId() );
}
}
if ( $orderId === $resumeOrderId ) {
$value = $orderId;
}
return $value;
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
public function registerForOrder( Order $order ): bool {
$success = (bool) add_action(
'woocommerce_checkout_order_processed',
function ( $orderId ) use ( $order ) {
try {
$order = $this->configureSessionAndOrder( (int) $orderId, $order );
wp_send_json_success( $order->toArray() );
} catch ( \RuntimeException $error ) {
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 $success;
}
public function configureSessionAndOrder( int $orderId, Order $order ): Order {
/**
* Set the order id in our session in order for
* us to resume this order in checkout.
*/
WC()->session->set( 'order_awaiting_payment', $orderId );
$wcOrder = wc_get_order( $orderId );
$wcOrder->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wcOrder->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$wcOrder->save_meta_data();
/**
* Patch Order so we have the \WC_Order id added.
*/
return $this->orderProcessor->patch_order( $wcOrder, $order );
}
}

View file

@ -1,19 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Helper;
class MessagesApply {
private $countries = array(
'US',
);
public function forCountry(): bool {
$region = wc_get_base_location();
$country = $region['country'];
return in_array( $country, $this->countries, true );
}
}

View file

@ -0,0 +1,174 @@
<?php
/**
* Handles the Early Order logic, when we need to create the WC_Order by ourselfs.
*
* @package Inpsyde\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Helper;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PrefixTrait;
/**
* Class EarlyOrderHandler
*/
class EarlyOrderHandler {
use PrefixTrait;
/**
* The State.
*
* @var State
*/
private $state;
/**
* The Order Processor.
*
* @var OrderProcessor
*/
private $order_processor;
/**
* The Session Handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* EarlyOrderHandler constructor.
*
* @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
) {
$this->state = $state;
$this->order_processor = $order_processor;
$this->session_handler = $session_handler;
$this->prefix = $prefix;
}
/**
* Whether early orders should be created at all.
*
* @return bool
*/
public function should_create_early_order(): bool {
return $this->state->current_state() === State::STATE_ONBOARDED;
}
//phpcs:disable WordPress.Security.NonceVerification.Recommended
/**
* Tries to determine the current WC Order Id based on the PayPal order
* and the current order in session.
*
* @param int|null $value The initial value.
*
* @return int|null
*/
public function determine_wc_order_id( int $value = null ): ?int {
if ( ! isset( $_REQUEST['ppcp-resume-order'] ) ) {
return $value;
}
$resume_order_id = (int) WC()->session->get( 'order_awaiting_payment' );
$order = $this->session_handler->order();
if ( ! $order ) {
return $value;
}
$order_id = false;
foreach ( $order->purchaseUnits() as $purchase_unit ) {
if ( $purchase_unit->customId() === sanitize_text_field( wp_unslash( $_REQUEST['ppcp-resume-order'] ) ) ) {
$order_id = (int) $this->sanitize_custom_id( $purchase_unit->customId() );
}
}
if ( $order_id === $resume_order_id ) {
$value = $order_id;
}
return $value;
}
//phpcs:enable WordPress.Security.NonceVerification.Recommended
/**
* Registers the necessary checkout actions for a given order.
*
* @param Order $order The PayPal order.
*
* @return bool
*/
public function register_for_order( Order $order ): bool {
$success = (bool) add_action(
'woocommerce_checkout_order_processed',
function ( $order_id ) use ( $order ) {
try {
$order = $this->configure_session_and_order( (int) $order_id, $order );
wp_send_json_success( $order->toArray() );
} catch ( \RuntimeException $error ) {
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 $success;
}
/**
* Configures the session, so we can pick up the order, once we pass through the checkout.
*
* @param int $order_id The Woocommerce order id.
* @param Order $order The PayPal order.
*
* @return Order
*/
public function configure_session_and_order( int $order_id, Order $order ): Order {
/**
* Set the order id in our session in order for
* us to resume this order in checkout.
*/
WC()->session->set( 'order_awaiting_payment', $order_id );
$wc_order = wc_get_order( $order_id );
$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();
/**
* Patch Order so we have the \WC_Order id added.
*/
return $this->order_processor->patch_order( $wc_order, $order );
}
}

View file

@ -0,0 +1,37 @@
<?php
/**
* Helper class to determine if credit messaging should be displayed.
*
* @package Inpsyde\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\Button\Helper;
/**
* Class MessagesApply
*/
class MessagesApply {
/**
* In which countries credit messaging is available.
*
* @var array
*/
private $countries = array(
'US',
);
/**
* Determines whether a credit messaging is enabled for the shops location country.
*
* @return bool
*/
public function for_country(): bool {
$region = wc_get_base_location();
$country = $region['country'];
return in_array( $country, $this->countries, true );
}
}

View file

@ -1,4 +1,9 @@
<?php
/**
* Helper class to determine how to proceed with an order depending on the 3d secure feedback.
*
* @package Inpsyde\PayPalCommerce\Button\Helper
*/
declare(strict_types=1);
@ -7,6 +12,9 @@ namespace Inpsyde\PayPalCommerce\Button\Helper;
use Inpsyde\PayPalCommerce\ApiClient\Entity\CardAuthenticationResult as AuthResult;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
/**
* Class ThreeDSecure
*/
class ThreeDSecure {
@ -19,10 +27,12 @@ class ThreeDSecure {
* Determine, how we proceed with a given order.
*
* @link https://developer.paypal.com/docs/business/checkout/add-capabilities/3d-secure/#authenticationresult
* @param Order $order
*
* @param Order $order The order for which the decission is needed.
*
* @return int
*/
public function proceedWithOrder( Order $order ): int {
public function proceed_with_order( Order $order ): int {
if ( ! $order->paymentSource() ) {
return self::NO_DECISION;
}
@ -41,15 +51,19 @@ class ThreeDSecure {
return self::RETRY;
}
if ( $result->liabilityShift() === AuthResult::LIABILITY_SHIFT_NO ) {
return $this->noLiabilityShift( $result );
return $this->no_liability_shift( $result );
}
return self::NO_DECISION;
}
/**
* Determines how to proceed depending on the Liability Shift.
*
* @param AuthResult $result The AuthResult object based on which we make the decision.
*
* @return int
*/
private function noLiabilityShift( AuthResult $result ): int {
private function no_liability_shift( AuthResult $result ): int {
if (
$result->enrollmentStatus() === AuthResult::ENROLLMENT_STATUS_BYPASS

View file

@ -1,4 +1,9 @@
<?php
/**
* The button module.
*
* @package Inpsyde\PayPalCommerce\Button
*/
declare(strict_types=1);
@ -6,19 +11,26 @@ namespace Inpsyde\PayPalCommerce\Button;
use Dhii\Container\ServiceProvider;
use Dhii\Modular\Module\ModuleInterface;
use Inpsyde\PayPalCommerce\Button\Assets\SmartButton;
use Inpsyde\PayPalCommerce\Button\Assets\SmartButtonInterface;
use Inpsyde\PayPalCommerce\Button\Endpoint\ApproveOrderEndpoint;
use Inpsyde\PayPalCommerce\Button\Endpoint\ChangeCartEndpoint;
use Inpsyde\PayPalCommerce\Button\Endpoint\CreateOrderEndpoint;
use Inpsyde\PayPalCommerce\Button\Endpoint\DataClientIdEndpoint;
use Inpsyde\PayPalCommerce\Button\Endpoint\RequestData;
use Inpsyde\PayPalCommerce\Button\Helper\EarlyOrderHandler;
use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface;
/**
* Class ButtonModule
*/
class ButtonModule implements ModuleInterface {
/**
* Sets up the module.
*
* @return ServiceProviderInterface
*/
public function setup(): ServiceProviderInterface {
return new ServiceProvider(
require __DIR__ . '/../services.php',
@ -27,57 +39,76 @@ class ButtonModule implements ModuleInterface {
}
/**
* @inheritDoc
* Runs the module.
*
* @param ContainerInterface $container The Container.
*/
public function run( ContainerInterface $container ) {
/**
* @var SmartButton $smartButton
*/
add_action(
'wp',
static function () use ( $container ) {
if ( is_admin() ) {
return;
}
$smartButton = $container->get( 'button.smart-button' );
$smartButton->renderWrapper();
$smart_button = $container->get( 'button.smart-button' );
/**
* The Smart Button.
*
* @var SmartButtonInterface $smart_button
*/
$smart_button->render_wrapper();
}
);
add_action(
'wp_enqueue_scripts',
static function () use ( $container ) {
$smartButton = $container->get( 'button.smart-button' );
$smartButton->enqueue();
$smart_button = $container->get( 'button.smart-button' );
/**
* The Smart Button.
*
* @var SmartButtonInterface $smart_button
*/
$smart_button->enqueue();
}
);
add_filter(
'woocommerce_create_order',
static function ( $value ) use ( $container ) {
$earlyOrderHelper = $container->get( 'button.helper.early-order-handler' );
$early_order_handler = $container->get( 'button.helper.early-order-handler' );
if ( ! is_null( $value ) ) {
$value = (int) $value;
}
/**
* @var EarlyOrderHandler $earlyOrderHelper
* The Early Order Handler
*
* @var EarlyOrderHandler $early_order_handler
*/
return $earlyOrderHelper->determineWcOrderId( $value );
return $early_order_handler->determine_wc_order_id( $value );
}
);
$this->registerAjaxEndpoints( $container );
$this->register_ajax_endpoints( $container );
}
private function registerAjaxEndpoints( ContainerInterface $container ) {
/**
* Registers the Ajax Endpoints.
*
* @param ContainerInterface $container The Container.
*/
private function register_ajax_endpoints( ContainerInterface $container ) {
add_action(
'wc_ajax_' . DataClientIdEndpoint::ENDPOINT,
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.data-client-id' );
/**
* The Data Client ID Endpoint.
*
* @var DataClientIdEndpoint $endpoint
*/
$endpoint->handleRequest();
$endpoint->handle_request();
}
);
@ -86,9 +117,11 @@ class ButtonModule implements ModuleInterface {
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.change-cart' );
/**
* The Change Cart Endpoint.
*
* @var ChangeCartEndpoint $endpoint
*/
$endpoint->handleRequest();
$endpoint->handle_request();
}
);
@ -97,9 +130,11 @@ class ButtonModule implements ModuleInterface {
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.approve-order' );
/**
* The Approve Order Endpoint.
*
* @var ApproveOrderEndpoint $endpoint
*/
$endpoint->handleRequest();
$endpoint->handle_request();
}
);
@ -108,9 +143,11 @@ class ButtonModule implements ModuleInterface {
static function () use ( $container ) {
$endpoint = $container->get( 'button.endpoint.create-order' );
/**
* The Create Order Endpoint.
*
* @var CreateOrderEndpoint $endpoint
*/
$endpoint->handleRequest();
$endpoint->handle_request();
}
);
}

View file

@ -99,10 +99,10 @@ class LoginSellerEndpoint implements EndpointInterface {
* @return bool
* @throws \Psr\SimpleCache\InvalidArgumentException When a cache item was not found.
*/
public function handleRequest(): bool {
public function handle_request(): bool {
try {
$data = $this->request_data->readRequest( $this->nonce() );
$data = $this->request_data->read_request( $this->nonce() );
$credentials = $this->login_seller_endpoint->credentialsFor(
$data['sharedId'],
$data['authCode'],

View file

@ -96,7 +96,7 @@ class OnboardingModule implements ModuleInterface {
*
* @var ChangeCartEndpoint $endpoint
*/
$endpoint->handleRequest();
$endpoint->handle_request();
}
);
}

View file

@ -260,7 +260,7 @@ class OrderProcessor {
}
$is_approved = in_array(
$this->threed_secure->proceedWithOrder( $order ),
$this->threed_secure->proceed_with_order( $order ),
array(
ThreeDSecure::NO_DECISION,
ThreeDSecure::PROCCEED,

View file

@ -236,7 +236,7 @@ class SettingsRenderer {
}
if (
in_array( 'messages', $config['requirements'], true )
&& ! $this->messages_apply->forCountry()
&& ! $this->messages_apply->for_country()
) {
continue;
}