Skip to content

Commit

Permalink
RPP: Payments skeleton (#7318)
Browse files Browse the repository at this point in the history
  • Loading branch information
RadoslavGeorgiev authored Oct 5, 2023
1 parent 1d52a33 commit 95125fd
Show file tree
Hide file tree
Showing 15 changed files with 593 additions and 22 deletions.
5 changes: 5 additions & 0 deletions changelog/rpp-6686-payments-skeleton
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Significance: patch
Type: dev
Comment: Adding the basic structure for payment states and context"


13 changes: 12 additions & 1 deletion includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
use WCPay\Internal\Service\PaymentProcessingService;
use WCPay\Internal\Payment\Factor;
use WCPay\Internal\Payment\Router;
use WCPay\Internal\Payment\State\CompletedState;

/**
* Gateway class for WooPayments
Expand Down Expand Up @@ -806,11 +807,21 @@ function_exists( 'wcs_order_contains_subscription' )
*
* @param WC_Order $order Order that needs payment.
* @return array|null Array if processed, null if the new process is not supported.
* @throws Exception If the payment process could not be completed.
*/
public function new_process_payment( WC_Order $order ) {
// Important: No factors are provided here, they were meant just for `Feature`.
$service = wcpay_get_container()->get( PaymentProcessingService::class );
return $service->process_payment( $order->get_id() );
$state = $service->process_payment( $order->get_id() );

if ( $state instanceof CompletedState ) {
return [
'result' => 'success',
'redirect' => $this->get_return_url( $order ),
];
}

throw new Exception( __( 'The payment process could not be completed.', 'woocommerce-payments' ) );
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@
namespace WCPay\Internal\DependencyManagement\ServiceProvider;

use Automattic\WooCommerce\Utilities\PluginUtil;
use WCPay\Container;
use WCPay\Core\Mode;
use WCPay\Database_Cache;
use WCPay\Internal\DependencyManagement\AbstractServiceProvider;
use WCPay\Internal\Payment\Router;
use WCPay\Internal\Payment\State\CompletedState;
use WCPay\Internal\Payment\State\InitialState;
use WCPay\Internal\Payment\State\StateFactory;
use WCPay\Internal\Service\PaymentProcessingService;
use WCPay\Internal\Service\ExampleService;
use WCPay\Internal\Service\ExampleServiceWithDependencies;
Expand All @@ -28,6 +32,9 @@ class PaymentsServiceProvider extends AbstractServiceProvider {
protected $provides = [
PaymentProcessingService::class,
Router::class,
StateFactory::class,
InitialState::class,
CompletedState::class,
ExampleService::class,
ExampleServiceWithDependencies::class,
];
Expand All @@ -38,7 +45,17 @@ class PaymentsServiceProvider extends AbstractServiceProvider {
public function register(): void {
$container = $this->getContainer();

$container->addShared( PaymentProcessingService::class );
$container->addShared( StateFactory::class )
->addArgument( Container::class );

$container->addShared( PaymentProcessingService::class )
->addArgument( StateFactory::class );

$container->add( InitialState::class )
->addArgument( StateFactory::class );

$container->add( CompletedState::class )
->addArgument( StateFactory::class );

$container->addShared( Router::class )
->addArgument( Database_Cache::class );
Expand Down
17 changes: 17 additions & 0 deletions src/Internal/Payment/Exception/StateTransitionException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<?php
/**
* Class StateTransitionException
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\Exception;

use Exception;

/**
* An error, indicating that the transition between states failed.
*/
class StateTransitionException extends Exception {

}
15 changes: 15 additions & 0 deletions src/Internal/Payment/PaymentContext.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Class PaymentContext
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment;

/**
* A context object, which is shared between payment states.
*/
class PaymentContext {

}
117 changes: 117 additions & 0 deletions src/Internal/Payment/State/AbstractPaymentState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
<?php
/**
* Class AbstractPaymentState
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\State;

use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;
use WCPay\Internal\Payment\PaymentContext;

/**
* Base class for payment states.
*/
abstract class AbstractPaymentState {
/**
* Holds the context of the payments.
*
* The context contains working payment data,
* while states are purely functional and stateless.
*
* @var PaymentContext
*/
private $context;

/**
* A factory for states.
*
* States can
*
* @var StateFactory
*/
private $state_factory;

/**
* Class constructor, only meant for storing dependencies.
*
* @param StateFactory $state_factory Factory for payment states.
*/
public function __construct( StateFactory $state_factory ) {
$this->state_factory = $state_factory;
}

/**
* Stores the payment context.
*
* @param PaymentContext $context Payment context.
*/
public function set_context( PaymentContext $context ) {
$this->context = $context;
}

/**
* Returns the payment context.
*
* @return PaymentContext
*/
public function get_context(): PaymentContext {
return $this->context;
}

/**
* Creates a new instance of a given payment state class.
*
* States control the payment flow, and allow transitions to the next state.
* This method should only be called whenever the process is ready to transition
* to the next state, as each new state will be considered the payment's latest one.
*
* @param string $state_class The class of the state to crate.
* @return AbstractPaymentState
* @throws StateTransitionException In case the new state could not be created.
* @throws ContainerException When the dependency container cannot instantiate the state.
*/
protected function create_state( string $state_class ) {
$state = $this->state_factory->create_state( $state_class, $this->context );

// This is where logging will be added.

return $state;
}

/**
* State-specific methods might declare a return type, but
* their hollow definitions here would only throw an exception.
* phpcs:disable Squiz.Commenting.FunctionComment.InvalidNoReturn
*/

/**
* Initialtes the payment process.
*
* @return AbstractPaymentState The next state.
* @throws StateTransitionException In case the new state was not found or could not be initialized.
* @psalm-suppress InvalidReturnType If this method does not throw, it will return a new state.
*/
public function process() {
$this->throw_unavailable_method_exception( __METHOD__ );
}

/**
* Throws an exception, indicating that a given method is not available.
*
* @param string $method_name The name of the called method.
* @throws StateTransitionException
*/
private function throw_unavailable_method_exception( string $method_name ) {
throw new StateTransitionException(
sprintf(
// translators: %1$s is the name of a method of the payment object, %2$s is its current state.
__( 'The %1$s method is not available in the current payment state (%2$s).', 'woocommerce-payments' ),
$method_name,
get_class( $this )
)
);
}
}
15 changes: 15 additions & 0 deletions src/Internal/Payment/State/CompletedState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Class CompletedState
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\State;

/**
* The state, which indicates that the payment processing has been completed.
*/
class CompletedState extends AbstractPaymentState {

}
27 changes: 27 additions & 0 deletions src/Internal/Payment/State/InitialState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php
/**
* Class InitialState
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\State;

use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;

/**
* Initial state, representing a freshly created payment.
*/
class InitialState extends AbstractPaymentState {
/**
* Initialtes the payment process.
*
* @return CompletedState The next state.
* @throws StateTransitionException In case the completed state could not be initialized.
* @throws ContainerException When the dependency container cannot instantiate the state.
*/
public function process() {
return $this->create_state( CompletedState::class );
}
}
63 changes: 63 additions & 0 deletions src/Internal/Payment/State/StateFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php
/**
* Class StateFactory
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\State;

use WCPay\Container;
use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;
use WCPay\Internal\Payment\PaymentContext;

/**
* A factory for payment states.
*
* The main purpose of this class is to work as an extension of the
* WooPayments dependency container, only allowing access to payment states
*/
class StateFactory {
/**
* Holds the WooPayments DI container.
*
* @var Container
*/
private $container;

/**
* Class constructor.
*
* @param Container $container Dependency container.
*/
public function __construct( Container $container ) {
$this->container = $container;
}

/**
* Creates a new state based on class name.
*
* @param string $state_class Name of the state class.
* @param PaymentContext $context Context for the new state.
* @return AbstractPaymentState The generated payment state instance.
* @throws ContainerException When the dependency container cannot instantiate the state.
* @throws StateTransitionException When the class name is not a state.
*/
public function create_state( string $state_class, PaymentContext $context ): AbstractPaymentState {
if ( ! is_subclass_of( $state_class, AbstractPaymentState::class ) ) {
throw new StateTransitionException(
sprintf(
// Translators: %1$s is the PHP class for a new payment state, %1$s is the state base class.
__( 'The class %1$s is not a subclass of %2$s', 'woocommerce-payments' ),
$state_class,
AbstractPaymentState::class
)
);
}

$state = $this->container->get( $state_class );
$state->set_context( $context );
return $state;
}
}
29 changes: 28 additions & 1 deletion src/Internal/Service/PaymentProcessingService.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,46 @@
namespace WCPay\Internal\Service;

use Exception; // Temporary exception! This service would have its own exception when more business logics are added.
use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\PaymentContext;
use WCPay\Internal\Payment\State\InitialState;
use WCPay\Internal\Payment\State\StateFactory;
use WCPay\Internal\Payment\Exception\StateTransitionException;

/**
* Payment Processing Service.
*/
class PaymentProcessingService {
/**
* Factory for states.
*
* @var StateFactory
*/
private $state_factory;

/**
* Service constructor.
*
* @param StateFactory $state_factory Factory for payment states.
*/
public function __construct( StateFactory $state_factory ) {
$this->state_factory = $state_factory;
}

/**
* Process payment.
*
* @param int $order_id Order ID provided by WooCommerce core.
*
* @throws Exception
* @throws StateTransitionException In case a state cannot be initialized.
* @throws ContainerException When the dependency container cannot instantiate the state.
*/
public function process_payment( int $order_id ) {
throw new Exception( 'Re-engineering payment process is in-progress. Sit tight, and wait more!' );
$context = new PaymentContext();
$state = $this->state_factory->create_state( InitialState::class, $context );
$state = $state->process();

return $state;
}
}
Loading

0 comments on commit 95125fd

Please sign in to comment.