-
Notifications
You must be signed in to change notification settings - Fork 72
RPP: Added error states #7739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
RPP: Added error states #7739
Changes from all commits
212ab30
025bb0c
1e8d502
5c5bc02
5ee1787
86e7f9f
b824b02
31cdd32
0a7c143
42f1538
8ca320a
5dcee81
dd9ca33
a7c1179
b8cf96b
24d0ec7
ca79944
6468323
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
Significance: minor | ||
Type: add | ||
|
||
Added error states in new payment processing flow. |
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -37,6 +37,13 @@ class PaymentContext { | |||||
*/ | ||||||
private $transitions = []; | ||||||
|
||||||
/** | ||||||
* Stores exception that is triggered before error state is created. | ||||||
* | ||||||
* @var \Exception | ||||||
*/ | ||||||
private $exception; | ||||||
|
||||||
/** | ||||||
* Constructs the class, receiving an order ID. | ||||||
* | ||||||
|
@@ -300,6 +307,24 @@ public function get_transitions(): array { | |||||
return $this->transitions; | ||||||
} | ||||||
|
||||||
/** | ||||||
* Stores state exception. | ||||||
* | ||||||
* @param \Exception $exception Exception to store. | ||||||
*/ | ||||||
public function set_exception( $exception ) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not insisting on this, but I'd prefer to call those Also, please add a proper type hint to the method:
Suggested change
Similar,y |
||||||
$this->set( 'exception', $exception ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Get state exception | ||||||
* | ||||||
* @return \Exception|null | ||||||
*/ | ||||||
public function get_exception() { | ||||||
return $this->get( 'exception' ); | ||||||
} | ||||||
|
||||||
/** | ||||||
* Sets the mode (test or prod). | ||||||
* | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
/** | ||
* Class AbstractPaymentErrorState | ||
* | ||
* @package WooCommerce\Payments | ||
*/ | ||
|
||
namespace WCPay\Internal\Payment\State; | ||
|
||
use Exception; | ||
use WCPay\Internal\Logger; | ||
use WCPay\Internal\Service\OrderService; | ||
|
||
/** | ||
* Base abstract class for all error states that will share common error state logic. | ||
*/ | ||
abstract class AbstractPaymentErrorState extends AbstractPaymentState { | ||
|
||
/** | ||
* Logger instance. | ||
* | ||
* @var Logger | ||
*/ | ||
private $logger; | ||
|
||
/** | ||
* Order service. | ||
* | ||
* @var OrderService | ||
*/ | ||
private $order_service; | ||
|
||
/** | ||
* Class constructor. | ||
* | ||
* @param StateFactory $state_factory State factory. | ||
* @param Logger $logger Logger service. | ||
* @param OrderService $order_service Order service. | ||
*/ | ||
public function __construct( StateFactory $state_factory, Logger $logger, OrderService $order_service ) { | ||
parent::__construct( $state_factory ); | ||
$this->logger = $logger; | ||
$this->order_service = $order_service; | ||
} | ||
|
||
/** | ||
* Handle error state. | ||
* | ||
* @throws Exception | ||
*/ | ||
public function handle_error_state() { | ||
$context = $this->get_context(); | ||
$order_id = $context->get_order_id(); | ||
$exception = $context->get_exception(); | ||
if ( $this->should_log_error() ) { | ||
$this->logger->error( "Failed to process order with ID: $order_id . Reason: " . $exception ); | ||
} | ||
|
||
if ( $this->should_mark_order_as_failed() ) { | ||
$reason = ''; | ||
if ( $exception ) { | ||
$reason = $exception->getMessage(); | ||
} | ||
$this->order_service->mark_order_as_failed( $order_id, $reason ); | ||
} | ||
|
||
// After everything is done, just throw the same exception and that's it. Gateway will pick it up and do its own thing. | ||
if ( null !== $exception ) { | ||
throw $exception; | ||
} | ||
throw new Exception( __( 'The payment process could not be completed.', 'woocommerce-payments' ) ); | ||
} | ||
|
||
/** | ||
* Determines whether an error should be logged. | ||
* | ||
* @return bool True if the error should be logged, otherwise false. | ||
*/ | ||
protected function should_log_error(): bool { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shouldn't all errors be logged by default? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think they need to, but I wasn't 100% certain, so I made it to false. We can easily set that all error states needs to be logged, by the default. |
||
return false; | ||
} | ||
Comment on lines
+74
to
+81
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Logging of the whole context is already built, and specific errors (ex. API errors) are logged already. I'd remove the logging functionality from erroneous states altogether. |
||
|
||
/** | ||
* Determines whether the order should be marked as failed. | ||
* | ||
* @return bool True if the order should be marked as failed, otherwise false. | ||
*/ | ||
protected function should_mark_order_as_failed(): bool { | ||
return false; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -106,49 +106,44 @@ public function __construct( | |
* | ||
* @param PaymentRequest $request The incoming payment processing request. | ||
* | ||
* @return AbstractPaymentState The next state. | ||
* @throws StateTransitionException In case the completed state could not be initialized. | ||
* @throws ContainerException When the dependency container cannot instantiate the state. | ||
* @throws Order_Not_Found_Exception Order could not be found. | ||
* @throws PaymentRequestException When data is not available or invalid. | ||
* @throws API_Exception When server request fails. | ||
* @throws Amount_Too_Small_Exception When the order amount is too small. | ||
* @return AbstractPaymentState The next state. | ||
* @throws StateTransitionException In case the completed state could not be initialized. | ||
* @throws ContainerException In case DI has exception. | ||
*/ | ||
public function start_processing( PaymentRequest $request ) { | ||
// Populate basic details from the request. | ||
$this->populate_context_from_request( $request ); | ||
|
||
// Populate further details from the order. | ||
$this->populate_context_from_order(); | ||
|
||
// Start multiple verification checks. | ||
$this->process_order_phone_number(); | ||
|
||
$duplicate_order_result = $this->process_duplicate_order(); | ||
if ( null !== $duplicate_order_result ) { | ||
return $duplicate_order_result; | ||
} | ||
|
||
$duplicate_payment_result = $this->process_duplicate_payment(); | ||
if ( null !== $duplicate_payment_result ) { | ||
return $duplicate_payment_result; | ||
} | ||
|
||
$context = $this->get_context(); | ||
$this->minimum_amount_service->verify_amount( | ||
$context->get_currency(), | ||
$context->get_amount() | ||
); | ||
// End multiple verification checks. | ||
|
||
/** | ||
* Payments are based on intents, and intents use customer objects for billing details. | ||
* | ||
* The customer is created/updated right before requesting the creation of | ||
* a payment intent, and the two actions must be adjacent to each-other. | ||
*/ | ||
try { | ||
$context = $this->get_context(); | ||
$this->populate_context_from_request( $request ); | ||
// Populate further details from the order. | ||
$this->populate_context_from_order(); | ||
|
||
// Start multiple verification checks. | ||
$this->process_order_phone_number(); | ||
|
||
$duplicate_order_result = $this->process_duplicate_order(); | ||
if ( null !== $duplicate_order_result ) { | ||
return $duplicate_order_result; | ||
} | ||
|
||
$duplicate_payment_result = $this->process_duplicate_payment(); | ||
if ( null !== $duplicate_payment_result ) { | ||
return $duplicate_payment_result; | ||
} | ||
|
||
/** | ||
* Payments are based on intents, and intents use customer objects for billing details. | ||
* | ||
* The customer is created/updated right before requesting the creation of | ||
* a payment intent, and the two actions must be adjacent to each-other. | ||
*/ | ||
Comment on lines
+133
to
+138
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This docblock is completely out of place now. |
||
|
||
$context = $this->get_context(); | ||
|
||
$this->minimum_amount_service->verify_amount( | ||
$context->get_currency(), | ||
$context->get_amount() | ||
); | ||
|
||
$order_id = $context->get_order_id(); | ||
|
||
// Create or update customer and customer details. | ||
|
@@ -161,23 +156,34 @@ public function start_processing( PaymentRequest $request ) { | |
// After customer is updated or created, make sure that intent is created. | ||
$intent = $this->payment_request_service->create_intent( $context ); | ||
$context->set_intent( $intent ); | ||
} catch ( Amount_Too_Small_Exception $e ) { | ||
$this->minimum_amount_service->store_amount_from_exception( $e ); | ||
throw $e; | ||
} catch ( Invalid_Request_Parameter_Exception | Extend_Request_Exception | Immutable_Parameter_Exception $e ) { | ||
return $this->create_state( SystemErrorState::class ); | ||
} | ||
|
||
// Intent requires authorization (3DS check). | ||
if ( Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { | ||
$this->order_service->update_order_from_intent_that_requires_action( $order_id, $intent, $context ); | ||
return $this->create_state( AuthenticationRequiredState::class ); | ||
} | ||
// Intent requires authorization (3DS check). | ||
if ( Intent_Status::REQUIRES_ACTION === $intent->get_status() ) { | ||
$this->order_service->update_order_from_intent_that_requires_action( $order_id, $intent, $context ); | ||
return $this->create_state( AuthenticationRequiredState::class ); | ||
} | ||
|
||
// All good. Proceed to processed state. | ||
$next_state = $this->create_state( ProcessedState::class ); | ||
// All good. Proceed to processed state. | ||
$next_state = $this->create_state( ProcessedState::class ); | ||
|
||
return $next_state->complete_processing(); | ||
return $next_state->complete_processing(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By positioning this line within the |
||
} catch ( Amount_Too_Small_Exception $e ) { | ||
$this->minimum_amount_service->store_amount_from_exception( $e ); | ||
return $this->create_error_state( SystemErrorState::class, $e ); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The amount being too small is not a system exception, it happens whenever the amount to pay is less than the required minimum. This is not an indication that there's something wrong with the system, just a payment error. |
||
} catch ( | ||
Invalid_Request_Parameter_Exception | | ||
Extend_Request_Exception | | ||
Immutable_Parameter_Exception | | ||
Order_Not_Found_Exception | | ||
StateTransitionException | | ||
ContainerException $e | ||
) { | ||
return $this->create_error_state( SystemErrorState::class, $e ); | ||
} catch ( API_Exception $e ) { | ||
return $this->create_error_state( WooPaymentsApiServerErrorState::class, $e ); | ||
} catch ( PaymentRequestException $e ) { | ||
return $this->create_error_state( PaymentRequestErrorState::class, $e ); | ||
} | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like request classes, there is no need to define a property of the object: It's all stored within
data
. With that in mind, this one is not used, and can be removed.