diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
index bc32188f1c5d4a3cdce7c071288f90182d663e01..231e4bcd13d841dd0de12bc7458d0b58018cb379 100644
--- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
@@ -137,7 +137,7 @@ class PaymentInformation extends PaymentCheckoutPaneBase {
     /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
     $payment_gateway = $this->order->get('payment_gateway')->entity;
     $summary = [];
-    if ($this->collectBillingProfileOnly() || !$payment_gateway) {
+    if (!$this->needsPayment()) {
       if ($billing_profile) {
         // Only the billing information was collected.
         $view_builder = $this->entityTypeManager->getViewBuilder('profile');
@@ -175,10 +175,9 @@ class PaymentInformation extends PaymentCheckoutPaneBase {
    * {@inheritdoc}
    */
   public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
-    if ($this->collectBillingProfileOnly()) {
-      // No payment is needed if we don't require payment method collection,
-      // and the order balance is zero. In that case, collect just the billing
-      // information.
+    if (!$this->order->getTotalPrice() || !$this->needsPayment() ) {
+      // No payment is needed if the order is free or has already been paid.
+      // In that case, collect just the billing information.
       $pane_form['#title'] = $this->t('Billing information');
       $pane_form = $this->buildBillingProfileForm($pane_form, $form_state);
       return $pane_form;
@@ -386,7 +385,7 @@ class PaymentInformation extends PaymentCheckoutPaneBase {
    * {@inheritdoc}
    */
   public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
-    if ($this->collectBillingProfileOnly()) {
+    if (!$this->order->getTotalPrice() || !$this->needsPayment()) {
       return;
     }
 
@@ -409,7 +408,7 @@ class PaymentInformation extends PaymentCheckoutPaneBase {
       // The billing profile is provided either because the order is free,
       // or the selected gateway does not support stored payment methods.
       // If it's the former, stop here.
-      if ($this->collectBillingProfileOnly()) {
+      if (!$this->needsPayment()) {
         return;
       }
     }
@@ -514,4 +513,26 @@ class PaymentInformation extends PaymentCheckoutPaneBase {
     return $message;
   }
 
+  /**
+   * Checks whether or not the order still needs payment.
+   *
+   * The order doesn't needs payment if:
+   * - The order is already paid;
+   * - The order has no total price;
+   * - The order has a total price of zero.
+   *
+   * @return bool
+   *   TRUE if the order still need to be paid, FALSE otherwise.
+   */
+  protected function needsPayment() {
+    if ($this->order->isPaid()) {
+      return FALSE;
+    }
+    if (!$this->order->getTotalPrice() || $this->order->getTotalPrice()->isZero()) {
+      // No total price or a total price of zero.
+      return FALSE;
+    }
+    return TRUE;
+  }
+
 }
diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php
index 51480526103923f643c1a627c9be7b9ebd424fe6..2a1e407d31e25b3ee3af4236169c11da4d2c43e6 100644
--- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php
+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentProcess.php
@@ -110,6 +110,10 @@ class PaymentProcess extends PaymentCheckoutPaneBase {
    * {@inheritdoc}
    */
   public function isVisible() {
+    if (!$this->needsPayment()) {
+      // No payment is needed if the order is free or has already been paid.
+      return FALSE;
+    }
     $payment_info_pane = $this->checkoutFlow->getPane('payment_information');
     if (!$payment_info_pane->isVisible() || $payment_info_pane->getStepId() == '_disabled') {
       // Hide the pane if the PaymentInformation pane has been disabled.
@@ -249,6 +253,28 @@ class PaymentProcess extends PaymentCheckoutPaneBase {
     return $step_id;
   }
 
+  /**
+   * Checks whether or not the order still needs payment.
+   *
+   * The order doesn't needs payment if:
+   * - The order is already paid;
+   * - The order has no total price;
+   * - The order has a total price of zero.
+   *
+   * @return bool
+   *   TRUE if the order still need to be paid, FALSE otherwise.
+   */
+  protected function needsPayment() {
+    if ($this->order->isPaid()) {
+      return FALSE;
+    }
+    if (!$this->order->getTotalPrice() || $this->order->getTotalPrice()->isZero()) {
+      // No total price or a total price of zero.
+      return FALSE;
+    }
+    return TRUE;
+  }
+
   /**
    * Creates the payment to be processed.
    *
diff --git a/modules/payment/tests/src/Functional/PaymentCheckoutTest.php b/modules/payment/tests/src/Functional/PaymentCheckoutTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..2257ce7b7617af035fee30ca7c72b6a73c2f6ccc
--- /dev/null
+++ b/modules/payment/tests/src/Functional/PaymentCheckoutTest.php
@@ -0,0 +1,220 @@
+<?php
+
+namespace Drupal\Tests\commerce_payment\Functional;
+
+use Drupal\commerce_order\Entity\OrderItem;
+use Drupal\commerce_payment\Entity\PaymentGateway;
+use Drupal\Tests\commerce\Functional\CommerceBrowserTestBase;
+
+/**
+ * Tests the integration between payments and checkout.
+ *
+ * @group commerce
+ */
+class PaymentCheckoutTest extends CommerceBrowserTestBase {
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * The product.
+   *
+   * @var \Drupal\commerce_product\Entity\ProductInterface
+   */
+  protected $product;
+
+  /**
+   * A non-reusable order payment method.
+   *
+   * @var \Drupal\commerce_payment\Entity\PaymentMethodInterface
+   */
+  protected $orderPaymentMethod;
+
+  /**
+   * The default profile's address.
+   *
+   * @var array
+   */
+  protected $defaultAddress = [
+    'country_code' => 'US',
+    'administrative_area' => 'SC',
+    'locality' => 'Greenville',
+    'postal_code' => '29616',
+    'address_line1' => '9 Drupal Ave',
+    'given_name' => 'Bryan',
+    'family_name' => 'Centarro',
+  ];
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'commerce_product',
+    'commerce_cart',
+    'commerce_checkout',
+    'commerce_payment',
+    'commerce_payment_example',
+  ];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getAdministratorPermissions() {
+    return array_merge([
+      'administer profile',
+    ], parent::getAdministratorPermissions());
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->store->set('billing_countries', ['FR', 'US']);
+    $this->store->save();
+
+    $variation = $this->createEntity('commerce_product_variation', [
+      'type' => 'default',
+      'sku' => strtolower($this->randomMachineName()),
+      'price' => [
+        'number' => '39.99',
+        'currency_code' => 'USD',
+      ],
+    ]);
+
+    /** @var \Drupal\commerce_product\Entity\ProductInterface $product */
+    $this->product = $this->createEntity('commerce_product', [
+      'type' => 'default',
+      'title' => 'My product',
+      'variations' => [$variation],
+      'stores' => [$this->store],
+    ]);
+
+    /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $skipped_gateway */
+    $skipped_gateway = PaymentGateway::create([
+      'id' => 'onsite_skipped',
+      'label' => 'On-site Skipped',
+      'plugin' => 'example_onsite',
+      'configuration' => [
+        'api_key' => '2342fewfsfs',
+        'payment_method_types' => ['credit_card'],
+      ],
+      'conditions' => [
+        [
+          'plugin' => 'order_total_price',
+          'configuration' => [
+            'operator' => '<',
+            'amount' => [
+              'number' => '1.00',
+              'currency_code' => 'USD',
+            ],
+          ],
+        ],
+      ],
+    ]);
+    $skipped_gateway->save();
+
+    /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
+    $payment_gateway = PaymentGateway::create([
+      'id' => 'onsite',
+      'label' => 'On-site',
+      'plugin' => 'example_onsite',
+      'configuration' => [
+        'api_key' => '2342fewfsfs',
+        'payment_method_types' => ['credit_card'],
+      ],
+    ]);
+    $payment_gateway->save();
+
+    /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
+    $payment_gateway = PaymentGateway::create([
+      'id' => 'offsite',
+      'label' => 'Off-site',
+      'plugin' => 'example_offsite_redirect',
+      'configuration' => [
+        'redirect_method' => 'post',
+        'payment_method_types' => ['credit_card'],
+      ],
+    ]);
+    $payment_gateway->save();
+
+    /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
+    $payment_gateway = PaymentGateway::create([
+      'id' => 'manual',
+      'label' => 'Manual',
+      'plugin' => 'manual',
+      'configuration' => [
+        'display_label' => 'Cash on delivery',
+        'instructions' => [
+          'value' => 'Sample payment instructions.',
+          'format' => 'plain_text',
+        ],
+      ],
+    ]);
+    $payment_gateway->save();
+
+    $default_profile = $this->createEntity('profile', [
+      'type' => 'customer',
+      'uid' => $this->adminUser->id(),
+      'address' => $this->defaultAddress,
+    ]);
+    $profile = $this->createEntity('profile', [
+      'type' => 'customer',
+      'uid' => 0,
+      'address' => [
+        'country_code' => 'US',
+        'postal_code' => '53177',
+        'locality' => 'Milwaukee',
+        'address_line1' => 'Pabst Blue Ribbon Dr',
+        'administrative_area' => 'WI',
+        'given_name' => 'Frederick',
+        'family_name' => 'Pabst',
+      ],
+    ]);
+    $payment_method = $this->createEntity('commerce_payment_method', [
+      'uid' => $this->adminUser->id(),
+      'type' => 'credit_card',
+      'payment_gateway' => 'onsite',
+      'card_type' => 'visa',
+      'card_number' => '1111',
+      'billing_profile' => $profile,
+      'reusable' => TRUE,
+      'expires' => strtotime('2028/03/24'),
+    ]);
+    $payment_method->setBillingProfile($profile);
+    $payment_method->save();
+
+    $this->orderPaymentMethod = $this->createEntity('commerce_payment_method', [
+      'type' => 'credit_card',
+      'payment_gateway' => 'onsite',
+      'card_type' => 'visa',
+      'card_number' => '9999',
+      'reusable' => FALSE,
+    ]);
+  }
+
+  /**
+   * Tests checkout after removing all order items from an order.
+   *
+   * We expect that the customer gets redirected to the cart when the order
+   * no longer has order items.
+   */
+  public function testCheckoutAfterRemovingOrderItem() {
+    $this->drupalGet($this->product->toUrl()->toString());
+    $this->submitForm([], 'Add to cart');
+    $this->drupalGet('checkout/1');
+
+    // Now remove the order item and go to checkout again.
+    OrderItem::load(1)->delete();
+    $this->drupalGet('checkout/1');
+    $this->submitForm([], 'Continue to review');
+  }
+
+}