Projet

Général

Profil

Paste
Télécharger (17,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / payment / includes / commerce_payment.checkout_pane.inc @ dbb0c974

1
<?php
2

    
3
/**
4
 * @file
5
 * Callback functions for the Payment module's checkout panes.
6
 */
7

    
8
// Constants that govern the behavior of the payment method checkout pane when
9
// no payment methods are enabled for an order.
10
define('COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY', 'empty');
11
define('COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT', 'empty_event');
12
define('COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE', 'message');
13
define('COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT', 'message_event');
14

    
15

    
16
/**
17
 * Checkout pane callback: returns the payment pane's settings form.
18
 */
19
function commerce_payment_pane_settings_form($checkout_pane) {
20
  $form = array();
21

    
22
  $form['commerce_payment_pane_require_method'] = array(
23
    '#type' => 'checkbox',
24
    '#title' => t('Require a payment method at all times, preventing checkout if none is available.'),
25
    '#default_value' => variable_get('commerce_payment_pane_require_method', FALSE),
26
  );
27
  $form['commerce_payment_pane_no_method_behavior'] = array(
28
    '#type' => 'radios',
29
    '#title' => t('Checkout pane behavior when no payment methods are enabled for an order'),
30
    '#description' => t('Note: regardless of your selection, no payment transaction will be created for the order upon checkout completion as they represent actual financial transactions.'),
31
    '#options' => array(
32
      COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY => t('Leave the payment checkout pane empty.'),
33
      COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT => t('Leave the payment checkout pane empty and trigger <em>When an order is first paid in full</em> on submission of free orders.'),
34
      COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE => t('Display a message in the pane indicating payment is not required for the order.'),
35
      COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT => t('Display a message in the pane indicating payment is not required and trigger <em>When an order is first paid in full</em> on submission of free orders.'),
36
    ),
37
    '#default_value' => variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE),
38
    '#states' => array(
39
      'visible' => array(
40
        ':input[name="commerce_payment_pane_require_method"]' => array('checked' => FALSE),
41
      ),
42
    ),
43
  );
44

    
45
  return $form;
46
}
47

    
48
/**
49
 * Payment pane: form callback.
50
 */
51
function commerce_payment_pane_checkout_form($form, &$form_state, $checkout_pane, $order) {
52
  $pane_form = array();
53

    
54
  // Invoke the payment methods event that will populate the order with
55
  // an array of method IDs for available payment methods.
56
  $order->payment_methods = array();
57
  rules_invoke_all('commerce_payment_methods', $order);
58

    
59
  // Sort the payment methods array by the enabling Rules' weight values.
60
  uasort($order->payment_methods, 'drupal_sort_weight');
61

    
62
  // Generate an array of payment method options for the checkout form.
63
  $options = array();
64

    
65
  foreach ($order->payment_methods as $instance_id => $method_info) {
66
    // Ensure we've received a valid payment method that can be used on the
67
    // checkout form.
68
    if ($payment_method = commerce_payment_method_load($method_info['method_id'])) {
69
      if (!empty($payment_method['checkout'])) {
70
        $options[$instance_id] = $payment_method['display_title'];
71
      }
72
    }
73
  }
74

    
75
  // If no payment methods were found, return the empty form.
76
  if (empty($options)) {
77
    if (!variable_get('commerce_payment_pane_require_method', FALSE)) {
78
      $behavior = variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE);
79

    
80
      switch ($behavior) {
81
        case COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE:
82
        case COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT:
83
          $pane_form['message'] = array(
84
            '#markup' => '<div>' . t('Payment is not required to complete your order.') . '</div>',
85
          );
86
          break;
87

    
88
        case COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY:
89
        case COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT:
90
        default:
91
          break;
92
      }
93

    
94
      return $pane_form;
95
    }
96
    else {
97
      $pane_form['message'] = array(
98
        '#markup' => '<div>' . t('Unfortunately we could not find any suitable payment methods, and we require a payment method to complete checkout.') . '<br /><strong>' . t('Please contact us to resolve any issues with your order.') . '</strong></div>',
99
      );
100
    }
101
  }
102

    
103
  // Store the payment methods in the form for validation purposes.
104
  $pane_form['payment_methods'] = array(
105
    '#type' => 'value',
106
    '#value' => $order->payment_methods,
107
  );
108

    
109
  // If at least one payment option is available...
