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.
*/ *
$sessionHandler = $container->get('session.handler'); * @var SessionHandler $session_handler
$bnCode = $sessionHandler->bnCode(); */
$session_handler = $container->get( 'session.handler' );
$bn_code = $session_handler->bnCode();
/** /**
* @var Settings $settings * The settings.
*/ *
$settings = $container->get('wcgateway.settings'); * @var Settings $settings
$intent = $settings->has('intent') && strtoupper((string) $settings->get('intent')) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE'; */
$applicationContextRepository = $container->get('api.repository.application-context'); $settings = $container->get( 'wcgateway.settings' );
$paypalRequestId = $container->get('api.repository.paypal-request-id'); $intent = $settings->has( 'intent' ) && strtoupper( (string) $settings->get( 'intent' ) ) === 'AUTHORIZE' ? 'AUTHORIZE' : 'CAPTURE';
return new OrderEndpoint( $application_context_repository = $container->get( 'api.repository.application-context' );
$container->get('api.host'), $pay_pal_request_id_repository = $container->get( 'api.repository.paypal-request-id' );
$container->get('api.bearer'), return new OrderEndpoint(
$orderFactory, $container->get( 'api.host' ),
$patchCollectionFactory, $container->get( 'api.bearer' ),
$intent, $order_factory,
$logger, $patch_collection_factory,
$applicationContextRepository, $intent,
$paypalRequestId, $logger,
$bnCode $application_context_repository,
); $pay_pal_request_id_repository,
}, $bn_code
'woocommerce.logger.woocommerce' => function (ContainerInterface $container): LoggerInterface { );
$settings = $container->get('wcgateway.settings'); },
if (! function_exists('wc_get_logger') || ! $settings->has('logging_enabled') || ! $settings->get('logging_enabled')) { 'woocommerce.logger.woocommerce' => function ( ContainerInterface $container ): LoggerInterface {
return new NullLogger(); $settings = $container->get( 'wcgateway.settings' );
} if ( ! function_exists( 'wc_get_logger' ) || ! $settings->has( 'logging_enabled' ) || ! $settings->get( 'logging_enabled' ) ) {
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);
@ -7,5 +12,5 @@ namespace Inpsyde\PayPalCommerce\WcGateway;
use Dhii\Modular\Module\ModuleInterface; use Dhii\Modular\Module\ModuleInterface;
return static function (): ModuleInterface { return static function (): ModuleInterface {
return new WcGatewayModule(); return new WcGatewayModule();
}; };

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,36 +265,38 @@ 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>
<td> <td>
<p> <p>
<?php <?php
/** /**
* @todo: Provide link to documentation. * We still need to provide a docs link.
*/ *
* @todo: Provide link to documentation.
*/
echo wp_kses_post( echo wp_kses_post(
sprintf( sprintf(
// translators: %1$s and %2$s is a link tag. // translators: %1$s and %2$s is a link tag.
__( __(
'3D Secure benefits cardholders and merchants by providing '3D Secure benefits cardholders and merchants by providing
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());
} }