diff --git a/README.txt b/README.txt
index 9ddedd2..3140581 100644
--- a/README.txt
+++ b/README.txt
@@ -1 +1,38 @@
-See major version branches.
+DESCRIPTION
+===========
+Paybox Service (http://www1.paybox.com) integration for the Drupal Commerce
+payment and checkout system.
+
+INSTALLATION INSTRUCTIONS FOR COMMERCE PAYBOX
+=============================================
+
+First you should make sure you have an Paybox Merchant account, and are ready
+to configure it:
+
+Installing & configuring the Paybox payment method in Drupal Commerce
+---------------------------------------------------------------------
+ - Install the Paxbox module.
+ - Paybox payment functions thanks to a CGI module. Download it in the download
+   section (http://www1.paybox.com/telechargement_focus.aspx?cat=3) of the
+   paybox site.
+ - Navigate to the admin section of the Paybox module (Store > Configuration >
+   Paybox System) and set the path to the CGI module.
+ - Enable the Paybox payment rule (Store > Configuration > Payment methods).
+ - Edit the Paybox payment rule "Action settings" with your paybox informations
+   (be sure to uncheck "Use Paybox test plateform" on production).
+
+Notes
+-----
+IMPORTANT : Paybox module require the Paybox servers to be able to contact your
+Drupal site for orders validation. During the test phase, the Paybox sandbox
+servers WILL NOT confirm the validation of the order process.
+
+To achieve more tests you'll have to allow your IP (usally 127.0.1.1 or
+127.0.0.1) to validate orders (Store > Configuration > Paybox System),
+and the call the URL yourself.
+
+AUTHOR
+======
+Samuel Hurel (http://drupal.org/user/789980) of IDEIA (http://ideia.fr).
+The author can be contacted for paid customizations of this module as well as
+Drupal consulting.
diff --git a/commerce_paybox.api.php b/commerce_paybox.api.php
new file mode 100644
index 0000000..0ad9125
--- /dev/null
+++ b/commerce_paybox.api.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Documents hooks provided by the Commerce Paybox module.
+ */
+
+/**
+ * Allow other modules to alter Paybox CGI module parameters.
+ *
+ * For a full list of allowed parameters, see ocumentation at
+ * http://www1.paybox.com/telechargement_focus.aspx?cat=3
+ *
+ * @param array $params
+ *   The Paybox CGI module parameters to be altered.
+ *
+ * @param object $order
+ *   A fully loaded order object.
+ *
+ * @param array $settings
+ *   The commerce payment method (paybox_offsite) settings.
+ */
+function hook_commerce_paybox_offsite_params_alter(&$params, $order, $settings) {
+  // The paybox site will allow only credit cards payment methods.
+  $params['PBX_TYPEPAIEMENT'] = 'CARTE';
+  $params['PBX_TYPECARTE'] = 'CB';
+}
diff --git a/commerce_paybox.info b/commerce_paybox.info
new file mode 100644
index 0000000..e9f59d3
--- /dev/null
+++ b/commerce_paybox.info
@@ -0,0 +1,13 @@
+name = Commerce Paybox
+description = Paybox integration for the Drupal Commerce payment system.
+package = Commerce (Paybox)
+dependencies[] = entity
+dependencies[] = commerce
+dependencies[] = commerce_ui
+dependencies[] = commerce_payment
+dependencies[] = commerce_order
+core = 7.x
+configure = admin/commerce/config/commerce_paybox
+
+; Simple tests
+files[] = tests/commerce_paybox.test
diff --git a/commerce_paybox.install b/commerce_paybox.install
new file mode 100644
index 0000000..bfd37ad
--- /dev/null
+++ b/commerce_paybox.install
@@ -0,0 +1,86 @@
+<?php
+
+/**
+ * @file
+ * Check Paybox System requirements
+ */
+
+/**
+ * Implements hook_requirements().
+ */
+function commerce_paybox_requirements($phase) {
+  $requirements = array();
+  $t = get_t();
+
+  if ($phase == 'runtime') {
+    // Check for requirements only if "paybox" payment method is enabled.
+    $rule_commerce_payment_paybox = rules_config_load('commerce_payment_paybox_offsite');
+    if ($rule_commerce_payment_paybox->active) {
+      // Check if the CGI module is present.
+      $requirements['paybox_cgi_module'] = array();
+      $cgi_module_path = variable_get('commerce_paybox_cgi_module_path', '');
+      $bin_path = DRUPAL_ROOT . '/' . $cgi_module_path;
+      $requirements['paybox_cgi_module']['title'] = $t('CGI module Paybox System');
+      if (is_file($bin_path) && is_executable($bin_path)) {
+        $requirements['paybox_cgi_module']['value'] = $cgi_module_path;
+        $requirements['paybox_cgi_module']['severity'] = REQUIREMENT_OK;
+      }
+      else {
+        $requirements['paybox_cgi_module']['value'] = $t('Paybox CGI module not found');
+        $requirements['paybox_cgi_module']['description'] = $t('The Paybox CGI module file is not found, go to the !url to configure it properly.', array('!url' => l($t('configuration page'), 'admin/commerce/config/commerce-paybox')));
+        $requirements['paybox_cgi_module']['severity'] = REQUIREMENT_ERROR;
+      }
+
+      // Check is PHP "safe mode" is disabled.
+      $requirements['paybox_php_safe_mode']['title'] = $t("PHP 'safe mode'");
+      if (ini_get('safe_mode')) {
+        $requirements['paybox_php_safe_mode']['value'] = $t('Enabled');
+        $requirements['paybox_php_safe_mode']['description'] = $t("Paybox module require the PHP 'safe mode' to be disabled.");
+        $requirements['paybox_php_safe_mode']['severity'] = REQUIREMENT_ERROR;
+      }
+      else {
+        $requirements['paybox_php_safe_mode']['value'] = $t('Disabled');
+        $requirements['paybox_php_safe_mode']['severity'] = REQUIREMENT_OK;
+      }
+
+      // Check if OpenSSL extension is loaded.
+      $requirements['paybox_openssl']['title'] = $t('OpenSSL');
+      if (extension_loaded('openssl')) {
+        $requirements['paybox_openssl']['value'] = OPENSSL_VERSION_TEXT;
+        $requirements['paybox_openssl']['severity'] = REQUIREMENT_OK;
+      }
+      else {
+        $requirements['paybox_openssl']['value'] = $t('Missing');
+        $requirements['paybox_openssl']['description'] = $t("Paybox module require the OpenSSL extension to be loaded.");
+        $requirements['paybox_openssl']['severity'] = REQUIREMENT_ERROR;
+      }
+    }
+  }
+
+  return $requirements;
+}
+
+/**
+ * Implements hook_install().
+ */
+function commerce_paybox_install() {
+  variable_set('commerce_paybox_pubkey_path', drupal_get_path('module', 'commerce_paybox') . '/pubkey.pem');
+
+  $paybox_servers = array(
+    '195.101.99.76',
+    '194.2.122.158',
+    '195.25.7.166',
+  );
+  variable_set('commerce_paybox_paybox_servers', $paybox_servers);
+}
+
+/**
+ * Implements hook_uninstall().
+ */
+function commerce_paybox_uninstall() {
+  variable_del('commerce_paybox_cgi_module_path');
+  variable_del('commerce_paybox_hmac_key');
+  variable_del('commerce_paybox_hmac_alg');
+  variable_del('commerce_paybox_pubkey_path');
+  variable_del('commerce_paybox_paybox_servers');
+}
diff --git a/commerce_paybox.module b/commerce_paybox.module
new file mode 100644
index 0000000..4250e75
--- /dev/null
+++ b/commerce_paybox.module
@@ -0,0 +1,928 @@
+<?php
+
+/**
+ * @file
+ * Implements Paybox payment services for use with Drupal Commerce.
+ */
+
+define('PAYBOX_URL', 'https://tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi');
+define('PAYBOX_SANDBOX_URL', 'https://preprod-tpeweb.paybox.com/cgi/MYchoix_pagepaiement.cgi');
+
+define('PAYBOX_DIRECT_URL', 'https://ppps.paybox.com/PPPS.php');
+define('PAYBOX_DIRECT_SANDBOX_URL', 'https://preprod-ppps.paybox.com/PPPS.php');
+
+/**
+ * Implements hook_menu().
+ */
+function commerce_paybox_menu() {
+  $items = array();
+
+  $items['commerce_paybox/auto'] = array(
+    'type' =>  MENU_CALLBACK,
+    'page callback' => 'commerce_paybox_offsite_auto',
+    'access callback' => 'commerce_paybox_offsite_is_paybox_server',
+  );
+
+  $items['admin/commerce/config/commerce_paybox'] = array(
+    'type' => MENU_NORMAL_ITEM,
+    'title' => 'Paybox System',
+    'description' => 'Administer Paybox System.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('commerce_paybox_settings'),
+    'access arguments' => array('configure store'),
+    'file' => 'includes/commerce_paybox.admin.inc',
+  );
+
+  return $items;
+}
+
+/**
+ * Implements hook_hook_info().
+ */
+function commerce_paybox_hook_info() {
+  return array(
+    'commerce_paybox_params_alter' => array(
+      'group' => 'commerce',
+    ),
+  );
+}
+
+/**
+ * Implements hook_commerce_payment_method_info().
+ */
+function commerce_paybox_commerce_payment_method_info() {
+  return array(
+    'paybox_offsite' => array(
+      'base' => 'commerce_paybox_offsite',
+      'title' => t('Paybox System (offsite)'),
+      'short_title' => t('Paybox System'),
+      'display_title' => t('Credit card (on Paybox site)'),
+      'description' => t("The Paybox System payment method. The customer will be asked for his card number on paybox.com."),
+      'terminal' => FALSE,
+      'offsite' => TRUE,
+      'offsite_autoredirect' => TRUE,
+    ),
+    'paybox_ppps' => array(
+      'base' => 'commerce_paybox_ppps',
+      'title' => t('Paybox Direct PPPS'),
+      'short_title' => t('Paybox Direct'),
+      'display_title' => t('Credit card (with Paybox)'),
+      'description' => t('Paybox System'),
+      'description' => t("The Paybox Direct PPPS payment method. The customer will be asked for his card number on this site."),
+    ),
+  );
+}
+
+
+/**
+ * Returns the default settings for the Paybox payment method.
+ *
+ * By default, use test server with test identifiers.
+ */
+function commerce_paybox_default_settings() {
+  return array(
+    'pbx_sandbox'   => TRUE,
+    'pbx_site'      => 1999888,
+    'pbx_rank'      => 99,
+    'pbx_id'        => 2,
+    'pbx_key'       => '1999888I',
+    'pbx_lang' => array(
+      'lang_auto'    => TRUE,
+      'default_lang' => 'GBR',
+    ),
+    'pbx_ppps_plus' => FALSE,
+  );
+}
+
+/**
+ * Returns the minimal settings form for Paybox ayment methods.
+ *
+ * @param array $settings
+ *   The settings used to set form's default values.
+ *
+ * @return array
+ *   The minimal settings form.
+ */
+function commerce_paybox_common_settings_form($settings) {
+  $form = array();
+
+  $form['pbx_sandbox'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Use Paybox test plateform'),
+    '#description' => t('If checked, no payments will occurs.'),
+    '#default_value' => $settings['pbx_sandbox'],
+  );
+
+  $form['pbx_site'] = array(
+    '#type' => 'textfield',
+    '#required' => TRUE,
+    '#title' => t('Site number'),
+    '#description' => t('Site number (TPE) given by the bank.'),
+    '#default_value' => $settings['pbx_site'],
+    '#element_validate' => array('element_validate_integer'),
+  );
+
+  $form['pbx_rank'] = array(
+    '#type' => 'textfield',
+    '#required' => TRUE,
+    '#title' => t('Rank number'),
+    '#description' => t('Rank number ("machine") given by the bank.'),
+    '#default_value' => $settings['pbx_rank'],
+    '#element_validate' => array('element_validate_integer'),
+  );
+
+  $form['pbx_id'] = array(
+    '#type' => 'textfield',
+    '#required' => TRUE,
+    '#title' => t('Paybox identifier'),
+    '#description' => t('Paybox identifier provided by Paybox Services at registration time.'),
+    '#default_value' => $settings['pbx_id'],
+    '#element_validate' => array('element_validate_integer'),
+  );
+
+  return $form;
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_settings_form().
+ */
+function commerce_paybox_offsite_settings_form($settings = NULL) {
+  // Merge settings with default settings.
+  $settings += commerce_paybox_default_settings();
+
+  $form = commerce_paybox_common_settings_form($settings);
+
+  $form['pbx_lang'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Paybox language'),
+    'lang_auto' => array(
+      '#type' => 'checkbox',
+      '#title' => t('Try to auto-detect language'),
+      '#default_value' => $settings['pbx_lang']['lang_auto'],
+      '#description' => t('If checked, module will try to automaically set language according to !url.', array('!url' => l(t('languages settings'), 'admin/config/regional/language'))),
+    ),
+    'default_lang' => array(
+      '#type' => 'select',
+      '#title' => t('Default language'),
+      '#description' => t('The language to use on Paybox site.'),
+      '#options' => commerce_paybox_get_paybox_languages(),
+      '#default_value' => $settings['pbx_lang']['default_lang'],
+    ),
+  );
+
+  return $form;
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_redirect_form().
+ */
+function commerce_paybox_offsite_redirect_form($form, &$form_state, $order, $payment_method) {
+  $settings = $payment_method['settings'];
+  // Merge settings with default settings.
+  $settings += commerce_paybox_default_settings();
+
+  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+  // Get payment total.
+  $total = $order_wrapper->commerce_order_total->amount->value();
+
+  // Get payment currency numeric code.
+  $currency_info = commerce_currencies();
+  $currency_code = $order_wrapper->commerce_order_total->currency_code->value();
+
+  // Create a new transaction.
+  $txn = commerce_payment_transaction_new('paybox_offsite', $order->order_id);
+  $txn->status = COMMERCE_PAYMENT_STATUS_PENDING;
+  $txn->instance_id = $payment_method['instance_id'];
+  $txn->amount = $total;
+  $txn->currency_code = $currency_code;
+  commerce_payment_transaction_save($txn);
+
+  // Get the language to use.
+  $pbx_lang = $settings['pbx_lang']['default_lang'];
+  if ($settings['pbx_lang']['lang_auto']) {
+    global $language;
+    if ($lang_auto = commerce_paybox_map_language($language->language)) {
+      $pbx_lang = $lang_auto;
+    }
+  }
+
+  $pbx_params = array(
+    // Required params.
+    'PBX_SITE'        => $settings['pbx_site'],
+    'PBX_RANG'        => $settings['pbx_rank'],
+    'PBX_TOTAL'       => str_pad($total, 3, '0', STR_PAD_LEFT),
+    'PBX_DEVISE'      => $currency_info[$currency_code]['numeric_code'],
+    'PBX_CMD'         => $txn->transaction_id,
+    'PBX_PORTEUR'     => $order->mail,
+    'PBX_RETOUR'      => "txnid:R;error:E;sig:K",
+    'PBX_IDENTIFIANT' => $settings['pbx_id'],
+    // Optionals params.
+    'PBX_EFFECTUE'    => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
+    'PBX_REFUSE'      => url('checkout/' . $order->order_id . '/payment/return/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
+    'PBX_ANNULE'      => url('checkout/' . $order->order_id . '/payment/back/' . $order->data['payment_redirect_key'], array('absolute' => TRUE)),
+    'PBX_REPONDRE_A'  => url('commerce_paybox/auto', array('absolute' => TRUE)),
+
+    'PBX_LANGUE'      => $pbx_lang,
+  );
+
+  // Allow other modules to alter Paybox CGI module parameters.
+  drupal_alter('commerce_paybox_offsite_params', $pbx_params, $order, $settings);
+
+  // If no HMAC key, use CGI module.
+  $hmac_key = variable_get('commerce_paybox_hmac_key');
+  if (!$hmac_key) {
+    // Paybox mode and output must not be altered. Mode 4 is "command line"
+    // mode, output D is "output only PXB_DATA".
+    $pbx_params['PBX_MODE'] = 4;
+    $pbx_params['PBX_OUTPUT'] = 'D';
+
+    // Construct the command line.
+    $bin_params = '';
+    foreach ($pbx_params as $name => $value) {
+      $safe_value = escapeshellarg($value);
+      $bin_params .= " $name=$safe_value";
+    }
+    $bin_path = variable_get('commerce_paybox_cgi_module_path', '');
+    $cmd = DRUPAL_ROOT . '/' . $bin_path . $bin_params;
+
+    // Execute the command line : returns PBX_DATA value.
+    $output = trim(shell_exec($cmd));
+
+    $matches = array();
+    if (empty($output) || preg_match('#PAYBOX INPUT ERROR \(code (-[0-9]*?)\)#ism', $output, $matches)) {
+      // Something goes wrong with GCI execution...
+      $form['error'] = array(
+        '#type' => 'item',
+        '#title' => t('Error'),
+        '#markup' => t('Paybox module initialisation failed.'),
+      );
+
+      // Log the error.
+      $log = 'Paybox CGI module execution has failed : error @code (shell command: "!cmd").';
+      $log_vars = array(
+        '@code' => $matches[1],
+        '!cmd' => $cmd,
+      );
+      $link = l(t('View transaction'), "admin/commerce/orders/$txn->order_id/payment/$txn->transaction_id/view");
+      watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR, $link);
+
+      // Create hidden inputs from $pbx_params.
+      $pbx_params['PBX_DATA'] = $output;
+    }
+  }
+  else {
+    $pbx_params['PBX_HASH'] = variable_get('commerce_paybox_hmac_alg', 'SHA512');
+    $pbx_params['PBX_TIME'] = date('c');
+
+    $form['#pre_render'][] = 'commerce_paybox_hmac_clean_form';
+
+    // Disable form_token (target isn't ourselves).
+    $form['#token'] = FALSE;
+
+    // Build of the string to hash matching POST's content
+    // used to generate PBX_HMAC.
+    $msg = '';
+    foreach ($pbx_params as $k => $v) {
+      $msg .= $k . "=" . $v . "&";
+    }
+    $msg = rtrim($msg, '&');
+
+    // Code as Paybox's example to generate PBX_HMAC key.
+    $binkey = pack("H*", $hmac_key);
+    $hmac_alg = strtolower($pbx_params['PBX_HASH']);
+    $hmac = hash_hmac($hmac_alg, $msg, $binkey);
+    $pbx_params['PBX_HMAC'] = strtoupper($hmac);
+  }
+
+  // Order of the fields NEEDS to be kept between form and
+  // hashed string: enforcing order with #weight
+  $pbx_weight = 0.01;
+  foreach ($pbx_params as $name => $value) {
+    $form[$name] = array(
+      '#type' => 'hidden',
+      '#value' => $value,
+      '#weight' => $pbx_weight,
+    );
+    $pbx_weight = $pbx_weight + 0.01;
+  }
+
+  $form['image'] = array(
+    '#theme' => 'image',
+    '#path' => drupal_get_path('module', 'commerce_paybox') . '/images/secu.png',
+    '#alt' => t('Secure payment by Paybox.'),
+    '#weight' => -999,
+  );
+
+  $form['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Pay with Paybox'),
+  );
+
+  if ($settings['pbx_sandbox']) {
+    $form['#action'] = PAYBOX_SANDBOX_URL;
+  }
+  else {
+    $form['#action'] = PAYBOX_URL;
+  }
+  return $form;
+}
+
+/**
+ * Callback for cleaning form in the HMAC case.
+ *
+ * Removes drupal's specific form_id, form_build_id, form_token and submit's
+ * value from client's POST to paybox's servers.
+ *
+ * @param array $form
+ *   A form array.
+ *
+ * @return array
+ *   A modified form array
+ */
+function commerce_paybox_hmac_clean_form($form) {
+  // Strip drupal's elements from form.
+  unset($form['form_token']);
+  unset($form['form_build_id']);
+  unset($form['form_id']);
+  $form['submit']['#name'] = '';
+  return $form;
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_redirect_form_validate().
+ */
+function commerce_paybox_offsite_redirect_form_validate($order, $payment_method) {
+  $txn = commerce_payment_transaction_load($_GET['txnid']);
+
+  if ($txn->status === COMMERCE_PAYMENT_STATUS_FAILURE) {
+    return FALSE;
+  }
+
+  if ($txn->status === COMMERCE_PAYMENT_STATUS_PENDING) {
+    drupal_set_message(t('Your order is still waiting for approval.'), 'warning');
+  }
+}
+
+/**
+ * Menu callback for offsite payment confirmation.
+ *
+ * This page is called up from server to server as soon as customers validate
+ * their payment (whether the payment is authorised or refused) (see hook_menu).
+ */
+function commerce_paybox_offsite_auto() {
+  if ($txn = commerce_payment_transaction_load($_GET['txnid'])) {
+    // Check parametres signature according to the Paybox public key.
+    if (commerce_paybox_offsite_check_sign($_SERVER['QUERY_STRING'])) {
+      // Save return message.
+      $txn->message = commerce_paybox_get_paybox_error_msg($_GET['error']);
+
+      // Check payment is accepted.
+      if ($_GET['error'] === '00000') {
+        $txn->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+      }
+      else {
+        // Log paybox signature errors.
+        $log = 'Error @error: !msg';
+        $log_vars = array(
+          '@error' => $_GET['error'],
+          '!msg' => $txn->message,
+        );
+        $log_link = l(t('View transaction'), "admin/commerce/orders/$txn->order_id/payment/$txn->transaction_id/view");
+        watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR, $log_link);
+
+        // Invalidate the transaction.
+        $txn->message = t($log, $log_vars);
+        $txn->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      }
+    }
+    else {
+      // Log paybox signature errors.
+      $log = 'Paybox System has failed to encrypt his own datas for transaction @txn_id, order @order_id.';
+      $log_vars = array(
+        '@txn_id' => $txn->transaction_id,
+        '@order_id' => $txn->order_id,
+      );
+      $log_link = l(t('View transaction'), "admin/commerce/orders/$txn->order_id/payment/$txn->transaction_id/view");
+      watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR, $log_link);
+
+      // Invalidate the transaction.
+      $txn->message = t('Paybox System has failed to encrypt his own datas.');
+      $txn->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+    }
+    commerce_payment_transaction_save($txn);
+  }
+  else {
+    // Log: failed to load the transaction.
+    $log = 'Paybox module cannot load the transaction returned by the Paybox server.';
+    $log_vars = array();
+    watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR);
+  }
+
+  // Output an empty HTML page.
+  return NULL;
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_settings_form().
+ */
+function commerce_paybox_ppps_settings_form($settings = NULL) {
+  // Merge settings with default settings.
+  $settings += commerce_paybox_default_settings();
+
+  $form = commerce_paybox_common_settings_form($settings);
+
+  $form['pbx_key'] = array(
+    '#type' => 'textfield',
+    '#required' => TRUE,
+    '#title' => t('Paybox key'),
+    '#description' => t('The pasword user to access the Paybox back-office interface.'),
+    '#default_value' => $settings['pbx_key'],
+  );
+
+  $form['pbx_ppps_plus'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Paybox Direct Plus'),
+    '#description' => t('Is the contract a Paybox Direct Plus contract?'),
+    '#default_value' => $settings['pbx_ppps_plus'],
+  );
+
+  return $form;
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_submit_form().
+ */
+function commerce_paybox_ppps_submit_form($payment_method, $pane_values, $checkout_pane, $order) {
+  module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card');
+
+  $fields = array(
+    'type' => array('visa', 'mastercard'),
+    'code' => '',
+  );
+
+  return commerce_payment_credit_card_form($fields);
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_submit_form_validate().
+ */
+function commerce_paybox_ppps_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) {
+  // Merge settings with default settings.
+  $payment_method['settings'] += commerce_paybox_default_settings();
+
+  // Paybox will validate everything, nothing to do here.
+}
+
+/**
+ * Implements CALLBACK_commerce_payment_method_submit_form_submit().
+ */
+function commerce_paybox_ppps_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) {
+  $settings = $payment_method['settings'];
+  // Merge settings with default settings.
+  $settings += commerce_paybox_default_settings();
+
+  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+  // Get payment total.
+  $total = $order_wrapper->commerce_order_total->amount->value();
+
+  // Get payment currency numeric code.
+  $currency_info = commerce_currencies();
+  $currency_code = $order_wrapper->commerce_order_total->currency_code->value();
+
+  // Create a new transaction.
+  $txn = commerce_payment_transaction_new('paybox_ppps', $order->order_id);
+  $txn->instance_id = $payment_method['instance_id'];
+  $txn->amount = $total;
+  $txn->currency_code = $currency_code;
+  commerce_payment_transaction_save($txn);
+
+  // Build POST datas.
+  $datas = array(
+    // Don't know why, but DATEQ have to be the first or Paybox server crash.
+    'DATEQ'        => format_date(time(), 'custom', 'dmY'),
+    'SITE'         => $settings['pbx_site'],
+    'RANG'         => $settings['pbx_rank'],
+    'TYPE'         => '00003',
+    'NUMQUESTION'  => str_pad($txn->transaction_id, 10, '0', STR_PAD_LEFT),
+    'MONTANT'      => str_pad($total, 3, '0', STR_PAD_LEFT),
+    'DEVISE'       => $currency_info[$currency_code]['numeric_code'],
+    'REFERENCE'    => 'order-' . $order->order_id,
+    'PORTEUR'      => $pane_form['credit_card']['number']['#value'],
+    'DATEVAL'      => $pane_form['credit_card']['exp_month']['#value'] . drupal_substr($pane_form['credit_card']['exp_year']['#value'], 2),
+    'CVV'          => $pane_form['credit_card']['code']['#value'],
+    'VERSION'      => $settings['pbx_ppps_plus'] ? '00104' : '00103',
+    'CLE'          => $settings['pbx_key'],
+    'IDENTIFIANT'  => $settings['pbx_id'],
+    'ACTIVITE'     => '024',
+    'DIFFERE'      => '000',
+  );
+
+  // Build request options.
+  $options = array(
+    'method' => 'POST',
+    'data' => drupal_http_build_query($datas),
+  );
+
+  if ($settings['pbx_sandbox']) {
+    $url = PAYBOX_DIRECT_SANDBOX_URL;
+  }
+  else {
+    $url = PAYBOX_DIRECT_URL;
+  }
+
+  // Send datas to Paybox server.
+  $result = drupal_http_request($url, $options);
+  if (isset($result->error)) {
+    $message = t('An error occured : @error', array('@error' => $result->error));
+
+    $txn->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+    $txn->message = $message;
+    commerce_payment_transaction_save($txn);
+
+    drupal_set_message($message, 'error');
+
+    // Log the error.
+    $log = 'An error occured while trying to contact Paybox Direct PPPS server : error @code : @error.';
+    $log_vars = array(
+      '@code' => $result->code,
+      '@error' => $result->error,
+    );
+    $log_link = l(t('View transaction'), "admin/commerce/orders/$txn->order_id/payment/$txn->transaction_id/view");
+    watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR, $log_link);
+
+    return FALSE;
+  }
+  else {
+    $response = array();
+    parse_str($result->data, $response);
+
+    // Save return message.
+    $txn->message = commerce_paybox_get_paybox_error_msg($response['CODEREPONSE']);
+    $txn->remote_id = $response['NUMTRANS'] . '-' . $response['NUMAPPEL'];
+
+    // Check payment is accepted.
+    if ($response['CODEREPONSE'] === '00000') {
+      $txn->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+      commerce_payment_transaction_save($txn);
+    }
+    else {
+      $message = t('Error @error: !msg', array('@error' => $response['CODEREPONSE'], '!msg' => $txn->message));
+
+      // Save the transaction.
+      $txn->status = COMMERCE_PAYMENT_STATUS_FAILURE;
+      $txn->message = $message;
+      commerce_payment_transaction_save($txn);
+      drupal_set_message($message, 'error');
+
+      // Log the error.
+      $log = 'Paybox Direct PPPS server returned an error : @error';
+      $log_vars = array('@error' => $message);
+      $log_link = l(t('View transaction'), "admin/commerce/orders/$txn->order_id/payment/$txn->transaction_id/view");
+      watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR, $log_link);
+
+      return FALSE;
+    }
+  }
+}
+
+/**
+ * Page access callback for commerce_paybox_auto.
+ *
+ * Checks if the client IP address is in the 'commerce_paybox_paybox_servers'
+ * variable (see hook_menu).
+ *
+ * @return bool
+ *   TRUE if client's IP is allowed, FALSE otherwise.
+ */
+function commerce_paybox_offsite_is_paybox_server() {
+  $paybox_servers = variable_get('commerce_paybox_paybox_servers', '');
+  return in_array(ip_address(), $paybox_servers);
+}
+
+/**
+ * Check if signature of the Paybox server's response URL is correct.
+ *
+ * @return bool
+ *   TRUE if signing is correct, FALSE otherwise.
+ */
+function commerce_paybox_offsite_check_sign($query_string) {
+  $matches = array();
+  if (preg_match('/(?:q=.*?&)?(.*)&sig=(.*)$/', $query_string, $matches)) {
+    $data = $matches[1];
+    $sig = base64_decode(urldecode($matches[2]));
+
+    $key_file = variable_get('commerce_paybox_pubkey_path', drupal_get_path('module', 'commerce_paybox') . '/pubkey.pem');
+    if ($key_file_content = file_get_contents($key_file)) {
+      if ($key = openssl_pkey_get_public($key_file_content)) {
+        return openssl_verify($data, $sig, $key);
+      }
+    }
+
+    $log = 'Cannot read Paybox System public key file (@file).';
+    $log_vars = array('@file' => $key_file);
+    watchdog('commerce_paybox', $log, $log_vars, WATCHDOG_ERROR);
+  }
+
+  return FALSE;
+}
+
+/**
+ * Return the languages list supported by Paybox module (internal use only).
+ *
+ * @return array
+ *   An associative array containing Paybox supported languages which keys are
+ *   Paybox language codes and values are translated language names.
+ */
+function commerce_paybox_get_paybox_languages() {
+  return array(
+    'FRA' => t('French'),
+    'GBR' => t('English'),
+    'ESP' => t('Spanish'),
+    'ITA' => t('Italian'),
+    'DEU' => t('German'),
+    'NLD' => t('Dutch'),
+    'SWE' => t('Swedish'),
+    'PRT' => t('Portuguese'),
+  );
+}
+
+/**
+ * Try to map the Drupal's language code to the Paybox language code.
+ *
+ * @param string $langcode
+ *   A language code (eg. 'en', 'fr', ...).
+ *
+ * @return string|false
+ *   The Paybox language code if a mapping is found, FALSE otherwise.
+ */
+function commerce_paybox_map_language($langcode) {
+  $lang_map = array(
+    'fr'    => 'FRA',
+    'en'    => 'GBR',
+    'en-gb' => 'GBR',
+    'es'    => 'ESP',
+    'it'    => 'ITA',
+    'de'    => 'DEU',
+    'nl'    => 'NLD',
+    'sv'    => 'SWE',
+    'pt'    => 'PRT',
+    'pt-pt' => 'PRT',
+    'pt-br' => 'PRT',
+  );
+
+  return isset($lang_map[$langcode]) ? $lang_map[$langcode] : FALSE;
+}
+
+/**
+ * Retrive the error message according to the error code from Paybox server.
+ *
+ * @param string $error
+ *   The error code returned by the Paybox System server.
+ *
+ * @return string
+ *   The translated error message.
+ */
+function commerce_paybox_get_paybox_error_msg($error) {
+  if (drupal_substr($error, 0, 3) == '001') {
+    $precise_error = drupal_substr($error, 2);
+    switch ($precise_error) {
+      case '00':
+        $msg = t('Transaction approved or successfully handled.');
+
+      case '02':
+        $msg = t('Contact the card issuer.');
+
+      case '03':
+        $msg = t('Invalid shop.');
+
+      case '04':
+        $msg = t('Keep the card.');
+
+      case '07':
+        $msg = t('Keep the card, special conditions.');
+
+      case '08':
+        $msg = t('Approve after holder identification.');
+
+      case '12':
+        $msg = t('Invalid transaction.');
+
+      case '13':
+        $msg = t('Invalid amount.');
+
+      case '14':
+        $msg = t('Invalid holder number.');
+
+      case '15':
+        $msg = t('Unknown card issuer.');
+
+      case '17':
+        $msg = t('Client has cancelled.');
+
+      case '19':
+        $msg = t('Try transaction again later.');
+
+      case '20':
+        $msg = t('Bad answer (error on server domain).');
+
+      case '24':
+        $msg = t('Unsupported file update.');
+
+      case '25':
+        $msg = t('Unable to locate record in file.');
+
+      case '26':
+        $msg = t('Duplicate record, old record has been replaced.');
+
+      case '27':
+        $msg = t('Edit error during file update.');
+
+      case '28':
+        $msg = t('Unauthorized file access.');
+
+      case '29':
+        $msg = t('Impossible file update.');
+
+      case '30':
+        $msg = t('Format error.');
+
+      case '33':
+        $msg = t('Validity date of the card reached.');
+
+      case '34':
+        $msg = t('Fraud suspicion.');
+
+      case '38':
+        $msg = t('Number of tries for confidential code reached.');
+
+      case '41':
+        $msg = t('Lost card.');
+
+      case '43':
+        $msg = t('Stolen card.');
+
+      case '51':
+        $msg = t('Insufficient funds or no credit left.');
+
+      case '54':
+        $msg = t('Validity date of the card reached.');
+
+      case '55':
+        $msg = t('Bad confidential code.');
+
+      case '56':
+        $msg = t('Card not in the file.');
+
+      case '57':
+        $msg = t('Transaction not authorized for this cardholder.');
+
+      case '58':
+        $msg = t('Transaction not authorized for this terminal.');
+
+      case '59':
+        $msg = t('Fraud suspicion.');
+
+      case '61':
+        $msg = t('Debit limit reached.');
+
+      case '63':
+        $msg = t('Security rules not followed.');
+
+      case '68':
+        $msg = t('Absent or late answer.');
+
+      case '75':
+        $msg = t('Number of tries for confidential code reached.');
+
+      case '76':
+        $msg = t('Cardholder already opposed, old record kept.');
+
+      case '90':
+        $msg = t('System temporary stopped.');
+
+      case '91':
+        $msg = t('Card provider is unreachable.');
+
+      case '94':
+        $msg = t('Duplicate question.');
+
+      case '96':
+        $msg = t('Bad system behavior.');
+
+      case '97':
+        $msg = t('Global surveillance timeout.');
+
+      case '98':
+        $msg = t('Server is unreachable.');
+
+      case '99':
+        $msg = t('Incident from initiator domain.');
+
+      default:
+        $msg = $precise_error;
+    }
+    return t('Payment refused by autorisation center (error @error).', array('@error' => $msg));
+  }
+  switch ($error) {
+    case '00000':
+      return t('Operation successful.');
+
+    case '00001':
+      return t('Connexion to autorise center failed.');
+
+    case '00002':
+      return t('An error in coherence has occurred.');
+
+    case '00003':
+      return t('Paybox error.');
+
+    case '00004':
+      return t('Owner number or cryptogram invalid.');
+
+    case '00005':
+      return t('Invalid question number .');
+
+    case '00006':
+      return t('Access refused or rank/site/is incorrect.');
+
+    case '00007':
+      return t('Invalid date.');
+
+    case '00008':
+      return t('Error on expiry date');
+
+    case '00009':
+      return t('Error creating subscription.');
+
+    case '00010':
+      return t('Unknow currency.');
+
+    case '00011':
+      return t('Wrong order total.');
+
+    case '00012':
+      return t('Invalid order reference.');
+
+    case '00013':
+      return t('This version is no longer upheld.');
+
+    case '00014':
+      return t('Incoherent frame received.');
+
+    case '00015':
+      return t('Error in access to previously referenced data.');
+
+    case '00016':
+      return t('User already exists.');
+
+    case '00017':
+      return t('User does not exist.');
+
+    case '00018':
+      return t('Transaction not found.');
+
+    case '00020':
+      return t('CVV not present.');
+
+    case '00021':
+      return t('Unauthorize card.');
+
+    case '00024':
+      return t('Error loading of the key.');
+
+    case '00025':
+      return t('Missing signature.');
+
+    case '00026':
+      return t('Missing key but the signature is present.');
+
+    case '00027':
+      return t('Error OpenSSL during the checking of the signature.');
+
+    case '00028':
+      return t('Unchecked signature.');
+
+    case '00029':
+      return t('Card non-compliant.');
+
+    case '00030':
+      return t('Timeout on checkout page (> 15 mn).');
+
+    case '00031':
+      return t('Reserved.');
+
+    case '00097':
+      return t('Timeout of connection ended.');
+
+    case '00098':
+      return t('Internal connection error.');
+
+    case '00099':
+      return t('Incoherence between the question and the answer. Try again later.');
+
+    default:
+      return t('Unknown error.');
+  }
+}
diff --git a/images/secu.png b/images/secu.png
new file mode 100644
index 0000000..93e07f6
Binary files /dev/null and b/images/secu.png differ
diff --git a/includes/commerce_paybox.admin.inc b/includes/commerce_paybox.admin.inc
new file mode 100644
index 0000000..b35ae6b
--- /dev/null
+++ b/includes/commerce_paybox.admin.inc
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the Commerce Paybox module.
+ */
+
+/**
+ * Builds and returns the Paybox settings form.
+ *
+ * @see commerce_paybox_settings_validate()
+ *
+ * @ingroup forms
+ */
+function commerce_paybox_settings($form, &$form_state) {
+  $rule_commerce_payment_paybox = rules_config_load('commerce_payment_paybox_offsite');
+
+  $cgi_module_path = variable_get('commerce_paybox_cgi_module_path', '');
+  $hmac_key = variable_get('commerce_paybox_hmac_key', '');
+  $hmac_alg = variable_get('commerce_paybox_hmac_alg', 'SHA512');
+  if ($rule_commerce_payment_paybox->active) {
+    $bin_path = DRUPAL_ROOT . '/' . $cgi_module_path;
+    if (is_file($bin_path) && !is_executable($bin_path)) {
+      $message = t('File "@path" is not executable. Please, check permissions on this file.', array('@path' => $cgi_module_path));
+      drupal_set_message($message, 'error');
+    }
+  }
+  else {
+    $form['notice'] = array(
+      '#type' => 'item',
+      '#title' => t('Notice'),
+      '#markup' => t('None of this settings will be used unless you activate the "Paybox System" payment method.'),
+    );
+  }
+
+  // Paybox key.
+  $form['commerce_paybox_hmac_key'] = array(
+    '#type' => 'textfield',
+    '#size' => '128',
+    '#title' => t('Paybox private key'),
+    '#description' => t("Private key generated on paybox's admin interface"),
+    '#default_value' => $hmac_key,
+  );
+
+  // Paybox key's algorithm.
+  $form['commerce_paybox_hmac_alg'] = array(
+    '#type' => 'select',
+    '#title' => t('Algorithm to use with HMAC method'),
+    '#description' => t('Select algorithm from allowed list (SHA512 by default)'),
+    '#options' => array(
+      'SHA512' => 'SHA512',
+      'SHA256' => 'SHA256',
+      'SHA384' => 'SHA384',
+      'RIPEMD160' => 'RIPEMD160',
+      'SHA224' => 'SHA224',
+      'MDC2' => 'MDC2',
+    ),
+    '#default' => $hmac_alg,
+  );
+
+  // Path to the CGI module.
+  $form['commerce_paybox_cgi_module_path'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Path to the Paybox System CGI module (if not using HMAC key, which is recommended)'),
+    '#description' => t('Path to the Paybox CGI module, relative to the drupal root filesystem (ex : sites/all/libraries/paybox/module_debian64bits_noyau2-6-18_libc2-3-6.cgi).'),
+    '#default_value' => $cgi_module_path,
+  );
+
+  // Path to the public key file.
+  $form['commerce_paybox_pubkey_path'] = array(
+    '#type' => 'textfield',
+    '#required' => $rule_commerce_payment_paybox->active,
+    '#title' => t('Path to the Paybox System public key file'),
+    '#description' => t('Path to the Paybox public key file, relative to the drupal root filesystem (ex : sites/all/libraries/paybox/pubkey.pem).'),
+    '#default_value' => variable_get('commerce_paybox_pubkey_path', ''),
+  );
+
+  $paybox_servers = variable_get('commerce_paybox_paybox_servers', array());
+  $form['commerce_paybox_paybox_servers'] = array(
+    '#type' => 'textarea',
+    '#required' => $rule_commerce_payment_paybox->active,
+    '#title' => t('Paybox IP addresses'),
+    '#description' => t('Paybox payment servers IP addresses (one per line)'),
+    '#default_value' => implode("\n", $paybox_servers),
+  );
+
+  return system_settings_form($form);
+}
+
+/**
+ * Validates the Paybox settings form.
+ */
+function commerce_paybox_settings_validate($form, &$form_state) {
+  $form_state['values']['commerce_paybox_cgi_module_path'] = trim($form_state['values']['commerce_paybox_cgi_module_path'], '/');
+
+  $hmac_key = $form_state['values']['commerce_paybox_hmac_key'];
+  if ($hmac_key == '') {
+    $cgi_module_path = $form_state['values']['commerce_paybox_cgi_module_path'];
+    if (!is_file(DRUPAL_ROOT . '/' . $cgi_module_path)) {
+      $message = t('No HMAC key defined, which is the preferred method for paybox');
+      form_set_error('commerce_paybox_hmac_key', $message);
+      $message = t('File "@path" could not be found on the server.', array('@path' => $cgi_module_path));
+      form_set_error('commerce_paybox_cgi_module_path', $message);
+    }
+  }
+
+  $pubkey_path = $form_state['values']['commerce_paybox_pubkey_path'];
+  if (!is_file(DRUPAL_ROOT . '/' . $pubkey_path)) {
+    $message = t('File "@path" could not be found on the server.', array('@path' => $pubkey_path));
+    form_set_error('commerce_paybox_pubkey_path', $message);
+  }
+
+  $form_state['values']['commerce_paybox_paybox_servers'] = array_map('trim', explode("\n", $form_state['values']['commerce_paybox_paybox_servers']));
+}
diff --git a/pubkey.pem b/pubkey.pem
new file mode 100644
index 0000000..310d1eb
--- /dev/null
+++ b/pubkey.pem
@@ -0,0 +1,6 @@
+-----BEGIN PUBLIC KEY-----
+MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDe+hkicNP7ROHUssGNtHwiT2Ew
+HFrSk/qwrcq8v5metRtTTFPE/nmzSkRnTs3GMpi57rBdxBBJW5W9cpNyGUh0jNXc
+VrOSClpD5Ri2hER/GcNrxVRP7RlWOqB1C03q4QYmwjHZ+zlM4OUhCCAtSWflB4wC
+Ka1g88CjFwRw/PB9kwIDAQAB
+-----END PUBLIC KEY-----
diff --git a/tests/commerce_paybox.test b/tests/commerce_paybox.test
new file mode 100644
index 0000000..83e275a
--- /dev/null
+++ b/tests/commerce_paybox.test
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Defines abstract base test class for the Paybox module tests.
+ */
+
+/**
+ * Test payment user interface.
+ */
+class CommercePayboxTestCase extends DrupalWebTestCase {
+
+  /**
+   * Implementation of getInfo().
+   */
+  public static function getInfo() {
+    return array(
+      'name' => t('Paybox System'),
+      'description' => t('Tests the commerce_paybox module.'),
+      'group' => t('Commerce Paybox'),
+    );
+  }
+
+  public function setUp() {
+    parent::setUp('commerce_paybox');
+  }
+
+  /**
+   * Tests the function commerce_paybox_check_sign().
+   */
+  public function testCheckSign() {
+    $group = 'Paybox signature';
+
+    $test_cases = array(
+      array(
+        'label' => t('Well signed query (w/ clean URLs).'),
+        'query' => drupal_http_build_query(array(
+          'txnid' => '61',
+          'error' => '00004',
+          'sig' => 'bfq0uj3553qkYln/6p/NShOpE9LGmrmPQWlWKziDR3k/aULYviqiF6Ez/FugTuH2E1TYpiuVFtf455GlRWIhyaPC6XdGc5KAtBzC8SehWOiZ3Cwf6PPEIhDsDsWvRVajjsx9wTp5XL2C7+LoKtftHUikOZic6jr62cnr1wAieOk=',
+        )),
+        'assert_func' => 'assertTrue',
+      ),
+      array(
+        'label' => t('Well signed query (w/o clean URLs).'),
+        'query' => drupal_http_build_query(array(
+          'q' => 'commerce_paybox/auto',
+          'txnid' => '61',
+          'error' => '00004',
+          'sig' => 'bfq0uj3553qkYln/6p/NShOpE9LGmrmPQWlWKziDR3k/aULYviqiF6Ez/FugTuH2E1TYpiuVFtf455GlRWIhyaPC6XdGc5KAtBzC8SehWOiZ3Cwf6PPEIhDsDsWvRVajjsx9wTp5XL2C7+LoKtftHUikOZic6jr62cnr1wAieOk=',
+        )),
+        'assert_func' => 'assertTrue',
+      ),
+      array(
+        'label' => t('Wrong signed query (w/ clean URLs).'),
+        'query' => drupal_http_build_query(array(
+          'txnid' => '61',
+          'error' => '00004',
+          'sig' => 'NOT A VALID SIGNATURE',
+        )),
+        'assert_func' => 'assertFalse',
+      ),
+      array(
+        'label' => t('Wrong signed query (w/o clean URLs).'),
+        'query' => drupal_http_build_query(array(
+          'q' => 'commerce_paybox/auto',
+          'txnid' => '61',
+          'error' => '00004',
+          'sig' => 'NOT A VALID SIGNATURE',
+        )),
+        'assert_func' => 'assertFalse',
+      ),
+    );
+
+    foreach ($test_cases as $test_case) {
+      $this->{$test_case['assert_func']}(commerce_paybox_offsite_check_sign($test_case['query']), $test_case['label'], $group);
+    }
+  }
+
+}