110
  if (!empty($options)) {
111
    // Add a radio select widget to specify the payment method.
112
    $pane_form['payment_method'] = array(
113
      '#type' => 'radios',
114
      '#options' => $options,
115
      '#ajax' => array(
116
        'callback' => 'commerce_payment_pane_checkout_form_details_refresh',
117
        'wrapper' => 'payment-details',
118
      ),
119
    );
120

    
121
    // Find the default payment method using either the preselected value stored
122
    // in the order / checkout pane or the first available method.
123
    $pane_values = !empty($form_state['values'][$checkout_pane['pane_id']]) ? $form_state['values'][$checkout_pane['pane_id']] : array();
124

    
125
    if (isset($pane_values['payment_method']) && isset($options[$pane_values['payment_method']])) {
126
      $default_value = $pane_values['payment_method'];
127
    }
128
    elseif (isset($form_state['input']['commerce_payment']['payment_method'])) {
129
      $default_value = $form_state['complete form']['commerce_payment']['payment_method']['#default_value'];
130
    }
131
    elseif (isset($order->data['payment_method']) && isset($options[$order->data['payment_method']])) {
132
      $default_value = $order->data['payment_method'];
133
    }
134
    else {
135
      reset($options);
136
      $default_value = key($options);
137
    }
138

    
139
    // Set the default value for the payment method radios.
140
    $pane_form['payment_method']['#default_value'] = $default_value;
141

    
142
    // Add the payment method specific form elements.
143
    $method_info = $order->payment_methods[$pane_form['payment_method']['#default_value']];
144
    $payment_method = commerce_payment_method_load($method_info['method_id']);
145
    $payment_method['settings'] = $method_info['settings'];
146

    
147
    if ($callback = commerce_payment_method_callback($payment_method, 'submit_form')) {
148
      $pane_form['payment_details'] = $callback($payment_method, $pane_values, $checkout_pane, $order);
149
    }
150
    else {
151
      $pane_form['payment_details'] = array();
152
    }
153

    
154
    $pane_form['payment_details']['#prefix'] = '<div id="payment-details">';
155
    $pane_form['payment_details']['#suffix'] = '</div>';
156
  }
157

    
158
  return $pane_form;
159
}
160

    
161
/**
162
 * Returns the payment details element for display via AJAX.
163
 */
164
function commerce_payment_pane_checkout_form_details_refresh($form, $form_state) {
165
  return $form['commerce_payment']['payment_details'];
166
}
167

    
168
/**
169
 * Payment pane: validation callback.
170
 */
171
function commerce_payment_pane_checkout_form_validate($form, &$form_state, $checkout_pane, $order) {
172
  $pane_id = $checkout_pane['pane_id'];
173

    
174
  // Only attempt validation if we actually had payment methods on the form.
175
  if (!empty($form[$pane_id]) && !empty($form_state['values'][$pane_id])) {
176
    $pane_form = $form[$pane_id];
177
    $pane_values = $form_state['values'][$pane_id];
178

    
179
    // Only attempt validation if there were payment methods available.
180
    if (!empty($pane_values['payment_methods'])) {
181
      // If the selected payment method was changed...
182
      if ($pane_values['payment_method'] != $pane_form['payment_method']['#default_value']) {
183
        // And the newly selected method has a valid form callback...
184
        if ($payment_method = commerce_payment_method_instance_load($pane_values['payment_method'])) {
185
          if (commerce_payment_method_callback($payment_method, 'submit_form')) {
186
            // Fail validation so the form is rebuilt to include the payment method
187
            // specific form elements.
188
            return FALSE;
189
          }
190
        }
191
      }
192

    
193
      // Delegate validation to the payment method callback.
194
      $payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
195

    
196
      if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_validate')) {
197
        // Initialize the payment details array to accommodate payment methods
198
        // that don't add any additional details to the checkout pane form.
199
        if (!isset($pane_values['payment_details'])) {
200
          $pane_values['payment_details'] = array();
201
        }
202

    
203
        $result = $callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, array($checkout_pane['pane_id'], 'payment_details'));
204

    
205
        // To prevent payment method validation routines from having to return TRUE
206
        // explicitly, only return FALSE if it was specifically returned.  Otherwise
207
        // default to TRUE.
208
        return $result === FALSE ? FALSE : TRUE;
209
      }
210
    }
211
    elseif (variable_get('commerce_payment_pane_require_method', FALSE)) {
212
      drupal_set_message(t('You cannot complete checkout without submitting payment. Please contact us if an error continues to prevent you from seeing valid payment methods for your order.'), 'error');
213
      return FALSE;
214
    }
