summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMaciej Zgadzaj2014-03-03 21:58:49 -0500
committerMaciej Zgadzaj2014-03-03 22:00:08 -0500
commitfcae37d51e2549b87e903f291627a6650f8f9ec4 (patch)
tree1b37aba89288d5d74e837e363cd5f1f1900fe5f5
parent6c84ae20894a47e9f3989f345ec9f37fc340e71e (diff)
Initial parallel payments support
-rw-r--r--commerce_marketplace.module20
-rw-r--r--modules/cart/commerce_marketplace_cart.module78
-rw-r--r--modules/checkout/commerce_marketplace_checkout.module208
-rw-r--r--modules/customer/commerce_marketplace_customer.module2
-rw-r--r--modules/order/commerce_marketplace_order.module22
-rw-r--r--modules/payment/commerce_marketplace_payment.module309
-rw-r--r--modules/payment/includes/commerce_marketplace_payment.admin.inc20
-rw-r--r--modules/payment/includes/commerce_marketplace_payment.checkout_pane.inc259
-rw-r--r--modules/shipping/commerce_marketplace_shipping.module153
9 files changed, 914 insertions, 157 deletions
diff --git a/commerce_marketplace.module b/commerce_marketplace.module
index d1f811b..5e89091 100644
--- a/commerce_marketplace.module
+++ b/commerce_marketplace.module
@@ -6,6 +6,26 @@
*/
/**
+ * Implements hook_menu().
+ */
+function commerce_marketplace_menu() {
+ $items = array();
+
+ $items['admin/commerce/marketplace'] = array(
+ 'title' => 'Marketplace',
+ 'description' => 'Configure Marketplace settings for your store.',
+ 'page callback' => 'system_admin_menu_block_page',
+ 'access arguments' => array('access administration pages'),
+ 'type' => MENU_NORMAL_ITEM,
+ 'weight' => 50,
+ 'file path' => drupal_get_path('module', 'system'),
+ 'file' => 'system.admin.inc',
+ );
+
+ return $items;
+}
+
+/**
* Implements hook_commerce_entity_access().
*/
function commerce_marketplace_commerce_entity_access($op, $entity, $account, $entity_type) {
diff --git a/modules/cart/commerce_marketplace_cart.module b/modules/cart/commerce_marketplace_cart.module
index e627233..490e05b 100644
--- a/modules/cart/commerce_marketplace_cart.module
+++ b/modules/cart/commerce_marketplace_cart.module
@@ -20,6 +20,33 @@ function commerce_marketplace_cart_views_api() {
*/
function commerce_marketplace_cart_menu_alter(&$items) {
$items['cart']['page callback'] = 'commerce_marketplace_cart_view';
+ $items['checkout']['page callback'] = 'commerce_marketplace_cart_checkout_router';
+}
+
+/**
+ * Redirects invalid checkout attempts or displays the checkout form if valid.
+ *
+ * Overrides commerce_cart_checkout_router() to check quantity of all orders
+ * in the order group.
+ *
+ * @see commerce_cart_checkout_router()
+ * @see commerce_marketplace_cart_menu_alter()
+ * @see commerce_cart_menu()
+ */
+function commerce_marketplace_cart_checkout_router() {
+ global $user;
+
+ // If no shopping cart order could be found, redirect away from checkout.
+ if (
+ !($orders = commerce_marketplace_cart_order_load_multiple($user->uid))
+ || !commerce_marketplace_order_quantity($orders)
+ ) {
+ drupal_set_message(t('Add some items to your cart and then try checking out.'));
+ drupal_goto(variable_get('commerce_checkout_empty_redirect', 'cart'));
+ }
+
+ $order = reset($orders);
+ drupal_goto('checkout/' . $order->order_id);
}
/**
@@ -45,7 +72,7 @@ function commerce_marketplace_cart_view() {
if ($quantity > 0) {
// Order ID view contextual filter should be a string
- // listing all order IDs separated by a comma.p
+ // listing all order IDs separated by a comma.
$order_ids = implode(',', array_keys($orders));
// Add the form for editing the cart contents.
@@ -59,7 +86,8 @@ function commerce_marketplace_cart_view() {
/**
* Implements hook_block_view_alter().
*
- * 'Shopping cart' block by default
+ * Alters 'Shopping cart' block to display all non-empty orders from the order
+ * group.
*
* @see commerce_cart_block_view()
*/
@@ -79,19 +107,19 @@ function commerce_marketplace_cart_block_view_alter(&$data, $block) {
if (commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()) <= 0) {
unset($orders[$order->order_id]);
}
+ }
- if (!empty($orders)) {
- // Order ID view contextual filter should be a string
- // listing all order IDs separated by a comma.p
- $order_ids = implode(',', array_keys($orders));
+ if (!empty($orders)) {
+ // Order ID view contextual filter should be a string
+ // listing all order IDs separated by a comma.p
+ $order_ids = implode(',', array_keys($orders));
- // Build the variables array to send to the cart block template.
- $variables = array(
- 'contents_view' => commerce_embed_view('commerce_cart_block', 'defaults', array($order_ids), $_GET['q']),
- );
+ // Build the variables array to send to the cart block template.
+ $variables = array(
+ 'contents_view' => commerce_embed_view('commerce_cart_block', 'defaults', array($order_ids), $_GET['q']),
+ );
- $content = theme('commerce_cart_block', $variables);
- }
+ $content = theme('commerce_cart_block', $variables);
}
}
@@ -217,8 +245,15 @@ function commerce_marketplace_cart_order_ids($uid = 0, $store_id = NULL) {
->propertyCondition('uid', $uid)
->propertyCondition('status', $status_ids, 'IN')
->propertyOrderBy('order_id', 'DESC');
+ // If store ID was provided as FALSE, it means we want orders with products
+ // not assigned to any store. And because EFQ does not support IS NULL
+ // condition, we have to tag the query here and add the condition in
+ // commerce_marketplace_cart_query_null_order_id_alter().
+ if ($store_id === FALSE) {
+ $query->addTag('null_order_id');
+ }
// If store ID was provided look for order in specific store only.
- if ($store_id) {
+ elseif ($store_id !== NULL) {
$query->fieldCondition('commerce_store', 'target_id', $store_id, '=');
}
// For anonymous users look for a shopping cart order ID in the session.
@@ -231,6 +266,18 @@ function commerce_marketplace_cart_order_ids($uid = 0, $store_id = NULL) {
}
/**
+ * Implements hook_query_TAG_alter().
+ *
+ * Adds extra condition to store select query not supported by default by EFQ.
+ *
+ * @see commerce_marketplace_cart_order_ids()
+ */
+function commerce_marketplace_cart_query_null_order_id_alter(QueryAlterableInterface $query) {
+ $query->leftJoin('field_data_commerce_store', 'fdcs', 'commerce_order.order_id = fdcs.entity_id');
+ $query->isNull('fdcs.commerce_store_target_id');
+}
+
+/**
* Implements hook_form_alter().
*/
function commerce_marketplace_cart_form_alter(&$form, &$form_state, $form_id) {
@@ -334,7 +381,10 @@ function commerce_marketplace_cart_product_add($uid, $line_item, $combine = TRUE
// First attempt to load the customer's shopping cart order.
$store = $line_item_wrapper->commerce_product->commerce_store->value();
$store_id = !empty($store) ? $store->store_id : NULL;
- $order = commerce_marketplace_cart_order_load($uid, $store_id);
+ // If product is not assigned to any store, we need to check for orders not
+ // assigned to any store either, which means passing FALSE as a second param
+ // to commerce_marketplace_cart_order_load().
+ $order = commerce_marketplace_cart_order_load($uid, $store_id ? $store_id : FALSE);
// If no order existed, create one now.
if (empty($order)) {
diff --git a/modules/checkout/commerce_marketplace_checkout.module b/modules/checkout/commerce_marketplace_checkout.module
index 0a47e28..098e84c 100644
--- a/modules/checkout/commerce_marketplace_checkout.module
+++ b/modules/checkout/commerce_marketplace_checkout.module
@@ -19,10 +19,6 @@ function commerce_marketplace_checkout_views_api() {
* Implements hook_menu_alter().
*/
function commerce_marketplace_checkout_menu_alter(&$items) {
-// $items['checkout']['page callback'] = 'commerce_marketplace_checkout_checkout_router';
-// $items['checkout']['module'] = 'commerce_marketplace_checkout';
-// $items['checkout']['file'] = 'commerce_marketplace_checkout.module';
-
$items['checkout/%commerce_order']['page callback'] = 'commerce_marketplace_checkout_router';
$items['checkout/%commerce_order']['module'] = 'commerce_marketplace_checkout';
$items['checkout/%commerce_order']['file'] = 'commerce_marketplace_checkout.module';
@@ -42,7 +38,6 @@ function commerce_marketplace_checkout_menu_alter(&$items) {
* @see commerce_checkout_router()
*/
function commerce_marketplace_checkout_router($order, $checkout_page = NULL) {
- global $user;
$checkout_pages = commerce_checkout_pages();
// If no checkout page is specified, default to the first one.
@@ -107,7 +102,6 @@ function commerce_marketplace_checkout_router($order, $checkout_page = NULL) {
'#markup' => '<h2>' . t('Order from @store', array('@store' => $store_name)) . '</h2>',
);
$form_id = 'commerce_checkout_form_' . $checkout_page['page_id'] . '_' . $group_order->order_id;
-// $form_id = 'commerce_checkout_form_' . $checkout_page['page_id'];
$return[] = drupal_get_form($form_id, $group_order, $checkout_page);
}
}
@@ -217,6 +211,44 @@ function commerce_marketplace_checkout_order_uri($order) {
}
/**
+ * Implements hook_commerce_checkout_pane_info_alter().
+ */
+function commerce_marketplace_checkout_commerce_checkout_pane_info_alter(&$checkout_panes) {
+ // Override "Shopping cart contents" pane on the "Checkout" and "Review order"
+ // steps to show line items from all orders and calculate correct total.
+ $checkout_panes['cart_contents']['callbacks']['checkout_form'] = 'commerce_marketplace_cart_contents_pane_checkout_form';
+ $checkout_panes['cart_contents']['callbacks']['review'] = 'commerce_marketplace_cart_contents_pane_review';
+
+ // Override "Shopping cart contents" pane settings form.
+ $checkout_panes['checkout_review']['callbacks']['settings_form'] = 'commerce_marketplace_checkout_review_pane_settings_form';
+}
+
+/**
+ * Checkout pane callback: returns the Shopping cart contents pane's settings
+ * form.
+ */
+function commerce_marketplace_checkout_review_pane_settings_form($checkout_pane) {
+ $form = array();
+
+ // At the moment of writing this the commerce_checkout_review_pane_settings_form()
+ // (which would be default settings form for the Review pane) does not exist
+ // yet, but in case it is added at any point in the future, let's first try
+ // to fetch and use it too.
+ if (function_exists('commerce_checkout_review_pane_settings_form')) {
+ $form = commerce_checkout_review_pane_settings_form($checkout_pane);
+ }
+
+ $form['commerce_marketplace_checkout_split_cart_contents'] = array(
+ '#type' => 'checkbox',
+ '#title' => t('Split <em>Shopping cart contents</em> view for marketplace orders.'),
+ '#description' => t(''),
+ '#default_value' => variable_get('commerce_marketplace_checkout_split_cart_contents', FALSE),
+ );
+
+ return $form;
+}
+
+/**
* Implements hook_form_alter().
*/
function commerce_marketplace_checkout_form_alter(&$form, &$form_state, $form_id) {
@@ -237,7 +269,17 @@ function commerce_marketplace_checkout_form_alter(&$form, &$form_state, $form_id
// Add extra submit functions to update statuses of all orders when moving
// within checkout steps and back from checkout to shopping cart.
if (strpos($form_id, 'commerce_checkout_form_') !== FALSE) {
+ // Add array with all orders in current order group.
+ if (!empty($form_state['order'])) {
+ $form_state['orders'] = !empty($form_state['order']->order_group) ? commerce_marketplace_order_group_load($form_state['order']->order_group) : array($form_state['order']->order_id => $form_state['order']);
+ }
+
$form_state['build_info']['files']['form'] = drupal_get_path('module', 'commerce_checkout') . '/includes/commerce_checkout.pages.inc';
+ if (!empty($form['buttons']['continue']['#validate'])) {
+ if (FALSE !== $key = array_search('commerce_checkout_form_validate', $form['buttons']['continue']['#validate'])) {
+ $form['buttons']['continue']['#validate'][$key] = 'commerce_marketplace_checkout_form_validate';
+ }
+ }
if (!empty($form['buttons']['continue']['#submit'])) {
if (FALSE !== $key = array_search('commerce_checkout_form_submit', $form['buttons']['continue']['#submit'])) {
$form['buttons']['continue']['#submit'][$key] = 'commerce_marketplace_checkout_form_submit';
@@ -354,16 +396,6 @@ function commerce_marketplace_cart_checkout_form_cancel_submit($form, &$form_sta
}
/**
- * Implements hook_commerce_checkout_pane_info_alter().
- */
-function commerce_marketplace_checkout_commerce_checkout_pane_info_alter(&$checkout_panes) {
- // Override "Shopping cart contents" pane on the "Checkout" and "Review order"
- // steps to show line items from all orders and calculate correct total.
- $checkout_panes['cart_contents']['callbacks']['checkout_form'] = 'commerce_marketplace_cart_contents_pane_checkout_form';
- $checkout_panes['cart_contents']['callbacks']['review'] = 'commerce_marketplace_cart_contents_pane_review';
-}
-
-/**
* Checkout pane callback: returns the cart contents view for inclusion in the
* checkout form on the "Checkout" page.
*
@@ -414,6 +446,25 @@ function commerce_marketplace_cart_contents_pane_review($form, $form_state, $che
if ($form_state['checkout_page']['page_id'] != 'reviews') {
// Process only orders with the 'cart' status type.
$orders = commerce_marketplace_cart_order_load_multiple($user->uid);
+
+ // If 'Split Shopping cart contents view for marketplace orders' checkbox
+ // in Review pane settings form is enabled, we need to display the same
+ // 'Shopping cart contents' view multiple times for each order separately
+ // (see commerce_marketplace_checkout_review_pane_settings_form()).
+ if (
+ variable_get('commerce_marketplace_checkout_split_cart_contents', FALSE)
+ && commerce_marketplace_order_count($orders) > 1
+ ) {
+ $output = '';
+ foreach ($orders as $group_order) {
+ $order_wrapper = entity_metadata_wrapper('commerce_order', $group_order);
+ $store_name = !empty($group_order->commerce_store) ? $order_wrapper->commerce_store->title->value() : variable_get('site_name', 'Drupal');
+ $output .= '<label>' . t('Order from @store', array('@store' => $store_name)) . '</label>';
+ $output .= commerce_embed_view($view_id, $display_id, array($group_order->order_id));
+ }
+ return $output;
+ }
+ // Otherwise all orders should be displayed combined in one view.
$order_ids = implode(',', array_keys($orders));
}
else {
@@ -423,20 +474,119 @@ function commerce_marketplace_cart_contents_pane_review($form, $form_state, $che
}
/**
+ * Validate handler for the continue button of the checkout form.
+ *
+ * This function calls the validation function of each pane, followed by
+ * the submit function if the validation succeeded. As long as one pane
+ * fails validation, we then ask for the form to be rebuilt. Once all the panes
+ * are happy, we move on to the next page.
+ *
+ * @see commerce_checkout_form()
+ * @see commerce_checkout_form_validate()
+ * @see commerce_marketplace_checkout_form_alter()
+ * @see commerce_marketplace_checkout_form_submit()
+ */
+function commerce_marketplace_checkout_form_validate($form, &$form_state) {
+ $checkout_page = $form_state['checkout_page'];
+
+ // Load a fresh copy of all the orders stored in the form.
+ $orders = commerce_order_load_multiple(array_keys($form_state['orders']));
+
+ // Catch and clear already pushed messages.
+ $previous_messages = drupal_get_messages();
+
+ // Load any pre-existing validation errors for the elements.
+ $errors = array();
+
+ foreach ((array) form_get_errors() as $element_path => $error) {
+ list($pane_id, ) = explode('][', $element_path, 2);
+ $errors[$pane_id][$element_path] = $error;
+ }
+
+ // Loop through the enabled checkout panes for the current page.
+ $form_validate = TRUE;
+ foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
+ $validate = TRUE;
+
+ // If any element in the pane failed validation, we mark the pane as
+ // unvalidated and replay the validation messages on top of it.
+ if (!empty($errors[$pane_id])) {
+ $validate = FALSE;
+
+ foreach ($errors[$pane_id] as $element_path => $message) {
+ if ($message) {
+ drupal_set_message($message, 'error');
+ }
+ }
+
+ if (isset($previous_messages['error'])) {
+ $previous_messages['error'] = array_values(array_diff($previous_messages['error'], $errors[$pane_id]));
+ }
+ }
+
+ // If the pane has defined a checkout form validate handler...
+ if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_validate')) {
+ // Give it a chance to process the submitted data.
+ foreach (element_children($orders) as $order_id) {
+ $validate &= $callback($form, $form_state, $checkout_pane, $orders[$order_id]);
+ }
+ }
+
+ // Catch and clear panes' messages.
+ $pane_messages = drupal_get_messages();
+
+ // Submit the pane if it validated.
+ if ($validate && $callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_submit')) {
+ foreach (element_children($orders) as $order_id) {
+ $callback($form, $form_state, $checkout_pane, $orders[$order_id]);
+ }
+ }
+
+ // Generate status messages.
+ $form_state['storage']['messages'][$pane_id] = array_merge_recursive($pane_messages, drupal_get_messages());
+
+ // A failed pane makes the form fail.
+ $form_validate &= $validate;
+ }
+
+ // Restore messages and form errors.
+ $_SESSION['messages'] = array_merge_recursive(array_filter($previous_messages), drupal_get_messages());
+ $form_errors = &drupal_static('form_set_error', array());
+ $form_state['storage']['errors'] = $form_errors;
+ $form_errors = array();
+
+ // Save the updated order object and reset the order in the form cache to
+ // ensure rebuilt forms use the updated order.
+ foreach (element_children($orders) as $order_id) {
+ commerce_order_save($orders[$order_id]);
+ if ($form_state['order']->order_id == $order_id) {
+ $form_state['order'] = $form_state['build_info']['args'][0] = $orders[$order_id];
+ }
+ }
+
+ // If a pane failed validation or the form state has otherwise been altered to
+ // initiate a rebuild, return without moving to the next checkout page.
+ if (!$form_validate || $form_state['rebuild']) {
+ $form_state['rebuild'] = TRUE;
+ }
+}
+
+/**
* Special submit handler for the continue button of the checkout form.
* Updates all orders statuses to reflect the checkout page.
*
* Overrides commerce_checkout_form_submit().
*
+ * @see commerce_checkout_form()
* @see commerce_checkout_form_submit()
+ * @see commerce_marketplace_checkout_form_alter()
+ * @see commerce_marketplace_checkout_form_validate()
*/
function commerce_marketplace_checkout_form_submit($form, &$form_state) {
- global $user;
$checkout_page = $form_state['checkout_page'];
- // Load a fresh copy of the order stored in the form.
- $order = commerce_order_load($form_state['order']->order_id);
- $orders = commerce_marketplace_cart_order_load_multiple($user->uid);
+ // Load a fresh copy of all the orders stored in the form.
+ $orders = commerce_order_load_multiple(array_keys($form_state['orders']));
// If we are going to redirect with checkout pane messages stored in the form
// state, they will not be displayed on a subsequent form build like normal.
@@ -452,27 +602,21 @@ function commerce_marketplace_checkout_form_submit($form, &$form_state) {
if (end($form_state['triggering_element']['#array_parents']) == 'continue') {
// If there is another checkout page...
if ($checkout_page['next_page']) {
- // If next checkout page is payment, we want to update only the current
- // order's status, as for all other orders from the group we need to go
- // to the Review page again to select their relevant payment method.
- if ($checkout_page['next_page'] == 'payment') {
- $orders = array($order);
- }
// Update all order statuses to reflect the next checkout page.
- foreach ($orders as $group_order) {
+ foreach (element_children($orders) as $order_id) {
// Do not allow to go change the status of orders already paid.
- $order_status = commerce_order_status_load($group_order->status);
+ $order_status = commerce_order_status_load($orders[$order_id]->status);
if ($order_status['cart']) {
- $group_order = commerce_order_status_update($group_order, 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Customer continued to the next checkout page via a submit button.'));
+ $orders[$order_id] = commerce_order_status_update($orders[$order_id], 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Customer continued to the next checkout page via a submit button.'));
// If it happens to be the complete page, process completion now.
if ($checkout_page['next_page'] == 'complete') {
- commerce_checkout_complete($group_order);
+ commerce_checkout_complete($orders[$order_id]);
}
// Re-add updated order to the form.
- if ($group_order->order_id == $form_state['order']->order_id) {
- $form_state['order'] = $group_order;
+ if ($order_id == $form_state['order']->order_id) {
+ $form_state['order'] = $orders[$order_id];
}
}
}
diff --git a/modules/customer/commerce_marketplace_customer.module b/modules/customer/commerce_marketplace_customer.module
index 2ff4c4a..2055b10 100644
--- a/modules/customer/commerce_marketplace_customer.module
+++ b/modules/customer/commerce_marketplace_customer.module
@@ -32,7 +32,7 @@ function commerce_marketplace_customer_profile_pane_checkout_form_submit($form,
// If there are more orders in the order group, we need to update them too.
$orders = commerce_marketplace_cart_order_load_multiple($user->uid);
- if (count($orders) > 1) {
+ if (commerce_marketplace_order_count($orders) > 1) {
// Customer profile should be already created at this stage, so we can
// get profile_id value from the already updated order.
diff --git a/modules/order/commerce_marketplace_order.module b/modules/order/commerce_marketplace_order.module
index 5522104..59fd83d 100644
--- a/modules/order/commerce_marketplace_order.module
+++ b/modules/order/commerce_marketplace_order.module
@@ -119,6 +119,28 @@ function commerce_marketplace_order_group_load($order_group) {
}
/**
+ * Returns number of non-empty orders in the order group.
+ *
+ * @param array $orders
+ * An array of orders in the order group.
+ *
+ * @return int
+ * A number of non-empty orders in the order group.
+ */
+function commerce_marketplace_order_count($orders) {
+ $count = 0;
+ if (is_array($orders) && !empty($orders)) {
+ foreach ($orders as $order) {
+ $wrapper = entity_metadata_wrapper('commerce_order', $order);
+ if (commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()) > 0) {
+ $count++;
+ }
+ }
+ }
+ return $count;
+}
+
+/**
* Overrides commerce_order_calculate_total().
*
* Calculates the order total, updating the commerce_order_total field data in
diff --git a/modules/payment/commerce_marketplace_payment.module b/modules/payment/commerce_marketplace_payment.module
index 3e66090..81eb008 100644
--- a/modules/payment/commerce_marketplace_payment.module
+++ b/modules/payment/commerce_marketplace_payment.module
@@ -6,11 +6,25 @@
*/
/**
+ * Default mode for sending marketplace payments.
+ */
+define('COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE', 'merchants');
+
+/**
* Implements hook_menu().
*/
function commerce_marketplace_payment_menu() {
$items = array();
+ $items['admin/commerce/marketplace/payment'] = array(
+ 'title' => 'Payments',
+ 'description' => 'Commerce marketplace payment settings.',
+ 'page callback' => 'drupal_get_form',
+ 'page arguments' => array('commerce_marketplace_payment_settings_form'),
+ 'access arguments' => array('configure store'),
+ 'file' => 'includes/commerce_marketplace_payment.admin.inc',
+ );
+
$items['admin/commerce/stores/%commerce_store/payment_methods'] = array(
'title' => 'Payment methods',
'type' => MENU_LOCAL_TASK,
@@ -70,7 +84,7 @@ function commerce_marketplace_payment_commerce_checkout_pane_info() {
'title' => t('Marketplace: Review'),
'file' => 'includes/commerce_marketplace_payment.checkout_pane.inc',
'base' => 'commerce_marketplace_review_pane',
- 'page' => 'reviews',
+ 'page' => 'disabled',
'fieldset' => FALSE,
'weight' => 1,
);
@@ -78,7 +92,7 @@ function commerce_marketplace_payment_commerce_checkout_pane_info() {
$checkout_panes['commerce_marketplace_payment'] = array(
'title' => t('Payment'),
'name' => t('Marketplace: Payment'),
- 'page' => 'reviews',
+ 'page' => 'disabled',
'file' => 'includes/commerce_marketplace_payment.checkout_pane.inc',
'base' => 'commerce_marketplace_payment_pane',
'weight' => 3,
@@ -86,7 +100,7 @@ function commerce_marketplace_payment_commerce_checkout_pane_info() {
$checkout_panes['commerce_marketplace_payment_redirect'] = array(
'title' => t('Marketplace: Off-site payment redirect'),
- 'page' => 'payment',
+ 'page' => 'disabled',
'locked' => TRUE,
'file' => 'includes/commerce_marketplace_payment.checkout_pane.inc',
'base' => 'commerce_marketplace_payment_redirect_pane',
@@ -98,15 +112,101 @@ function commerce_marketplace_payment_commerce_checkout_pane_info() {
/**
* Implements hook_commerce_checkout_pane_info_alter().
*
- * Disabled default commerce_payment module panes: commerce_payment and
- * commerce_payment_redirect, which are replaced by commerce_marketplace_payment
- * and commerce_marketplace_payment_redirect.
- *
* @see commerce_marketplace_payment_commerce_checkout_pane_info()
*/
function commerce_marketplace_payment_commerce_checkout_pane_info_alter(&$checkout_panes) {
- $checkout_panes['commerce_payment']['page'] = 'disabled';
- $checkout_panes['commerce_payment_redirect']['page'] = 'disabled';
+ global $user;
+
+ // @TODO: Do something with this shite.
+ // This chunk of code is taken from commerce_checkout_panes(), and is meant to
+ // populate missing $checkout_panes defaults. The issue here is that function
+ // commerce_checkout_panes() indirectly calls itself through
+ // commerce_marketplace_cart_order_load_multiple() below, and the second time
+ // $checkout_panes in commerce_checkout_panes() are already populated, but not
+ // yet fully, so we go directly to the second section ($conditions checks)
+ // without fully going through the first one.
+ // Something like:
+ // commerce_order_statuses()
+ // hook_commerce_order_status_info()
+ // commerce_checkout_commerce_order_status_info()
+ // commerce_checkout_pages()
+ // commerce_checkout_panes()
+ // hook_commerce_checkout_pane_info_alter()
+ // commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ // commerce_marketplace_cart_order_load_multiple()
+ // commerce_marketplace_cart_order_ids()
+ // commerce_order_statuses()
+ foreach ($checkout_panes as $pane_id => $checkout_pane) {
+ // Set some defaults for the checkout pane.
+ $defaults = array(
+ 'base' => $pane_id,
+ 'name' => $checkout_pane['title'],
+ 'page' => 'checkout',
+ 'locked' => FALSE,
+ 'fieldset' => TRUE,
+ 'collapsible' => FALSE,
+ 'collapsed' => FALSE,
+ 'weight' => 0,
+ 'enabled' => TRUE,
+ 'review' => TRUE,
+ 'callbacks' => array(),
+ 'file' => '',
+ );
+ $checkout_pane += $defaults;
+ // Merge in default callbacks.
+ foreach (array('settings_form', 'checkout_form', 'checkout_form_validate', 'checkout_form_submit', 'review') as $callback) {
+ if (!isset($checkout_pane['callbacks'][$callback])) {
+ $checkout_pane['callbacks'][$callback] = $checkout_pane['base'] . '_' . $callback;
+ }
+ }
+ $checkout_panes[$pane_id] = $checkout_pane;
+ }
+
+ // When payments should be sent directly to the merchants, but there is no
+ // enabled parallel payment common for all orders in the order group, we need
+ // to use marketplace-specific checkout panes instead of the default ones.
+ // Also see commerce_marketplace_shipping_commerce_checkout_pane_info_alter()
+ $menu_item = menu_get_item();
+ if (strpos($menu_item['path'], 'checkout/%') === 0) {
+ $order = $menu_item['page_arguments'][0];
+ }
+ else {
+ // This will load only orders with the 'shopping cart' status, while here
+ // we need ALL orders from the order group, regardless of their status
+ // (even those with the 'Payment' status) - that's why we'd need to load
+ // them again below based on the order_group ID.
+ $orders = commerce_marketplace_cart_order_load_multiple($user->uid);
+ $order = reset($orders);
+ }
+ if (
+ !empty($order->order_group)
+ && ($orders = commerce_marketplace_order_group_load($order->order_group))
+ ) {
+ // If there is no enabled parallel payment common for all orders in the
+ // order group, we need to use marketplace-specific checkout panes instead
+ // of the default ones.
+ if (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ && commerce_marketplace_order_count($orders) > 1
+ ) {
+ // Disable default panes.
+ $checkout_panes['commerce_payment']['page'] = 'disabled';
+ $checkout_panes['commerce_payment_redirect']['page'] = 'disabled';
+ // Enable marketplace-specific panes.
+ $checkout_panes['commerce_marketplace_review']['page'] = 'reviews';
+ $checkout_panes['commerce_marketplace_payment']['page'] = 'reviews';
+ $checkout_panes['commerce_marketplace_payment_redirect']['page'] = 'payment';
+ }
+ // When sending payment to main store, or sending to merchants directly,
+ // but there is at least one enabled common parallel payment method.
+ else {
+ $checkout_panes['commerce_marketplace_payment']['page'] = $checkout_panes['commerce_payment']['page'];
+ $checkout_panes['commerce_payment']['page'] = 'disabled';
+ $checkout_panes['commerce_marketplace_payment_redirect']['page'] = $checkout_panes['commerce_payment_redirect']['page'];
+ $checkout_panes['commerce_payment_redirect']['page'] = 'disabled';
+ }
+ }
}
/**
@@ -132,6 +232,69 @@ function commerce_marketplace_payment_enabled_methods() {
}
/**
+ * Returns all enabled parallel payment methods.
+ *
+ * @return array
+ * An array of enabled parallel payment methods.
+ */
+function commerce_marketplace_payment_enabled_parallel_methods() {
+ $methods = commerce_marketplace_payment_enabled_methods();
+ foreach ($methods as $key => $method) {
+ if (empty($method['parallel'])) {
+ unset($methods[$key]);
+ }
+ }
+ return $methods;
+}
+
+/**
+ * Returns all enabled parallel payment methods common for all orders in the
+ * order group.
+ *
+ * @param array $orders
+ * An array of orders in the order group.
+ *
+ * @return array|false
+ * An array of enabled parallel payment methods common for all orders in the
+ * order group or FALSE if no such payment method exists.
+ */
+function commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders) {
+
+ if (variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) != 'merchants') {
+ return FALSE;
+ }
+
+ if (empty($orders) || commerce_marketplace_order_count($orders) <= 1) {
+ return FALSE;
+ }
+
+ // Populate orders with their relevant payment methods.
+ foreach ($orders as $order) {
+ // Invoke the payment methods event that will populate the order with
+ // an array of method IDs for available payment methods.
+ $order->payment_methods = array();
+ rules_invoke_all('commerce_payment_methods', $order);
+ // Keep only methods enabled for the order's store.
+ commerce_marketplace_payment_filter_store_payment_methods($order);
+ // Update order payment methods with relevant store payment method settings.
+ commerce_marketplace_payment_update_store_payment_method_settings($order);
+ }
+
+ $enabled_parallel_methods = commerce_marketplace_payment_enabled_parallel_methods();
+ foreach ($enabled_parallel_methods as $key => $enabled_parallel_method) {
+ $method_name = $enabled_parallel_method['method_id'] . '|commerce_payment_' . $enabled_parallel_method['method_id'];
+ $enabled_parallel_methods[$key] = $method_name;
+ foreach ($orders as $order) {
+ if (!in_array($method_name, array_keys($order->payment_methods))) {
+ unset($enabled_parallel_methods[$key]);
+ }
+ }
+ }
+
+ return $enabled_parallel_methods;
+}
+
+/**
* Implements hook_query_TAG_alter().
*
* This actually is almost an exact copy of
@@ -233,14 +396,134 @@ function commerce_marketplace_payment_commerce_store_form_submit($form, &$form_s
}
/**
+ * Implements hook_module_implements_alter().
+ *
+ * Make sure that commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ * is called after commerce_marketplace_shipping_commerce_checkout_pane_info_alter(),
+ * so that shipping pane is always displayed on the correct page.
+ *
+ * This needs to be done because commerce_marketplace_shipping module by
+ * default moves Shipping pane to the Reviews checkout page, and it's only
+ * commerce_marketplace_payment module that checks where the Shipping pane
+ * really should be displayed based on enabled payment methods for each order
+ * (displaying Shipping pane on the Reviews page if there is no common parallel
+ * payment method enabled for all stores for current order group, or moving
+ * it back to its default Shipping page if such parallel payment method is
+ * available).
+ *
+ * @see commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ * @see commerce_marketplace_shipping_commerce_checkout_pane_info_alter()
+ */
+function commerce_marketplace_payment_module_implements_alter(&$implementations, $hook) {
+ if ($hook == 'form_alter') {
+ // Move commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ // implementation to the end of the list.
+ $group = $implementations['commerce_marketplace_payment'];
+ unset($implementations['commerce_marketplace_payment']);
+ $implementations['commerce_marketplace_payment'] = $group;
+ }
+}
+
+/**
* Implements hook_form_alter().
+ *
+ * @see commerce_marketplace_checkout_form_alter()
+ * @see commerce_marketplace_checkout_form_submit()
+ * @see commerce_marketplace_payment_module_implements_alter()
*/
function commerce_marketplace_payment_form_alter(&$form, &$form_state, $form_id) {
- if (strpos($form_id, 'commerce_checkout_form_reviews_') === 0) {
- // Add pane form wrappers so that we can update it via ajax callback
- // (including Shopping cart contents view).
- $wrapper_id = 'commerce-checkout-form-reviews-form-' . $form_state['order']->order_id;
+ // Add extra wrapper for the whole form, so that we can update it via ajax
+ // callback (see commerce_marketplace_payment_pane_checkout_form()).
+ // Also see commerce_marketplace_shipping_form_alter().
+ if (!empty($form['commerce_marketplace_payment'])) {
+ $wrapper_id = 'commerce-checkout-form-' . $form_state['order']->order_id;
$form['#prefix'] = '<div id="' . $wrapper_id . '">';
$form['#suffix'] = '</div>';
}
+
+ // $form_state['orders'] array containing all orders in the current order
+ // group was added in commerce_marketplace_checkout_form_alter(). It should
+ // include all orders which should be processed after submitting the form in
+ // commerce_marketplace_checkout_form_submit(). In case though when next page
+ // is payment, and payments should be sent directly to merchants, and there
+ // is no enabled parallel payment common for all orders in the order group,
+ // we want to process each order separately.
+ // This should always be called after commerce_marketplace_checkout_form_alter()
+ // (see commerce_marketplace_payment_module_implements_alter()).
+ if (
+ strpos($form_id, 'commerce_checkout_form_') !== FALSE
+ && $form_state['checkout_page']['next_page'] == 'payment'
+ && variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($form_state['orders'])
+ ) {
+ $form_state['orders'] = array($form_state['order']->order_id => $form_state['order']);
+ }
+}
+
+function commerce_marketplace_payment_filter_store_payment_methods(&$order) {
+ // Verify that all marketplace payment methods are enable for the store
+ // related to the current order, and if some methods are not enabled,
+ // or not configured, remove them from the options.
+ $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
+ $store = $order_wrapper->commerce_store->value();
+ if (!empty($store)) {
+ $payment_method_definitions = commerce_marketplace_payment_enabled_methods();
+ foreach ($order->payment_methods as $payment_method_long_id => $details) {
+ $keys = explode('|', $payment_method_long_id);
+ $payment_method_short_id = reset($keys);
+
+ $payment_method = $payment_method_definitions[$payment_method_short_id];
+ $settings_form_callback = $payment_method['callbacks']['settings_form'];
+
+ if (
+ // Payment method has to be enabled for the current store.
+ empty($store->data['payment_methods']['enabled'][$payment_method_short_id])
+ // Payment method has to be configured for the current store.
+ || empty($store->data['payment_methods']['settings'][$payment_method_short_id]) && function_exists($settings_form_callback)
+ ) {
+ unset($order->payment_methods[$payment_method_long_id]);
+ }
+ }
+ }
+
+ // Sort the payment methods array by the enabling Rules' weight values.
+ uasort($order->payment_methods, 'drupal_sort_weight');
+}
+
+/**
+ * Updates default payment method settings embedded in the order object
+ * with payment method settings of the store an order is assigned to.
+ *
+ * @param object $order
+ * An order which payment method settings are to be updated.
+ */
+function commerce_marketplace_payment_update_store_payment_method_settings(&$order) {
+ $wrapper = entity_metadata_wrapper('commerce_order', $order);
+ if (!empty($wrapper->commerce_store)) {
+ $store = $wrapper->commerce_store->value();
+ foreach ($order->payment_methods as $key => $method) {
+ if (!empty($store->data['payment_methods']['settings'][$method['method_id']])) {
+ $order->payment_methods[$key]['settings'] = $store->data['payment_methods']['settings'][$method['method_id']];
+ }
+ }
+ }
+}
+
+/**
+ * Updates default payment method settings with settings of the store an order
+ * is assigned to.
+ *
+ * @param array $payment_method
+ * A payment method which settings are to be updated.
+ * @param object $order
+ * An order which updated payment method settings are to be taken from.
+ */
+function commerce_marketplace_payment_update_payment_method_settings(&$payment_method, $order) {
+ $wrapper = entity_metadata_wrapper('commerce_order', $order);
+ if (!empty($wrapper->commerce_store)) {
+ $store = $wrapper->commerce_store->value();
+ if (!empty($store->data['payment_methods']['settings'][$payment_method['method_id']])) {
+ $payment_method['settings'] = $store->data['payment_methods']['settings'][$payment_method['method_id']];
+ }
+ }
}
diff --git a/modules/payment/includes/commerce_marketplace_payment.admin.inc b/modules/payment/includes/commerce_marketplace_payment.admin.inc
index 39a180e..517bdd0 100644
--- a/modules/payment/includes/commerce_marketplace_payment.admin.inc
+++ b/modules/payment/includes/commerce_marketplace_payment.admin.inc
@@ -1,6 +1,26 @@
<?php
/**
+ * Form constructor for marketplace settings page.
+ */
+function commerce_marketplace_payment_settings_form($form, &$form_state) {
+
+ $options = array(
+ 'main_store' => t('main store'),
+ 'merchants' => t('merchants'),
+ );
+ $form['commerce_store_payment_mode'] = array(
+ '#type' => 'select',
+ '#title' => t('Send payments to'),
+ '#description' => t('Select how payments for marketplace orders (orders from multiple stores) should be handled. <em>Merchants</em> will try to send payments directly to relevant merchants (assuming they all have the same payment method supporting parallel payments enabled). <em>Main store</em> will send the payment to the marketplace owner, allowing for processing it manually later.'),
+ '#options' => $options,
+ '#default_value' => variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE),
+ );
+
+ return system_settings_form($form);
+}
+
+/**
* Page callback: Displays list of payment methods available for a store.
*
* @param $store
diff --git a/modules/payment/includes/commerce_marketplace_payment.checkout_pane.inc b/modules/payment/includes/commerce_marketplace_payment.checkout_pane.inc
index 04e53c0..c3997d4 100644
--- a/modules/payment/includes/commerce_marketplace_payment.checkout_pane.inc
+++ b/modules/payment/includes/commerce_marketplace_payment.checkout_pane.inc
@@ -81,6 +81,13 @@ function commerce_marketplace_payment_pane_settings_form($checkout_pane) {
'#default_value' => variable_get('commerce_marketplace_payment_pane_pay_label', 'Pay for this order'),
);
+ $form['commerce_marketplace_payment_pane_paid'] = array(
+ '#type' => 'textarea',
+ '#title' => t('Paid order info'),
+ '#default_value' => variable_get('commerce_marketplace_payment_pane_paid', 'This order has been already paid.'),
+ '#rows' => 2,
+ );
+
$form['commerce_marketplace_payment_pane_allow_back'] = array(
'#type' => 'checkbox',
'#title' => t('Display <em>Go back</em> link next to each <em>Pay for this order</em> button.'),
@@ -93,12 +100,20 @@ function commerce_marketplace_payment_pane_settings_form($checkout_pane) {
/**
* Payment pane: form callback.
*
+ * This pane in used in two cases:
+ *
+ * @see commerce_payment_pane_checkout_form()
* @see commerce_marketplace_payment_pane_checkout_form_validate()
* @see commerce_marketplace_payment_pane_checkout_form_submit()
*/
function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
$pane_form = array();
+ // Make sure this file is always included.
+ $form_state['build_info']['files']['marketplace_form'] = drupal_get_path('module', 'commerce_marketplace_payment') . '/includes/commerce_marketplace_payment.checkout_pane.inc';
+
+ $orders = !empty($order->order_group) ? commerce_marketplace_order_group_load($order->order_group) : array($order);
+
// @TODO: What is the safest way to check if order has already been paid for?
$order_status = commerce_order_status_load($order->status);
if ($order_status['cart']) {
@@ -107,34 +122,32 @@ function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $c
// an array of method IDs for available payment methods.
$order->payment_methods = array();
rules_invoke_all('commerce_payment_methods', $order);
-
- // Verify that all marketplace payment methods are enable for the store
- // related to the current order, and if some methods are not enabled,
- // or not configured, remove them from the options.
- $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
- $store = $order_wrapper->commerce_store->value();
- if (!empty($store)) {
- $payment_method_definitions = commerce_marketplace_payment_enabled_methods();
- foreach ($order->payment_methods as $payment_method_long_id => $details) {
- $keys = explode('|', $payment_method_long_id);
- $payment_method_short_id = reset($keys);
-
- $payment_method = $payment_method_definitions[$payment_method_short_id];
- $settings_form_callback = $payment_method['callbacks']['settings_form'];
-
- if (
- // Payment method has to be enabled for the current store.
- empty($store->data['payment_methods']['enabled'][$payment_method_short_id])
- // Payment method has to be configured for the current store.
- || empty($store->data['payment_methods']['settings'][$payment_method_short_id]) && function_exists($settings_form_callback)
- ) {
- unset($order->payment_methods[$payment_method_long_id]);
- }
- }
+ // Keep only methods enabled for the order's store.
+ if (variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants') {
+ commerce_marketplace_payment_filter_store_payment_methods($order);
+ }
+ // Update order payment methods with relevant store payment method settings.
+ commerce_marketplace_payment_update_store_payment_method_settings($order);
+
+ // When payments are sent directly to merchants, and there is at least one
+ // enabled parallel payment method common for all orders in the order group,
+ // we want to offer only those parallel payment methods to the customer,
+ // let's then get rid of all other methods.
+ if (
+// ($orders = commerce_marketplace_cart_order_load_multiple($user->uid))
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && commerce_marketplace_order_count($orders) > 1
+ && ($parallel_methods = commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders))
+ ) {
+ $order->payment_methods = array_intersect_key($order->payment_methods, array_flip($parallel_methods));
+ $form_state['orders'] = $orders;
+ }
+ elseif (variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'main_store') {
+ $form_state['orders'] = $orders;
+ }
+ else {
+ $form_state['orders'] = array($order);
}
-
- // Sort the payment methods array by the enabling Rules' weight values.
- uasort($order->payment_methods, 'drupal_sort_weight');
// Generate an array of payment method options for the checkout form.
$options = array();
@@ -175,7 +188,7 @@ function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $c
'#options' => $options,
'#ajax' => array(
'callback' => 'commerce_marketplace_payment_pane_checkout_form_details_refresh',
- 'wrapper' => 'commerce-checkout-form-reviews-form-' . $form_state['order']->order_id,
+ 'wrapper' => 'commerce-checkout-form-' . $form_state['order']->order_id,
),
);
@@ -205,6 +218,14 @@ function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $c
$payment_method = commerce_payment_method_load($method_info['method_id']);
$payment_method['settings'] = $method_info['settings'];
+ $wrapper = entity_metadata_wrapper('commerce_order', $order);
+ if (!empty($wrapper->commerce_store)) {
+ $store = $wrapper->commerce_store->value();
+ if (!empty($store->data['payment_methods']['settings'][$method_info['method_id']])) {
+ $payment_method['settings'] = $store->data['payment_methods']['settings'][$method_info['method_id']];
+ }
+ }
+
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) {
$pane_form['payment_details'] = $callback($payment_method, $pane_values, $checkout_pane, $order);
}
@@ -218,7 +239,7 @@ function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $c
}
else {
$pane_form['payment_details'] = array(
- '#markup' => t('This order has been already paid.'),
+ '#markup' => t(variable_get('commerce_marketplace_payment_pane_paid', 'This order has been already paid.')),
);
}
@@ -227,6 +248,8 @@ function commerce_marketplace_payment_pane_checkout_form($form, &$form_state, $c
/**
* Returns the payment details element for display via AJAX.
+ *
+ * @see commerce_payment_pane_checkout_form_details_refresh()
*/
function commerce_marketplace_payment_pane_checkout_form_details_refresh($form, $form_state) {
return $form;
@@ -235,6 +258,7 @@ function commerce_marketplace_payment_pane_checkout_form_details_refresh($form,
/**
* Payment pane: validation callback.
*
+ * @see commerce_payment_pane_checkout_form_validate()
* @see commerce_marketplace_payment_pane_checkout_form()
* @see commerce_marketplace_payment_pane_checkout_form_submit()
*/
@@ -245,6 +269,7 @@ function commerce_marketplace_payment_pane_checkout_form_validate($form, &$form_
if (!empty($form[$pane_id]) && !empty($form_state['values'][$pane_id])) {
$pane_form = $form[$pane_id];
$pane_values = $form_state['values'][$pane_id];
+ $orders = commerce_marketplace_order_group_load($order->order_group);
// Only attempt validation if there were payment methods available.
if (!empty($pane_values['payment_methods'])) {
@@ -252,6 +277,14 @@ function commerce_marketplace_payment_pane_checkout_form_validate($form, &$form_
if ($pane_values['payment_method'] != $pane_form['payment_method']['#default_value']) {
// And the newly selected method has a valid form callback...
if ($payment_method = commerce_payment_method_instance_load($pane_values['payment_method'])) {
+ // @DIFF: Update payment method with relevant store payment method settings.
+ if (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ ) {
+ commerce_marketplace_payment_update_payment_method_settings($payment_method, $order);
+ }
+
if (commerce_payment_method_callback($payment_method, 'submit_form')) {
// Fail validation so the form is rebuilt to include the payment method
// specific form elements.
@@ -262,6 +295,13 @@ function commerce_marketplace_payment_pane_checkout_form_validate($form, &$form_
// Delegate validation to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
+ // @DIFF: Update payment method with relevant store payment method settings.
+ if (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ ) {
+ commerce_marketplace_payment_update_payment_method_settings($payment_method, $order);
+ }
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_validate')) {
// Initialize the payment details array to accommodate payment methods
@@ -296,6 +336,11 @@ function commerce_marketplace_payment_pane_checkout_form_validate($form, &$form_
/**
* Payment pane: submit callback.
*
+ * Calls original submit callback for each order which should be paid, hoping
+ * that the original callback won't drupal_goto() anywhere, as it will break
+ * the functionality.
+ *
+ * @see commerce_payment_pane_checkout_form_submit()
* @see commerce_marketplace_payment_pane_checkout_form()
* @see commerce_marketplace_payment_pane_checkout_form_validate()
*/
@@ -322,6 +367,14 @@ function commerce_marketplace_payment_pane_checkout_form_submit($form, &$form_st
if ($balance = commerce_payment_order_balance($order)) {
// Delegate submit to the payment method callback.
$payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
+ // @DIFF: Update payment method with relevant store payment method settings.
+ $orders = commerce_marketplace_order_group_load($order->order_group);
+ if (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ ) {
+ commerce_marketplace_payment_update_payment_method_settings($payment_method, $order);
+ }
if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_submit')) {
// Initialize the payment details array to accommodate payment methods
@@ -342,11 +395,21 @@ function commerce_marketplace_payment_pane_checkout_form_submit($form, &$form_st
/**
* Payment redirect pane: form callback.
+ *
+ * @see commerce_payment_redirect_pane_checkout_form()
*/
function commerce_marketplace_payment_redirect_pane_checkout_form(&$form, &$form_state, $checkout_pane, $order) {
+ $orders = commerce_marketplace_order_group_load($order->order_group);
+
// 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 (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ ) {
+ commerce_marketplace_payment_update_payment_method_settings($payment_method, $order);
+ }
}
else {
$payment_method = FALSE;
@@ -362,54 +425,78 @@ function commerce_marketplace_payment_redirect_pane_checkout_form(&$form, &$form
}
// Advance the customer to the next step of the checkout process.
- commerce_payment_redirect_pane_next_page($order, $log);
+ foreach ($orders as $group_order) {
+ commerce_payment_redirect_pane_next_page($group_order, $log);
+ if ($order->order_id == $group_order->order_id) {
+ $order = $group_order;
+ }
+ }
drupal_goto(commerce_marketplace_checkout_order_uri($order));
}
// If the user came to the cancel URL...
- if (arg(3) == 'back' && arg(4) == $order->data['payment_redirect_key']) {
- // Perform any payment cancellation functions if necessary.
- if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_back')) {
- $callback($order, $payment_method);
- }
+ if (arg(3) == 'back') {
+ foreach ($orders as $group_order) {
+ if (arg(4) == $group_order->data['payment_redirect_key']) {
+ // Perform any payment cancellation functions if necessary.
+ if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_back')) {
+ $callback($group_order, $payment_method);
+ }
- // Send the customer to the previous step of the checkout process.
- commerce_payment_redirect_pane_previous_page($order, t('Customer canceled payment at the payment gateway.'));
- drupal_goto(commerce_marketplace_checkout_order_uri($order));
+ // Send the customer to the previous step of the checkout process.
+ commerce_payment_redirect_pane_previous_page($group_order, t('Customer canceled payment at the payment gateway.'));
+ }
+ if ($order->order_id == $group_order->order_id) {
+ $order = $group_order;
+ }
+ }
+ drupal_goto(commerce_checkout_order_uri($order));
}
// If the user came to the return URL...
- if (arg(3) == 'return' && arg(4) == $order->data['payment_redirect_key']) {
- // Check for a validate handler on return.
- $validate_callback = commerce_payment_method_callback($payment_method, 'redirect_form_validate');
-
- // If there is no validate handler or if there is and it isn't FALSE...
- if (!$validate_callback || $validate_callback($order, $payment_method) !== FALSE) {
- // Perform any submit functions if necessary.
- if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_submit')) {
- $callback($order, $payment_method);
- }
+ if (arg(3) == 'return') {
+ foreach ($orders as $group_order) {
+ if (arg(4) == $group_order->data['payment_redirect_key']) {
+ // Check for a validate handler on return.
+ $validate_callback = commerce_payment_method_callback($payment_method, 'redirect_form_validate');
+
+ // If there is no validate handler or if there is and it isn't FALSE...
+ if (!$validate_callback || $validate_callback($group_order, $payment_method) !== FALSE) {
+ // Perform any submit functions if necessary.
+ if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_submit')) {
+ $callback($group_order, $payment_method);
+ }
- // Send the customer on to the next checkout page.
- commerce_payment_redirect_pane_next_page($order, t('Customer successfully submitted payment at the payment gateway.'));
- drupal_goto(commerce_marketplace_checkout_order_uri($order));
- }
- else {
- // Otherwise display the failure message and send the customer back.
- drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
+ // Send the customer on to the next checkout page.
+ commerce_payment_redirect_pane_next_page($group_order, t('Customer successfully submitted payment at the payment gateway.'));
+ }
+ else {
+ // Otherwise display the failure message and send the customer back.
+ 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.'));
- drupal_goto(commerce_marketplace_checkout_order_uri($order));
+ commerce_payment_redirect_pane_previous_page($group_order, t('Customer payment submission failed at the payment gateway.'));
+ }
+ }
+ if ($order->order_id == $group_order->order_id) {
+ $order = $group_order;
+ }
}
+ drupal_goto(commerce_checkout_order_uri($order));
}
// If the function to build the redirect form exists...
if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form')) {
// Generate a key to use in the return URL from the redirected service if it
// does not already exist.
- if (empty($order->data['payment_redirect_key'])) {
- $order->data['payment_redirect_key'] = drupal_hash_base64(time());
- commerce_order_save($order);
+ $payment_redirect_key = drupal_hash_base64(time());
+ foreach ($orders as $group_order) {
+ if (empty($group_order->data['payment_redirect_key'])) {
+ $group_order->data['payment_redirect_key'] = $payment_redirect_key;
+ commerce_order_save($group_order);
+ if ($order->order_id == $group_order->order_id) {
+ $order = $group_order;
+ }
+ }
}
// If the payment method has the 'offsite_autoredirect' option enabled, add
@@ -419,9 +506,46 @@ function commerce_marketplace_payment_redirect_pane_checkout_form(&$form, &$form
$form['help']['#markup'] = '<div class="checkout-help">' . t('Please wait while you are redirected to the payment server. If nothing happens within 10 seconds, please click on the button below.') . '</div>';
}
- // Merge the new form into the current form array, preserving the help text
- // if it exists. We also add a wrapper so the form can be easily submitted.
- $form += drupal_get_form($callback, $order, $payment_method);
+ // When dealing with multiple orders, but sending only one payment, we need
+ // to calculate summarized values and provide them to the payment gateway.
+ // This includes not only total amount, but also "order number" (which will
+ // consist of several numbers put together) and possibly other data as well.
+ if (
+ commerce_marketplace_order_count($orders) > 1
+ && (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) != 'merchants'
+ || variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ )
+ ) {
+ // Let's use current order for the basis.
+ $marketplace_order = $order;
+ $marketplace_order_wrapper = entity_metadata_wrapper('commerce_order', $marketplace_order);
+
+ // Calculate marketplace order totals from all order totals.
+ $marketplace_order_total = commerce_marketplace_order_calculate_total($orders);
+ $marketplace_order_wrapper->commerce_order_total = $marketplace_order_total;
+
+ // Prepare new 'order number' to be passed to the payment gateway,
+ // also add all line items from all orders.
+ $marketplace_order_number = '';
+ $commerce_line_items = array();
+ foreach ($orders as $group_order) {
+ $marketplace_order_number[] = $group_order->order_number;
+ $commerce_line_items = array_merge_recursive($commerce_line_items, $group_order->commerce_line_items);
+ }
+ $marketplace_order->order_number = implode(', ', $marketplace_order_number);
+ $marketplace_order->commerce_line_items = $commerce_line_items;
+
+ // Merge the new form into the current form array, preserving the help text
+ // if it exists. We also add a wrapper so the form can be easily submitted.
+ $form += drupal_get_form($callback, $marketplace_order, $payment_method);
+ }
+ else {
+ // Merge the new form into the current form array, preserving the help text
+ // if it exists. We also add a wrapper so the form can be easily submitted.
+ $form += drupal_get_form($callback, $order, $payment_method);
+ }
$form['#prefix'] = '<div class="payment-redirect-form">';
$form['#suffix'] = '</div>';
@@ -435,12 +559,15 @@ function commerce_marketplace_payment_redirect_pane_checkout_form(&$form, &$form
/**
* Utility function: return a payment redirect page for POST.
*
- * @param $action
+ * @param string $action
* The destination URL the values should be posted to.
- * @param $values
+ * @param array $values
* An associative array of values that will be posted to the destination URL.
- * @return
+ *
+ * @return array
* A renderable array.
+ *
+ * @see commerce_payment_post_redirect_form()
*/
function commerce_marketplace_payment_post_redirect_form($action, array $values = array()) {
$form = array(
diff --git a/modules/shipping/commerce_marketplace_shipping.module b/modules/shipping/commerce_marketplace_shipping.module
index cea3ed3..6150f1f 100644
--- a/modules/shipping/commerce_marketplace_shipping.module
+++ b/modules/shipping/commerce_marketplace_shipping.module
@@ -7,15 +7,82 @@
/**
* Implements hook_commerce_checkout_pane_info_alter().
- *
- * Move 'Shipping service' checkout pane provided by commerce_shipping module
- * to 'Reviews' checkout page, so it is displayed there for each order from the
- * order group, between 'Marketplace: Review' (weight 10) and 'Marketplace:
- * Payment' (weight 30) panes.
*/
function commerce_marketplace_shipping_commerce_checkout_pane_info_alter(&$checkout_panes) {
- $checkout_panes['commerce_shipping']['title'] = t('Marketplace: Shipping');
- $checkout_panes['commerce_shipping']['page'] = 'reviews';
+ global $user;
+
+ // @TODO: Do something with this shite.
+ // This chunk of code is taken from commerce_checkout_panes(), and is meant to
+ // populate missing $checkout_panes defaults. The issue here is that function
+ // commerce_checkout_panes() indirectly calls itself through
+ // commerce_marketplace_cart_order_load_multiple() below, and the second time
+ // $checkout_panes in commerce_checkout_panes() are already populated, but not
+ // yet fully, so we go directly to the second section ($conditions checks)
+ // without fully going through the first one.
+ // Something like:
+ // commerce_order_statuses()
+ // hook_commerce_order_status_info()
+ // commerce_checkout_commerce_order_status_info()
+ // commerce_checkout_pages()
+ // commerce_checkout_panes()
+ // hook_commerce_checkout_pane_info_alter()
+ // commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ // commerce_marketplace_cart_order_load_multiple()
+ // commerce_marketplace_cart_order_ids()
+ // commerce_order_statuses()
+ foreach ($checkout_panes as $pane_id => $checkout_pane) {
+ // Set some defaults for the checkout pane.
+ $defaults = array(
+ 'base' => $pane_id,
+ 'name' => $checkout_pane['title'],
+ 'page' => 'checkout',
+ 'locked' => FALSE,
+ 'fieldset' => TRUE,
+ 'collapsible' => FALSE,
+ 'collapsed' => FALSE,
+ 'weight' => 0,
+ 'enabled' => TRUE,
+ 'review' => TRUE,
+ 'callbacks' => array(),
+ 'file' => '',
+ );
+ $checkout_pane += $defaults;
+ // Merge in default callbacks.
+ foreach (array('settings_form', 'checkout_form', 'checkout_form_validate', 'checkout_form_submit', 'review') as $callback) {
+ if (!isset($checkout_pane['callbacks'][$callback])) {
+ $checkout_pane['callbacks'][$callback] = $checkout_pane['base'] . '_' . $callback;
+ }
+ }
+ $checkout_panes[$pane_id] = $checkout_pane;
+ }
+
+ // When payments should be sent directly to the merchants, but there is no
+ // enabled parallel payment common for all orders in the order group, we use
+ // marketplace-specific checkout panes instead of the default ones, at the
+ // same time moving some standard panes (ie. Shipping in this case) to a
+ // different checkout page.
+ // Also see commerce_marketplace_payment_commerce_checkout_pane_info_alter()
+ $menu_item = menu_get_item();
+ if (strpos($menu_item['path'], 'checkout/%') === 0) {
+ $order = $menu_item['page_arguments'][0];
+ }
+ else {
+ // This will load only orders with the 'shopping cart' status, while here
+ // we need ALL orders from the order group, regardless of their status
+ // (even those with the 'Payment' status) - that's why we'd need to load
+ // them again below based on the order_group ID.
+ $orders = commerce_marketplace_cart_order_load_multiple($user->uid);
+ $order = reset($orders);
+ }
+ if (
+ variable_get('commerce_store_payment_mode', COMMERCE_MARKETPLACE_PAYMENT_DEFAULT_MODE) == 'merchants'
+ && !empty($order->order_group)
+ && ($orders = commerce_marketplace_order_group_load($order->order_group))
+ && !commerce_marketplace_payment_common_enabled_parallel_payment_methods($orders)
+ && commerce_marketplace_order_count($orders) > 1
+ ) {
+ $checkout_panes['commerce_shipping']['page'] = 'reviews';
+ }
}
/**
@@ -23,7 +90,11 @@ function commerce_marketplace_shipping_commerce_checkout_pane_info_alter(&$check
*/
function commerce_marketplace_shipping_form_alter(&$form, &$form_state, $form_id) {
- if (strpos($form_id, 'commerce_checkout_form_reviews_') === 0) {
+ // Add extra wrapper for the whole form, so that we can update it via ajax
+ // callback (see commerce_marketplace_payment_pane_checkout_form()).
+ // Also see commerce_marketplace_payment_form_alter().
+ if (!empty($form['commerce_shipping'])) {
+
// Do not display shipping method selection form for orders that already have
// been paid.
$order_status = commerce_order_status_load($form_state['order']->status);
@@ -31,16 +102,17 @@ function commerce_marketplace_shipping_form_alter(&$form, &$form_state, $form_id
unset($form['commerce_shipping']);
}
- // Add pane form wrappers so that we can update it via ajax callback
- // (including Shopping cart contents view).
- $wrapper_id = 'commerce-checkout-form-reviews-form-' . $form_state['order']->order_id;
- $form['#prefix'] = '<div id="' . $wrapper_id . '">';
- $form['#suffix'] = '</div>';
-
// Update default shipping method selection ajax callback to ours, so that
// we can update the whole pane content (including shipping costs in the
// Shopping cart contents view), not only shipping method selection radios.
- if (!empty($form['commerce_shipping'])) {
+ if (!empty($form['commerce_shipping']['shipping_service'])) {
+
+ // Add generic form wrapper with the order ID.
+ $wrapper_id = 'commerce-checkout-form-' . $form_state['order']->order_id;
+ $form['#prefix'] = '<div id="' . $wrapper_id . '">';
+ $form['#suffix'] = '</div>';
+
+ // Update ajax callback to refresh the whole form.
$form['commerce_shipping']['shipping_service']['#ajax'] = array(
'callback' => 'commerce_marketplace_shipping_pane_service_details_refresh',
'wrapper' => $wrapper_id,
@@ -50,14 +122,21 @@ function commerce_marketplace_shipping_form_alter(&$form, &$form_state, $form_id
// Shipping service pane form was not submitted yet, but one of available
// services is always selected by default, so we need to add it to the order
// anyway, so it is shown in Shopping cart contents view above.
- if (!isset($form_state['triggering_element']) && !empty($form['commerce_shipping'])) {
+ if (
+ !isset($form_state['triggering_element'])
+ && !empty($form['commerce_shipping']['shipping_service']['#default_value'])
+ // This should be done only when we have multiple orders displayed on the
+ // Reviews checkout page (so generally when Marketplace Review pane is
+ // displayed), as otherwise standard shipping submit should be enough.
+ && !empty($form['commerce_marketplace_review'])
+ ) {
$selected_shipping_service = $form['commerce_shipping']['shipping_service']['#default_value'];
// Add shopping service to the current order.
$order = commerce_order_load($form_state['order']->order_id);
module_load_include('inc', 'commerce_shipping', 'commerce_shipping.rules');
commerce_shipping_rate_apply($order, $selected_shipping_service);
commerce_order_save($order);
- $form_state['order'] = $order;
+ $form_state['order'] = $form_state['orders'][$order->order_id] = $order;
// Update Shopping cart contents view.
list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
if (!empty($view_id) && !empty($display_id)) {
@@ -65,20 +144,32 @@ function commerce_marketplace_shipping_form_alter(&$form, &$form_state, $form_id
}
}
- // If shipping method selection subform was submitted, update relevant order
- // and Shopping cart contents view.
- if (isset($form_state['triggering_element']) && $form_state['triggering_element']['#name'] == 'commerce_shipping[shipping_service]') {
- // Update shipping service for the current order.
- $checkout_pane_id = array('pane_id' => 'commerce_shipping');
- $order = commerce_order_load($form_state['order']->order_id);
- commerce_shipping_pane_checkout_form_submit($form, $form_state, $checkout_pane_id, $order);
- commerce_order_save($order);
- $form_state['order'] = $order;
- // Update Shopping cart contents view.
- list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
- if (!empty($view_id) && !empty($display_id)) {
- $form['commerce_marketplace_review']['review']['#data']['cart_contents']['data'] = commerce_embed_view($view_id, $display_id, array($form_state['order']->order_id));
- }
+ }
+
+ // If shipping method selection subform was submitted, update relevant orders
+ // and Shopping cart contents view.
+ if (
+ isset($form_state['triggering_element'])
+ && $form_state['triggering_element']['#name'] == 'commerce_shipping[shipping_service]'
+ // This should be done only when we have multiple orders displayed on the
+ // Reviews checkout page (so generally when Marketplace Review pane is
+ // displayed), as otherwise standard shipping submit should be enough.
+ && !empty($form['commerce_marketplace_review'])
+ ) {
+ // Update shipping service for the current order.
+ $checkout_pane_id = array('pane_id' => 'commerce_shipping');
+ $order = commerce_order_load($form_state['order']->order_id);
+ module_load_include('inc', 'commerce_shipping', 'includes/commerce_shipping.checkout_pane');
+ commerce_shipping_pane_checkout_form_submit($form, $form_state, $checkout_pane_id, $order);
+ commerce_order_save($order);
+
+ // Add updated order back to the form state.
+ $form_state['order'] = $form_state['orders'][$order->order_id] = $order;
+
+ // Update Shopping cart contents view.
+ list($view_id, $display_id) = explode('|', variable_get('commerce_cart_contents_pane_view', 'commerce_cart_summary|default'));
+ if (!empty($view_id) && !empty($display_id)) {
+ $form['commerce_marketplace_review']['review']['#data']['cart_contents']['data'] = commerce_embed_view($view_id, $display_id, array($form_state['order']->order_id));
}
}