Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post_validation.js
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post_validation.js	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post_validation.js	(revision )
@@ -0,0 +1,24 @@
+// jQuery wrapper
+(function ($) {
+    // Define a Drupal behaviour with a custom name
+    Drupal.behaviors.commerceSecurePayAUDirectPostAddCreditCardExpiryValidator = {
+        attach: function (context) {
+            // Add an eventlistener to the document reacting on the
+            // 'clientsideValidationAddCustomRules' event.
+
+            $(document).bind('clientsideValidationAddCustomRules', function(event){
+                // Add your custom method with the 'addMethod' function of jQuery.validator
+                // http://docs.jquery.com/Plugins/Validation/Validator/addMethod#namemethodmessage
+
+                jQuery.validator.addMethod("creditCardExpiry", function(value, element, param) {
+                    expiryYear = $(":input[name='EPS_EXPIRYYEAR']").val();
+                    expiryMonth = $(":input[name='EPS_EXPIRYMONTH']").val();
+                    expiry = expiryYear + '' + expiryMonth;
+
+                    return expiry >= param;
+
+                }, jQuery.format('Field can not be empty'));
+            });
+        }
+    }
+})(jQuery);
\ No newline at end of file
Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post.info
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post.info	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post.info	(revision )
@@ -0,0 +1,10 @@
+name = Commerce Securepay(.com.au) Direct Post
+description = Direct Post payment method for SecurePay AU (Australian Post) and NAB
+package = Commerce (payment)
+dependencies[] = commerce
+dependencies[] = commerce_payment
+dependencies[] = clientside_validation
+dependencies[] = clientside_validation_fapi
+dependencies[] = clientside_validation_form
+core = 7.x
+files[] = commerce_securepayau_direct_post.test
\ No newline at end of file
Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.module
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.module	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.module	(revision )
@@ -0,0 +1,147 @@
+<?php
+
+/**
+ * @file
+ * Mock module that emulates Direct Post endpoint
+ */
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_securepayau_direct_post_mock_endpoint_menu() {
+  $items['commerce_securepayau_direct_post_local_test'] = array(
+    'title' => 'Local Test',
+    'description' => 'A test endpoint which emulates Direct Post',
+    'page callback' => 'commerce_securepayau_direct_post_local_test',
+    'page arguments' => array(1),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * This is a test menu callback which simulates Direct Post endpoint. It accepts
+ * form data as specified in the payment gateway documentation, calls CALLBACKURL
+ * and redirects back to REDIRECTURL
+ *
+ * When using a /form argument, it provide a form where POST data can be modifiedbefore
+ * posting back to the callback url. Change the payment redirect form's target
+ * attribute from "/commerce_securepayau_direct_post_local_test" to
+ * "commerce_securepayau_direct_post_local_test/form" to use form mode.
+ *
+ * Card numbers to use
+ * Valid: 4444333322221111
+ * Declined: 1111111111111117
+ * Invalid fingerprint: 5161418187977663
+ * CCV: any 3 letters
+ * Expiry: actual month or future
+ * @param $mode
+ * @return string
+ */
+function commerce_securepayau_direct_post_local_test($mode) {
+
+  // Prepare default data for a valid Approved response
+  $fingerprint_fields = array(
+    'merchant' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_MERCHANT_ID,
+    'refid' => $_POST['EPS_REFERENCEID'],
+    'amount' => $_POST['EPS_AMOUNT'],
+    'timestamp' => $_POST['EPS_TIMESTAMP'],
+    'summarycode' => 1,
+  );
+  $data = array(
+    'timestamp' => $_POST['EPS_TIMESTAMP'],
+    'fingerprint' => rawurlencode(commerce_securepayau_direct_post_generate_result_fingerprint($fingerprint_fields, COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD)),
+    'txnid' => $_POST['EPS_TIMESTAMP'] . '-' . mt_rand(1, 100),
+    'merchant' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_MERCHANT_ID,
+    'restext' => 'Approved',
+    'rescode' => '00',
+    'expirydate' => $_POST['EPS_EXPIRYMONTH'] . $_POST['EPS_EXPIRYYEAR'],
+    'settdate' => date('Ymd', strtotime('tomorrow')),
+    'refid' => $_POST['EPS_REFERENCEID'],
+    'pan' => substr($_POST['EPS_CARDNUMBER'], 0, 6) . '...' . substr($_POST['EPS_CARDNUMBER'], -3),
+    'summarycode' => 1
+  );
+
+  switch ($_POST['EPS_CARDNUMBER']) {
+    case '4444333322221111': //Approve
+      break;
+    case '5161418187977663': //Invalid fingerprint
+      // Bad fingerprint response
+      // Just changing the summary code and re-generate fingerprint with a different summarycode
+      $data['summarycode'] = 1;
+      $fingerprint_fields['summarycode'] = 3;
+      $data['fingerprint'] = rawurlencode(commerce_securepayau_direct_post_generate_result_fingerprint($fingerprint_fields, COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD));
+      break;
+    default: //Decline
+      // Decline response
+      // Just changing the summary code and re-generate fingerprint
+      $data['summarycode'] = 2;
+      $data['rescode'] = '05';
+      $data['restext'] = 'Insufficient Funds';
+      $fingerprint_fields['summarycode'] = $data['summarycode'];
+      $data['fingerprint'] = rawurlencode(commerce_securepayau_direct_post_generate_result_fingerprint($fingerprint_fields, COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD));
+
+  }
+
+
+  $return_url = $_POST['EPS_RESULTURL'] . '?' . http_build_query($data);
+  $callback_url = $_POST['EPS_CALLBACKURL'];
+
+  if ($mode == 'form') {
+
+
+    $return = '';
+    $return .= '<hr />Received POST values:';
+    $return .= '<pre>' . check_plain(print_r($_POST, TRUE)) . '</pre>';
+    $return .= '<hr />';
+    $return .= '<fieldset><legend>Send back the following response:</legend>';
+
+    $return .= '<p>In order to simulate a response</p>';
+    $return .= '<ol>';
+    $return .= '<li>Click Submit to call CALLBACKURL with the form details above.</li>';
+    $return .= '<li>Come back with the browser\'s back button.</li>';
+    $return .= '<li>Click here to ' . l('REDIRECT', $_POST['EPS_RESULTURL'] . '?' . http_build_query($data)) . ' back to checkout process.</li>';
+    $return .= '</ol>';
+    $form = drupal_get_form('commerce_securepayau_direct_post_mock_endpoint_callback_form', $callback_url, $data);
+    $return .= drupal_render($form);
+    $return .= '</fieldset>';
+    return $return;
+  }
+  else {
+    // Calling EPS_CALLBACKURL with the prepared POST data
+    drupal_http_request($callback_url, array(
+      'method' => 'POST',
+      'data' => http_build_query($data),
+      'headers' => array('Content-Type' => 'application/x-www-form-urlencoded')
+    ));
+
+    // Redirect the user to the EPS_RESULTURL
+    header('Location: ' . $return_url);
+
+  }
+
+
+}
+
+/**
+ * Form builder for Callback form
+ */
+function commerce_securepayau_direct_post_mock_endpoint_callback_form($form, &$form_state, $callback_url, $data) {
+  foreach ($data as $name => $value) {
+    $form[$name] = array(
+      '#type' => 'textfield',
+      '#title' => check_plain($name),
+      '#default_value' => $value
+    );
+  }
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => 'Submit',
+  );
+  $form['#action'] = url($callback_url, array('external' => TRUE));
+
+  return $form;
+
+}
\ No newline at end of file
Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post.module
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post.module	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post.module	(revision )
@@ -0,0 +1,844 @@
+<?php
+/**
+ * @file
+ * Direct Post payment method for NAB and SecurePay AU (Australian Post).
+ */
+
+// NAB URLs:
+// The URL for test authorizations.
+define('COMMERCE_SECUREPAYAU_DIRECT_POST_NAB_DP_TEST_AUTH_URL', 'https://transact.nab.com.au/test/directpostv2/authorise');
+
+// The URL for live authorizations.
+define('COMMERCE_SECUREPAYAU_DIRECT_POST_NAB_DP_LIVE_AUTH_URL', 'https://transact.nab.com.au/live/directpostv2/authorise');
+
+// DirectPost URLs:
+// The URL for test authorizations.
+define('COMMERCE_SECUREPAYAU_DIRECT_POST_SECUREPAY_DP_TEST_AUTH_URL', 'https://api.securepay.com.au/test/directpost/authorise');
+
+// The URL for live authorizations.
+define('COMMERCE_SECUREPAYAU_DIRECT_POST_SECUREPAY_DP_LIVE_AUTH_URL', 'https://api.securepay.com.au/live/directpost/authorise');
+
+// Transaction types:
+// Payment
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_TXNTYPE_PAYMENT', '0');
+// Pre Authorisation
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_TXNTYPE_PREAUTH', '1');
+
+// Standard Result Fields
+// Summarycode
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_SUMMARYCODE_APPROVED', '1');
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_SUMMARYCODE_DECLINED_BANK', '2');
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_SUMMARYCODE_DECLINED_OTHER', '3');
+
+// As we need a test credentials for the mock API endpoint, we define it:
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_MERCHANT_ID', 'ABC1234');
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD', 'abc1234');
+
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CARD_NUMBER', '4444333322221111');
+DEFINE('COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CCV', '123');
+
+/**
+ * Implements hook_commerce_payment_method_info().
+ */
+function commerce_securepayau_direct_post_commerce_payment_method_info() {
+  $payment_methods['securepayau_direct_post'] = array(
+    'base' => 'commerce_securepayau_direct_post',
+    'title' => t('Securepay AU Direct Post'),
+    'short_title' => t('Australian Post SecurePay'),
+    'description' => t('Australian Post SecurePay Direct Post method'),
+    'terminal' => FALSE,
+    'offsite' => TRUE,
+    'offsite_autoredirect' => FALSE,
+  );
+
+  $payment_methods['nab_direct_post'] = array(
+    'base' => 'commerce_securepayau_direct_post',
+    'title' => t('NAB Direct Post'),
+    'short_title' => t('NAB'),
+    'description' => t('National Australia Bank Direct Post method'),
+    'terminal' => FALSE,
+    'offsite' => TRUE,
+    'offsite_autoredirect' => FALSE,
+  );
+
+  return $payment_methods;
+
+}
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_securepayau_direct_post_menu() {
+  $items['commerce_securepayau_direct_post/callback/%commerce_order'] = array(
+    'title' => 'Payment result',
+    'page callback' => 'commerce_securepayau_direct_post_callback',
+    'page arguments' => array(2),
+    'access callback' => 'commerce_securepayau_direct_post_callback_access',
+    'access arguments' => array(2),
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+
+}
+
+
+/**
+ * Payment method callback: settings form.
+ */
+function commerce_securepayau_direct_post_settings_form($settings) {
+  $form = array();
+
+
+  // Add some defaults.
+  $settings = (array) $settings + array(
+      'currency' => 'AUD',
+      'payment_reference' => 'Web Order #[commerce-order:order-id]',
+      'merchant_id' => '',
+      'password' => '',
+      'mode' => 'test',
+      'txn_type' => COMMERCE_SECUREPAYAU_DIRECT_POST_TXNTYPE_PAYMENT,
+      'card_types_accepted' => array('visa', 'mastercard'),
+      'callback_ip_white_list_enable' => 0,
+      'auto_fill_credit_card' => 0,
+      'callback_ip_white_list' => '127.0.0.1',
+    );
+
+
+  $form['payment_reference'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Unique payment reference'),
+    '#description' => t('The payment reference field is used to tag orders with an identifier meaningful to you. This may be your invoice number, or could be a unique tracking number produced as part of your own web site. The payment reference is available to the Result URL and emails, and appears in the online SecurePay Merchant Administration transaction history list. Order tokens can be used in this field.'),
+    '#default_value' => $settings['payment_reference'],
+    '#required' => TRUE,
+  );
+
+  $form['merchant_id'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Merchant ID'),
+    '#description' => t('SecurePay Customer Support will supply your Merchant ID when your account is activated. The Merchant ID will be of the format <em>ABC0010</em>, where <em>ABC</em> is your unique three-letter account code, also used for logging in to the SecurePay Merchant Log In. Use @defaultid if you don\'t have one and want to use Local Test mode.',
+      array('@defaultid' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_MERCHANT_ID)),
+    '#default_value' => $settings['merchant_id'],
+    '#required' => TRUE,
+  );
+
+  $form['password'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Transaction password'),
+    '#description' => t('Please contact support to be issued a transaction password. Use @defaultpassword if you don\'t have one and want to use Local Test mode.',
+      array('@defaultpassword' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD)),
+    '#default_value' => $settings['password'],
+    '#required' => TRUE,
+  );
+
+  $form['mode'] = array(
+    '#type' => 'select',
+    '#title' => t('Transaction mode'),
+    '#description' => t('Transaction mode used when sending requests. Local Test mode emulates a bank gateway, so it does not require working payment gateway account. Mock Endpoint (commerce_securepayau_direct_post_mock_endpoint) module must be enabled to use this feature.'),
+    '#options' => array(
+      'local_test' => t('Local Test'),
+      'test' => t('Test'),
+      'live' => t('Live'),
+    ),
+    '#default_value' => $settings['mode'],
+    '#required' => TRUE,
+  );
+
+  $form['callback_ip_white_list_enable'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Enable Callback IP White list'),
+    '#default_value' => $settings['callback_ip_white_list_enable'],
+  );
+
+
+  $form['callback_ip_white_list'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Callback IP White list'),
+    '#description' => t('For increased security, you can provide a white list of IP addresses or ranges to only allow bank servers to access payment callback. One IP or range per line. ' .
+      'Please check with your support contact before enabling this feature!'),
+    '#default_value' => $settings['callback_ip_white_list'],
+  );
+
+  $form['auto_fill_credit_card'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Automatically fill credit card form with test card'),
+    '#description' => t('If checked, the following card data is automatically filled into the form: Card Number: @number, Expiry date: next December, CCV: @ccv', array(
+      '@number' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CARD_NUMBER,
+      '@ccv' => COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CCV
+    )),
+    '#default_value' => $settings['auto_fill_credit_card'],
+  );
+
+  $form['txn_type'] = array(
+    '#type' => 'select',
+    '#title' => t('Transaction type'),
+    '#description' => t('Payments are real-time, immediately authorised card transactions. A pre-authorisation is a transaction that reserves funds on a card account. The Merchant can then complete the transaction at a later date and receive the funds. If the pre-authorisation is never completed, it expires, usually after five days. After this, the reserved funds are again available to the card holder.'),
+    '#options' => array(
+      COMMERCE_SECUREPAYAU_DIRECT_POST_TXNTYPE_PAYMENT => t('Payment'),
+      COMMERCE_SECUREPAYAU_DIRECT_POST_TXNTYPE_PREAUTH => t('Pre-Authorisation'),
+    ),
+    '#default_value' => $settings['txn_type'],
+    '#required' => TRUE,
+  );
+
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+  $direct_post_options = array_flip(array(
+    'visa',
+    'mastercard',
+    'amex',
+    'dc',
+    'jcb'
+  ));
+  $card_type_options = array_intersect_key(commerce_payment_credit_card_types(), $direct_post_options);
+
+  $form['card_types_accepted'] = array(
+    '#type' => 'checkboxes',
+    '#title' => t('Card types accepted'),
+    '#options' => $card_type_options,
+    '#description' => t('Direct Post accepts Visa and MasterCard card by default. Contact support before you enable other card types.'),
+    '#default_value' => $settings['card_types_accepted'],
+  );
+
+  $form['currency'] = array(
+    '#type' => 'select',
+    '#title' => t('Currency code'),
+    '#description' => t('If your account supports multi-currency, you may optionally set the currency of the transaction to one other than AUD'),
+    '#default_value' => $settings['currency'],
+    '#options' => commerce_currency_code_options_list(),
+    '#required' => TRUE,
+  );
+
+  return $form;
+}
+
+/**
+ * Payment method callback; generation callback for the payment redirect form.
+ *
+ * Returns form elements that should be submitted to the redirected payment
+ * service; because of the array merge that happens upon return, the service’s
+ * URL that should receive the POST variables should be set in the #action
+ * property of the returned form array.
+ */
+function commerce_securepayau_direct_post_redirect_form($form, &$form_state, $order, $payment_method) {
+  // Return an error if the enabling action's settings haven't been configured.
+  if (empty($payment_method['settings']['mode'])) {
+    drupal_set_message(t('Payment method is not configured for use.'), 'error');
+    return $form;
+  }
+
+  //Setting up mandatory fields for payment transaction
+
+  $mandatory_fields = array(
+    'EPS_MERCHANT' => commerce_securepayau_direct_post_get_merchant_id($payment_method),
+    'EPS_TXNTYPE' => $payment_method['settings']['txn_type'],
+    'EPS_RESULTURL' => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
+    'EPS_CALLBACKURL' => url('commerce_securepayau_direct_post/callback/' . $order->order_id . '/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
+    'EPS_REDIRECT' => 'TRUE',
+    'EPS_REFERENCEID' => token_replace($payment_method['settings']['payment_reference'], array('commerce-order' => $order)),
+    'EPS_AMOUNT' => commerce_securepayau_direct_post_price_amount($order, $payment_method),
+    'EPS_TIMESTAMP' => commerce_securepayau_direct_post_get_current_timestamp(),
+  );
+
+  //Generating fingerprint:
+  $mandatory_fields['EPS_FINGERPRINT'] = rawurlencode(commerce_securepayau_direct_post_generate_payment_fingerprint($mandatory_fields, commerce_securepayau_direct_post_get_transaction_password($payment_method)));
+
+  foreach ($mandatory_fields as $field_name => $field_value) {
+    $form[$field_name] = array(
+      '#type' => 'hidden',
+      '#value' => $field_value
+    );
+  }
+
+
+  // Create credit card form fields
+  // We will use commerce_payment_credit_card_form() function from Commcerce Payment
+  // so we need to load the module
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+
+  // Set that we want to have CCV code field and which type of card do we accept
+  $elements = array(
+    'code' => '',
+    'type' => $payment_method['settings']['card_types_accepted'],
+  );
+
+  // Generate the form
+  $credit_card = commerce_payment_credit_card_form($elements);
+  $form['credit_card'] = $credit_card['credit_card'];
+
+  //Set up validation
+
+  // validate: Numbers only with correct length
+  $form['credit_card']['number']['#rules'] = array(
+    array(
+      'rule' => 'credit_card',
+      'error' => t('Please enter a valid %field'),
+    ),
+    array(
+      'rule' => 'numeric',
+      'error' => t('Please use only numbers for %field'),
+    ),
+  );
+
+  // expiry must be future date
+  $form['credit_card']['exp_month']['#rules'] = array(
+    array(
+      'rule' => 'credit_card_expiry',
+      'error' => t('Please enter a valid %field'),
+    ),
+  );
+
+  // validate: Numbers only with correct length
+  $form['credit_card']['code']['#rules'] = array(
+    array(
+      'rule' => 'numeric',
+      'error' => t('Please use only numbers for %field'),
+    ),
+    array(
+      'rule' => 'length[3, 4]',
+      'error' => t('Please enter a valid %field'),
+    ),
+  );
+
+
+  // Rename fields to match Payment Gateway specification
+  $fields_to_rename = array(
+    'number' => 'EPS_CARDNUMBER',
+    'exp_month' => 'EPS_EXPIRYMONTH',
+    'exp_year' => 'EPS_EXPIRYYEAR',
+    'code' => 'EPS_CCV',
+  );
+
+  foreach ($fields_to_rename as $rename_from => $rename_to) {
+    // We keep the original structure and field names, just change the #name attribute
+    $form['credit_card'][$rename_from]['#name'] = $rename_to;
+  }
+
+  if (isset($payment_method['settings']['auto_fill_credit_card']) && $payment_method['settings']['auto_fill_credit_card'] == TRUE) {
+    $form['credit_card']['number']['#default_value'] = COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CARD_NUMBER;
+    $form['credit_card']['exp_year']['#default_value'] = date('y', strtotime('+1 year'));
+    $form['credit_card']['exp_month']['#default_value'] = '12';
+    $form['credit_card']['code']['#default_value'] = COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_CCV;
+  }
+
+  // Set action URL based on current payment method configuration
+  $form['#action'] = commerce_securepayau_direct_post_get_endpoint_url($payment_method);
+
+  // Place action elements to the end of the form
+  $form['actions']['#weight'] = 100;
+
+  // Create submit and cancel actions
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Process payment'),
+  );
+  $form['actions']['cancel'] = array(
+    '#type' => 'markup',
+    '#markup' => l(t('Cancel payment'), 'checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key']),
+    '#prefix' => t('or') . '&nbsp;',
+  );
+
+  return $form;
+}
+
+/**
+ * Access callback for payment processing.
+ */
+function commerce_securepayau_direct_post_callback_access($order) {
+  $access_granted = FALSE;
+  if (arg(3) == $order->data['payment_redirect_key']) {
+
+    // First load the order's specified payment method instance.
+    if (!empty($order->data['payment_method'])) {
+      $payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
+
+      if (!empty($payment_method['settings']['callback_ip_white_list']) && $payment_method['settings']['callback_ip_white_list'] == 1) {
+
+        // Users' IP address.
+        $requestIp = ip_address();
+
+        $ips = explode("\n", str_replace("\r", "\n", $payment_method['settings']['callback_ip_white_list']));
+
+        foreach ($ips as $ip) {
+          // Borrowed from \Symfony\Component\HttpFoundation\IpUtils::checkIp()
+          if (FALSE !== strpos($ip, '/')) {
+            list($address, $netmask) = explode('/', $ip, 2);
+            if ($netmask < 1 || $netmask > 32) {
+              return FALSE;
+            }
+          }
+          else {
+            $address = $ip;
+            $netmask = 32;
+          }
+          $in_this_range = 0 === substr_compare(sprintf('%032b', ip2long($requestIp)), sprintf('%032b', ip2long($address)), 0, $netmask);
+
+          if ($in_this_range) {
+            $access_granted = TRUE;
+          }
+        }
+      }
+      else {
+        $access_granted = TRUE;
+      }
+    }
+  }
+  return $access_granted;
+}
+
+
+/**
+ * Callback handler for EPS_CALLBACKURL
+ * This method is called by the bank endpoint direclty and should not viewed by customer's browser.
+ * After this page is called by bank, it redirects the user to EPS_REDIRECTURL
+ * @param $order
+ *  an order object (autol-loader wildcard)
+ */
+function commerce_securepayau_direct_post_callback($order) {
+
+  // Validate payment redirect key
+
+  if (arg(3) == $order->data['payment_redirect_key']) {
+
+    // First load the order's specified payment method instance.
+    if (!empty($order->data['payment_method'])) {
+      $payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
+
+      // Validate input fields and fingerprint
+      $filtered_inputs = commerce_securepayau_direct_post_validate_response($order, $payment_method, INPUT_POST);
+
+      if ($filtered_inputs !== FALSE) {
+
+        $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+        // First check if there is another transaction record with the same remote id
+        $transaction = commerce_securepayau_direct_post_payment_transaction_load($filtered_inputs['txnid']);
+        if ($transaction) {
+          watchdog('commerce_securepayau_direct_post', 'Callback for Order @order_number ignored: refund transaction already created.', array('@order_number' => $order->order_number), WATCHDOG_NOTICE);
+        }
+        else {
+          // Create a new payment transaction, set it based on the result status (success or failure)
+
+          $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
+          $transaction->instance_id = $payment_method['instance_id'];
+          $transaction->remote_id = $filtered_inputs['txnid'];
+          $transaction->amount = $order_wrapper->commerce_order_total->amount->value();
+          $transaction->currency_code = $payment_method['settings']['currency'];
+          $transaction->payload[REQUEST_TIME] = $_POST;
+
+          // Move checkout process forward or backward based on result status (success or failure)
+          switch ($filtered_inputs['summarycode']) {
+            case 1: //Approved
+              $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+              $transaction->message .= t('The payment has completed.');
+              commerce_payment_redirect_pane_next_page($order, t('Customer successfully submitted payment at the payment gateway.'));
+              break;
+            case 2: //Declined by the bank
+            case 3: //Declined by other reason
+              // Although we receive more detailed result codes in 'rescode' and 'restext',
+              // for security reasons we just display a generic error and log the details.
+              $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+              $transaction->message .= t("The payment has failed.");
+              watchdog('commerce_securepayau_direct_post', 'Payment transaction declined. Result: @rescode @restext', array(
+                '@rescode' => $filtered_inputs['rescode'],
+                '@restext' => $filtered_inputs['restext']
+              ), WATCHDOG_INFO);
+              drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
+              commerce_payment_redirect_pane_previous_page($order, t('Customer payment submission failed at the payment gateway.'));
+              break;
+          }
+
+          commerce_payment_transaction_save($transaction);
+        }
+      }
+
+    }
+
+
+  }
+
+}
+
+/**
+ * Payment method callback; validation callback for redirected payments.
+ *
+ * Upon return from a redirected payment service, this callback provides the
+ * payment method an opportunity to validate any returned data before proceeding
+ * to checkout completion; should return TRUE or FALSE indicating whether or not
+ * the customer should proceed to checkout completion or go back a step in the
+ * checkout process from the payment page.
+ *
+ * @param $order
+ *   The order object.
+ * @param $payment_method
+ *   The payment method array.
+ * @return boolean
+ *   TRUE if the customer should proceed to checkout completion or FALSE to go
+ *   back one step in the checkout process.
+ */
+function commerce_securepayau_direct_post_redirect_form_validate($order, $payment_method) {
+
+  $filtered_inputs = commerce_securepayau_direct_post_validate_response($order, $payment_method, INPUT_GET);
+
+  if ($filtered_inputs !== FALSE) {
+    // Check the transaction result summary code
+    switch ($filtered_inputs['summarycode']) {
+      case 1: //Approved
+        return TRUE;
+        break;
+      default: //Declined or unknown
+        drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
+        return FALSE;
+    }
+  }
+
+  // For sure, returning false by default.
+  return FALSE;
+}
+
+/**
+ * Payment method callback; submission callback for redirected payments.
+ *
+ * Upon return from a redirected payment service, this callback provides the
+ * payment method an opportunity to perform any submission functions necessary
+ * before the customer is redirected to checkout completion.
+ *
+ * @param $order
+ *   The order object.
+ * @param $payment_method
+ *   The payment method array.
+ */
+function commerce_securepayau_direct_post_redirect_form_submit($order, $payment_method) {
+}
+
+
+/* Supplementary functions */
+
+/**
+ * Retrieves payment endpoint URL based on current method and settings (test/live)
+ * @param $payment_method
+ * @return string
+ */
+function commerce_securepayau_direct_post_get_endpoint_url($payment_method) {
+
+  if ($payment_method['settings']['mode'] == 'local_test') {
+    if (module_exists('commerce_securepayau_direct_post_mock_endpoint')) {
+      return url('commerce_securepayau_direct_post_local_test', array('absolute' => TRUE));
+    }
+    else {
+      throw new Exception('Module commerce_securepayau_direct_post_mock_endpoint must be enabled to use Local Test mode.');
+    }
+  }
+
+  switch ($payment_method['method_id']) {
+    case 'nab_direct_post':
+      switch ($payment_method['settings']['mode']) {
+        case 'test':
+          return COMMERCE_SECUREPAYAU_DIRECT_POST_NAB_DP_TEST_AUTH_URL;
+          break;
+        case 'live':
+          return COMMERCE_SECUREPAYAU_DIRECT_POST_NAB_DP_LIVE_AUTH_URL;
+          break;
+      }
+      break;
+    case 'securepayau_direct_post':
+      switch ($payment_method['settings']['mode']) {
+        case 'test':
+          return COMMERCE_SECUREPAYAU_DIRECT_POST_SECUREPAY_DP_TEST_AUTH_URL;
+          break;
+        case 'live':
+          return COMMERCE_SECUREPAYAU_DIRECT_POST_SECUREPAY_DP_LIVE_AUTH_URL;
+          break;
+      }
+      break;
+  }
+}
+
+
+/**
+ * Returns the appropriate merchant ID based on payment method configuration
+ * If the method is configured as 'local_test' mode, it returns a pre-defined ID
+ * @param $payment_method_instance
+ * @return bool|string
+ * @throws \Exception
+ */
+function commerce_securepayau_direct_post_get_merchant_id($payment_method_instance) {
+  if (!$payment_method_instance) {
+    throw new Exception(t('No Payment Method Instance found.'));
+  }
+  return ($payment_method_instance['settings']['mode'] == 'local_test') ? COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_MERCHANT_ID : $payment_method_instance['settings']['merchant_id'];
+}
+
+/**
+ * Returns the appropriate transaction password based on payment method configuration
+ * If the method is configured as 'local_test' mode, it returns a pre-defined ID
+ * @param $payment_method_instance
+ * @return bool|string
+ * @throws \Exception
+ */
+function commerce_securepayau_direct_post_get_transaction_password($payment_method_instance) {
+  if (!$payment_method_instance) {
+    throw new Exception(t('No Payment Method Instance found.'));
+  }
+  return ($payment_method_instance['settings']['mode'] == 'local_test') ? COMMERCE_SECUREPAYAU_DIRECT_POST_LOCAL_TEST_PASSWORD : $payment_method_instance['settings']['password'];
+}
+
+/**
+ * Returns the current time as a NAB friendly timestamp.
+ * @return string
+ *  Timestamp string in YYYYMMDDHHMMSS format
+ */
+function commerce_securepayau_direct_post_get_current_timestamp() {
+  $datetime = new DateTime();
+  $datetime->setTimezone(new DateTimezone('UTC'));
+  $timestamp = $datetime->format('YmdHis');
+  return $timestamp;
+}
+
+/**
+ * Generates payment fingerprint
+ *
+ * @param $settings_fields
+ *  An associative array containing settings required to fingerprint generation
+ * @param $password
+ * @return Base64-encoded fingerprint string
+ */
+function commerce_securepayau_direct_post_generate_payment_fingerprint($settings_fields, $password) {
+  $signature_fields = array(
+    $settings_fields['EPS_MERCHANT'],
+    $password,
+    $settings_fields['EPS_TXNTYPE'],
+    $settings_fields['EPS_REFERENCEID'],
+    $settings_fields['EPS_AMOUNT'],
+    $settings_fields['EPS_TIMESTAMP'],
+  );
+
+  return sha1(implode('|', $signature_fields));
+}
+
+/**
+ * Generates result fingerprint
+ *
+ * @param $fields
+ *  An associative array containing settings required to fingerprint generation
+ * @param $password
+ * @return Base64-encoded fingerprint string
+ */
+function commerce_securepayau_direct_post_generate_result_fingerprint($fields, $password) {
+  $signature_fields = array(
+    $fields['merchant'],
+    $password,
+    $fields['refid'],
+    $fields['amount'],
+    $fields['timestamp'],
+    $fields['summarycode'],
+  );
+
+  return sha1(implode('|', $signature_fields));
+}
+
+/**
+ * Returns an order amount as a decimal value.
+ *
+ * @param $order
+ * @param $payment_method
+ * @return string The decimal price amount.
+ * The decimal price amount.
+ */
+function commerce_securepayau_direct_post_price_amount($order, $payment_method) {
+  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+  // Determine the currency code to use to actually process the transaction,
+  $currency_code = $payment_method['settings']['currency'];
+  $order_currency_code = $order_wrapper->commerce_order_total->currency_code->value();
+
+  $amount = $order_wrapper->commerce_order_total->amount->value();
+
+
+  $rounded_amount = commerce_currency_round(commerce_currency_convert($amount, $order_currency_code, $currency_code), commerce_currency_load($currency_code));
+
+  return number_format(commerce_currency_amount_to_decimal($rounded_amount, $currency_code), 2, '.', '');
+}
+
+/**
+ * Compares two strings.
+ *
+ * Copied from Symfony:
+ * https://github.com/symfony/symfony/blob/v2.6.4/src/Symfony/Component/Security/Core/Util/StringUtils.php
+ *
+ * This method implements a constant-time algorithm to compare strings.
+ * Regardless of the used implementation, it will leak length information.
+ *
+ * @param string $knownString The string of known length to compare against
+ * @param string $userInput The string that the user can control
+ *
+ * @return bool true if the two strings are the same, false otherwise
+ * @return bool true if the two strings are the same, false otherwise
+ */
+function commerce_securepayau_direct_post_equals($knownString, $userInput) {
+  $knownString = (string) $knownString;
+  $userInput = (string) $userInput;
+  $knownLen = strlen($knownString);
+  $userLen = strlen($userInput);
+  // Extend the known string to avoid uninitialized string offsets
+  $knownString .= $userInput;
+  // Set the result to the difference between the lengths
+  $result = $knownLen - $userLen;
+  // Note that we ALWAYS iterate over the user-supplied length
+  // This is to mitigate leaking length information
+  for ($i = 0; $i < $userLen; $i++) {
+    $result |= (ord($knownString[$i]) ^ ord($userInput[$i]));
+  }
+  // They are only identical strings if $result is exactly 0...
+  return 0 === $result;
+}
+
+/**
+ * Validates return values by checking fingerprint and summarycode
+ * @param $order
+ * @param $payment_method
+ * @param int $input_source
+ * @return bool|array
+ *  boolean FALSE if the fields or the fingerprint are not valid
+ *  array of filtered/sanitized fields (name-value pairs)
+ */
+function commerce_securepayau_direct_post_validate_response($order, $payment_method, $input_source = INPUT_POST) {
+  // First we validate the input
+  $data = array(
+    'summarycode' => array(
+      'filter' => FILTER_VALIDATE_INT,
+      'options' => array('min_range' => 1, 'max_range' => 3)
+    ),
+    'rescode' => FILTER_SANITIZE_ENCODED,
+    'restext' => FILTER_SANITIZE_ENCODED,
+    'refid' => FILTER_SANITIZE_STRING,
+    'txnid' => FILTER_SANITIZE_STRING,
+    'settdate' => FILTER_SANITIZE_ENCODED,
+    'pan' => FILTER_SANITIZE_ENCODED,
+    'expirydate' => FILTER_SANITIZE_ENCODED,
+    'merchant' => FILTER_SANITIZE_ENCODED,
+    'timestamp' => FILTER_SANITIZE_ENCODED,
+    'fingerprint' => FILTER_SANITIZE_ENCODED,
+  );
+
+  $filtered_inputs = filter_input_array($input_source, $data);
+
+  // Doing a time-constant string comparison on fingerprint
+  $fingerprint_fields = array(
+    'merchant' => commerce_securepayau_direct_post_get_merchant_id($payment_method),
+    'refid' => token_replace($payment_method['settings']['payment_reference'], array('commerce-order' => $order)),
+    'amount' => commerce_securepayau_direct_post_price_amount($order, $payment_method),
+    'timestamp' => $filtered_inputs['timestamp'],
+    'summarycode' => $filtered_inputs['summarycode'],
+  );
+  $expected_fingerprint = commerce_securepayau_direct_post_generate_result_fingerprint($fingerprint_fields, commerce_securepayau_direct_post_get_transaction_password($payment_method));
+  if (!commerce_securepayau_direct_post_equals($expected_fingerprint, $filtered_inputs['fingerprint'])) {
+    watchdog('commerce_securepayau_direct_post', 'Invalid fingerprint. Expected: @expected, got: @got. Full payload: !payload', array(
+      '@expected' => $expected_fingerprint,
+      '@got' => $filtered_inputs['fingerprint'],
+      '!payload' => '<pre>' . check_plain(print_r($_POST, TRUE)) . '</pre>'
+    ), WATCHDOG_WARNING);
+    // As this is a potentially security breach, we don't provide detailed information to the end user
+    return FALSE;
+  }
+
+  // If the summarycode didn't exist or outside of the specified range
+  if ($filtered_inputs['summarycode'] === NULL) {
+    return FALSE;
+  }
+
+  return $filtered_inputs;
+
+}
+
+/**
+ * Loads the payment transaction matching the external transaction ID.
+ *
+ * @param $txn_id
+ *   The external transaction ID to search for in the remote_id field.
+ * @return bool|mixed The loaded payment transaction.
+ * The loaded payment transaction.
+ */
+function commerce_securepayau_direct_post_payment_transaction_load($txn_id) {
+  $transactions = commerce_payment_transaction_load_multiple(array(), array('remote_id' => $txn_id));
+  return $transactions ? reset($transactions) : FALSE;
+}
+
+
+/**
+ * Implements hook_fapi_validation_rules().
+ */
+function commerce_securepayau_direct_post_fapi_validation_rules() {
+  return array(
+    'credit_card' => array(
+      'callback' => 'commerce_securepayau_direct_post_credit_card_validate',
+      'error_msg' => t('Please enter a valid %field'),
+    ),
+    'credit_card_expiry' => array(
+      'callback' => 'commerce_securepayau_direct_post_credit_card_expiry_validate',
+      'error_msg' => t('Please enter a valid %field'),
+    ),
+  );
+}
+
+/**
+ * Implements hook_clientside_validation_rule_alter()
+ */
+function commerce_securepayau_direct_post_clientside_validation_rule_alter(&$js_rules, $element, $context) {
+  switch ($context['type']) {
+    case 'fapi':
+      if ($context['rule']['callback'] == 'commerce_securepayau_direct_post_credit_card_validate') {
+        _commerce_securepayau_direct_post_credit_card_validate($element['#name'], $element['#title'], $js_rules);
+      }
+      if ($context['rule']['callback'] == 'commerce_securepayau_direct_post_credit_card_expiry_validate') {
+        _commerce_securepayau_direct_post_credit_card_expiry_validate($element['#name'], $element['#title'], $js_rules);
+      }
+      break;
+
+    case 'element_validate':
+      if (in_array('commerce_securepayau_direct_post_credit_card_expiry_validate', $context['functions'])) {
+        _commerce_securepayau_direct_post_credit_card_expiry_validate($element['#name'], $element['#title'], $js_rules);
+      }
+      break;
+  }
+}
+
+/**
+ * Credit card number validation callback
+ */
+function commerce_securepayau_direct_post_credit_card_validate($value) {
+  // Because the credit card field bypasses this server altogether, this validation should
+  // never be required. Just here for API completeness.
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+  return commerce_payment_validate_credit_card_number($value);
+}
+
+function _commerce_securepayau_direct_post_credit_card_validate($name, $title, &$js_rules) {
+  // Uses the jquery.validate inbuilt credit card validation
+  $title = variable_get('clientside_validation_prefix', '') . $title . variable_get('clientside_validation_suffix', '');
+  $js_rules[$name]['creditcard'] = TRUE;
+  $js_rules[$name]['messages']['creditcard'] = t('Please enter a valid !field.', array('!field' => $title));
+}
+
+/**
+ * Expiry validation callback
+ */
+function commerce_securepayau_direct_post_credit_card_expiry_validate($element, &$form_state, $form) {
+  $today = date('Ym', REQUEST_TIME);
+  $expiry = $form_state['values']['credit_card']['exp_year'] . $form_state['values']['credit_card']['exp_month'];
+
+  $title = $element['#title'];
+
+  if ($today > $expiry) {
+    form_set_error('expiry_container', t('Please check the !field.', array('!field' => $title)));
+  }
+}
+
+function _commerce_securepayau_direct_post_credit_card_expiry_validate($name, $title, &$js_rules) {
+  drupal_add_js(drupal_get_path('module', 'commerce_securepayau_direct_post') . '/commerce_securepayau_direct_post_validation.js');
+
+  $title = variable_get('clientside_validation_prefix', '') . $title . variable_get('clientside_validation_suffix', '');
+  $js_rules[$name]['creditCardExpiry'] = date('Ym', REQUEST_TIME);
+  $js_rules[$name]['messages']['creditCardExpiry'] = t('Please enter a valid expiration.', array('!field' => $title));
+}
+
+
Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.info
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.info	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post_mock_endpoint/commerce_securepayau_direct_post_mock_endpoint.info	(revision )
@@ -0,0 +1,5 @@
+name = Commerce Securepay/NAB Direct Post Mock Endpoint
+description = Provides a mock endpoint to test SecurePay/NAB Direct Post module
+package = Commerce (payment)
+dependencies[] = commerce_securepayau_direct_post
+core = 7.x
\ No newline at end of file
Index: commerce_securepayau_direct_post/commerce_securepayau_direct_post.test
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- commerce_securepayau_direct_post/commerce_securepayau_direct_post.test	(revision )
+++ commerce_securepayau_direct_post/commerce_securepayau_direct_post.test	(revision )
@@ -0,0 +1,127 @@
+<?php
+
+/**
+ * @file
+ * Simpletest cases for testing commerce_securepayau_direct_post
+ */
+
+/**
+ * Class CommerceSecurepayauDirectPostTestCase
+ */
+class CommerceSecurepayauDirectPostTestCase extends CommerceBaseTestCase {
+
+  protected $order;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Commerce Securepay(.com.au) Direct Post',
+      'description' => 'Ensure that commerce_securepayau_direct_post module works properly.',
+      'group' => 'Drupal Commerce'
+    );
+  }
+
+  public function setUp() {
+    $modules = parent::setUpHelper('all', array(
+      'commerce_securepayau_direct_post',
+      'commerce_securepayau_direct_post_mock_endpoint'
+    ));
+    parent::setUp($modules);
+
+    $this->enableCurrencies(array('AUD'));
+    $this->store_admin = $this->createStoreAdmin();
+    $this->store_customer = $this->createStoreCustomer();
+
+    // The rule that sends a mail after checkout completion should be disabled
+    //  as it returns an error caused by how mail messages are stored.
+    $rules_config = rules_config_load('commerce_checkout_order_email');
+    $rules_config->active = FALSE;
+    $rules_config->save();
+
+  }
+
+  /**
+   * Test SecurePay AU Direct Post payment method on checkout process using an authenticated user.
+   */
+  public function testCommerceSecurepayauDirectPostPayment() {
+
+    // Log in as store administrator user.
+    $this->drupalLogin($this->store_admin);
+
+    // Go to payment methods page.
+    $this->drupalGet('admin/commerce/config/payment-methods');
+
+    // Go to edit payment rule.
+    $this->clickLink(t('Securepay AU Direct Post'));
+    $this->clickLink(t('Enable payment method: Securepay AU Direct Post'));
+    $payment_settings = array(
+      'parameter[payment_method][settings][payment_method][settings][payment_reference]' => 'Web Order #[commerce-order:order-id]',
+      'parameter[payment_method][settings][payment_method][settings][merchant_id]' => 'ABC123',
+      'parameter[payment_method][settings][payment_method][settings][password]' => 'abc123',
+      'parameter[payment_method][settings][payment_method][settings][mode]' => 'local_test',
+      'parameter[payment_method][settings][payment_method][settings][auto_fill_credit_card]' => 1,
+      //@todo: don't use this, fill cc details in place
+      'parameter[payment_method][settings][payment_method][settings][currency]' => 'AUD'
+    );
+    $this->drupalPost(NULL, $payment_settings, t('Save'));
+
+    $this->drupalPost(NULL, array('settings[active]' => 1), t('Save changes'));
+
+    // Log in as normal user.
+    $this->drupalLogin($this->store_customer);
+
+    // Order creation, in cart status.
+    $this->testProduct = $this->createDummyProduct('MYPRD-01', 'Test Product', 100, 'AUD');
+    $this->order = $this->createDummyOrder($this->store_customer->uid, array($this->testProduct->product_id => 1));
+
+    // Access to checkout page.
+    $this->drupalGet($this->getCommerceUrl('checkout'));
+
+    // Generate random information, as city, postal code, etc.
+    $address_info = $this->generateAddressInformation();
+
+    // Fill in the billing address information.
+    $billing_pane = $this->xpath("//select[starts-with(@name, 'customer_profile_billing[commerce_customer_address]')]");
+    $this->drupalPostAJAX(NULL, array((string) $billing_pane[0]['name'] => 'US'), (string) $billing_pane[0]['name']);
+
+    // Fill in the required information for billing pane, with a random State.
+    $info = array(
+      'customer_profile_billing[commerce_customer_address][und][0][name_line]' => $address_info['name_line'],
+      'customer_profile_billing[commerce_customer_address][und][0][thoroughfare]' => $address_info['thoroughfare'],
+      'customer_profile_billing[commerce_customer_address][und][0][locality]' => $address_info['locality'],
+      'customer_profile_billing[commerce_customer_address][und][0][administrative_area]' => 'KY',
+      'customer_profile_billing[commerce_customer_address][und][0][postal_code]' => $address_info['postal_code'],
+    );
+    $this->drupalPost(NULL, $info, t('Continue to next step'));
+    $this->drupalPostAJAX(NULL, array('commerce_payment[payment_method]' => 'securepayau_direct_post|commerce_payment_securepayau_direct_post'), 'commerce_payment[payment_method]');
+
+    // Finish checkout process
+    $this->drupalPost(NULL, array(), t('Continue to next step'));
+
+    // @todo: Fill credit card details
+
+    // Process payment
+    $this->drupalPost(NULL, array(), t('Process payment'));
+
+
+    // Reload the order directly from db to update status.
+    $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE);
+
+    // Order status should be pending when completing checkout process.
+    $this->assertEqual(reset($order)->status, 'pending', t('Order status is \'Pending\' after completing checkout'));
+
+    // Check if the completion message has been displayed.
+    $this->assertTitle(t('Checkout complete') . ' | Drupal', t('Title of the page is \'Checkout complete\' when finishing the checkout process'));
+    $this->assertText(t('Your order number is @order-number.', array('@order-number' => $this->order->order_number)), t('Completion message for the checkout is correctly displayed'));
+
+
+  }
+
+  /**
+   * @todo: Tests for different use cases:
+   * - Rejected transaction
+   * - Invalid values in form (if testing clienside_validation is possible)
+   * - Double transaction
+   * - Attempt to complete payment with an invalid fingerprint (security)
+   */
+
+}
\ No newline at end of file
