Skip to content
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

RPP - Connect the duplicate payment prevention service (Verification) #7450

Merged
merged 60 commits into from
Oct 26, 2023
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
f18f0f5
Copy and register Duplicate_Payment_Prevention_Service to src
htdat Oct 10, 2023
97d2059
Remove WC_Payment_Gateway_WCPay dependency, update OrderService to pr…
htdat Oct 10, 2023
2894f8f
Move const SUCCESSFUL_INTENT_STATUS in gateway to Intent_Status data …
htdat Oct 11, 2023
048de80
Replace methods for order object with respective OrderService methods
htdat Oct 12, 2023
3d76492
Replace init with __construct for DPPS.
htdat Oct 12, 2023
a965a55
Update WC()->session as a dependency
htdat Oct 13, 2023
b4e57e3
Update is_order_received_page and global $wp to call via LegacyProxy
htdat Oct 13, 2023
e519d52
Change to use WC_Session to use translation layer SessionService
htdat Oct 13, 2023
15dc967
Handling previously paid orders with TODOs
htdat Oct 13, 2023
a8c478b
Handle getting the order ID from PreviousPaidOrderDetectedState
htdat Oct 16, 2023
cc8e0b3
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 17, 2023
deecd84
Update correct method name set_previous_paid_order_id
htdat Oct 17, 2023
15f51d1
Separate detecting duplicate order, and clean up actions
htdat Oct 17, 2023
5fd1be4
Update naming - use "duplicate order"
htdat Oct 17, 2023
ad88db8
Move down the verification step of get_previous_paid_duplicate_order_id
htdat Oct 17, 2023
ce69ac1
Remove maybe, use update_session_processing_order
htdat Oct 17, 2023
53e4dfb
Correct return url when handle DuplicateOrderDetectedState
htdat Oct 17, 2023
e7fd1a9
Handling existing successful intents, change name to authorized intents
htdat Oct 17, 2023
2a3d462
Update to use is_authorized for intents
htdat Oct 17, 2023
9b41472
Address reviews (simple changes)
htdat Oct 18, 2023
d09d1e0
Address review - remove OrderService::update_order_status_from_intent
htdat Oct 18, 2023
9cb3e3a
Update handling get_authorized_payment_intent_attached_to_order
htdat Oct 18, 2023
01e204c
Address review - Use LegacyProxy for `wc_get_is_paid_statuses`
htdat Oct 18, 2023
0678ad0
Add more checks for get_previous_paid_duplicate_order_id
htdat Oct 18, 2023
96e79e8
Tests - fix InitialStateTest
htdat Oct 18, 2023
ee0b6fd
Fix Psalm errors
htdat Oct 18, 2023
b519c99
Add tests for OrderService
htdat Oct 18, 2023
1b2f985
Add tests for SessionService
htdat Oct 18, 2023
abe3480
Add tests for PaymentContext
htdat Oct 18, 2023
b75d401
Move duplicate logics to private functions in InitialState
htdat Oct 19, 2023
7f6a1b4
Add DuplicatePaymentPreventionServiceTest
htdat Oct 19, 2023
e9e5674
Add tests for get_authorized_payment_intent_attached_to_order
htdat Oct 19, 2023
f211d4f
Add WooPay button location option (#7431)
mdmoore Oct 17, 2023
7a64e25
Add feature flag check to early WooPay session request (#7459)
bborman22 Oct 18, 2023
f0183ae
Adding Internal Logger (#7462)
jessy-p Oct 18, 2023
f62868d
Replace Logger as dep, and add tests for it
htdat Oct 19, 2023
162f8a7
Fix logic
htdat Oct 19, 2023
800e9ce
Add test_get_previous_paid_duplicate_order_id
htdat Oct 19, 2023
5763bc8
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 23, 2023
2eb79a6
Add PaymentContextTest::test_intent from PR 7471
htdat Oct 23, 2023
3d2caef
InitialStateTest - fix test_process due to new protected methods
htdat Oct 23, 2023
1397459
Add test_process_then_detected_duplicates for InitialState
htdat Oct 23, 2023
5728092
Add tests for process_duplicate_order and process_duplicate_payment
htdat Oct 23, 2023
95ed11b
Register the hook in the new service, remove the similar one in the l…
htdat Oct 23, 2023
d12e3e8
Fix psalm errors
htdat Oct 23, 2023
c8f5497
Add changelog
htdat Oct 23, 2023
bb8abea
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 23, 2023
cd68516
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 23, 2023
6f849c4
Fix InitialStateTest after the merge
htdat Oct 23, 2023
beafe0c
Update @todo to ProcessedState
htdat Oct 23, 2023
51d012b
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 25, 2023
621dd12
Add mocked_suit in DuplicatePaymentPreventionServiceTest
htdat Oct 25, 2023
c7de0d1
Simplify detecting payment intent
htdat Oct 25, 2023
963541d
Multiple nitpicks - spaces matter
htdat Oct 25, 2023
1f23264
Update more specific return types
htdat Oct 25, 2023
32f45fa
Merge branch 'develop' into rpp/7411-duplicate-prevention
RadoslavGeorgiev Oct 25, 2023
e688822
Changer $this->sut to $this->mocked_sut
htdat Oct 26, 2023
ff25309
Move back to the local var $mock_sut
htdat Oct 26, 2023
c7a932d
Move remove_session_processing_order to transition from ProcessedStat…
htdat Oct 26, 2023
f273a60
Merge branch 'develop' into rpp/7411-duplicate-prevention
htdat Oct 26, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions includes/admin/class-wc-rest-payments-orders-controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ public function capture_terminal_payment( WP_REST_Request $request ) {
Logger::error( 'Payment capture rejected due to failed validation: order id on intent is incorrect or missing.' );
return new WP_Error( 'wcpay_intent_order_mismatch', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
}
if ( ! in_array( $intent->get_status(), WC_Payment_Gateway_WCPay::SUCCESSFUL_INTENT_STATUS, true ) ) {
if ( ! in_array( $intent->get_status(), Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
}

Expand Down Expand Up @@ -283,7 +283,7 @@ public function capture_authorization( WP_REST_Request $request ) {
Logger::error( 'Payment capture rejected due to failed validation: order id on intent is incorrect or missing.' );
return new WP_Error( 'wcpay_intent_order_mismatch', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
}
if ( ! in_array( $intent->get_status(), WC_Payment_Gateway_WCPay::SUCCESSFUL_INTENT_STATUS, true ) ) {
if ( ! in_array( $intent->get_status(), Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
return new WP_Error( 'wcpay_payment_uncapturable', __( 'The payment cannot be captured', 'woocommerce-payments' ), [ 'status' => 409 ] );
}

Expand Down
2 changes: 1 addition & 1 deletion includes/class-duplicate-payment-prevention-service.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public function check_payment_intent_attached_to_order_succeeded( WC_Order $orde
return;
};

if ( ! in_array( $intent_status, WC_Payment_Gateway_WCPay::SUCCESSFUL_INTENT_STATUS, true ) ) {
if ( ! in_array( $intent_status, Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
return;
}

Expand Down
25 changes: 12 additions & 13 deletions includes/class-wc-payment-gateway-wcpay.php
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
use WCPay\Duplicate_Payment_Prevention_Service;
use WCPay\Fraud_Prevention\Fraud_Prevention_Service;
use WCPay\Fraud_Prevention\Fraud_Risk_Tools;
use WCPay\Internal\Payment\State\PreviousPaidOrderDetectedState;
use WCPay\Internal\Service\DuplicatePaymentPreventionService;
use WCPay\Logger;
use WCPay\Payment_Information;
use WCPay\Payment_Methods\UPE_Payment_Gateway;
Expand Down Expand Up @@ -90,17 +92,6 @@ class WC_Payment_Gateway_WCPay extends WC_Payment_Gateway_CC {
'deposit_schedule_monthly_anchor' => 'deposit_schedule_monthly_anchor',
];

/**
* Stripe intents that are treated as successfully created.
*
* @type array
*/
const SUCCESSFUL_INTENT_STATUS = [
Intent_Status::SUCCEEDED,
Intent_Status::REQUIRES_CAPTURE,
Intent_Status::PROCESSING,
];

const UPDATE_SAVED_PAYMENT_METHOD = 'wcpay_update_saved_payment_method';

/**
Expand Down Expand Up @@ -819,6 +810,14 @@ public function new_process_payment( WC_Order $order ) {
$service = wcpay_get_container()->get( PaymentProcessingService::class );
$state = $service->process_payment( $order->get_id(), $manual_capture );

if ( $state instanceof PreviousPaidOrderDetectedState ) {
return [
'result' => 'success',
'redirect' => $this->get_return_url( $order ), // TODO: This should be PaymentContext::get_previous_order_id(). But how to get it as we do expose PaymentContext here at the moment.
htdat marked this conversation as resolved.
Show resolved Hide resolved
DuplicatePaymentPreventionService::FLAG_PREVIOUS_ORDER_PAID => 'yes',
];
}

if ( $state instanceof CompletedState ) {
return [
'result' => 'success',
Expand Down Expand Up @@ -1383,7 +1382,7 @@ public function process_payment_for_order( $cart, $payment_information, $schedul
}

if ( ! empty( $intent ) ) {
if ( ! in_array( $status, self::SUCCESSFUL_INTENT_STATUS, true ) ) {
if ( ! in_array( $status, Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
$intent_failed = true;
}

Expand Down Expand Up @@ -2972,7 +2971,7 @@ public function update_order_status() {
}
$this->order_service->update_order_status_from_intent( $order, $intent );

if ( in_array( $status, self::SUCCESSFUL_INTENT_STATUS, true ) ) {
if ( in_array( $status, Intent_Status::SUCCESSFUL_STATUSES, true ) ) {
wc_reduce_stock_levels( $order_id );
WC()->cart->empty_cart();

Expand Down
11 changes: 11 additions & 0 deletions includes/constants/class-intent-status.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,15 @@ class Intent_Status extends Base_Constant {
const REQUIRES_CAPTURE = 'requires_capture';
const CANCELED = 'canceled';
const SUCCEEDED = 'succeeded';

/**
* Stripe intents that are treated as successfully created.
*
* @type array
*/
const SUCCESSFUL_STATUSES = [
htdat marked this conversation as resolved.
Show resolved Hide resolved
self::SUCCEEDED,
self::REQUIRES_CAPTURE,
self::PROCESSING,
];
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
use WCPay\Internal\Proxy\LegacyProxy;
use WCPay\Internal\Service\Level3Service;
use WCPay\Internal\Service\OrderService;
use WCPay\Internal\Service\SessionService;

/**
* WCPay payments generic service provider.
Expand Down Expand Up @@ -45,5 +46,8 @@ public function register(): void {
->addArgument( OrderService::class )
->addArgument( WC_Payments_Account::class )
->addArgument( LegacyProxy::class );

$container->addShared( SessionService::class )
->addArgument( LegacyProxy::class );
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,19 @@
use WCPay\Internal\Payment\State\CompletedState;
use WCPay\Internal\Payment\State\InitialState;
use WCPay\Internal\Payment\State\PaymentErrorState;
use WCPay\Internal\Payment\State\PreviousPaidOrderDetectedState;
use WCPay\Internal\Payment\State\StateFactory;
use WCPay\Internal\Payment\State\SystemErrorState;
use WCPay\Internal\Proxy\HooksProxy;
use WCPay\Internal\Proxy\LegacyProxy;
use WCPay\Internal\Service\DuplicatePaymentPreventionService;
use WCPay\Internal\Service\PaymentProcessingService;
use WCPay\Internal\Service\ExampleService;
use WCPay\Internal\Service\ExampleServiceWithDependencies;
use WCPay\Internal\Service\Level3Service;
use WCPay\Internal\Service\OrderService;
use WCPay\Internal\Service\PaymentRequestService;
use WCPay\Internal\Service\SessionService;

/**
* WCPay payments service provider.
Expand All @@ -47,6 +51,7 @@ class PaymentsServiceProvider extends AbstractServiceProvider {
ExampleService::class,
ExampleServiceWithDependencies::class,
PaymentRequestService::class,
DuplicatePaymentPreventionService::class,
htdat marked this conversation as resolved.
Show resolved Hide resolved
];

/**
Expand All @@ -64,12 +69,21 @@ public function register(): void {

$container->addShared( PaymentRequestService::class );

$container->addShared( PaymentRequestService::class );
htdat marked this conversation as resolved.
Show resolved Hide resolved

$container->addShared( DuplicatePaymentPreventionService::class )
->addArgument( OrderService::class )
->addArgument( SessionService::class )
->addArgument( HooksProxy::class )
->addArgument( LegacyProxy::class );

$container->add( InitialState::class )
->addArgument( StateFactory::class )
->addArgument( OrderService::class )
->addArgument( WC_Payments_Customer_Service::class )
->addArgument( Level3Service::class )
->addArgument( PaymentRequestService::class );
->addArgument( PaymentRequestService::class )
->addArgument( DuplicatePaymentPreventionService::class );

$container->add( CompletedState::class )
->addArgument( StateFactory::class );
Expand All @@ -80,6 +94,9 @@ public function register(): void {
$container->add( PaymentErrorState::class )
->addArgument( StateFactory::class );

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

$container->addShared( Router::class )
->addArgument( Database_Cache::class );

Expand Down
20 changes: 20 additions & 0 deletions src/Internal/Payment/PaymentContext.php
Original file line number Diff line number Diff line change
Expand Up @@ -245,4 +245,24 @@ public function set_customer_id( string $customer_id ) {
public function get_customer_id(): ?string {
return $this->get( 'customer_id' );
}

/**
* Set the previous paid order ID.
*
* @param int $paid_session_order_id Paid session order ID.
*
* @return void
*/
public function set_previous_order_id( int $paid_session_order_id ) {
$this->set( 'previous_paid_order_id', $paid_session_order_id );
}

/**
* Gets the previous paid order ID.
*
* @return int|null
*/
public function get_previous_order_id(): ?int {
return $this->get( 'previous_paid_order_id' );
}
}
37 changes: 27 additions & 10 deletions src/Internal/Payment/State/InitialState.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
namespace WCPay\Internal\Payment\State;

use WC_Payments_Customer_Service;
use WCPay\Internal\Service\DuplicatePaymentPreventionService;
use WCPay\Vendor\League\Container\Exception\ContainerException;
use WCPay\Internal\Payment\Exception\StateTransitionException;
use WCPay\Internal\Service\OrderService;
Expand Down Expand Up @@ -53,43 +54,59 @@ class InitialState extends AbstractPaymentState {
*/
private $payment_request_service;

/**
* Duplicate Payment Prevention service.
*
* @var DuplicatePaymentPreventionService
*/
private $dpps;

/**
* Class constructor, only meant for storing dependencies.
*
* @param StateFactory $state_factory Factory for payment states.
* @param OrderService $order_service Service for order-related actions.
* @param WC_Payments_Customer_Service $customer_service Service for managing remote customers.
* @param Level3Service $level3_service Service for Level3 Data.
* @param PaymentRequestService $payment_request_service Connection with the server.
* @param StateFactory $state_factory Factory for payment states.
* @param OrderService $order_service Service for order-related actions.
* @param WC_Payments_Customer_Service $customer_service Service for managing remote customers.
* @param Level3Service $level3_service Service for Level3 Data.
* @param PaymentRequestService $payment_request_service Connection with the server.
* @param DuplicatePaymentPreventionService $dpps Service for preventing duplicate payments.
*/
public function __construct(
StateFactory $state_factory,
OrderService $order_service,
WC_Payments_Customer_Service $customer_service,
Level3Service $level3_service,
PaymentRequestService $payment_request_service
PaymentRequestService $payment_request_service,
DuplicatePaymentPreventionService $dpps
) {
parent::__construct( $state_factory );

$this->order_service = $order_service;
$this->customer_service = $customer_service;
$this->level3_service = $level3_service;
$this->payment_request_service = $payment_request_service;
$this->dpps = $dpps;
}

/**
* Initialtes the payment process.
* Initiates the payment process.
*
* @param PaymentRequest $request The incoming payment processing request.
* @return CompletedState The next state.
* @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.
*/
public function process( PaymentRequest $request ) {
$context = $this->get_context();
$order_id = $context->get_order_id();
$context = $this->get_context();
$order_id = $context->get_order_id();
$paid_session_order_id = $this->dpps->get_paid_session_processing_order( $order_id );
if ( ! is_null( $paid_session_order_id ) ) {
// TODO: move the add note, and order deletion from get_paid_session_processing_order to here.
$context->set_previous_order_id( $paid_session_order_id );
return $this->create_state( PreviousPaidOrderDetectedState::class );
}

// Populate basic details from the request.
$this->populate_context_from_request( $request );
Expand Down
15 changes: 15 additions & 0 deletions src/Internal/Payment/State/PreviousPaidOrderDetectedState.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
/**
* Class PreviousPaidOrderDetectedState
*
* @package WooCommerce\Payments
*/

namespace WCPay\Internal\Payment\State;

/**
* State used when a recent previous paid order from the same customer with the same cart hash is detected.
*/
class PreviousPaidOrderDetectedState extends AbstractPaymentState {

}
Loading