From daabbfd66d81cd6f2b9f12cb4a8cba92f3d71305 Mon Sep 17 00:00:00 2001
From: Nathan Vergunst-Kolozsvári <naveko@862572.no-reply.drupal.org>
Date: Sun, 12 May 2013 11:35:48 +0200
Subject: by naveko, skilip, roderik: Push functionality.

---
 commerce_buckaroo.api.php              |  22 ++++++
 commerce_buckaroo.module               |  25 +++++-
 includes/commerce_buckaroo.admin.inc   |   7 ++
 includes/commerce_buckaroo.payment.inc | 137 +++++++++++++++++++++++++++++++++
 4 files changed, 188 insertions(+), 3 deletions(-)
 create mode 100644 commerce_buckaroo.api.php

diff --git a/commerce_buckaroo.api.php b/commerce_buckaroo.api.php
new file mode 100644
index 0000000..c0036d8
--- /dev/null
+++ b/commerce_buckaroo.api.php
@@ -0,0 +1,22 @@
+<?php
+/**
+ * @file
+ * API documentation for the Commerce Buckaroo module.
+ */
+
+/**
+ * This function gets called when a push request is successfully saved as a
+ * Drupal Commerce transaction. (These are
+ * - all new transactions;
+ * - transactions received repeatedly: only the ones with status 'success'.)
+ *
+ * @param object $transaction
+ *   Commerce Payment Transaction object that was just saved.
+ * @param array $response
+ *   Array containing response from buckaroo server (a cleaned $_REQUEST).
+ *   Note all parameters (the array keys) are lowercased.
+ */
+function hook_commerce_buckaroo_push($transaction, $response) {
+
+  // For example: send transaction/order details on to a remote server.
+}
diff --git a/commerce_buckaroo.module b/commerce_buckaroo.module
index d8014bc..9dc2682 100644
--- a/commerce_buckaroo.module
+++ b/commerce_buckaroo.module
@@ -31,6 +31,12 @@ function commerce_buckaroo_menu() {
     'access arguments' => array('administer buckaroo'),
     'file' => 'includes/commerce_buckaroo.admin.inc',
   );
+  $items['buckaroo_push'] = array(
+    'page callback' => 'commerce_buckaroo_handle_push',
+    'type' => MENU_CALLBACK,
+    'file' => 'includes/commerce_buckaroo.payment.inc',
+    'access callback' => TRUE,
+  );
 
   return $items;
 }