215
  }
216
  else {
217
    // Otherwise ensure we don't have any leftover payment method data in the
218
    // order's data array.
219
    unset($order->data['payment_method'], $order->data['payment_redirect_key']);
220
  }
221

    
222
  // Nothing to validate.
223
  return TRUE;
224
}
225

    
226
/**
227
 * Payment pane: submit callback.
228
 */
229
function commerce_payment_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
230
  // Check to make sure there are no validation issues with other form elements
231
  // before executing payment method callbacks.
232
  if (form_get_errors()) {
233
    drupal_set_message(t('Your payment will not be processed until all errors on the page have been addressed.'), 'warning');
234
    return FALSE;
235
  }
236

    
237
  $pane_id = $checkout_pane['pane_id'];
238

    
239
  // Only submit if we actually had payment methods on the form.
240
  if (!empty($form[$pane_id]) && !empty($form_state['values'][$pane_id])) {
241
    $pane_form = $form[$pane_id];
242
    $pane_values = $form_state['values'][$pane_id];
243

    
244
    // Only process if there were payment methods available.
245
    if ($pane_values['payment_methods']) {
246
      $order->data['payment_method'] = $pane_values['payment_method'];
247

    
248
      // If we can calculate a single order total for the order...
249
      if ($balance = commerce_payment_order_balance($order)) {
250
        // Delegate submit to the payment method callback.
251
        $payment_method = commerce_payment_method_instance_load($pane_values['payment_method']);
252

    
253
        if ($callback = commerce_payment_method_callback($payment_method, 'submit_form_submit')) {
254
          // Initialize the payment details array to accommodate payment methods
255
          // that don't add any additional details to the checkout pane form.
256
          if (empty($pane_values['payment_details'])) {
257
            $pane_values['payment_details'] = array();
258
          }
259

    
260
          // If payment fails, rebuild the checkout form without progressing.
261
          if ($callback($payment_method, $pane_form['payment_details'], $pane_values['payment_details'], $order, $balance) === FALSE) {
262
            $form_state['rebuild'] = TRUE;
263
          }
264
        }
265
      }
266
    }
267
  }
268
  else {
269
    // If there were no payment methods on the form, check to see if the pane is
270
    // configured to trigger "When an order is first paid in full" on submission
271
    // for free orders.
272
    $behavior = variable_get('commerce_payment_pane_no_method_behavior', COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE);
273

    
274
    if (in_array($behavior, array(COMMERCE_PAYMENT_PANE_NO_METHOD_EMPTY_EVENT, COMMERCE_PAYMENT_PANE_NO_METHOD_MESSAGE_EVENT))) {
275
      // Check the balance of the order.
276
      $balance = commerce_payment_order_balance($order);
277

    
278
      if (!empty($balance) && $balance['amount'] <= 0) {
279
        // Trigger the event now for free orders, simulating payment being
280
        // submitted on pane submission that brings the balance to 0. Use an
281
        // empty transaction, as we wouldn't typically save a transaction where
282
        // a financial transaction has not actually occurred.
283
        rules_invoke_all('commerce_payment_order_paid_in_full', $order, commerce_payment_transaction_new('', $order->order_id));
284

    
285
        // Update the order's data array to indicate this just happened.
286
        $order->data['commerce_payment_order_paid_in_full_invoked'] = TRUE;
287
      }
288
    }
289
  }
290
}
291

    
292
/**
293
 * Payment redirect pane: form callback.
294
 */
295
function commerce_payment_redirect_pane_checkout_form(&$form, &$form_state, $checkout_pane, $order) {
296
  // First load the order's specified payment method instance.
297
  if (!empty($order->data['payment_method'])) {
298
    $payment_method = commerce_payment_method_instance_load($order->data['payment_method']);
299
  }
300
  else {
301
    $payment_method = FALSE;
302
  }
303

    
304
  // If the payment method doesn't exist or does not require a redirect...
305
  if (!$payment_method || !$payment_method['offsite']) {
306
    if (!$payment_method) {
307
      $log = t('Customer skipped the Payment page because no payment was required.');
308
    }
309
    else {
310
      $log = t('Customer skipped the Payment page because payment was already submitted.');
311
    }
312

    
313
    // Advance the customer to the next step of the checkout process.
314
    commerce_payment_redirect_pane_next_page($order, $log);
315
    drupal_goto(commerce_checkout_order_uri($order));
316
  }
317

    
318
  // If the user came to the cancel URL...
319
  if (arg(3) == 'back' && arg(4) == $order->data['payment_redirect_key']) {
320
    // Perform any payment cancellation functions if necessary.
321
    if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_back')) {
322
      $callback($order, $payment_method);
323
    }
324

    
325
    // Send the customer to the previous step of the checkout process.
326
    commerce_payment_redirect_pane_previous_page($order, t('Customer canceled payment at the payment gateway.'));
327
    drupal_goto(commerce_checkout_order_uri($order));
328
  }
