code styles for the wc gateway module

This commit is contained in:
David Remer 2020-08-28 08:13:45 +03:00
parent 7fcda592f7
commit cf0b5b0e25
33 changed files with 3453 additions and 2620 deletions

View file

@ -108,6 +108,6 @@ class EarlyOrderHandler {
/** /**
* Patch Order so we have the \WC_Order id added. * Patch Order so we have the \WC_Order id added.
*/ */
return $this->orderProcessor->patchOrder( $wcOrder, $order ); return $this->orderProcessor->patch_order( $wcOrder, $order );
} }
} }

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The extensions of the gateway module.
*
* @package Inpsyde\PayPalCommerce\WcGateway
*/
declare(strict_types=1); declare(strict_types=1);
@ -12,72 +17,76 @@ use Inpsyde\Woocommerce\Logging\Logger\WooCommerceLogger;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
return [ return array(
'api.merchant_email' => static function (ContainerInterface $container): string { 'api.merchant_email' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
return $settings->has('merchant_email') ? (string) $settings->get('merchant_email') : ''; return $settings->has( 'merchant_email' ) ? (string) $settings->get( 'merchant_email' ) : '';
}, },
'api.merchant_id' => static function (ContainerInterface $container): string { 'api.merchant_id' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
return $settings->has('merchant_id') ? (string) $settings->get('merchant_id') : ''; return $settings->has( 'merchant_id' ) ? (string) $settings->get( 'merchant_id' ) : '';
}, },
'api.partner_merchant_id' => static function (): string { 'api.partner_merchant_id' => static function (): string {
// ToDo: Replace with the real merchant id of platform // @ToDo: Replace with the real merchant id of platform
return 'KQ8FCM66JFGDL'; return 'KQ8FCM66JFGDL';
}, },
'api.key' => static function (ContainerInterface $container): string { 'api.key' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
$key = $settings->has('client_id') ? (string) $settings->get('client_id') : ''; $key = $settings->has( 'client_id' ) ? (string) $settings->get( 'client_id' ) : '';
return $key; return $key;
}, },
'api.secret' => static function (ContainerInterface $container): string { 'api.secret' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
return $settings->has('client_secret') ? (string) $settings->get('client_secret') : ''; return $settings->has( 'client_secret' ) ? (string) $settings->get( 'client_secret' ) : '';
}, },
'api.prefix' => static function (ContainerInterface $container): string { 'api.prefix' => static function ( ContainerInterface $container ): string {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
return $settings->has('prefix') ? (string) $settings->get('prefix') : 'WC-'; return $settings->has( 'prefix' ) ? (string) $settings->get( 'prefix' ) : 'WC-';
}, },
'api.endpoint.order' => static function (ContainerInterface $container): OrderEndpoint { 'api.endpoint.order' => static function ( ContainerInterface $container ): OrderEndpoint {
$orderFactory = $container->get('api.factory.order'); $order_factory = $container->get( 'api.factory.order' );
$patchCollectionFactory = $container->get('api.factory.patch-collection-factory'); $patch_collection_factory = $container->get( 'api.factory.patch-collection-factory' );
$logger = $container->get('woocommerce.logger.woocommerce'); $logger = $container->get( 'woocommerce.logger.woocommerce' );
/** /**
* @var SessionHandler $sessionHandler * The session handler.
*
* @var SessionHandler $session_handler
*/ */
$sessionHandler = $container->get('session.handler'); $session_handler = $container->get( 'session.handler' );
$bnCode = $sessionHandler->bnCode(); $bn_code = $session_handler->bnCode();
/** /**
* The settings.
*
* @var Settings $settings * @var Settings $settings
*/ */
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
$intent = $settings->has('intent') && strtoupper((string) $settings->get('intent')) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE'; $intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
$applicationContextRepository = $container->get('api.repository.application-context'); $application_context_repository = $container->get( 'api.repository.application-context' );
$paypalRequestId = $container->get('api.repository.paypal-request-id'); $pay_pal_request_id_repository = $container->get( 'api.repository.paypal-request-id' );
return new OrderEndpoint( return new OrderEndpoint(
$container->get('api.host'), $container->get( 'api.host' ),
$container->get('api.bearer'), $container->get( 'api.bearer' ),
$orderFactory, $order_factory,
$patchCollectionFactory, $patch_collection_factory,
$intent, $intent,
$logger, $logger,
$applicationContextRepository, $application_context_repository,
$paypalRequestId, $pay_pal_request_id_repository,
$bnCode $bn_code
); );
}, },
'woocommerce.logger.woocommerce' => function (ContainerInterface $container): LoggerInterface { 'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface {
$settings = $container->get('wcgateway.settings'); $settings = $container->get( 'wcgateway.settings' );
if (! function_exists('wc_get_logger') || ! $settings->has('logging_enabled') || ! $settings->get('logging_enabled')) { if ( ! function_exists( 'wc_get_logger' ) || ! $settings->has( 'logging_enabled' ) || ! $settings->get( 'logging_enabled' ) ) {
return new NullLogger(); return new NullLogger();
} }
$source = $container->get('woocommerce.logger.source'); $source = $container->get( 'woocommerce.logger.source' );
return new WooCommerceLogger( return new WooCommerceLogger(
wc_get_logger(), wc_get_logger(),
$source $source
); );
}, },
]; );

View file

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

File diff suppressed because it is too large Load diff

View file

@ -1,87 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Admin;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
class OrderTablePaymentStatusColumn {
private const COLUMN_KEY = 'ppcp_payment_status';
private const INTENT = 'authorize';
private const AFTER_COLUMN_KEY = 'order_status';
private $settings;
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
public function register( array $columns ): array {
if ( ! $this->settings->has( 'intent' ) || $this->settings->get( 'intent' ) !== self::INTENT ) {
return $columns;
}
$statusColumnPosition = array_search( self::AFTER_COLUMN_KEY, array_keys( $columns ), true );
$toInsertPosition = false === $statusColumnPosition ? count( $columns ) : $statusColumnPosition + 1;
$columns = array_merge(
array_slice( $columns, 0, $toInsertPosition ),
array(
self::COLUMN_KEY => __( 'Payment Captured', 'woocommerce-paypal-commerce-gateway' ),
),
array_slice( $columns, $toInsertPosition )
);
return $columns;
}
public function render( string $column, int $wcOrderId ) {
if ( ! $this->settings->has( 'intent' ) || $this->settings->get( 'intent' ) !== self::INTENT ) {
return;
}
if ( self::COLUMN_KEY !== $column ) {
return;
}
$wcOrder = wc_get_order( $wcOrderId );
if ( ! is_a( $wcOrder, \WC_Order::class ) || ! $this->renderForOrder( $wcOrder ) ) {
return;
}
if ( $this->isCaptured( $wcOrder ) ) {
$this->renderCompletedStatus();
return;
}
$this->renderIncompletedStatus();
}
private function renderForOrder( \WC_Order $order ): bool {
return ! empty( $order->get_meta( PayPalGateway::CAPTURED_META_KEY ) );
}
private function isCaptured( \WC_Order $wcOrder ): bool {
$captured = $wcOrder->get_meta( PayPalGateway::CAPTURED_META_KEY );
return wc_string_to_bool( $captured );
}
private function renderCompletedStatus() {
printf(
'<span class="dashicons dashicons-yes">
<span class="screen-reader-text">%s</span>
</span>',
esc_html__( 'Payment captured', 'woocommerce-paypal-commerce-gateway' )
);
}
private function renderIncompletedStatus() {
printf(
'<mark class="onbackorder">%s</mark>',
esc_html__( 'Not captured', 'woocommerce-paypal-commerce-gateway' )
);
}
}

View file

@ -0,0 +1,140 @@
<?php
/**
* Renders the columns to display to the merchant, which orders have been authorized and
* which have not been authorized yet.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Admin
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Admin;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class OrderTablePaymentStatusColumn
*/
class OrderTablePaymentStatusColumn {
private const COLUMN_KEY = 'ppcp_payment_status';
private const INTENT = 'authorize';
private const AFTER_COLUMN_KEY = 'order_status';
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* OrderTablePaymentStatusColumn constructor.
*
* @param Settings $settings The Settings.
*/
public function __construct( Settings $settings ) {
$this->settings = $settings;
}
/**
* Register the columns.
*
* @param array $columns The existing columns.
*
* @return array
*/
public function register( array $columns ): array {
if ( ! $this->settings->has( 'intent' ) || $this->settings->get( 'intent' ) !== self::INTENT ) {
return $columns;
}
$status_column_position = array_search( self::AFTER_COLUMN_KEY, array_keys( $columns ), true );
$to_insert_position = false === $status_column_position ? count( $columns ) : $status_column_position + 1;
$columns = array_merge(
array_slice( $columns, 0, $to_insert_position ),
array(
self::COLUMN_KEY => __( 'Payment Captured', 'woocommerce-paypal-commerce-gateway' ),
),
array_slice( $columns, $to_insert_position )
);
return $columns;
}
/**
* Render the column.
*
* @param string $column The column.
* @param int $wc_order_id The id or the Woocommerce order.
*/
public function render( string $column, int $wc_order_id ) {
if ( ! $this->settings->has( 'intent' ) || $this->settings->get( 'intent' ) !== self::INTENT ) {
return;
}
if ( self::COLUMN_KEY !== $column ) {
return;
}
$wc_order = wc_get_order( $wc_order_id );
if ( ! is_a( $wc_order, \WC_Order::class ) || ! $this->render_for_order( $wc_order ) ) {
return;
}
if ( $this->is_captured( $wc_order ) ) {
$this->render_completed_status();
return;
}
$this->render_incomplete_status();
}
/**
* Whether to render the authorization status of an order or not.
*
* @param \WC_Order $order The Woocommerce order.
*
* @return bool
*/
private function render_for_order( \WC_Order $order ): bool {
return ! empty( $order->get_meta( PayPalGateway::CAPTURED_META_KEY ) );
}
/**
* Whether the order has been captured or not.
*
* @param \WC_Order $wc_order The Woocommerce order.
*
* @return bool
*/
private function is_captured( \WC_Order $wc_order ): bool {
$captured = $wc_order->get_meta( PayPalGateway::CAPTURED_META_KEY );
return wc_string_to_bool( $captured );
}
/**
* Renders the captured status.
*/
private function render_completed_status() {
printf(
'<span class="dashicons dashicons-yes">
<span class="screen-reader-text">%s</span>
</span>',
esc_html__( 'Payment captured', 'woocommerce-paypal-commerce-gateway' )
);
}
/**
* Renders the "not captured" status.
*/
private function render_incomplete_status() {
printf(
'<mark class="onbackorder">%s</mark>',
esc_html__( 'Not captured', 'woocommerce-paypal-commerce-gateway' )
);
}
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Renders the not captured information.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Admin
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,12 +11,20 @@ namespace Inpsyde\PayPalCommerce\WcGateway\Admin;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway; use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class PaymentStatusOrderDetail
*/
class PaymentStatusOrderDetail { class PaymentStatusOrderDetail {
public function render( int $wcOrderId ) { /**
$wcOrder = new \WC_Order( $wcOrderId ); * Renders the not captured information.
$intent = $wcOrder->get_meta( PayPalGateway::INTENT_META_KEY ); *
$captured = $wcOrder->get_meta( PayPalGateway::CAPTURED_META_KEY ); * @param int $wc_order_id The Woocommerce order id.
*/
public function render( int $wc_order_id ) {
$wc_order = new \WC_Order( $wc_order_id );
$intent = $wc_order->get_meta( PayPalGateway::INTENT_META_KEY );
$captured = $wc_order->get_meta( PayPalGateway::CAPTURED_META_KEY );
if ( strcasecmp( $intent, 'AUTHORIZE' ) !== 0 ) { if ( strcasecmp( $intent, 'AUTHORIZE' ) !== 0 ) {
return; return;
@ -31,7 +44,7 @@ class PaymentStatusOrderDetail {
esc_html__( esc_html__(
'To capture the payment select capture action from the list below.', 'To capture the payment select capture action from the list below.',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), )
); );
} }
} }

View file

@ -1,124 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Checkout;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Shipping;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
/**
* Service that fills checkout address fields
* with address selected via PayPal
*/
class CheckoutPayPalAddressPreset {
private $shippingCache = array();
/**
* @var SessionHandler
*/
private $sessionHandler;
/**
* @param SessionHandler $sessionHandler
*/
public function __construct( SessionHandler $sessionHandler ) {
$this->sessionHandler = $sessionHandler;
}
/**
* @wp-hook woocommerce_checkout_get_value
* @param string|null
* @param string $fieldId
*
* @return string|null
*/
public function filterCheckoutFiled( $defaultValue, $fieldId ): ?string {
if ( ! is_string( $defaultValue ) ) {
$defaultValue = null;
}
if ( ! is_string( $fieldId ) ) {
return $defaultValue;
}
return $this->readPresetForField( $fieldId ) ?? $defaultValue;
}
private function readPresetForField( string $fieldId ): ?string {
$order = $this->sessionHandler->order();
if ( ! $order ) {
return null;
}
$shipping = $this->readShippingFromOrder();
$payer = $order->payer();
$addressMap = array(
'billing_address_1' => 'addressLine1',
'billing_address_2' => 'addressLine2',
'billing_postcode' => 'postalCode',
'billing_country' => 'countryCode',
'billing_city' => 'adminArea2',
'billing_state' => 'adminArea1',
);
$payerNameMap = array(
'billing_last_name' => 'surname',
'billing_first_name' => 'givenName',
);
$payerMap = array(
'billing_email' => 'emailAddress',
);
$payerPhoneMap = array(
'billing_phone' => 'nationalNumber',
);
if ( array_key_exists( $fieldId, $addressMap ) && $shipping ) {
return $shipping->address()->{$addressMap[ $fieldId ]}() ?: null;
}
if ( array_key_exists( $fieldId, $payerNameMap ) && $payer ) {
return $payer->name()->{$payerNameMap[ $fieldId ]}() ?: null;
}
if ( array_key_exists( $fieldId, $payerMap ) && $payer ) {
return $payer->{$payerMap[ $fieldId ]}() ?: null;
}
if (
array_key_exists( $fieldId, $payerPhoneMap )
&& $payer
&& $payer->phone()
&& $payer->phone()->phone()
) {
return $payer->phone()->phone()->{$payerPhoneMap[ $fieldId ]}() ?: null;
}
return null;
}
private function readShippingFromOrder(): ?Shipping {
$order = $this->sessionHandler->order();
if ( ! $order ) {
return null;
}
if ( array_key_exists( $order->id(), $this->shippingCache ) ) {
return $this->shippingCache[ $order->id() ];
}
$shipping = null;
foreach ( $this->sessionHandler->order()->purchaseUnits() as $unit ) {
$shipping = $unit->shipping();
if ( $shipping ) {
break;
}
}
$this->shippingCache[ $order->id() ] = $shipping;
return $shipping;
}
}

View file

@ -1,67 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Checkout;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Container\ContainerInterface;
class DisableGateways {
private $sessionHandler;
private $settings;
public function __construct(
SessionHandler $sessionHandler,
ContainerInterface $settings
) {
$this->sessionHandler = $sessionHandler;
$this->settings = $settings;
}
public function handler( array $methods ): array {
if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) {
return $methods;
}
if (
! $this->settings->has( 'merchant_email' )
|| ! is_email( $this->settings->get( 'merchant_email' ) )
) {
unset( $methods[ PayPalGateway::ID ] );
unset( $methods[ CreditCardGateway::ID ] );
return $methods;
}
if ( ! $this->settings->has( 'client_id' ) || empty( $this->settings->get( 'client_id' ) ) ) {
unset( $methods[ CreditCardGateway::ID ] );
}
if ( ! $this->needsToDisableGateways() ) {
return $methods;
}
if ( $this->isCreditCard() ) {
return array( CreditCardGateway::ID => $methods[ CreditCardGateway::ID ] );
}
return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] );
}
private function needsToDisableGateways(): bool {
return $this->sessionHandler->order() !== null;
}
private function isCreditCard(): bool {
$order = $this->sessionHandler->order();
if ( ! $order ) {
return false;
}
if ( ! $order->paymentSource() || ! $order->paymentSource()->card() ) {
return false;
}
return true;
}
}

