diff --git a/payment/uc_payment/tests/uc_payment.test b/payment/uc_payment/tests/uc_payment.test
index 65e538a..ec30fb2 100644
--- a/payment/uc_payment/tests/uc_payment.test
+++ b/payment/uc_payment/tests/uc_payment.test
@@ -58,4 +58,38 @@ class UbercartPaymentPaneTestCase extends UbercartTestHelper {
     $this->drupalGet('cart/checkout');
     $this->assertNoText('Order total:');
   }
+
+  /**
+   * Tests free orders.
+   */
+  public function testFreeOrders() {
+    $free_product = $this->createProduct(array('sell_price' => 0));
+    variable_set('uc_payment_method_check_checkout', TRUE);
+
+    // Check that paid products cannot be purchased for free.
+    $this->drupalGet('cart/checkout');
+    $this->assertText('Check or money order');
+    $this->assertNoText('No payment required');
+    $this->assertNoText('Subtotal: $0.00');
+
+    // Check that a mixture of free and paid products cannot be purchased for free.
+    $this->drupalPost('node/' . $free_product->nid, array(), t('Add to cart'));
+    $this->drupalGet('cart/checkout');
+    $this->assertText('Check or money order');
+    $this->assertNoText('No payment required');
+    $this->assertNoText('Subtotal: $0.00');
+
+    // Check that free products can be purchased successfully with no payment.
+    $this->drupalPost('cart', array(), t('Remove'));
+    $this->drupalPost('cart', array(), t('Remove'));
+    $this->drupalPost('node/' . $free_product->nid, array(), t('Add to cart'));
+    $this->drupalGet('cart/checkout');
+    $this->assertNoText('Check or money order');
+    $this->assertText('No payment required');
+    $this->assertText('Subtotal: $0.00');
+
+    // Check that this is the only available payment method.
+    $this->assertNoText('Select a payment method from the following options.');
+    $this->assertFieldByXPath("//input[@name='panes[payment][payment_method]' and @disabled='disabled']");
+  }
 }
diff --git a/payment/uc_payment/uc_payment.api.php b/payment/uc_payment/uc_payment.api.php
index b8a9ed0..cbf1be1 100644
--- a/payment/uc_payment/uc_payment.api.php
+++ b/payment/uc_payment/uc_payment.api.php
@@ -223,5 +223,21 @@ function hook_uc_payment_method_alter(&$methods) {
 }
 
 /**
+ * Alter payment methods available at checkout.
+ *
+ * @param $methods
+ *   Array of payment methods passed by reference. Keys are payment method IDs,
+ *   strings are payment method titles.
+ * @param $order
+ *   The order that is being checked out.
+ */
+function hook_uc_payment_method_checkout_alter(&$methods, $order) {
+  // Remove the Check payment method for orders under $100.
+  if ($order->order_total < 100) {
+    unset($methods['check']);
+  }
+}
+
+/**
  * @} End of "addtogroup hooks".
  */
diff --git a/payment/uc_payment/uc_payment.module b/payment/uc_payment/uc_payment.module
index 9217e5d..b5d0363 100644
--- a/payment/uc_payment/uc_payment.module
+++ b/payment/uc_payment/uc_payment.module
@@ -236,6 +236,67 @@ function uc_payment_uc_order_state() {
 }
 
 /**
+ * Implements hook_uc_payment_method().
+ */
+function uc_payment_uc_payment_method() {
+  $methods['free_order'] = array(
+    'name' => t('Free order'),
+    'title' => t('No payment required'),
+    'desc' => t('Allow customers with !zero order totals to checkout without paying.', array('!zero' => uc_currency_format(0))),
+    'callback' => 'uc_payment_method_free_order',
+    'checkout' => TRUE,
+    'no_gateway' => TRUE,
+    'weight' => 0,
+  );
+
+  return $methods;
+}
+
+/**
+ * Implements hook_uc_payment_method_checkout_alter().
+ */
+function uc_payment_uc_payment_method_checkout_alter(&$methods, $order) {
+  if (isset($methods['free_order'])) {
+    if ($order->order_total < 0.01) {
+      // Unset all other payment methods if this is a free order.
+      foreach (array_keys($methods) as $key) {
+        if ($key != 'free_order') {
+          unset($methods[$key]);
+        }
+      }
+    }
+    else {
+      // Disallow this payment method if the order is not free.
+      unset($methods['free_order']);
+    }
+  }
+}
+
+/**
+ * Payment method callback for free orders.
+ *
+ * @see uc_payment_uc_payment_method()
+ */
+function uc_payment_method_free_order($op, &$order) {
+  switch ($op) {
+    case 'cart-details':
+      return array(
+        '#markup' => t('Continue with checkout to complete your order.'),
+      );
+
+    case 'order-submit':
+      if ($order->order_total >= 0.01) {
+        return array(array(
+          'pass' => FALSE,
+          'message' => t('We cannot process your order without payment.'),
+        ));
+      }
+
+      uc_payment_enter($order->order_id, 'free_order', 0, 0, NULL, t('Checkout completed for a free order.'));
+  }
+}
+
+/**
  * Returns a formatted list of line items for an order total preview.
  *
  * @param $return
diff --git a/payment/uc_payment/uc_payment_checkout_pane.inc b/payment/uc_payment/uc_payment_checkout_pane.inc
index 35a280d..1b11f9a 100644
--- a/payment/uc_payment/uc_payment_checkout_pane.inc
+++ b/payment/uc_payment/uc_payment_checkout_pane.inc
@@ -41,6 +41,8 @@ function uc_checkout_pane_payment($op, &$order, $form = NULL, &$form_state = NUL
         }
       }
 
+      drupal_alter('uc_payment_method_checkout', $options, $order);
+
       $description = '';
       if (!$options) {
         $description = t('Checkout cannot be completed without any payment methods enabled. Please contact an administrator to resolve the issue.');