329

    
330
  // If the user came to the return URL...
331
  if (arg(3) == 'return' && arg(4) == $order->data['payment_redirect_key']) {
332
    // Check for a validate handler on return.
333
    $validate_callback = commerce_payment_method_callback($payment_method, 'redirect_form_validate');
334

    
335
    // If there is no validate handler or if there is and it isn't FALSE...
336
    if (!$validate_callback || $validate_callback($order, $payment_method) !== FALSE) {
337
      // Perform any submit functions if necessary.
338
      if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form_submit')) {
339
        $callback($order, $payment_method);
340
      }
341

    
342
      // Send the customer on to the next checkout page.
343
      commerce_payment_redirect_pane_next_page($order, t('Customer successfully submitted payment at the payment gateway.'));
344
      drupal_goto(commerce_checkout_order_uri($order));
345
    }
346
    else {
347
      // Otherwise display the failure message and send the customer back.
348
      drupal_set_message(t('Payment failed at the payment server. Please review your information and try again.'), 'error');
349

    
350
      commerce_payment_redirect_pane_previous_page($order, t('Customer payment submission failed at the payment gateway.'));
351
      drupal_goto(commerce_checkout_order_uri($order));
352
    }
353
  }
354

    
355
  // If the function to build the redirect form exists...
356
  if ($callback = commerce_payment_method_callback($payment_method, 'redirect_form')) {
357
    // Generate a key to use in the return URL from the redirected service if it
358
    // does not already exist.
359
    if (empty($order->data['payment_redirect_key'])) {
360
      $order->data['payment_redirect_key'] = drupal_hash_base64(time());
361
      commerce_order_save($order);
362
    }
363

    
364
    // If the payment method has the 'offsite_autoredirect' option enabled, add
365
    // the redirection behavior.
366
    if (!empty($payment_method['offsite_autoredirect'])) {
367
      $form['#attached']['js'][] = drupal_get_path('module', 'commerce_payment') . '/commerce_payment.js';
368
      $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>';
369
    }
370

    
371
    // Merge the new form into the current form array, preserving the help text
372
    // if it exists. We also add a wrapper so the form can be easily submitted.
373
    $form += drupal_get_form($callback, $order, $payment_method);
374

    
375
    $form['#prefix'] = '<div class="payment-redirect-form">';
376
    $form['#suffix'] = '</div>';
377
  }
378
  else {
379
    // Alert the administrator that the module does not provide a required form.
380
    drupal_set_message(t('The %title payment method indicates it is offsite but does not define the necessary form to process the redirect.', array('%title' => $payment_method['title'])), 'error');
381
  }
382
}
383

    
384
/**
385
 * Utility function: return a payment redirect page for POST.
386
 *
387
 * @param $action
388
 *   The destination URL the values should be posted to.
389
 * @param $values
390
 *   An associative array of values that will be posted to the destination URL.
391
 * @return
392
 *   A renderable array.
393
 */
394
function commerce_payment_post_redirect_form($action, array $values = array()) {
395
  $form = array(
396
    '#type' => 'form',
397
    '#action' => $action,
398
    '#method' => 'POST',
399
    '#id' => '',
400
    '#attributes' => array(),
401
  );
402
  foreach ($values as $key => $value) {
403
    $form[$value] = array(
404
      '#type' => 'hidden',
405
      '#name' => $key,
406
      '#value' => $value,
407
      '#id' => '',
408
      '#attributes' => array(),
409
    );
410
  }
411
  $form['submit'] = array(
412
    '#type' => 'submit',
413
    '#id' => '',
414
    '#value' => t('Proceed to payment'),
415
  );
416

    
417
  return array(
418
    'form' => array(
419
      '#type' => 'markup',
420
      '#markup' => drupal_render($form),
421
    ),
422
  );
423
}