diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml index a313650bb..1a330de35 100644 --- a/.github/workflows/php.yml +++ b/.github/workflows/php.yml @@ -29,4 +29,4 @@ jobs: - name: Run test suite run: ./vendor/bin/phpunit - name: Run Woocommerce coding standards - run: ./vendor/bin/phpcs src inc modules --extensions=php + run: ./vendor/bin/phpcs src modules --extensions=php diff --git a/modules/ppcp-session/src/class-sessionhandler.php b/modules/ppcp-session/src/class-sessionhandler.php index 380abbbaf..49ca77dc1 100644 --- a/modules/ppcp-session/src/class-sessionhandler.php +++ b/modules/ppcp-session/src/class-sessionhandler.php @@ -32,6 +32,14 @@ class SessionHandler { */ private $bn_code = ''; + /** + * If PayPal respondes with INSTRUMENT_DECLINED, we only + * want to go max. three times through the process of trying again. + * + * @var int + */ + private $insufficient_funding_tries = 0; + /** * Returns the order. * @@ -76,14 +84,35 @@ class SessionHandler { return $this; } + /** + * Returns how many times the customer tried to use the PayPal Gateway in this session. + * + * @return int + */ + public function insufficient_funding_tries() : int { + return $this->insufficient_funding_tries; + } + + /** + * Increments the number of tries, the customer has done in this session. + * + * @return SessionHandler + */ + public function increment_insufficient_funding_tries() : SessionHandler { + $this->insufficient_funding_tries++; + $this->store_session(); + return $this; + } + /** * Destroys the session data. * * @return SessionHandler */ public function destroy_session_data() : SessionHandler { - $this->order = null; - $this->bn_code = ''; + $this->order = null; + $this->bn_code = ''; + $this->insufficient_funding_tries = 0; $this->store_session(); return $this; } diff --git a/modules/ppcp-wc-gateway/src/Endpoint/class-returnurlendpoint.php b/modules/ppcp-wc-gateway/src/Endpoint/class-returnurlendpoint.php index 4c3ceb57f..efdc640c9 100644 --- a/modules/ppcp-wc-gateway/src/Endpoint/class-returnurlendpoint.php +++ b/modules/ppcp-wc-gateway/src/Endpoint/class-returnurlendpoint.php @@ -77,6 +77,14 @@ class ReturnUrlEndpoint { $success = $this->gateway->process_payment( $wc_order_id ); if ( isset( $success['result'] ) && 'success' === $success['result'] ) { + add_filter( + 'allowed_redirect_hosts', + function( $allowed_hosts ) : array { + $allowed_hosts[] = 'www.paypal.com'; + $allowed_hosts[] = 'www.sandbox.paypal.com'; + return (array) $allowed_hosts; + } + ); wp_safe_redirect( $success['redirect'] ); exit(); } diff --git a/modules/ppcp-wc-gateway/src/Gateway/class-paypalgateway.php b/modules/ppcp-wc-gateway/src/Gateway/class-paypalgateway.php index c117a9c23..7b1372fda 100644 --- a/modules/ppcp-wc-gateway/src/Gateway/class-paypalgateway.php +++ b/modules/ppcp-wc-gateway/src/Gateway/class-paypalgateway.php @@ -184,6 +184,7 @@ class PayPalGateway extends \WC_Payment_Gateway { */ //phpcs:disable WordPress.Security.NonceVerification.Recommended if ( isset( $_REQUEST['ppcp-resume-order'] ) && $wc_order->has_status( 'processing' ) ) { + $this->session_handler->destroy_session_data(); return array( 'result' => 'success', 'redirect' => $this->get_return_url( $wc_order ), @@ -193,6 +194,7 @@ class PayPalGateway extends \WC_Payment_Gateway { try { if ( $this->order_processor->process( $wc_order, $woocommerce ) ) { + $this->session_handler->destroy_session_data(); return array( 'result' => 'success', 'redirect' => $this->get_return_url( $wc_order ), @@ -200,10 +202,18 @@ class PayPalGateway extends \WC_Payment_Gateway { } } catch ( PayPalApiException $error ) { if ( $error->has_detail( 'INSTRUMENT_DECLINED' ) ) { + $this->session_handler->increment_insufficient_funding_tries(); $host = $this->config->has( 'sandbox_on' ) && $this->config->get( 'sandbox_on' ) ? 'https://www.sandbox.paypal.com/' : 'https://www.paypal.com/'; $url = $host . 'checkoutnow?token=' . $this->session_handler->order()->id(); - + if ( $this->session_handler->insufficient_funding_tries() >= 3 ) { + $this->session_handler->destroy_session_data(); + wc_add_notice( + __( 'Please use a different payment method.', 'paypal-for-woocommerce' ), + 'error' + ); + return null; + } return array( 'result' => 'success', 'redirect' => $url, diff --git a/modules/ppcp-wc-gateway/src/Processor/class-orderprocessor.php b/modules/ppcp-wc-gateway/src/Processor/class-orderprocessor.php index 7f0ec013a..47373c2e8 100644 --- a/modules/ppcp-wc-gateway/src/Processor/class-orderprocessor.php +++ b/modules/ppcp-wc-gateway/src/Processor/class-orderprocessor.php @@ -131,7 +131,7 @@ class OrderProcessor { */ public function process( \WC_Order $wc_order, \WooCommerce $woocommerce ): bool { $order = $this->session_handler->order(); - if (! $order) { + if ( ! $order ) { return false; } $wc_order->update_meta_data( PayPalGateway::ORDER_ID_META_KEY, $order->id() ); diff --git a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php index 785108f03..a1260a248 100644 --- a/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php +++ b/tests/PHPUnit/WcGateway/Gateway/WcGatewayTest.php @@ -37,6 +37,8 @@ class WcGatewayTest extends TestCase $authorizedOrderActionNotice = Mockery::mock(AuthorizeOrderActionNotice::class); $settings = Mockery::mock(Settings::class); $sessionHandler = Mockery::mock(SessionHandler::class); + $sessionHandler + ->shouldReceive('destroy_session_data'); $settings ->shouldReceive('has')->andReturnFalse(); $testee = new PayPalGateway(