View file

@ -0,0 +1,152 @@
<?php
/**
* Service that fills checkout address fields
* with address selected via PayPal
*
* @package Inpsyde\PayPalCommerce\WcGateway\Checkout
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Checkout;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Shipping;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
/**
* Class CheckoutPayPalAddressPreset
*/
class CheckoutPayPalAddressPreset {
/**
* Caches Shipping objects for orders.
*
* @var array
*/
private $shipping_cache = array();
/**
* The Session Handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* CheckoutPayPalAddressPreset constructor.
*
* @param SessionHandler $session_handler The session handler.
*/
public function __construct( SessionHandler $session_handler ) {
$this->session_handler = $session_handler;
}
/**
* Filters the checkout fields to replace values if necessary.
*
* @wp-hook woocommerce_checkout_get_value
*
* @param string|null $default_value The default value.
* @param string $field_id The field ID.
*
* @return string|null
*/
public function filter_checkout_field( $default_value, $field_id ): ?string {
if ( ! is_string( $default_value ) ) {
$default_value = null;
}
if ( ! is_string( $field_id ) ) {
return $default_value;
}
return $this->read_preset_for_field( $field_id ) ?? $default_value;
}
/**
* Returns the value for a checkout field from an PayPal order if given.
*
* @param string $field_id The ID of the field.
*
* @return string|null
*/
private function read_preset_for_field( string $field_id ): ?string {
$order = $this->session_handler->order();
if ( ! $order ) {
return null;
}
$shipping = $this->read_shipping_from_order();
$payer = $order->payer();
$address_map = array(
'billing_address_1' => 'addressLine1',
'billing_address_2' => 'addressLine2',
'billing_postcode' => 'postalCode',
'billing_country' => 'countryCode',
'billing_city' => 'adminArea2',
'billing_state' => 'adminArea1',
);
$payer_name_map = array(
'billing_last_name' => 'surname',
'billing_first_name' => 'givenName',
);
$payer_map = array(
'billing_email' => 'emailAddress',
);
$payer_phone_map = array(
'billing_phone' => 'nationalNumber',
);
if ( array_key_exists( $field_id, $address_map ) && $shipping ) {
return $shipping->address()->{$address_map[ $field_id ]}() ? $shipping->address()->{$address_map[ $field_id ]}() : null;
}
if ( array_key_exists( $field_id, $payer_name_map ) && $payer ) {
return $payer->name()->{$payer_name_map[ $field_id ]}() ? $payer->name()->{$payer_name_map[ $field_id ]}() : null;
}
if ( array_key_exists( $field_id, $payer_map ) && $payer ) {
return $payer->{$payer_map[ $field_id ]}() ? $payer->{$payer_map[ $field_id ]}() : null;
}
if (
array_key_exists( $field_id, $payer_phone_map )
&& $payer
&& $payer->phone()
&& $payer->phone()->phone()
) {
return $payer->phone()->phone()->{$payer_phone_map[ $field_id ]}() ? $payer->phone()->phone()->{$payer_phone_map[ $field_id ]}() : null;
}
return null;
}
/**
* Returns the Shipping object for an order, if given.
*
* @return Shipping|null
*/
private function read_shipping_from_order(): ?Shipping {
$order = $this->session_handler->order();
if ( ! $order ) {
return null;
}
if ( array_key_exists( $order->id(), $this->shipping_cache ) ) {
return $this->shipping_cache[ $order->id() ];
}
$shipping = null;
foreach ( $this->session_handler->order()->purchaseUnits() as $unit ) {
$shipping = $unit->shipping();
if ( $shipping ) {
break;
}
}
$this->shipping_cache[ $order->id() ] = $shipping;
return $shipping;
}
}

View file

@ -0,0 +1,112 @@
<?php
/**
* Determines whether specific gateways need to be disabled.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Checkout
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Checkout;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Psr\Container\ContainerInterface;
/**
* Class DisableGateways
*/
class DisableGateways {
/**
* The Session Handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The Settings.
*
* @var ContainerInterface
*/
private $settings;
/**
* DisableGateways constructor.
*
* @param SessionHandler $session_handler The Session Handler.
* @param ContainerInterface $settings The Settings.
*/
public function __construct(
SessionHandler $session_handler,
ContainerInterface $settings
) {
$this->session_handler = $session_handler;
$this->settings = $settings;
}
/**
* Controls the logic for enabling/disabling gateways.
*
* @param array $methods The Gateways.
*
* @return array
*/
public function handler( array $methods ): array {
if ( ! isset( $methods[ PayPalGateway::ID ] ) && ! isset( $methods[ CreditCardGateway::ID ] ) ) {
return $methods;
}
if (
! $this->settings->has( 'merchant_email' )
|| ! is_email( $this->settings->get( 'merchant_email' ) )
) {
unset( $methods[ PayPalGateway::ID ] );
unset( $methods[ CreditCardGateway::ID ] );
return $methods;
}
if ( ! $this->settings->has( 'client_id' ) || empty( $this->settings->get( 'client_id' ) ) ) {
unset( $methods[ CreditCardGateway::ID ] );
}
if ( ! $this->needs_to_disable_gateways() ) {
return $methods;
}
if ( $this->is_credit_card() ) {
return array( CreditCardGateway::ID => $methods[ CreditCardGateway::ID ] );
}
return array( PayPalGateway::ID => $methods[ PayPalGateway::ID ] );
}
/**
* Whether the Gateways need to be disabled. When we come to the checkout with a running PayPal
* session, we need to disable the other Gateways, so the customer can smoothly sail through the
* process.
*
* @return bool
*/
private function needs_to_disable_gateways(): bool {
return $this->session_handler->order() !== null;
}
/**
* Whether the current PayPal session is done via DCC payment.
*
* @return bool
*/
private function is_credit_card(): bool {
$order = $this->session_handler->order();
if ( ! $order ) {
return false;
}
if ( ! $order->paymentSource() || ! $order->paymentSource()->card() ) {
return false;
}
return true;
}
}

View file

@ -1,52 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PrefixTrait;
class ReturnUrlEndpoint {
use PrefixTrait;
public const ENDPOINT = 'ppc-return-url';
private $gateway;
private $orderEndpoint;
public function __construct( PayPalGateway $gateway, OrderEndpoint $orderEndpoint, string $prefix ) {
$this->gateway = $gateway;
$this->orderEndpoint = $orderEndpoint;
$this->prefix = $prefix;
}
public function handleRequest() {
if ( ! isset( $_GET['token'] ) ) {
exit;
}
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
$order = $this->orderEndpoint->order( $token );
if ( ! $order ) {
exit;
}
$wcOrderId = $this->sanitize_custom_id( $order->purchaseUnits()[0]->customId() );
if ( ! $wcOrderId ) {
exit;
}
$wcOrder = wc_get_order( $wcOrderId );
if ( ! $wcOrder ) {
exit;
}
$success = $this->gateway->process_payment( $wcOrderId );
if ( isset( $success['result'] ) && $success['result'] === 'success' ) {
wp_redirect( $success['redirect'] );
exit;
}
wp_redirect( wc_get_checkout_url() );
exit;
}
}

View file