@@ -114,7 +120,14 @@ function commerce_buckaroo_redirect_form($form, &$form_state, $order, $payment_m
 }
 
 /**
- * Implements hook_redirect_form_validate.
+ * Validation handler for the redirect form. This only gets executed after
+ * Buckaroo has accepted payment and the user is redirected back to
+ * this website.
+ *
+ * Buckaroo may have made a push request before this time (but the order was not
+ * officially set to 'completed' then, by Drupal Commerce).
+ *
+ * Note all parameters from Buckaroo's request (the array keys) are lowercased.
  */
 function commerce_buckaroo_redirect_form_validate($order, $payment_method) {
   module_load_include('inc', 'commerce_buckaroo', 'includes/commerce_buckaroo.payment');
@@ -122,6 +135,11 @@ function commerce_buckaroo_redirect_form_validate($order, $payment_method) {
   // Process Buckaroo's response
   $response = array_map('check_plain', $_REQUEST);
 
+  // Always create a new transaction (despite a 'push request' possibly
+  // already having created another transaction for this order?)
+  // Note the 'order_id' and 'method' parts of $payment_status are not used;
+  // they are always inserted as-is from the data we collected before the user
+  // was redirected to buckaroo.
   $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id);
 
   // Parse result code
@@ -131,8 +149,9 @@ function commerce_buckaroo_redirect_form_validate($order, $payment_method) {
   $transaction->instance_id = $payment_method['instance_id'];
   $transaction->remote_id = $response['bpe_trx'];
   // payment amount details
-  $transaction->amount = $order->commerce_order_total[LANGUAGE_NONE][0]['amount'];
-  $transaction->currency_code = $order->commerce_order_total[LANGUAGE_NONE][0]['currency_code'];
+  $wrapper = entity_metadata_wrapper('commerce_order', $order);
+  $transaction->amount = $wrapper->commerce_order_total->amount->value();
+  $transaction->currency_code = $wrapper->commerce_order_total->currency_code->value();
   // payment status
   $transaction->remote_status = $response['bpe_result'];
   $transaction->status = $payment_status['code'];
diff --git a/includes/commerce_buckaroo.admin.inc b/includes/commerce_buckaroo.admin.inc
index 540f7b7..c7d3db5 100644
--- a/includes/commerce_buckaroo.admin.inc
+++ b/includes/commerce_buckaroo.admin.inc
@@ -62,6 +62,13 @@ function commerce_buckaroo_settings() {
     '#default_value' => variable_get('buckaroo_mode', '0'),
   );
 
+  $form['buckaroo_push_enable'] = array(
+    '#type' => 'select',
+    '#title' => t('Enable Buckaroo push'),
+    '#default_value' => variable_get('buckaroo_push_enable', 0),
+    '#options' => array('Off', 'On'),
+  );
+
   return system_settings_form($form);
 }
 
diff --git a/includes/commerce_buckaroo.payment.inc b/includes/commerce_buckaroo.payment.inc
index 3b156ee..352c2f7 100644
--- a/includes/commerce_buckaroo.payment.inc
+++ b/includes/commerce_buckaroo.payment.inc
@@ -64,6 +64,124 @@ function commerce_buckaroo_build_redirect_form($form, &$form_state, $order, $set
 }
 
 /**
+ * Menu callback; takes action on 'push' (callback from Buckaroo server).
+ *
+ * Note all parameters from Buckaroo's request (the array keys) are lowercased.
+ */
+function commerce_buckaroo_handle_push() {
+
+  if (!variable_get('buckaroo_push_enable', 0)) {
+    watchdog('commerce_buckaroo', 'Push request received, but functionality not enabled.',
+      array(), WATCHDOG_ERROR);
+    return;
+  }
+
+  $response = array_map('check_plain', $_REQUEST);
+
+  if (empty($response)) {
+    watchdog('commerce_buckaroo', 'Sending order through Buckaroo Push is impossible due to empty response.',
+      array(), WATCHDOG_ERROR);
+    return;
+  }
+
+  $parsed_result = commerce_buckaroo_result_parse($response['bpe_result']);
+
+  if ($parsed_result['code'] !== COMMERCE_PAYMENT_STATUS_SUCCESS) {
+    watchdog('commerce_buckaroo',
+      "Transaction with remote ID @id failed payment. 'bpe_result' is %r, response is %c / %m",
+      array(
+        '@id' => $response['bpe_trx'],
+        '%r'  => $response['bpe_result'],
+        '%c'  => $parsed_result['code'],
+        '%m'  => $parsed_result['message']
+      ), WATCHDOG_WARNING);
+  }
+
+  $query = new EntityFieldQuery;
+  $result = $query
+    ->entityCondition('entity_type', 'commerce_payment_transaction')
+    ->propertyCondition('remote_id', $response['bpe_trx'])
+    // transaction_id has an index; created/updated does not.
+    ->propertyOrderBy('transaction_id', 'DESC')
+    ->execute();
+
+  $transaction = FALSE;
+  if (!$result) {
+    // Save new transaction.
+
+    if (empty($parsed_result['order_id']) ||
+        !is_numeric($parsed_result['order_id'])) {
+
+      watchdog('commerce_buckaroo',
+        'Order ID could not be evaluated from Buckaroo response: %r',
+        array('%r' => $response), WATCHDOG_ERROR);
+    }
+    elseif (empty($parsed_result['method'])) {
+
+      watchdog('commerce_buckaroo',
+        'Payment method could not be evaluated from Buckaroo response: %r',
+        array('%r' => $response), WATCHDOG_ERROR);
+    }
+    elseif (!$order = commerce_order_load($parsed_result['order_id'])) {
+
+      watchdog('commerce_buckaroo',
+        'Order ID @id could not be loaded from Drupal? (remote ID @bid)',
+        array('@id' => $parsed_result['order_id'], '@bid' => $response['bpe_trx']), WATCHDOG_ERROR);
+    }
+    else {
+
+      $wrapper = entity_metadata_wrapper('commerce_order', $order);
+
+      $transaction = commerce_payment_transaction_new($parsed_result['method'],
+        $parsed_result['order_id']);
+
+      // identifying data
+      $transaction->instance_id = $parsed_result['method'] . '|commerce_payment_' . $parsed_result['method'];
+      $transaction->remote_id = $response['bpe_trx'];
+      // payment amount details
+      $transaction->amount = $wrapper->commerce_order_total->amount->value();
+      $transaction->currency_code = $wrapper->commerce_order_total->currency_code->value();
+      // payment status
+      $transaction->remote_status = $response['bpe_result'];
+      $transaction->status = $parsed_result['code'];
+      $transaction->message = $parsed_result['message'];
+
+      commerce_payment_transaction_save($transaction);
+    }
+  }
+  elseif ($parsed_result['code'] === COMMERCE_PAYMENT_STATUS_SUCCESS) {
+    // Update existing transaction, only upon success.
+    // (Normally, a new transaction is created.)
+
+    if (!$parsed_result['method']) {
+      watchdog('commerce_buckaroo', "Unrecognized 'bpe_result' value %r in push request URL",
+        array('%r' => $response['bpe_result']), WATCHDOG_WARNING);
+    }
+
+    $keys = array_keys($result['commerce_payment_transaction']);
+    if ($transaction = commerce_payment_transaction_load($keys[0])) {
+
+      $transaction->payment_method = $parsed_result['method'];
+      $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS;
+      $transaction->message = $parsed_result['message'];
+      $transaction->remote_status = $response['bpe_result'];
+
+      commerce_payment_transaction_save($transaction);
+    }
+    else {
+      watchdog('commerce_buckaroo',
+        'Transaction @id could not be loaded from Drupal?',
+        array('@id' => $keys[0]), WATCHDOG_ERROR);
+    }
+  }
+
+  if ($transaction) {
+    // Transaction was created/saved correctly; the order can be derived from it.
+    module_invoke_all('commerce_buckaroo_push', $transaction, $response);
+  }
+}
+
+/**
  * Parse Buckaroo result code
  *
  * @param $code
@@ -209,7 +327,26 @@ function commerce_buckaroo_result_parse($code) {
       $msg = t('Unknown status (response code @code)', array('@code' => $code));
   }
 
+  // Derive order ID and payment method too. Don't warn here if these cannot be
+  // derived; they're not used for non-push requests.
+
+  $method = '';
+  if (($code > 99 && $code < 105) || ($code > 200 && $code < 204)) {
+    $method = 'buckaroo_creditcard';
+  }
+  elseif (($code > 119 && $code < 141)) {
+    $method = 'buckaroo_paypal';
+  }
+  elseif (($code > 799 && $code < 817)) {
+    $method = 'buckaroo_ideal';
+  }
+
+  $fragments = explode('-', (string)$response['bpe_invoice']);
+  $order_id = reset($fragments);
+
   return array(
+    'order_id' => $order_id,
+    'method' => $method,
     'code' => $st,
     'message' => $msg,
   );
-- 
1.7.12.4 (Apple Git-37)

