Overview

Provides a rules, field and entity based approach to recurring billing for Drupal commerce.

Features

Auto-generate new orders based on recurring configuration.

Requirements

What's working

  • Module sets up the required product types, order types and fields.
  • Module provides logic to find orders that need new instances created.
  • Module creates new instances via Rules action.
  • Module tracks payment due date of auto-generated orders.
  • Auto-initialisation of recurring orders based on product types at time of checkout via Rules.

What's written and needs testing

The following have been recently committed and need testing.
If you can post your issues in the issue queue, that'd be great.

  • Auto processing of payments (payment method agnostic) via Commerce Card on File.

What's coming

  • Generation of invoices and payment receipts with auto-emailing via Rules.
  • Admin forms to show future recurring orders and order where payments due/did not go through.
  • Ability for user to cancel their recurring order and associated rules events to allow other modules to react.

Usage

Create a new recurring product and set the billing interval.
Add that product to a content type using a product reference field.
Checkout an order with that product.
New orders will generate on cron.

Integrating your payment gateway

Integration is via commerce_cardonfile.
When you implement hook_commerce_payment_method_info add the following to your method definitions:

  $payment_methods['foo'] = array(
    ....
    'cardonfile' => array(
      'delete callback' => 'foo_cardonfile_delete',
      'charge callback' => 'foo_cardonfile_charge',
      'update callback' => 'foo_cardonfile_update'
    )
  );

Then you implement the delete, update and charge callbacks as required. For example callbacks see Commerce EzyPay. The delete and update callbacks are handled via commerce_cardonfile, additional examples are in Commerce Authnet. The charge callback is unique to the recurring framework and takes the following signature:

/*
 * Implements commerce_cardonfile charge callback
 *
 * @param $order object
 *    The order being charged
 * @param $parent_order object
 *    The parent order from which the recurring order was derived
 *
 * @return bool
 *   TRUE if the payment was successfully processed
 */
function foo_cardonfile_charge($order, $parent_order) {
  // Retrieve the card on file
  $wrapper = entity_metadata_wrapper('commerce_order', $order);
  $instance_id = $parent_order->data['payment_method'];
  $payment_method = commerce_payment_method_instance_load($instance_id);
  $settings = $payment_method['settings'];
  $cards_on_file = commerce_cardonfile_data_load_multiple($order->uid, $payment_method['instance_id']);
  // do something with our $cards_on_file
  
  // return TRUE if successful
}

Saving card on file data for later retrieval

Although this relates to commerce_cardonfile, your payment method should use code similar to the following in order to save the card on file during the first checkout.

/**
 * Payment method callback: checkout form submission.
 */
function commerce_ezypay_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
  // Do normal processing stuff
  $response = do_some_processing();

  // Prepare card on file
  $card_data = array(
    'uid' => $order->uid,
    'payment_method' => $payment_method['method_id'],
    'instance_id' => $payment_method['instance_id'],
    'remote_id' => $response->some_token,
    'card_type' => $pane_values['credit_card']['card_type'],
    'card_name' => $pane_values['credit_card']['owner'],
    'card_number' => substr($pane_values['credit_card']['number'], -4),
    'card_exp_month' => $pane_values['credit_card']['exp_month'],
    'card_exp_year' => $pane_values['credit_card']['exp_year'],
    'status' => 1,
  );

  // Save the new card on file.
  commerce_cardonfile_data_save($card_data);
}

Known Issues