@ -0,0 +1,86 @@
<?php
/**
* Controls the endpoint for customers returning from PayPal.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Endpoint
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Endpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\Webhooks\Handler\PrefixTrait;
/**
* Class ReturnUrlEndpoint
*/
class ReturnUrlEndpoint {
use PrefixTrait;
public const ENDPOINT = 'ppc-return-url';
/**
* The PayPal Gateway.
*
* @var PayPalGateway
*/
private $gateway;
/**
* The Order Endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* ReturnUrlEndpoint constructor.
*
* @param PayPalGateway $gateway The PayPal Gateway.
* @param OrderEndpoint $order_endpoint The Order Endpoint.
* @param string $prefix The prefix.
*/
public function __construct( PayPalGateway $gateway, OrderEndpoint $order_endpoint, string $prefix ) {
$this->gateway = $gateway;
$this->order_endpoint = $order_endpoint;
$this->prefix = $prefix;
}
/**
* Handles the incoming request.
*/
public function handle_request() {
// phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( ! isset( $_GET['token'] ) ) {
exit();
}
$token = sanitize_text_field( wp_unslash( $_GET['token'] ) );
// phpcs:enable WordPress.Security.NonceVerification.Recommended
$order = $this->order_endpoint->order( $token );
if ( ! $order ) {
exit();
}
$wc_order_id = $this->sanitize_custom_id( $order->purchaseUnits()[0]->customId() );
if ( ! $wc_order_id ) {
exit();
}
$wc_order = wc_get_order( $wc_order_id );
if ( ! $wc_order ) {
exit();
}
$success = $this->gateway->process_payment( $wc_order_id );
if ( isset( $success['result'] ) && 'success' === $success['result'] ) {
wp_safe_redirect( $success['redirect'] );
exit();
}
wp_safe_redirect( wc_get_checkout_url() );
exit();
}
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The Not Found Exception for the Settings Container.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Exception
*/
declare(strict_types=1); declare(strict_types=1);
@ -7,6 +12,9 @@ namespace Inpsyde\PayPalCommerce\WcGateway\Exception;
use Exception; use Exception;
use Psr\Container\NotFoundExceptionInterface; use Psr\Container\NotFoundExceptionInterface;
/**
* Class NotFoundException
*/
class NotFoundException extends Exception implements NotFoundExceptionInterface { class NotFoundException extends Exception implements NotFoundExceptionInterface {

View file

@ -1,10 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Gateway;
interface WcGatewayInterface {
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The Credit card gateway.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1); declare(strict_types=1);
@ -11,32 +16,48 @@ use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
//phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /**
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType * Class CreditCardGateway
//phpcs:disable Inpsyde.CodeQuality.NoAccessors.NoGetter */
//phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType
class CreditCardGateway extends PayPalGateway { class CreditCardGateway extends PayPalGateway {
public const ID = 'ppcp-credit-card-gateway'; public const ID = 'ppcp-credit-card-gateway';
private $moduleUrl; /**
* The URL to the module.
*
* @var string
*/
private $module_url;
/**
* CreditCardGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order processor.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments processor.
* @param AuthorizeOrderActionNotice $notice The Notices.
* @param ContainerInterface $config The settings.
* @param string $module_url The URL to the module.
* @param SessionHandler $session_handler The Session Handler.
*/
public function __construct( public function __construct(
SettingsRenderer $settingsRenderer, SettingsRenderer $settings_renderer,
OrderProcessor $orderProcessor, OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorizedPayments, AuthorizedPaymentsProcessor $authorized_payments_processor,
AuthorizeOrderActionNotice $notice, AuthorizeOrderActionNotice $notice,
ContainerInterface $config, ContainerInterface $config,
string $moduleUrl, string $module_url,
SessionHandler $sessionHandler SessionHandler $session_handler
) { ) {
$this->id = self::ID; $this->id = self::ID;
$this->orderProcessor = $orderProcessor; $this->order_processor = $order_processor;
$this->authorizedPayments = $authorizedPayments; $this->authorized_payments = $authorized_payments_processor;
$this->notice = $notice; $this->notice = $notice;
$this->settingsRenderer = $settingsRenderer; $this->settings_renderer = $settings_renderer;
$this->config = $config; $this->config = $config;
$this->sessionHandler = $sessionHandler; $this->session_handler = $session_handler;
if ( if (
defined( 'PPCP_FLAG_SUBSCRIPTION' ) defined( 'PPCP_FLAG_SUBSCRIPTION' )
&& PPCP_FLAG_SUBSCRIPTION && PPCP_FLAG_SUBSCRIPTION
@ -82,9 +103,12 @@ class CreditCardGateway extends PayPalGateway {
) )
); );
$this->moduleUrl = $moduleUrl; $this->module_url = $module_url;
} }
/**
* Initialize the form fields.
*/
public function init_form_fields() { public function init_form_fields() {
$this->form_fields = array( $this->form_fields = array(
'enabled' => array( 'enabled' => array(
@ -99,15 +123,25 @@ class CreditCardGateway extends PayPalGateway {
); );
} }
/**
* Renders the settings.
*
* @return string
*/
public function generate_ppcp_html(): string { public function generate_ppcp_html(): string {
ob_start(); ob_start();
$this->settingsRenderer->render( true ); $this->settings_renderer->render( true );
$content = ob_get_contents(); $content = ob_get_contents();
ob_end_clean(); ob_end_clean();
return $content; return $content;
} }
/**
* Returns the title of the gateway.
*
* @return string
*/
public function get_title() { public function get_title() {
if ( is_admin() ) { if ( is_admin() ) {
@ -119,12 +153,12 @@ class CreditCardGateway extends PayPalGateway {
return $title; return $title;
} }
$titleOptions = $this->cardLabels(); $title_options = $this->card_labels();
$images = array_map( $images = array_map(
function ( string $type ) use ( $titleOptions ): string { function ( string $type ) use ( $title_options ): string {
return '<img return '<img
title="' . esc_attr( $titleOptions[ $type ] ) . '" title="' . esc_attr( $title_options[ $type ] ) . '"
src="' . esc_url( $this->moduleUrl ) . '/assets/images/' . esc_attr( $type ) . '.svg" src="' . esc_url( $this->module_url ) . '/assets/images/' . esc_attr( $type ) . '.svg"
class="ppcp-card-icon" class="ppcp-card-icon"
> '; > ';
}, },
@ -133,7 +167,12 @@ class CreditCardGateway extends PayPalGateway {
return $title . implode( '', $images ); return $title . implode( '', $images );
} }
private function cardLabels(): array { /**
* Returns an array of credit card names.
*
* @return array
*/
private function card_labels(): array {
return array( return array(
'visa' => _x( 'visa' => _x(
'Visa', 'Visa',

View file

@ -1,59 +1,100 @@
<?php <?php
/**
* The PayPal Payment Gateway
*
* @package Inpsyde\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1); declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Gateway; namespace Inpsyde\PayPalCommerce\WcGateway\Gateway;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException; use Inpsyde\PayPalCommerce\ApiClient\Exception\PayPalApiException;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\Button\Assets\SmartButton;
use Inpsyde\PayPalCommerce\Onboarding\Render\OnboardingRenderer;
use Inpsyde\PayPalCommerce\Session\SessionHandler; use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice; use Inpsyde\PayPalCommerce\WcGateway\Notice\AuthorizeOrderActionNotice;
use Inpsyde\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor; use Inpsyde\PayPalCommerce\WcGateway\Processor\AuthorizedPaymentsProcessor;
use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor; use Inpsyde\PayPalCommerce\WcGateway\Processor\OrderProcessor;
use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsFields;
use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsRenderer; use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
//phpcs:disable PSR1.Methods.CamelCapsMethodName.NotCamelCaps /**
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType * Class PayPalGateway
*/
class PayPalGateway extends \WC_Payment_Gateway { class PayPalGateway extends \WC_Payment_Gateway {
public const ID = 'ppcp-gateway'; public const ID = 'ppcp-gateway';
public const CAPTURED_META_KEY = '_ppcp_paypal_captured'; public const CAPTURED_META_KEY = '_ppcp_paypal_captured';
public const INTENT_META_KEY = '_ppcp_paypal_intent'; public const INTENT_META_KEY = '_ppcp_paypal_intent';
public const ORDER_ID_META_KEY = '_ppcp_paypal_order_id'; public const ORDER_ID_META_KEY = '_ppcp_paypal_order_id';
protected $settingsRenderer; /**
protected $authorizedPayments; * The Settings Renderer.
protected $notice; *
protected $orderProcessor; * @var SettingsRenderer
protected $config; */
protected $sessionHandler; protected $settings_renderer;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
protected $authorized_payments;
/**
* The Authorized Order Action Notice.
*
* @var AuthorizeOrderActionNotice
*/
protected $notice;
/**
* The processor for orders.
*
* @var OrderProcessor
*/
protected $order_processor;
/**
* The settings.
*
* @var ContainerInterface
*/
protected $config;
/**
* The Session Handler.
*
* @var SessionHandler
*/
protected $session_handler;
/**
* PayPalGateway constructor.
*
* @param SettingsRenderer $settings_renderer The Settings Renderer.
* @param OrderProcessor $order_processor The Order Processor.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
* @param AuthorizeOrderActionNotice $notice The Order Action Notice object.
* @param ContainerInterface $config The settings.
* @param SessionHandler $session_handler The Session Handler.
*/
public function __construct( public function __construct(
SettingsRenderer $settingsRenderer, SettingsRenderer $settings_renderer,
OrderProcessor $orderProcessor, OrderProcessor $order_processor,
AuthorizedPaymentsProcessor $authorizedPayments, AuthorizedPaymentsProcessor $authorized_payments_processor,
AuthorizeOrderActionNotice $notice, AuthorizeOrderActionNotice $notice,
ContainerInterface $config, ContainerInterface $config,
SessionHandler $sessionHandler SessionHandler $session_handler
) { ) {
$this->id = self::ID; $this->id = self::ID;
$this->orderProcessor = $orderProcessor; $this->order_processor = $order_processor;
$this->authorizedPayments = $authorizedPayments; $this->authorized_payments = $authorized_payments_processor;
$this->notice = $notice; $this->notice = $notice;
$this->settingsRenderer = $settingsRenderer; $this->settings_renderer = $settings_renderer;
$this->config = $config; $this->config = $config;
$this->sessionHandler = $sessionHandler; $this->session_handler = $session_handler;
if ( if (
defined( 'PPCP_FLAG_SUBSCRIPTION' ) defined( 'PPCP_FLAG_SUBSCRIPTION' )
&& PPCP_FLAG_SUBSCRIPTION && PPCP_FLAG_SUBSCRIPTION
@ -97,11 +138,19 @@ class PayPalGateway extends \WC_Payment_Gateway {
); );
} }
/**
* Whether the Gateway needs to be setup.
*
* @return bool
*/
public function needs_setup(): bool { public function needs_setup(): bool {
return true; return true;
} }
/**
* Initializes the form fields.
*/
public function init_form_fields() { public function init_form_fields() {
$this->form_fields = array( $this->form_fields = array(
'enabled' => array( 'enabled' => array(
@ -116,10 +165,17 @@ class PayPalGateway extends \WC_Payment_Gateway {
); );
} }
public function process_payment( $orderId ): ?array { /**
* Process a payment for an Woocommerce order.
*
* @param int $order_id The Woocommerce order id.
*
* @return array|null
*/
public function process_payment( $order_id ): ?array {
global $woocommerce; global $woocommerce;
$wcOrder = wc_get_order( $orderId ); $wc_order = wc_get_order( $order_id );
if ( ! is_a( $wcOrder, \WC_Order::class ) ) { if ( ! is_a( $wc_order, \WC_Order::class ) ) {
return null; return null;
} }
@ -127,26 +183,26 @@ class PayPalGateway extends \WC_Payment_Gateway {
* If the WC_Order is payed through the approved webhook. * If the WC_Order is payed through the approved webhook.
*/ */
//phpcs:disable WordPress.Security.NonceVerification.Recommended //phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wcOrder->has_status( 'processing' ) ) { if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) {
return array( return array(
'result' => 'success', 'result' => 'success',
'redirect' => $this->get_return_url( $wcOrder ), 'redirect' => $this->get_return_url( $wc_order ),
); );
} }
//phpcs:enable WordPress.Security.NonceVerification.Recommended //phpcs:enable WordPress.Security.NonceVerification.Recommended
try { try {
if ( $this->orderProcessor->process( $wcOrder, $woocommerce ) ) { if ( $this->order_processor->process( $wc_order, $woocommerce ) ) {
return array( return array(
'result' => 'success', 'result' => 'success',
'redirect' => $this->get_return_url( $wcOrder ), 'redirect' => $this->get_return_url( $wc_order ),
); );
} }
} catch ( PayPalApiException $error ) { } catch ( PayPalApiException $error ) {
if ( $error->hasDetail( 'INSTRUMENT_DECLINED' ) ) { if ( $error->hasDetail( 'INSTRUMENT_DECLINED' ) ) {
$host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ? $host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ?
'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/';
$url = $host . 'checkoutnow?token=' . $this->sessionHandler->order()->id(); $url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id();
return array( return array(
'result' => 'success', 'result' => 'success',
@ -154,7 +210,7 @@ class PayPalGateway extends \WC_Payment_Gateway {
); );
} }
$this->sessionHandler->destroySessionData(); $this->session_handler->destroySessionData();
wc_add_notice( wc_add_notice(
__( __(
'Something went wrong. Please try again.', 'Something went wrong. Please try again.',
@ -167,54 +223,71 @@ class PayPalGateway extends \WC_Payment_Gateway {
return null; return null;
} }
public function captureAuthorizedPayment( \WC_Order $wcOrder ): bool { /**
$isProcessed = $this->authorizedPayments->process( $wcOrder ); * Captures an authorized payment for an Woocommerce order.
$this->renderAuthorizationMessageForStatus( $this->authorizedPayments->lastStatus() ); *
* @param \WC_Order $wc_order The Woocommerce order.
*
* @return bool
*/
public function capture_authorized_payment( \WC_Order $wc_order ): bool {
$is_processed = $this->authorized_payments->process( $wc_order );
$this->render_authorization_message_for_status( $this->authorized_payments->last_status() );
if ( $isProcessed ) { if ( $is_processed ) {
$wcOrder->add_order_note( $wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' ) __( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' )
); );
$wcOrder->set_status( 'processing' ); $wc_order->set_status( 'processing' );
$wcOrder->update_meta_data( self::CAPTURED_META_KEY, 'true' ); $wc_order->update_meta_data( self::CAPTURED_META_KEY, 'true' );
$wcOrder->save(); $wc_order->save();
return true; return true;
} }
if ( $this->authorizedPayments->lastStatus() === AuthorizedPaymentsProcessor::ALREADY_CAPTURED ) { if ( $this->authorized_payments->last_status() === AuthorizedPaymentsProcessor::ALREADY_CAPTURED ) {
if ( $wcOrder->get_status() === 'on-hold' ) { if ( $wc_order->get_status() === 'on-hold' ) {
$wcOrder->add_order_note( $wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' ) __( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' )
); );
$wcOrder->set_status( 'processing' ); $wc_order->set_status( 'processing' );
} }
$wcOrder->update_meta_data( self::CAPTURED_META_KEY, 'true' ); $wc_order->update_meta_data( self::CAPTURED_META_KEY, 'true' );
$wcOrder->save(); $wc_order->save();
return true; return true;
} }
return false; return false;
} }
private function renderAuthorizationMessageForStatus( string $status ) { /**
* Displays the notice for a status.
*
* @param string $status The status.
*/
private function render_authorization_message_for_status( string $status ) {
$messageMapping = array( $message_mapping = array(
AuthorizedPaymentsProcessor::SUCCESSFUL => AuthorizeOrderActionNotice::SUCCESS, AuthorizedPaymentsProcessor::SUCCESSFUL => AuthorizeOrderActionNotice::SUCCESS,
AuthorizedPaymentsProcessor::ALREADY_CAPTURED => AuthorizeOrderActionNotice::ALREADY_CAPTURED, AuthorizedPaymentsProcessor::ALREADY_CAPTURED => AuthorizeOrderActionNotice::ALREADY_CAPTURED,
AuthorizedPaymentsProcessor::INACCESSIBLE => AuthorizeOrderActionNotice::NO_INFO, AuthorizedPaymentsProcessor::INACCESSIBLE => AuthorizeOrderActionNotice::NO_INFO,
AuthorizedPaymentsProcessor::NOT_FOUND => AuthorizeOrderActionNotice::NOT_FOUND, AuthorizedPaymentsProcessor::NOT_FOUND => AuthorizeOrderActionNotice::NOT_FOUND,
); );
$displayMessage = ( isset( $messageMapping[ $status ] ) ) ? $display_message = ( isset( $message_mapping[ $status ] ) ) ?
$messageMapping[ $status ] $message_mapping[ $status ]
: AuthorizeOrderActionNotice::FAILED; : AuthorizeOrderActionNotice::FAILED;
$this->notice->displayMessage( $displayMessage ); $this->notice->display_message( $display_message );
} }
/**
* Renders the settings.
*
* @return string
*/
public function generate_ppcp_html(): string { public function generate_ppcp_html(): string {
ob_start(); ob_start();
$this->settingsRenderer->render( false ); $this->settings_renderer->render( false );
$content = ob_get_contents(); $content = ob_get_contents();
ob_end_clean(); ob_end_clean();
return $content; return $content;

View file

@ -0,0 +1,18 @@
<?php
/**
* The WcGateway interface.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Gateway
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Gateway;
/**
* Interface WcGatewayInterface
*/
interface WcGatewayInterface {
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Contains the messages to display, when capturing an authorization manually.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Notice
*/
declare(strict_types=1); declare(strict_types=1);
@ -6,6 +11,9 @@ namespace Inpsyde\PayPalCommerce\WcGateway\Notice;
use Inpsyde\PayPalCommerce\AdminNotices\Entity\Message; use Inpsyde\PayPalCommerce\AdminNotices\Entity\Message;
/**
* Class AuthorizeOrderActionNotice
*/
class AuthorizeOrderActionNotice { class AuthorizeOrderActionNotice {
public const QUERY_PARAM = 'ppcp-authorized-message'; public const QUERY_PARAM = 'ppcp-authorized-message';
@ -16,9 +24,14 @@ class AuthorizeOrderActionNotice {
public const SUCCESS = 84; public const SUCCESS = 84;
public const NOT_FOUND = 85; public const NOT_FOUND = 85;
/**
* Returns the current message if there is one.
*
* @return Message|null
*/
public function message(): ?Message { public function message(): ?Message {
$message = $this->currentMessage(); $message = $this->current_message();
if ( ! $message ) { if ( ! $message ) {
return null; return null;
} }
@ -26,7 +39,12 @@ class AuthorizeOrderActionNotice {
return new Message( $message['message'], $message['type'] ); return new Message( $message['message'], $message['type'] );
} }
private function currentMessage(): array { /**
* Returns the current message.
*
* @return array
*/
private function current_message(): array {
$messages[ self::NO_INFO ] = array( $messages[ self::NO_INFO ] = array(
'message' => __( 'message' => __(
'Could not retrieve information. Try again later.', 'Could not retrieve information. Try again later.',
@ -67,18 +85,23 @@ class AuthorizeOrderActionNotice {
if ( ! isset( $_GET[ self::QUERY_PARAM ] ) ) { // Input ok. if ( ! isset( $_GET[ self::QUERY_PARAM ] ) ) { // Input ok.
return array(); return array();
} }
$messageId = absint( $_GET[ self::QUERY_PARAM ] ); // Input ok. $message_id = absint( $_GET[ self::QUERY_PARAM ] ); // Input ok.
//phpcs:enable WordPress.Security.NonceVerification.Recommended //phpcs:enable WordPress.Security.NonceVerification.Recommended
return ( isset( $messages[ $messageId ] ) ) ? $messages[ $messageId ] : array(); return ( isset( $messages[ $message_id ] ) ) ? $messages[ $message_id ] : array();
} }
public function displayMessage( int $messageCode ): void { /**
* Adds the query parameter for the message to 'redirect_post_location'.
*
* @param int $message_code The message code.
*/
public function display_message( int $message_code ): void {
add_filter( add_filter(
'redirect_post_location', 'redirect_post_location',
static function ( $location ) use ( $messageCode ) { static function ( $location ) use ( $message_code ) {
return add_query_arg( return add_query_arg(
self::QUERY_PARAM, self::QUERY_PARAM,
$messageCode, $message_code,
$location $location
); );
} }

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Registers the admin message to "connect your account" if necessary.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Notice
*/
declare(strict_types=1); declare(strict_types=1);
@ -9,35 +14,63 @@ use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings; use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/**
* Class ConnectAdminNotice
*/
class ConnectAdminNotice { class ConnectAdminNotice {
/**
* The state.
*
* @var State
*/
private $state; private $state;
/**
* The settings.
*
* @var ContainerInterface
*/
private $settings; private $settings;
/**
* ConnectAdminNotice constructor.
*
* @param State $state The state.
* @param ContainerInterface $settings The settings.
*/
public function __construct( State $state, ContainerInterface $settings ) { public function __construct( State $state, ContainerInterface $settings ) {
$this->state = $state; $this->state = $state;
$this->settings = $settings; $this->settings = $settings;
} }
public function connectMessage(): ?Message { /**
if ( ! $this->shouldDisplay() ) { * Returns the message.
*
* @return Message|null
*/
public function connect_message(): ?Message {
if ( ! $this->should_display() ) {
return null; return null;
} }
$message = sprintf( $message = sprintf(
/* translators: %1$s the gateway name */ /* translators: %1$s the gateway name. */
__( __(
'PayPal Payments is almost ready. To get started, <a href="%1$s">connect your account</a>.', 'PayPal Payments is almost ready. To get started, <a href="%1$s">connect your account</a>.',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
), ),
// TODO: find a better way to get the url
admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' ) admin_url( 'admin.php?page=wc-settings&tab=checkout&section=ppcp-gateway' )
); );
return new Message( $message, 'warning' ); return new Message( $message, 'warning' );
} }
protected function shouldDisplay(): bool { /**
// TODO: decide on what condition to display * Whether the message should display.
*
* @return bool
*/
protected function should_display(): bool {
return $this->state->currentState() < State::STATE_PROGRESSIVE; return $this->state->currentState() < State::STATE_PROGRESSIVE;
} }
} }

View file

@ -1,110 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Processor;
use Exception;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
class AuthorizedPaymentsProcessor {
public const SUCCESSFUL = 'SUCCESSFUL';
public const ALREADY_CAPTURED = 'ALREADY_CAPTURED';
public const FAILED = 'FAILED';
public const INACCESSIBLE = 'INACCESSIBLE';
public const NOT_FOUND = 'NOT_FOUND';
private $orderEndpoint;
private $paymentsEndpoint;
private $lastStatus = '';
public function __construct(
OrderEndpoint $orderEndpoint,
PaymentsEndpoint $paymentsEndpoint
) {
$this->orderEndpoint = $orderEndpoint;
$this->paymentsEndpoint = $paymentsEndpoint;
}
public function process( \WC_Order $wcOrder ): bool {
try {
$order = $this->payPalOrderFromWcOrder( $wcOrder );
} catch ( Exception $exception ) {
if ( $exception->getCode() === 404 ) {
$this->lastStatus = self::NOT_FOUND;
return false;
}
$this->lastStatus = self::INACCESSIBLE;
return false;
}
$authorizations = $this->allAuthorizations( $order );
if ( ! $this->areAuthorizationToCapture( ...$authorizations ) ) {
$this->lastStatus = self::ALREADY_CAPTURED;
return false;
}
try {
$this->captureAuthorizations( ...$authorizations );
} catch ( Exception $exception ) {
$this->lastStatus = self::FAILED;
return false;
}
$this->lastStatus = self::SUCCESSFUL;
return true;
}
public function lastStatus(): string {
return $this->lastStatus;
}
private function payPalOrderFromWcOrder( \WC_Order $wcOrder ): Order {
$orderId = $wcOrder->get_meta( PayPalGateway::ORDER_ID_META_KEY );
return $this->orderEndpoint->order( $orderId );
}
private function allAuthorizations( Order $order ): array {
$authorizations = array();
foreach ( $order->purchaseUnits() as $purchaseUnit ) {
foreach ( $purchaseUnit->payments()->authorizations() as $authorization ) {
$authorizations[] = $authorization;
}
}
return $authorizations;
}
private function areAuthorizationToCapture( Authorization ...$authorizations ): bool {
return (bool) count( $this->authorizationsToCapture( ...$authorizations ) );
}
private function captureAuthorizations( Authorization ...$authorizations ) {
$uncapturedAuthorizations = $this->authorizationsToCapture( ...$authorizations );
foreach ( $uncapturedAuthorizations as $authorization ) {
$this->paymentsEndpoint->capture( $authorization->id() );
}
}
/**
* @param Authorization ...$authorizations
* @return Authorization[]
*/
private function authorizationsToCapture( Authorization ...$authorizations ): array {
return array_filter(
$authorizations,
static function ( Authorization $authorization ): bool {
return $authorization->status()->is( AuthorizationStatus::CREATED )
|| $authorization->status()->is( AuthorizationStatus::PENDING );
}
);
}
}

View file

@ -1,165 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Processor;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\Button\Helper\ThreeDSecure;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
class OrderProcessor {
private $sessionHandler;
private $cartRepository;
private $orderEndpoint;
private $paymentsEndpoint;
private $orderFactory;
private $threedSecure;
private $authorizedPaymentsProcessor;
private $settings;
private $lastError = '';
public function __construct(
SessionHandler $sessionHandler,
CartRepository $cartRepository,
OrderEndpoint $orderEndpoint,
PaymentsEndpoint $paymentsEndpoint,
OrderFactory $orderFactory,
ThreeDSecure $threedSecure,
AuthorizedPaymentsProcessor $authorizedPaymentsProcessor,
Settings $settings
) {
$this->sessionHandler = $sessionHandler;
$this->cartRepository = $cartRepository;
$this->orderEndpoint = $orderEndpoint;
$this->paymentsEndpoint = $paymentsEndpoint;
$this->orderFactory = $orderFactory;
$this->threedSecure = $threedSecure;
$this->authorizedPaymentsProcessor = $authorizedPaymentsProcessor;
$this->settings = $settings;
}
public function process( \WC_Order $wcOrder, \WooCommerce $woocommerce ): bool {
$order = $this->sessionHandler->order();
$wcOrder->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wcOrder->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$errorMessage = null;
if ( ! $order || ! $this->orderIsApproved( $order ) ) {
$errorMessage = __(
'The payment has not been approved yet.',
'woocommerce-paypal-commerce-gateway'
);
}
if ( $errorMessage ) {
$this->lastError = sprintf(
// translators: %s is the message of the error.
__( 'Payment error: %s', 'woocommerce-paypal-commerce-gateway' ),
$errorMessage
);
return false;
}
$order = $this->patchOrder( $wcOrder, $order );
if ( $order->intent() === 'CAPTURE' ) {
$order = $this->orderEndpoint->capture( $order );
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->orderEndpoint->authorize( $order );
$wcOrder->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
}
$wcOrder->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-commerce-gateway' )
);
if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'CAPTURE' ) {
$wcOrder->update_status(
'processing',
__( 'Payment received.', 'woocommerce-paypal-commerce-gateway' )
);
}
if ( $this->captureAuthorizedDownloads( $order ) && $this->authorizedPaymentsProcessor->process( $wcOrder ) ) {
$wcOrder->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' )
);
$wcOrder->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'true' );
$wcOrder->update_status( 'processing' );
}
$woocommerce->cart->empty_cart();
$this->sessionHandler->destroySessionData();
$this->lastError = '';
return true;
}
private function captureAuthorizedDownloads( Order $order ): bool {
if (
! $this->settings->has( 'capture_for_virtual_only' )
|| ! $this->settings->get( 'capture_for_virtual_only' )
) {
return false;
}
if ( $order->intent() === 'CAPTURE' ) {
return false;
}
/**
* We fetch the order again as the authorize endpoint (from which the Order derives)
* drops the item's category, making it impossible to check, if purchase units contain
* physical goods.
*/
$order = $this->orderEndpoint->order( $order->id() );
foreach ( $order->purchaseUnits() as $unit ) {
if ( $unit->containsPhysicalGoodsItems() ) {
return false;
}
}
return true;
}
public function lastError(): string {
return $this->lastError;
}
public function patchOrder( \WC_Order $wcOrder, Order $order ): Order {
$updatedOrder = $this->orderFactory->fromWcOrder( $wcOrder, $order );
$order = $this->orderEndpoint->patchOrderWith( $order, $updatedOrder );
return $order;
}
private function orderIsApproved( Order $order ): bool {
if ( $order->status()->is( OrderStatus::APPROVED ) ) {
return true;
}
if ( ! $order->paymentSource() || ! $order->paymentSource()->card() ) {
return false;
}
$isApproved = in_array(
$this->threedSecure->proceedWithOrder( $order ),
array(
ThreeDSecure::NO_DECISION,
ThreeDSecure::PROCCEED,
),
true
);
return $isApproved;
}
}

View file

@ -0,0 +1,182 @@
<?php
/**
* Authorizes payments for a given Woocommerce order.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Processor
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Processor;
use Exception;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Authorization;
use Inpsyde\PayPalCommerce\ApiClient\Entity\AuthorizationStatus;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
/**
* Class AuthorizedPaymentsProcessor
*/
class AuthorizedPaymentsProcessor {
public const SUCCESSFUL = 'SUCCESSFUL';
public const ALREADY_CAPTURED = 'ALREADY_CAPTURED';
public const FAILED = 'FAILED';
public const INACCESSIBLE = 'INACCESSIBLE';
public const NOT_FOUND = 'NOT_FOUND';
/**
* The Order endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The Payments endpoint.
*
* @var PaymentsEndpoint
*/
private $payments_endpoint;
/**
* The last status.
*
* @var string
*/
private $last_status = '';
/**
* AuthorizedPaymentsProcessor constructor.
*
* @param OrderEndpoint $order_endpoint The Order endpoint.
* @param PaymentsEndpoint $payments_endpoint The Payments endpoint.
*/
public function __construct(
OrderEndpoint $order_endpoint,
PaymentsEndpoint $payments_endpoint
) {
$this->order_endpoint = $order_endpoint;
$this->payments_endpoint = $payments_endpoint;
}
/**
* Process a Woocommerce order.
*
* @param \WC_Order $wc_order The Woocommerce order.
*
* @return bool
*/
public function process( \WC_Order $wc_order ): bool {
try {
$order = $this->paypal_order_from_wc_order( $wc_order );
} catch ( Exception $exception ) {
if ( $exception->getCode() === 404 ) {
$this->last_status = self::NOT_FOUND;
return false;
}
$this->last_status = self::INACCESSIBLE;
return false;
}
$authorizations = $this->all_authorizations( $order );
if ( ! $this->are_authorzations_to_capture( ...$authorizations ) ) {
$this->last_status = self::ALREADY_CAPTURED;
return false;
}
try {
$this->capture_authorizations( ...$authorizations );
} catch ( Exception $exception ) {
$this->last_status = self::FAILED;
return false;
}
$this->last_status = self::SUCCESSFUL;
return true;
}
/**
* Returns the last status.
*
* @return string
*/
public function last_status(): string {
return $this->last_status;
}
/**
* Returns the PayPal order from a given Woocommerce order.
*
* @param \WC_Order $wc_order The Woocommerce order.
*
* @return Order
*/
private function paypal_order_from_wc_order( \WC_Order $wc_order ): Order {
$order_id = $wc_order->get_meta( PayPalGateway::ORDER_ID_META_KEY );
return $this->order_endpoint->order( $order_id );
}
/**
* Returns all Authorizations from an order.
*
* @param Order $order The order.
*
* @return array
*/
private function all_authorizations( Order $order ): array {
$authorizations = array();
foreach ( $order->purchaseUnits() as $purchase_unit ) {
foreach ( $purchase_unit->payments()->authorizations() as $authorization ) {
$authorizations[] = $authorization;
}
}
return $authorizations;
}
/**
* Whether Authorizations need to be captured.
*
* @param Authorization ...$authorizations All Authorizations.
*
* @return bool
*/
private function are_authorzations_to_capture( Authorization ...$authorizations ): bool {
return (bool) count( $this->authorizations_to_capture( ...$authorizations ) );
}
/**
* Captures the authorizations.
*
* @param Authorization ...$authorizations All authorizations.
*/
private function capture_authorizations( Authorization ...$authorizations ) {
$uncaptured_authorizations = $this->authorizations_to_capture( ...$authorizations );
foreach ( $uncaptured_authorizations as $authorization ) {
$this->payments_endpoint->capture( $authorization->id() );
}
}
/**
* The authorizations which need to be captured.
*
* @param Authorization ...$authorizations All Authorizations.
* @return Authorization[]
*/
private function authorizations_to_capture( Authorization ...$authorizations ): array {
return array_filter(
$authorizations,
static function ( Authorization $authorization ): bool {
return $authorization->status()->is( AuthorizationStatus::CREATED )
|| $authorization->status()->is( AuthorizationStatus::PENDING );
}
);
}
}

View file

@ -0,0 +1,272 @@
<?php
/**
* Processes orders for the gateways.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Processor
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Processor;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\OrderEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Endpoint\PaymentsEndpoint;
use Inpsyde\PayPalCommerce\ApiClient\Entity\Order;
use Inpsyde\PayPalCommerce\ApiClient\Entity\OrderStatus;
use Inpsyde\PayPalCommerce\ApiClient\Factory\OrderFactory;
use Inpsyde\PayPalCommerce\ApiClient\Repository\CartRepository;
use Inpsyde\PayPalCommerce\Button\Helper\ThreeDSecure;
use Inpsyde\PayPalCommerce\Session\SessionHandler;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\WcGateway\Settings\Settings;
/**
* Class OrderProcessor
*/
class OrderProcessor {
/**
* The Session Handler.
*
* @var SessionHandler
*/
private $session_handler;
/**
* The Cart Repository.
*
* @var CartRepository
*/
private $cart_repository;
/**
* The Order Endpoint.
*
* @var OrderEndpoint
*/
private $order_endpoint;
/**
* The Payments Endpoint.
*
* @var PaymentsEndpoint
*/
private $payments_endpoint;
/**
* The Order Factory.
*
* @var OrderFactory
*/
private $order_factory;
/**
* The helper for 3d secure.
*
* @var ThreeDSecure
*/
private $threed_secure;
/**
* The processor for authorized payments.
*
* @var AuthorizedPaymentsProcessor
*/
private $authorized_payments_processor;
/**
* The settings.
*
* @var Settings
*/
private $settings;
/**
* The last error.
*
* @var string
*/
private $last_error = '';
/**
* OrderProcessor constructor.
*
* @param SessionHandler $session_handler The Session Handler.
* @param CartRepository $cart_repository The Cart Repository.
* @param OrderEndpoint $order_endpoint The Order Endpoint.
* @param PaymentsEndpoint $payments_endpoint The Payments Endpoint.
* @param OrderFactory $order_factory The Order Factory.
* @param ThreeDSecure $three_d_secure The ThreeDSecure Helper.
* @param AuthorizedPaymentsProcessor $authorized_payments_processor The Authorized Payments Processor.
* @param Settings $settings The Settings.
*/
public function __construct(
SessionHandler $session_handler,
CartRepository $cart_repository,
OrderEndpoint $order_endpoint,
PaymentsEndpoint $payments_endpoint,
OrderFactory $order_factory,
ThreeDSecure $three_d_secure,
AuthorizedPaymentsProcessor $authorized_payments_processor,
Settings $settings
) {
$this->session_handler = $session_handler;
$this->cart_repository = $cart_repository;
$this->order_endpoint = $order_endpoint;
$this->payments_endpoint = $payments_endpoint;
$this->order_factory = $order_factory;
$this->threed_secure = $three_d_secure;
$this->authorized_payments_processor = $authorized_payments_processor;
$this->settings = $settings;
}
/**
* Processes a given Woocommerce order and captured/authorizes the connected PayPal orders.
*
* @param \WC_Order $wc_order The Woocommerce order.
* @param \WooCommerce $woocommerce The Woocommerce object.
*
* @return bool
*/
public function process( \WC_Order $wc_order, \WooCommerce $woocommerce ): bool {
$order = $this->session_handler->order();
$wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() );
$wc_order->update_meta_data( PayPalGateway::INTENT_META_KEY, $order->intent() );
$error_message = null;
if ( ! $order || ! $this->order_is_approved( $order ) ) {
$error_message = __(
'The payment has not been approved yet.',
'woocommerce-paypal-commerce-gateway'
);
}
if ( $error_message ) {
$this->last_error = sprintf(
// translators: %s is the message of the error.
__( 'Payment error: %s', 'woocommerce-paypal-commerce-gateway' ),
$error_message
);
return false;
}
$order = $this->patch_order( $wc_order, $order );
if ( $order->intent() === 'CAPTURE' ) {
$order = $this->order_endpoint->capture( $order );
}
if ( $order->intent() === 'AUTHORIZE' ) {
$order = $this->order_endpoint->authorize( $order );
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'false' );
}
$wc_order->update_status(
'on-hold',
__( 'Awaiting payment.', 'woocommerce-paypal-commerce-gateway' )
);
if ( $order->status()->is( OrderStatus::COMPLETED ) && $order->intent() === 'CAPTURE' ) {
$wc_order->update_status(
'processing',
__( 'Payment received.', 'woocommerce-paypal-commerce-gateway' )
);
}
if ( $this->capture_authorized_downloads( $order ) && $this->authorized_payments_processor->process( $wc_order ) ) {
$wc_order->add_order_note(
__( 'Payment successfully captured.', 'woocommerce-paypal-commerce-gateway' )
);
$wc_order->update_meta_data( PayPalGateway::CAPTURED_META_KEY, 'true' );
$wc_order->update_status( 'processing' );
}
$woocommerce->cart->empty_cart();
$this->session_handler->destroySessionData();
$this->last_error = '';
return true;
}
/**
* Returns if an order should be captured immediately.
*
* @param Order $order The PayPal order.
*
* @return bool
*/
private function capture_authorized_downloads( Order $order ): bool {
if (
! $this->settings->has( 'capture_for_virtual_only' )
|| ! $this->settings->get( 'capture_for_virtual_only' )
) {
return false;
}
if ( $order->intent() === 'CAPTURE' ) {
return false;
}
/**
* We fetch the order again as the authorize endpoint (from which the Order derives)
* drops the item's category, making it impossible to check, if purchase units contain
* physical goods.
*/
$order = $this->order_endpoint->order( $order->id() );
foreach ( $order->purchaseUnits() as $unit ) {
if ( $unit->containsPhysicalGoodsItems() ) {
return false;
}
}
return true;
}
/**
* Returns the last error.
*
* @return string
*/
public function last_error(): string {
return $this->last_error;
}
/**
* Patches a given PayPal order with a Woocommerce order.
*
* @param \WC_Order $wc_order The Woocommerce order.
* @param Order $order The PayPal order.
*
* @return Order
*/
public function patch_order( \WC_Order $wc_order, Order $order ): Order {
$updated_order = $this->order_factory->fromWcOrder( $wc_order, $order );
$order = $this->order_endpoint->patchOrderWith( $order, $updated_order );
return $order;
}
/**
* Whether a given order is approved.
*
* @param Order $order The order.
*
* @return bool
*/
private function order_is_approved( Order $order ): bool {
if ( $order->status()->is( OrderStatus::APPROVED ) ) {
return true;
}
if ( ! $order->paymentSource() || ! $order->paymentSource()->card() ) {
return false;
}
$is_approved = in_array(
$this->threed_secure->proceedWithOrder( $order ),
array(
ThreeDSecure::NO_DECISION,
ThreeDSecure::PROCCEED,
),
true
);
return $is_approved;
}
}

View file

@ -1,201 +0,0 @@
<?php
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Settings;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\Webhooks\WebhookRegistrar;
use Psr\SimpleCache\CacheInterface;
class SettingsListener {
public const NONCE = 'ppcp-settings';
private $settings;
private $settingFields;
private $webhookRegistrar;
private $cache;
private $state;
public function __construct(
Settings $settings,
array $settingFields,
WebhookRegistrar $webhookRegistrar,
CacheInterface $cache,
State $state
) {
$this->settings = $settings;
$this->settingFields = $settingFields;
$this->webhookRegistrar = $webhookRegistrar;
$this->cache = $cache;
$this->state = $state;
}
public function listen() {
if ( ! $this->isValidUpdateRequest() ) {
return;
}
/**
* Nonce verification is done in self::isValidUpdateRequest
*/
//phpcs:disable WordPress.Security.NonceVerification.Missing
//phpcs:disable WordPress.Security.NonceVerification.Recommended
if ( isset( $_POST['save'] ) && sanitize_text_field( wp_unslash( $_POST['save'] ) ) === 'reset' ) {
$this->settings->reset();
$this->settings->persist();
$this->webhookRegistrar->unregister();
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY );
}
return;
}
//phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
//phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
/**
* Sanitization is done at a later stage.
*/
$rawData = ( isset( $_POST['ppcp'] ) ) ? (array) wp_unslash( $_POST['ppcp'] ) : array();
$settings = $this->retrieveSettingsFromRawData( $rawData );
if ( $_GET['section'] === PayPalGateway::ID ) {
$settings['enabled'] = isset( $_POST['woocommerce_ppcp-gateway_enabled'] )
&& absint( $_POST['woocommerce_ppcp-gateway_enabled'] ) === 1;
}
if ( $_GET['section'] === CreditCardGateway::ID ) {
$dccEnabledPostKey = 'woocommerce_ppcp-credit-card-gateway_enabled';
$settings['dcc_gateway_enabled'] = isset( $_POST[ $dccEnabledPostKey ] )
&& absint( $_POST[ $dccEnabledPostKey ] ) === 1;
}
$this->maybeRegisterWebhooks( $settings );
foreach ( $settings as $id => $value ) {
$this->settings->set( $id, $value );
}
$this->settings->persist();
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY );
}
//phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotValidated
//phpcs:enable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
//phpcs:enable WordPress.Security.NonceVerification.Missing
}
private function maybeRegisterWebhooks( array $settings ) {
if ( ! $this->settings->has( 'client_id' ) && $settings['client_id'] ) {
$this->webhookRegistrar->register();
}
if ( $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) ) {
$currentSecret = $this->settings->has( 'client_secret' ) ?
$this->settings->get( 'client_secret' ) : '';
if (
$settings['client_id'] !== $this->settings->get( 'client_id' )
|| $settings['client_secret'] !== $currentSecret
) {
$this->webhookRegistrar->unregister();
$this->webhookRegistrar->register();
}
}
}
//phpcs:disable Inpsyde.CodeQuality.NestingLevel.MaxExceeded
//phpcs:disable Generic.Metrics.CyclomaticComplexity.TooHigh
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong
private function retrieveSettingsFromRawData( array $rawData ): array {
if ( ! isset( $_GET['section'] ) ) {
return array();
}
$settings = array();
foreach ( $this->settingFields as $key => $config ) {
if ( ! in_array( $this->state->currentState(), $config['screens'], true ) ) {
continue;
}
if (
$config['gateway'] === 'dcc'
&& sanitize_text_field( wp_unslash( $_GET['section'] ) ) !== 'ppcp-credit-card-gateway'
) {
continue;
}
if (
$config['gateway'] === 'paypal'
&& sanitize_text_field( wp_unslash( $_GET['section'] ) ) !== 'ppcp-gateway'
) {
continue;
}
switch ( $config['type'] ) {
case 'checkbox':
$settings[ $key ] = isset( $rawData[ $key ] );
break;
case 'text':
case 'ppcp-text-input':
case 'ppcp-password':
$settings[ $key ] = isset( $rawData[ $key ] ) ? sanitize_text_field( $rawData[ $key ] ) : '';
break;
case 'password':
if ( empty( $rawData[ $key ] ) ) {
break;
}
$settings[ $key ] = sanitize_text_field( $rawData[ $key ] );
break;
case 'ppcp-multiselect':
$values = isset( $rawData[ $key ] ) ? (array) $rawData[ $key ] : array();
$valuesToSave = array();
foreach ( $values as $index => $rawValue ) {
$value = sanitize_text_field( $rawValue );
if ( ! in_array( $value, array_keys( $config['options'] ), true ) ) {
continue;
}
$valuesToSave[] = $value;
}
$settings[ $key ] = $valuesToSave;
break;
case 'select':
$options = array_keys( $config['options'] );
$settings[ $key ] = isset( $rawData[ $key ] ) && in_array(
sanitize_text_field( $rawData[ $key ] ),
$options,
true
) ? sanitize_text_field( $rawData[ $key ] ) : null;
break;
}
}
return $settings;
}
//phpcs:enable Inpsyde.CodeQuality.NestingLevel.MaxExceeded
//phpcs:enable Generic.Metrics.CyclomaticComplexity.TooHigh
private function isValidUpdateRequest(): bool {
if (
! isset( $_REQUEST['section'] )
|| ! in_array(
sanitize_text_field( wp_unslash( $_REQUEST['section'] ) ),
array( 'ppcp-gateway', 'ppcp-credit-card-gateway' ),
true
)
) {
return false;
}
if ( ! current_user_can( 'manage_options' ) ) {
return false;
}
if (
! isset( $_POST['ppcp-nonce'] )
|| ! wp_verify_nonce(
sanitize_text_field( wp_unslash( $_POST['ppcp-nonce'] ) ),
self::NONCE
)
) {
return false;
}
return true;
}
}

View file

@ -1,23 +1,39 @@
<?php <?php
/**
* The settings object.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Settings
*/
declare(strict_types=1); declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Settings; namespace Inpsyde\PayPalCommerce\WcGateway\Settings;
use Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException; use Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\WcGatewayInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/**
* Class Settings
*/
class Settings implements ContainerInterface { class Settings implements ContainerInterface {
public const KEY = 'woocommerce-ppcp-settings'; public const KEY = 'woocommerce-ppcp-settings';
/**
* The settings.
*
* @var array
*/
private $settings = array(); private $settings = array();
public function __construct() { /**
} * Returns the value for an id.
*
// phpcs:disable Inpsyde.CodeQuality.ReturnTypeDeclaration.NoReturnType * @param string $id The value identificator.
// phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType *
* @return mixed
* @throws NotFoundException When nothing was found.
*/
public function get( $id ) { public function get( $id ) {
if ( ! $this->has( $id ) ) { if ( ! $this->has( $id ) ) {
throw new NotFoundException(); throw new NotFoundException();
@ -25,24 +41,45 @@ class Settings implements ContainerInterface {
return $this->settings[ $id ]; return $this->settings[ $id ];
} }
/**
* Whether a value exists.
*
* @param string $id The value identificator.
*
* @return bool
*/
public function has( $id ) { public function has( $id ) {
$this->load(); $this->load();
return array_key_exists( $id, $this->settings ); return array_key_exists( $id, $this->settings );
} }
/**
* Sets a value.
*
* @param string $id The value identificator.
* @param mixed $value The value.
*/
public function set( $id, $value ) { public function set( $id, $value ) {
$this->load(); $this->load();
$this->settings[ $id ] = $value; $this->settings[ $id ] = $value;
} }
/**
* Stores the settings to the database.
*/
public function persist() { public function persist() {
update_option( self::KEY, $this->settings ); update_option( self::KEY, $this->settings );
} }
/**
* Resets the onboarding.
*
* @return bool
*/
public function reset(): bool { public function reset(): bool {
$this->load(); $this->load();
$fieldsToReset = array( $fields_to_reset = array(
'enabled', 'enabled',
'dcc_gateway_enabled', 'dcc_gateway_enabled',
'intent', 'intent',
@ -50,13 +87,18 @@ class Settings implements ContainerInterface {
'client_secret', 'client_secret',
'merchant_email', 'merchant_email',
); );
foreach ( $fieldsToReset as $id ) { foreach ( $fields_to_reset as $id ) {
$this->settings[ $id ] = null; $this->settings[ $id ] = null;
} }
return true; return true;
} }
/**
* Loads the settings.
*
* @return bool
*/
private function load(): bool { private function load(): bool {
if ( $this->settings ) { if ( $this->settings ) {

View file

@ -0,0 +1,277 @@
<?php
/**
* Listens to requests and updates the settings if necessary.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Settings
*/
declare(strict_types=1);
namespace Inpsyde\PayPalCommerce\WcGateway\Settings;
use Inpsyde\PayPalCommerce\ApiClient\Authentication\PayPalBearer;
use Inpsyde\PayPalCommerce\Onboarding\State;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\CreditCardGateway;
use Inpsyde\PayPalCommerce\WcGateway\Gateway\PayPalGateway;
use Inpsyde\PayPalCommerce\Webhooks\WebhookRegistrar;
use Psr\SimpleCache\CacheInterface;
/**
* Class SettingsListener
*/
class SettingsListener {
public const NONCE = 'ppcp-settings';
/**
* The Settings.
*
* @var Settings
*/
private $settings;
/**
* Array contains the setting fields.
*
* @var array
*/
private $setting_fields;
/**
* The Webhook Registrar.
*
* @var WebhookRegistrar
*/
private $webhook_registrar;
/**
* The Cache.
*
* @var CacheInterface
*/
private $cache;
/**
* The State.
*
* @var State
*/
private $state;
/**
* SettingsListener constructor.
*
* @param Settings $settings The settings.
* @param array $setting_fields The setting fields.
* @param WebhookRegistrar $webhook_registrar The Webhook Registrar.
* @param CacheInterface $cache The Cache.
* @param State $state The state.
*/
public function __construct(
Settings $settings,
array $setting_fields,
WebhookRegistrar $webhook_registrar,
CacheInterface $cache,
State $state
) {
$this->settings = $settings;
$this->setting_fields = $setting_fields;
$this->webhook_registrar = $webhook_registrar;
$this->cache = $cache;
$this->state = $state;
}
/**
* Listens to the request.
*
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException When a setting was not found.
* @throws \Psr\SimpleCache\InvalidArgumentException When the argument was invalid.
*/
public function listen() {
if ( ! $this->is_valid_update_request() ) {
return;
}
/**
* Nonce verification has been done in is_valid_update_request().
*
* phpcs:disable WordPress.Security.NonceVerification.Missing
* phpcs:disable WordPress.Security.NonceVerification.Recommended
*/
if ( isset( $_POST['save'] ) && sanitize_text_field( wp_unslash( $_POST['save'] ) ) === 'reset' ) {
$this->settings->reset();
$this->settings->persist();
$this->webhook_registrar->unregister();
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY );
}
return;
}
/**
* Sanitization is done in retrieve_settings_from_raw_data().
*
* phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
*/
$raw_data = ( isset( $_POST['ppcp'] ) ) ? (array) wp_unslash( $_POST['ppcp'] ) : array();
// phpcs:enable phpcs:disable WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
$settings = $this->retrieve_settings_from_raw_data( $raw_data );
if ( isset( $_GET['section'] ) && PayPalGateway::ID === $_GET['section'] ) {
$settings['enabled'] = isset( $_POST['woocommerce_ppcp-gateway_enabled'] )
&& 1 === absint( $_POST['woocommerce_ppcp-gateway_enabled'] );
}
if ( isset( $_GET['section'] ) && CreditCardGateway::ID === $_GET['section'] ) {
$dcc_enabled_post_key = 'woocommerce_ppcp-credit-card-gateway_enabled';
$settings['dcc_gateway_enabled'] = isset( $_POST[ $dcc_enabled_post_key ] )
&& 1 === absint( $_POST[ $dcc_enabled_post_key ] );
}
$this->maybe_register_webhooks( $settings );
foreach ( $settings as $id => $value ) {
$this->settings->set( $id, $value );
}
$this->settings->persist();
if ( $this->cache->has( PayPalBearer::CACHE_KEY ) ) {
$this->cache->delete( PayPalBearer::CACHE_KEY );
}
// phpcs:enable WordPress.Security.NonceVerification.Missing
// phpcs:enable WordPress.Security.NonceVerification.Recommended
}
/**
* Depending on the settings change, we might need to register or unregister the Webhooks at PayPal.
*
* @param array $settings The settings.
*
* @throws \Inpsyde\PayPalCommerce\WcGateway\Exception\NotFoundException If a setting hasn't been found.
*/
private function maybe_register_webhooks( array $settings ) {
if ( ! $this->settings->has( 'client_id' ) && $settings['client_id'] ) {
$this->webhook_registrar->register();
}
if ( $this->settings->has( 'client_id' ) && $this->settings->get( 'client_id' ) ) {
$current_secret = $this->settings->has( 'client_secret' ) ?
$this->settings->get( 'client_secret' ) : '';
if (
$settings['client_id'] !== $this->settings->get( 'client_id' )
|| $settings['client_secret'] !== $current_secret
) {
$this->webhook_registrar->unregister();
$this->webhook_registrar->register();
}
}
}
/**
* Sanitizes the settings input data and returns a valid settings array.
*
* @param array $raw_data The Raw data.
*
* @return array
*/
private function retrieve_settings_from_raw_data( array $raw_data ): array {
/**
* Nonce verification has already been done.
* phpcs:disable WordPress.Security.NonceVerification.Recommended
*/
if ( ! isset( $_GET['section'] ) ) {
return array();
}
$settings = array();
foreach ( $this->setting_fields as $key => $config ) {
if ( ! in_array( $this->state->currentState(), $config['screens'], true ) ) {
continue;
}
if (
'dcc' === $config['gateway']
&& sanitize_text_field( wp_unslash( $_GET['section'] ) ) !== 'ppcp-credit-card-gateway'
) {
continue;
}
if (
'paypal' === $config['gateway']
&& sanitize_text_field( wp_unslash( $_GET['section'] ) ) !== 'ppcp-gateway'
) {
continue;
}
switch ( $config['type'] ) {
case 'checkbox':
$settings[ $key ] = isset( $raw_data[ $key ] );
break;
case 'text':
case 'ppcp-text-input':
case 'ppcp-password':
$settings[ $key ] = isset( $raw_data[ $key ] ) ? sanitize_text_field( $raw_data[ $key ] ) : '';
break;
case 'password':
if ( empty( $raw_data[ $key ] ) ) {
break;
}
$settings[ $key ] = sanitize_text_field( $raw_data[ $key ] );
break;
case 'ppcp-multiselect':
$values = isset( $raw_data[ $key ] ) ? (array) $raw_data[ $key ] : array();
$values_to_save = array();
foreach ( $values as $index => $raw_value ) {
$value = sanitize_text_field( $raw_value );
if ( ! in_array( $value, array_keys( $config['options'] ), true ) ) {
continue;
}
$values_to_save[] = $value;
}
$settings[ $key ] = $values_to_save;
break;
case 'select':
$options = array_keys( $config['options'] );
$settings[ $key ] = isset( $raw_data[ $key ] ) && in_array(
sanitize_text_field( $raw_data[ $key ] ),
$options,
true
) ? sanitize_text_field( $raw_data[ $key ] ) : null;
break;
}
}
// phpcs:enable WordPress.Security.NonceVerification.Recommended
return $settings;
}
/**
* Evaluates whether the current request is supposed to update the settings.
*
* @return bool
*/
private function is_valid_update_request(): bool {
if (
! isset( $_REQUEST['section'] )
|| ! in_array(
sanitize_text_field( wp_unslash( $_REQUEST['section'] ) ),
array( 'ppcp-gateway', 'ppcp-credit-card-gateway' ),
true
)
) {
return false;
}
if ( ! current_user_can( 'manage_options' ) ) {
return false;
}
if (
! isset( $_POST['ppcp-nonce'] )
|| ! wp_verify_nonce(
sanitize_text_field( wp_unslash( $_POST['ppcp-nonce'] ) ),
self::NONCE
)
) {
return false;
}
return true;
}
}

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* Renders the settings of the Gateways.
*
* @package Inpsyde\PayPalCommerce\WcGateway\Settings
*/
declare(strict_types=1); declare(strict_types=1);
@ -9,42 +14,92 @@ use Inpsyde\PayPalCommerce\Button\Helper\MessagesApply;
use Inpsyde\PayPalCommerce\Onboarding\State; use Inpsyde\PayPalCommerce\Onboarding\State;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/**
* Class SettingsRenderer
*/
class SettingsRenderer { class SettingsRenderer {
/**
* The settings.
*
* @var ContainerInterface
*/
private $settings; private $settings;
/**
* The current onboarding state.
*
* @var State
*/
private $state; private $state;
/**
* The setting fields.
*
* @var array
*/
private $fields; private $fields;
private $dccApplies;
private $messagesApply; /**
* Helper to see if DCC gateway can be shown.
*
* @var DccApplies
*/
private $dcc_applies;
/**
* Helper to see if messages are supposed to show up.
*
* @var MessagesApply
*/
private $messages_apply;
/**
* SettingsRenderer constructor.
*
* @param ContainerInterface $settings The Settings.
* @param State $state The current state.
* @param array $fields The setting fields.
* @param DccApplies $dcc_applies Whether DCC gateway can be shown.
* @param MessagesApply $messages_apply Whether messages can be shown.
*/
public function __construct( public function __construct(
ContainerInterface $settings, ContainerInterface $settings,
State $state, State $state,
array $fields, array $fields,
DccApplies $dccApplies, DccApplies $dcc_applies,
MessagesApply $messagesApply MessagesApply $messages_apply
) { ) {
$this->settings = $settings; $this->settings = $settings;
$this->state = $state; $this->state = $state;
$this->fields = $fields; $this->fields = $fields;
$this->dccApplies = $dccApplies; $this->dcc_applies = $dcc_applies;
$this->messagesApply = $messagesApply; $this->messages_apply = $messages_apply;
} }
//phpcs:disable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType /**
public function renderMultiSelect( $field, $key, $config, $value ): string { * Renders the multiselect field.
*
* @param string $field The current field HTML.
* @param string $key The current key.
* @param array $config The configuration array.
* @param string $value The current value.
*
* @return string
*/
public function render_multiselect( $field, $key, $config, $value ): string {
if ( $config['type'] !== 'ppcp-multiselect' ) { if ( 'ppcp-multiselect' !== $config['type'] ) {
return $field; return $field;
} }
$options = array(); $options = array();
foreach ( $config['options'] as $optionKey => $optionValue ) { foreach ( $config['options'] as $option_key => $option_value ) {
$selected = ( in_array( $optionKey, $value, true ) ) ? 'selected="selected"' : ''; $selected = ( in_array( $option_key, $value, true ) ) ? 'selected="selected"' : '';
$options[] = '<option value="' . esc_attr( $optionKey ) . '" ' . $selected . '>' . $options[] = '<option value="' . esc_attr( $option_key ) . '" ' . $selected . '>' .
esc_html( $optionValue ) . esc_html( $option_value ) .
'</option>'; '</option>';
} }
@ -62,9 +117,19 @@ class SettingsRenderer {
return $html; return $html;
} }
public function renderPassword( $field, $key, $config, $value ): string { /**
* Renders the password input field.
*
* @param string $field The current field HTML.
* @param string $key The current key.
* @param array $config The configuration array.
* @param string $value The current value.
*
* @return string
*/
public function render_password( $field, $key, $config, $value ): string {
if ( $config['type'] !== 'ppcp-password' ) { if ( 'ppcp-password' !== $config['type'] ) {
return $field; return $field;
} }
@ -84,9 +149,20 @@ class SettingsRenderer {
return $html; return $html;
} }
public function renderTextInput( $field, $key, $config, $value ): string {
if ( $config['type'] !== 'ppcp-text-input' ) { /**
* Renders the text input field.
*
* @param string $field The current field HTML.
* @param string $key The current key.
* @param array $config The configuration array.
* @param string $value The current value.
*
* @return string
*/
public function render_text_input( $field, $key, $config, $value ): string {
if ( 'ppcp-text-input' !== $config['type'] ) {
return $field; return $field;
} }
@ -106,9 +182,19 @@ class SettingsRenderer {
return $html; return $html;
} }
public function renderHeading( $field, $key, $config, $value ): string { /**
* Renders the heading field.
*
* @param string $field The current field HTML.
* @param string $key The current key.
* @param array $config The configuration array.
* @param string $value The current value.
*
* @return string
*/
public function render_heading( $field, $key, $config, $value ): string {
if ( $config['type'] !== 'ppcp-heading' ) { if ( 'ppcp-heading' !== $config['type'] ) {
return $field; return $field;
} }
@ -120,11 +206,13 @@ class SettingsRenderer {
return $html; return $html;
} }
//phpcs:enable Inpsyde.CodeQuality.ArgumentTypeDeclaration.NoArgumentType
//phpcs:disable Inpsyde.CodeQuality.NestingLevel.High /**
//phpcs:disable Inpsyde.CodeQuality.FunctionLength.TooLong * Renders the settings.
public function render( bool $isDcc ) { *
* @param bool $is_dcc Whether it is the DCC gateway or not.
*/
public function render( bool $is_dcc ) {
$nonce = wp_create_nonce( SettingsListener::NONCE ); $nonce = wp_create_nonce( SettingsListener::NONCE );
?> ?>
@ -134,21 +222,21 @@ class SettingsRenderer {
if ( ! in_array( $this->state->currentState(), $config['screens'], true ) ) { if ( ! in_array( $this->state->currentState(), $config['screens'], true ) ) {
continue; continue;
} }
if ( $isDcc && ! in_array( $config['gateway'], array( 'all', 'dcc' ), true ) ) { if ( $is_dcc && ! in_array( $config['gateway'], array( 'all', 'dcc' ), true ) ) {
continue; continue;
} }
if ( ! $isDcc && ! in_array( $config['gateway'], array( 'all', 'paypal' ), true ) ) { if ( ! $is_dcc && ! in_array( $config['gateway'], array( 'all', 'paypal' ), true ) ) {
continue; continue;
} }
if ( if (
in_array( 'dcc', $config['requirements'], true ) in_array( 'dcc', $config['requirements'], true )
&& ! $this->dccApplies->forCountryCurrency() && ! $this->dcc_applies->forCountryCurrency()
) { ) {
continue; continue;
} }
if ( if (
in_array( 'messages', $config['requirements'], true ) in_array( 'messages', $config['requirements'], true )
&& ! $this->messagesApply->forCountry() && ! $this->messages_apply->forCountry()
) { ) {
continue; continue;
} }
@ -156,12 +244,12 @@ class SettingsRenderer {
$key = 'ppcp[' . $field . ']'; $key = 'ppcp[' . $field . ']';
$id = 'ppcp-' . $field; $id = 'ppcp-' . $field;
$config['id'] = $id; $config['id'] = $id;
$thTd = $config['type'] !== 'ppcp-heading' ? 'td' : 'th'; $th_td = 'ppcp-heading' !== $config['type'] ? 'td' : 'th';
$colspan = $config['type'] !== 'ppcp-heading' ? 1 : 2; $colspan = 'ppcp-heading' !== $config['type'] ? 1 : 2;
?> ?>
<tr valign="top" id="<?php echo esc_attr( 'field-' . $field ); ?>"> <tr valign="top" id="<?php echo esc_attr( 'field-' . $field ); ?>">
<?php if ( $config['type'] !== 'ppcp-heading' ) : ?> <?php if ( 'ppcp-heading' !== $config['type'] ) : ?>
<th> <th>
<label <label
for="<?php echo esc_attr( $id ); ?>" for="<?php echo esc_attr( $id ); ?>"
@ -177,18 +265,18 @@ class SettingsRenderer {
?> ?>
</th> </th>
<?php endif; ?> <?php endif; ?>
<<?php echo esc_attr( $thTd ); ?> colspan="<?php echo (int) $colspan; ?>"> <<?php echo esc_attr( $th_td ); ?> colspan="<?php echo (int) $colspan; ?>">
<?php <?php
$config['type'] === 'ppcp-text' ? 'ppcp-text' === $config['type'] ?
$this->renderText( $config ) $this->render_text( $config )
: woocommerce_form_field( $key, $config, $value ); : woocommerce_form_field( $key, $config, $value );
?> ?>
</<?php echo esc_attr( $thTd ); ?>> </<?php echo esc_attr( $th_td ); ?>>
</tr> </tr>
<?php <?php
endforeach; endforeach;
if ( $isDcc ) : if ( $is_dcc ) :
?> ?>
<tr> <tr>
<th><?php esc_html_e( '3D Secure', 'woocommerce-paypal-commerce-gateway' ); ?></th> <th><?php esc_html_e( '3D Secure', 'woocommerce-paypal-commerce-gateway' ); ?></th>
@ -196,6 +284,8 @@ class SettingsRenderer {
<p> <p>
<?php <?php
/** /**
* We still need to provide a docs link.
*
* @todo: Provide link to documentation. * @todo: Provide link to documentation.
*/ */
echo wp_kses_post( echo wp_kses_post(
@ -206,7 +296,7 @@ class SettingsRenderer {
an additional layer of verification using Verified by Visa, an additional layer of verification using Verified by Visa,
MasterCard SecureCode and American Express SafeKey. MasterCard SecureCode and American Express SafeKey.
%1$sLearn more about 3D Secure.%2$s', %1$sLearn more about 3D Secure.%2$s',
'woocommerce - paypal - commerce - gateway' 'woocommerce-paypal-commerce-gateway'
), ),
'<a href = "#">', '<a href = "#">',
'</a>' '</a>'
@ -219,9 +309,13 @@ class SettingsRenderer {
<?php <?php
endif; endif;
} }
//phpcs:enable Inpsyde.CodeQuality.NestingLevel.High
private function renderText( array $config ) { /**
* Renders the ppcp-text field given a configuration.
*
* @param array $config The configuration array.
*/
private function render_text( array $config ) {
echo wp_kses_post( $config['text'] ); echo wp_kses_post( $config['text'] );
if ( isset( $config['hidden'] ) ) { if ( isset( $config['hidden'] ) ) {
$value = $this->settings->has( $config['hidden'] ) ? $value = $this->settings->has( $config['hidden'] ) ?

View file

@ -1,4 +1,9 @@
<?php <?php
/**
* The Gateway module.
*
* @package Inpsyde\PayPalCommerce\WcGateway
*/
declare(strict_types=1); declare(strict_types=1);
@ -9,7 +14,6 @@ use Dhii\Modular\Module\ModuleInterface;
use Inpsyde\PayPalCommerce\AdminNotices\Repository\Repository; use Inpsyde\PayPalCommerce\AdminNotices\Repository\Repository;
use Inpsyde\PayPalCommerce\ApiClient\Helper\DccApplies; use Inpsyde\PayPalCommerce\ApiClient\Helper\DccApplies;
use Inpsyde\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository; use Inpsyde\PayPalCommerce\ApiClient\Repository\PayPalRequestIdRepository;
use Inpsyde\PayPalCommerce\WcGateway\Admin\OrderDetail;
use Inpsyde\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn; use Inpsyde\PayPalCommerce\WcGateway\Admin\OrderTablePaymentStatusColumn;
use Inpsyde\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail; use Inpsyde\PayPalCommerce\WcGateway\Admin\PaymentStatusOrderDetail;
use Inpsyde\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset; use Inpsyde\PayPalCommerce\WcGateway\Checkout\CheckoutPayPalAddressPreset;
@ -23,8 +27,16 @@ use Inpsyde\PayPalCommerce\WcGateway\Settings\SettingsRenderer;
use Interop\Container\ServiceProviderInterface; use Interop\Container\ServiceProviderInterface;
use Psr\Container\ContainerInterface; use Psr\Container\ContainerInterface;
/**
* Class WcGatewayModule
*/
class WcGatewayModule implements ModuleInterface { class WcGatewayModule implements ModuleInterface {
/**
* Setup the module.
*
* @return ServiceProviderInterface
*/
public function setup(): ServiceProviderInterface { public function setup(): ServiceProviderInterface {
return new ServiceProvider( return new ServiceProvider(
require __DIR__ . '/../services.php', require __DIR__ . '/../services.php',
@ -32,28 +44,35 @@ class WcGatewayModule implements ModuleInterface {
); );
} }
/**
* Runs the module.
*
* @param ContainerInterface $container The container.
*/
public function run( ContainerInterface $container ) { public function run( ContainerInterface $container ) {
$this->registerPaymentGateway( $container ); $this->register_payment_gateways( $container );
$this->registerOrderFunctionality( $container ); $this->register_order_functionality( $container );
$this->registerColumns( $container ); $this->register_columns( $container );
$this->registerCheckoutAddressPreset( $container ); $this->register_checkout_paypal_address_preset( $container );
$this->ajaxGatewayEnabler( $container ); $this->ajax_gateway_enabler( $container );
add_filter( add_filter(
Repository::NOTICES_FILTER, Repository::NOTICES_FILTER,
static function ( $notices ) use ( $container ): array { static function ( $notices ) use ( $container ): array {
$notice = $container->get( 'wcgateway.notice.connect' ); $notice = $container->get( 'wcgateway.notice.connect' );
/** /**
* The Connect Admin Notice object.
*
* @var ConnectAdminNotice $notice * @var ConnectAdminNotice $notice
*/ */
$connectMessage = $notice->connectMessage(); $connect_message = $notice->connect_message();
if ( $connectMessage ) { if ( $connect_message ) {
$notices[] = $connectMessage; $notices[] = $connect_message;
} }
$authorizeOrderAction = $container->get( 'wcgateway.notice.authorize-order-action' ); $authorize_order_action = $container->get( 'wcgateway.notice.authorize-order-action' );
$authorizedMessage = $authorizeOrderAction->message(); $authorized_message = $authorize_order_action->message();
if ( $authorizedMessage ) { if ( $authorized_message ) {
$notices[] = $authorizedMessage; $notices[] = $authorized_message;
} }
return $notices; return $notices;
@ -74,14 +93,21 @@ class WcGatewayModule implements ModuleInterface {
static function () use ( $container ) { static function () use ( $container ) {
$endpoint = $container->get( 'wcgateway.endpoint.return-url' ); $endpoint = $container->get( 'wcgateway.endpoint.return-url' );
/** /**
* The Endpoint.
*
* @var ReturnUrlEndpoint $endpoint * @var ReturnUrlEndpoint $endpoint
*/ */
$endpoint->handleRequest(); $endpoint->handle_request();
} }
); );
} }
private function ajaxGatewayEnabler( ContainerInterface $container ) { /**
* Adds the functionality to listen to the ajax enable gateway switch.
*
* @param ContainerInterface $container The container.
*/
private function ajax_gateway_enabler( ContainerInterface $container ) {
add_action( add_action(
'wp_ajax_woocommerce_toggle_gateway_enabled', 'wp_ajax_woocommerce_toggle_gateway_enabled',
static function () use ( $container ) { static function () use ( $container ) {
@ -97,11 +123,13 @@ class WcGatewayModule implements ModuleInterface {
} }
/** /**
* The settings.
*
* @var Settings $settings * @var Settings $settings
*/ */
$settings = $container->get( 'wcgateway.settings' ); $settings = $container->get( 'wcgateway.settings' );
$key = $_POST['gateway_id'] === PayPalGateway::ID ? 'enabled' : ''; $key = PayPalGateway::ID === $_POST['gateway_id'] ? 'enabled' : '';
if ( $_POST['gateway_id'] === CreditCardGateway::ID ) { if ( CreditCardGateway::ID === $_POST['gateway_id'] ) {
$key = 'dcc_gateway_enabled'; $key = 'dcc_gateway_enabled';
} }
if ( ! $key ) { if ( ! $key ) {
@ -118,17 +146,24 @@ class WcGatewayModule implements ModuleInterface {
); );
} }
private function registerPaymentGateWay( ContainerInterface $container ) { /**
* Registers the payment gateways.
*
* @param ContainerInterface $container The container.
*/
private function register_payment_gateways( ContainerInterface $container ) {
add_filter( add_filter(
'woocommerce_payment_gateways', 'woocommerce_payment_gateways',
static function ( $methods ) use ( $container ): array { static function ( $methods ) use ( $container ): array {
$methods[] = $container->get( 'wcgateway.paypal-gateway' ); $methods[] = $container->get( 'wcgateway.paypal-gateway' );
$dccApplies = $container->get( 'api.helpers.dccapplies' ); $dcc_applies = $container->get( 'api.helpers.dccapplies' );
/** /**
* @var DccApplies $dccApplies * The DCC Applies object.
*
* @var DccApplies $dcc_applies
*/ */
if ( $dccApplies->forCountryCurrency() ) { if ( $dcc_applies->forCountryCurrency() ) {
$methods[] = $container->get( 'wcgateway.credit-card-gateway' ); $methods[] = $container->get( 'wcgateway.credit-card-gateway' );
} }
return (array) $methods; return (array) $methods;
@ -148,12 +183,14 @@ class WcGatewayModule implements ModuleInterface {
static function ( $field, $key, $args, $value ) use ( $container ) { static function ( $field, $key, $args, $value ) use ( $container ) {
$renderer = $container->get( 'wcgateway.settings.render' ); $renderer = $container->get( 'wcgateway.settings.render' );
/** /**
* The Settings Renderer object.
*
* @var SettingsRenderer $renderer * @var SettingsRenderer $renderer
*/ */
$field = $renderer->renderMultiSelect( $field, $key, $args, $value ); $field = $renderer->render_multiselect( $field, $key, $args, $value );
$field = $renderer->renderPassword( $field, $key, $args, $value ); $field = $renderer->render_password( $field, $key, $args, $value );
$field = $renderer->renderTextInput( $field, $key, $args, $value ); $field = $renderer->render_text_input( $field, $key, $args, $value );
$field = $renderer->renderHeading( $field, $key, $args, $value ); $field = $renderer->render_heading( $field, $key, $args, $value );
return $field; return $field;
}, },
10, 10,
@ -165,6 +202,8 @@ class WcGatewayModule implements ModuleInterface {
static function ( $methods ) use ( $container ): array { static function ( $methods ) use ( $container ): array {
$disabler = $container->get( 'wcgateway.disabler' ); $disabler = $container->get( 'wcgateway.disabler' );
/** /**
* The Gateay disabler.
*
* @var DisableGateways $disabler * @var DisableGateways $disabler
*/ */
return $disabler->handler( (array) $methods ); return $disabler->handler( (array) $methods );
@ -172,39 +211,53 @@ class WcGatewayModule implements ModuleInterface {
); );
} }
private function registerOrderFunctionality( ContainerInterface $container ) { /**
* Registers the authorize order functionality.
*
* @param ContainerInterface $container The container.
*/
private function register_order_functionality( ContainerInterface $container ) {
add_filter( add_filter(
'woocommerce_order_actions', 'woocommerce_order_actions',
static function ( $orderActions ): array { static function ( $order_actions ): array {
$orderActions['ppcp_authorize_order'] = __( $order_actions['ppcp_authorize_order'] = __(
'Capture authorized PayPal payment', 'Capture authorized PayPal payment',
'woocommerce-paypal-commerce-gateway' 'woocommerce-paypal-commerce-gateway'
); );
return $orderActions; return $order_actions;
} }
); );
add_action( add_action(
'woocommerce_order_action_ppcp_authorize_order', 'woocommerce_order_action_ppcp_authorize_order',
static function ( \WC_Order $wcOrder ) use ( $container ) { static function ( \WC_Order $wc_order ) use ( $container ) {
/** /**
* The PayPal Gateway.
*
* @var PayPalGateway $gateway * @var PayPalGateway $gateway
*/ */
$gateway = $container->get( 'wcgateway.paypal-gateway' ); $gateway = $container->get( 'wcgateway.paypal-gateway' );
$gateway->captureAuthorizedPayment( $wcOrder ); $gateway->capture_authorized_payment( $wc_order );
} }
); );
} }
private function registerColumns( ContainerInterface $container ) { /**
* Registers the additional columns on the order list page.
*
* @param ContainerInterface $container The container.
*/
private function register_columns( ContainerInterface $container ) {
add_action( add_action(
'woocommerce_order_actions_start', 'woocommerce_order_actions_start',
static function ( $wcOrderId ) use ( $container ) { static function ( $wc_order_id ) use ( $container ) {
/** /**
* The Payment Status Order Detail.
*
* @var PaymentStatusOrderDetail $class * @var PaymentStatusOrderDetail $class
*/ */
$class = $container->get( 'wcgateway.admin.order-payment-status' ); $class = $container->get( 'wcgateway.admin.order-payment-status' );
$class->render( intval( $wcOrderId ) ); $class->render( intval( $wc_order_id ) );
} }
); );
@ -212,41 +265,54 @@ class WcGatewayModule implements ModuleInterface {
'manage_edit-shop_order_columns', 'manage_edit-shop_order_columns',
static function ( $columns ) use ( $container ) { static function ( $columns ) use ( $container ) {
/** /**
* @var OrderTablePaymentStatusColumn $paymentStatusColumn * The Order Table Payment Status object.
*
* @var OrderTablePaymentStatusColumn $payment_status_column
*/ */
$paymentStatusColumn = $container->get( 'wcgateway.admin.orders-payment-status-column' ); $payment_status_column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
return $paymentStatusColumn->register( $columns ); return $payment_status_column->register( $columns );
} }
); );
add_action( add_action(
'manage_shop_order_posts_custom_column', 'manage_shop_order_posts_custom_column',
static function ( $column, $wcOrderId ) use ( $container ) { static function ( $column, $wc_order_id ) use ( $container ) {
/** /**
* @var OrderTablePaymentStatusColumn $paymentStatusColumn * The column object.
*
* @var OrderTablePaymentStatusColumn $payment_status_column
*/ */
$paymentStatusColumn = $container->get( 'wcgateway.admin.orders-payment-status-column' ); $payment_status_column = $container->get( 'wcgateway.admin.orders-payment-status-column' );
$paymentStatusColumn->render( $column, intval( $wcOrderId ) ); $payment_status_column->render( $column, intval( $wc_order_id ) );
}, },
10, 10,
2 2
); );
} }
private function registerCheckoutAddressPreset( ContainerInterface $container ): void { /**
* Registers the PayPal Address preset to overwrite Shipping in checkout.
*
* @param ContainerInterface $container The container.
*/
private function register_checkout_paypal_address_preset( ContainerInterface $container ): void {
add_filter( add_filter(
'woocommerce_checkout_get_value', 'woocommerce_checkout_get_value',
static function ( ...$args ) use ( $container ) { static function ( ...$args ) use ( $container ) {
// Its important to not instantiate the service too early as it /**
// depends on SessionHandler and WooCommerce Session * Its important to not instantiate the service too early as it
* depends on SessionHandler and WooCommerce Session.
*/
/** /**
* The CheckoutPayPalAddressPreset object.
*
* @var CheckoutPayPalAddressPreset $service * @var CheckoutPayPalAddressPreset $service
*/ */
$service = $container->get( 'wcgateway.checkout.address-preset' ); $service = $container->get( 'wcgateway.checkout.address-preset' );
return $service->filterCheckoutFiled( ...$args ); return $service->filter_checkout_field( ...$args );
}, },
10, 10,
2 2

View file

@ -40,7 +40,7 @@ class CheckoutPayPalAddressPresetTest extends TestCase
self::assertSame( self::assertSame(
$expected, $expected,
$testee->filterCheckoutFiled(null, $fieldId) $testee->filter_checkout_field(null, $fieldId)
); );
} }

View file

@ -174,7 +174,7 @@ class WcGatewayTest extends TestCase
$sessionHandler $sessionHandler
); );
$this->assertTrue($testee->captureAuthorizedPayment($wcOrder)); $this->assertTrue($testee->capture_authorized_payment($wcOrder));
} }
public function testCaptureAuthorizedPaymentHasAlreadyBeenCaptured() { public function testCaptureAuthorizedPaymentHasAlreadyBeenCaptured() {
@ -220,7 +220,7 @@ class WcGatewayTest extends TestCase
$sessionHandler $sessionHandler
); );
$this->assertTrue($testee->captureAuthorizedPayment($wcOrder)); $this->assertTrue($testee->capture_authorized_payment($wcOrder));
} }
/** /**
@ -259,7 +259,7 @@ class WcGatewayTest extends TestCase
$sessionHandler $sessionHandler
); );
$this->assertFalse($testee->captureAuthorizedPayment($wcOrder)); $this->assertFalse($testee->capture_authorized_payment($wcOrder));
} }
public function dataForTestCaptureAuthorizedPaymentNoActionableFailures() : array public function dataForTestCaptureAuthorizedPaymentNoActionableFailures() : array

View file

@ -63,7 +63,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
->with(PayPalGateway::ORDER_ID_META_KEY) ->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn($orderId); ->andReturn($orderId);
$this->assertTrue($testee->process($wcOrder)); $this->assertTrue($testee->process($wcOrder));
$this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $testee->lastStatus()); $this->assertEquals(AuthorizedPaymentsProcessor::SUCCESSFUL, $testee->last_status());
} }
public function testInaccessible() { public function testInaccessible() {
@ -82,7 +82,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
->with(PayPalGateway::ORDER_ID_META_KEY) ->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn($orderId); ->andReturn($orderId);
$this->assertFalse($testee->process($wcOrder)); $this->assertFalse($testee->process($wcOrder));
$this->assertEquals(AuthorizedPaymentsProcessor::INACCESSIBLE, $testee->lastStatus()); $this->assertEquals(AuthorizedPaymentsProcessor::INACCESSIBLE, $testee->last_status());
} }
public function testNotFound() { public function testNotFound() {
@ -101,7 +101,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
->with(PayPalGateway::ORDER_ID_META_KEY) ->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn($orderId); ->andReturn($orderId);
$this->assertFalse($testee->process($wcOrder)); $this->assertFalse($testee->process($wcOrder));
$this->assertEquals(AuthorizedPaymentsProcessor::NOT_FOUND, $testee->lastStatus()); $this->assertEquals(AuthorizedPaymentsProcessor::NOT_FOUND, $testee->last_status());
} }
public function testCaptureFails() { public function testCaptureFails() {
@ -149,7 +149,7 @@ class AuthorizedPaymentsProcessorTest extends TestCase
->with(PayPalGateway::ORDER_ID_META_KEY) ->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn($orderId); ->andReturn($orderId);
$this->assertFalse($testee->process($wcOrder)); $this->assertFalse($testee->process($wcOrder));
$this->assertEquals(AuthorizedPaymentsProcessor::FAILED, $testee->lastStatus()); $this->assertEquals(AuthorizedPaymentsProcessor::FAILED, $testee->last_status());
} }
public function testAllAreCaptured() { public function testAllAreCaptured() {
@ -197,6 +197,6 @@ class AuthorizedPaymentsProcessorTest extends TestCase
->with(PayPalGateway::ORDER_ID_META_KEY) ->with(PayPalGateway::ORDER_ID_META_KEY)
->andReturn($orderId); ->andReturn($orderId);
$this->assertFalse($testee->process($wcOrder)); $this->assertFalse($testee->process($wcOrder));
$this->assertEquals(AuthorizedPaymentsProcessor::ALREADY_CAPTURED, $testee->lastStatus()); $this->assertEquals(AuthorizedPaymentsProcessor::ALREADY_CAPTURED, $testee->last_status());
} }
} }

View file

@ -266,7 +266,7 @@ class OrderProcessorTest extends TestCase
$orderIntent $orderIntent
); );
$this->assertFalse($testee->process($wcOrder, $woocommerce)); $this->assertFalse($testee->process($wcOrder, $woocommerce));
$this->assertNotEmpty($testee->lastError()); $this->assertNotEmpty($testee->last_error());
} }