Projet

Général

Profil

Paste
Télécharger (37,1 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / payment / commerce_payment.module @ b858700c

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines the payment system and checkout integration.
6
 */
7

    
8

    
9
// Local payment transaction status definitions:
10

    
11
// Pending is used when a transaction has been initialized but is still awaiting
12
// resolution; e.g. a CC authorization awaiting capture or an e-check payment
13
// pending at the payment provider.
14
define('COMMERCE_PAYMENT_STATUS_PENDING', 'pending');
15

    
16
// Success is used when a transaction has completed resulting in money being
17
// transferred from the customer to the store or vice versa.
18
define('COMMERCE_PAYMENT_STATUS_SUCCESS', 'success');
19

    
20
// Failure is used when a transaction cannot be completed or is rejected.
21
define('COMMERCE_PAYMENT_STATUS_FAILURE', 'failure');
22

    
23
// Credit card transaction types definitions:
24

    
25
// Used to just authorize an amount on a credit card account.
26
define('COMMERCE_CREDIT_AUTH_ONLY', 'authorize');
27

    
28
// User to just capture an amount on a credit card account.
29
define('COMMERCE_CREDIT_CAPTURE_ONLY', 'capture');
30

    
31
// Used to capture funds from a prior authorization.
32
define('COMMERCE_CREDIT_PRIOR_AUTH_CAPTURE', 'prior_auth_capture');
33

    
34
// Used to authorize and capture money all at once.
35
define('COMMERCE_CREDIT_AUTH_CAPTURE', 'auth_capture');
36

    
37
// Used to set up a credit card reference through the payment gateway.
38
define('COMMERCE_CREDIT_REFERENCE_SET', 'reference_set');
39

    
40
// Used to capture funds using a credit card reference.
41
define('COMMERCE_CREDIT_REFERENCE_TXN', 'reference_txn');
42

    
43
// Used to remove a reference from the payment gateway.
44
define('COMMERCE_CREDIT_REFERENCE_REMOVE', 'reference_remove');
45

    
46
// Used to credit funds to a reference at the payment gateway.
47
define('COMMERCE_CREDIT_REFERENCE_CREDIT', 'reference_credit');
48

    
49
// Used to credit funds to a credit card account.
50
define('COMMERCE_CREDIT_CREDIT', 'credit');
51

    
52
// Used to void a transaction before the transaction clears.
53
define('COMMERCE_CREDIT_VOID', 'void');
54

    
55
/**
56
 * Implements of hook_entity_info().
57
 */
58
function commerce_payment_entity_info() {
59
  $return = array(
60
    'commerce_payment_transaction' => array(
61
      'label' => t('Commerce Payment transaction'),
62
      'controller class' => 'CommercePaymentTransactionEntityController',
63
      'base table' => 'commerce_payment_transaction',
64
      'revision table' => 'commerce_payment_transaction_revision',
65
      'fieldable' => FALSE,
66
      'entity keys' => array(
67
        'id' => 'transaction_id',
68
        'revision' => 'revision_id',
69
        'bundle' => 'payment_method',
70
        'label' => 'transaction_id', // TODO: Update to use a custom callback.
71
      ),
72
      'bundle keys' => array(
73
        'bundle' => 'payment_method',
74
      ),
75
      'bundles' => array(),
76
      'load hook' => 'commerce_payment_transaction_load',
77
      'view modes' => array(
78
        'administrator' => array(
79
          'label' => t('Administrator'),
80
          'custom settings' => FALSE,
81
        ),
82
      ),
83
      'uri callback' => 'commerce_payment_transaction_uri',
84
      'access callback' => 'commerce_payment_transaction_access',
85
      'access arguments' => array(
86
        'user key' => 'uid',
87
        'access tag' => 'commerce_payment_transaction_access',
88
      ),
89
      'token type' => 'commerce-payment-transaction',
90
      'metadata controller class' => '',
91
      'permission labels' => array(
92
        'singular' => t('payment transaction'),
93
        'plural' => t('payment transactions'),
94
      ),
95

    
96
      // Prevent Redirect alteration of the payment transaction form.
97
      'redirect' => FALSE,
98
    ),
99
  );
100

    
101
  foreach (commerce_payment_methods() as $method_id => $payment_method) {
102
    $return['commerce_payment_transaction']['bundles'][$method_id] = array(
103
      'label' => $payment_method['title'],
104
    );
105
  }
106

    
107
  return $return;
108
}
109

    
110
/**
111
 * Entity uri callback: gives modules a chance to specify a path for a payment
112
 * transaction.
113
 */
114
function commerce_payment_transaction_uri($transaction) {
115
  // Allow modules to specify a path, returning the first one found.
116
  foreach (module_implements('commerce_payment_transaction_uri') as $module) {
117
    $uri = module_invoke($module, 'commerce_payment_transaction_uri', $transaction);
118

    
119
    // If the implementation returned data, use that now.
120
    if (!empty($uri)) {
121
      return $uri;
122
    }
123
  }
124

    
125
  return NULL;
126
}
127

    
128
/**
129
 * Implements hook_hook_info().
130
 */
131
function commerce_payment_hook_info() {
132
  $hooks = array(
133
    'commerce_payment_totals_row_info' => array(
134
      'group' => 'commerce',
135
    ),
136
    'commerce_payment_totals_row_info_alter' => array(
137
      'group' => 'commerce',
138
    ),
139
    'commerce_payment_method_info' => array(
140
      'group' => 'commerce',
141
    ),
142
    'commerce_payment_method_info_alter' => array(
143
      'group' => 'commerce',
144
    ),
145
    'commerce_payment_methods' => array(
146
      'group' => 'commerce',
147
    ),
148
    'commerce_payment_transaction_status_info' => array(
149
      'group' => 'commerce',
150
    ),
151
    'commerce_payment_transaction_uri' => array(
152
      'group' => 'commerce'
153
    ),
154
    'commerce_transaction_view' => array(
155
      'group' => 'commerce',
156
    ),
157
    'commerce_payment_transaction_access' => array(
158
      'group' => 'commerce',
159
    ),
160
    'commerce_payment_transaction_insert' => array(
161
      'group' => 'commerce',
162
    ),
163
    'commerce_payment_transaction_update' => array(
164
      'group' => 'commerce',
165
    ),
166
    'commerce_payment_transaction_delete' => array(
167
      'group' => 'commerce',
168
    ),
169
    'commerce_payment_order_paid_in_full' => array(
170
      'group' => 'commerce',
171
    ),
172
  );
173

    
174
  return $hooks;
175
}
176

    
177
/**
178
 * Implements hook_permission().
179
 */
180
function commerce_payment_permission() {
181
  return array(
182
    'administer payment methods' => array(
183
      'title' => t('Administer payment methods'),
184
      'description' => t('Allows users to configure enabled payment methods.'),
185
      'restrict access' => TRUE,
186
    ),
187
    'administer payments' => array(
188
      'title' => t('Administer payments'),
189
      'description' => t('Allows users to perform any payment action for any order and view transaction payloads.'),
190
      'restrict access' => TRUE,
191
    ),
192
    'view payments' => array(
193
      'title' => t('View payments'),
194
      'description' => t('Allows users to view the payments made to an order.'),
195
      'restrict access' => TRUE,
196
    ),
197
    'create payments' => array(
198
      'title' => t('Create payments'),
199
      'description' => t('Allows users to create new payments for orders.'),
200
      'restrict access' => TRUE,
201
    ),
202
    'update payments' => array(
203
      'title' => t('Update payments'),
204
      'description' => t('Allows users to update payments via payment method specific operations links.'),
205
      'restrict access' => TRUE,
206
    ),
207
    'delete payments' => array(
208
      'title' => t('Delete payments'),
209
      'description' => t('Allows users to delete payments on orders they can access.'),
210
      'restrict access' => TRUE,
211
    ),
212
  );
213
}
214

    
215
/**
216
 * Implements hook_theme().
217
 */
218
function commerce_payment_theme() {
219
  return array(
220
    'commerce_payment_transaction' => array(
221
      'variables' => array('order' => NULL, 'transaction' => NULL, 'view_mode' => NULL),
222
    ),
223
    'commerce_payment_transaction_status_text' => array(
224
      'variables' => array('text' => NULL, 'transaction_status' => NULL),
225
    ),
226
    'commerce_payment_transaction_status_icon' => array(
227
      'variables' => array('transaction_status' => NULL),
228
    ),
229
    'commerce_payment_totals' => array(
230
      'variables' => array('rows' => array(), 'form' => NULL, 'totals' => array(), 'view' => NULL),
231
      'path' => drupal_get_path('module', 'commerce_payment') . '/theme',
232
      'template' => 'commerce-payment-totals',
233
    ),
234
  );
235
}
236

    
237
/**
238
 * Adds the necessary CSS for the line item summary template.
239
 */
240
function template_preprocess_commerce_payment_totals(&$variables) {
241
  drupal_add_css(drupal_get_path('module', 'commerce_payment') . '/theme/commerce_payment.admin.css');
242
}
243

    
244
/**
245
 * Implements hook_modules_enabled().
246
 */
247
function commerce_payment_modules_enabled($modules) {
248
  commerce_payment_methods_reset();
249
  _commerce_payment_default_rules_reset($modules);
250
}
251

    
252
/**
253
 * Resets default Rules if necessary when modules are enabled or disabled.
254
 *
255
 * @param $modules
256
 *   An array of module names that have been enabled or disabled.
257
 */
258
function _commerce_payment_default_rules_reset($modules) {
259
  $reset_default_rules = FALSE;
260

    
261
  // Look for any module defining a new payment method.
262
  foreach ($modules as $module) {
263
    if (function_exists($module . '_commerce_payment_method_info')) {
264
      $reset_default_rules = TRUE;
265
    }
266
  }
267

    
268
  // If we found a module defining a new payment method, we need to rebuild the
269
  // default Rules especially for this module so the default payment method Rule
270
  // will appear properly for this module.
271
  if ($reset_default_rules) {
272
    entity_defaults_rebuild();
273
    rules_clear_cache(TRUE);
274
  }
275
}
276

    
277
/**
278
 * Implements hook_commerce_checkout_page_info().
279
 */
280
function commerce_payment_commerce_checkout_page_info() {
281
  $checkout_pages = array();
282

    
283
  $checkout_pages['payment'] = array(
284
    'title' => t('Payment'),
285
    'help' => t('Use the button below to proceed to the payment server.'),
286
    'status_cart' => FALSE,
287
    'locked' => TRUE,
288
    'buttons' => FALSE,
289
    'weight' => 20,
290
  );
291

    
292
  return $checkout_pages;
293
}
294

    
295
/**
296
 * Implements hook_commerce_checkout_pane_info().
297
 */
298
function commerce_payment_commerce_checkout_pane_info() {
299
  $checkout_panes = array();
300

    
301
  $checkout_panes['commerce_payment'] = array(
302
    'title' => t('Payment'),
303
    'page' => 'review',
304
    'file' => 'includes/commerce_payment.checkout_pane.inc',
305
    'base' => 'commerce_payment_pane',
306
    'weight' => 10,
307
  );
308

    
309
  $checkout_panes['commerce_payment_redirect'] = array(
310
    'title' => t('Off-site payment redirect'),
311
    'page' => 'payment',
312
    'locked' => TRUE,
313
    'file' => 'includes/commerce_payment.checkout_pane.inc',
314
    'base' => 'commerce_payment_redirect_pane',
315
  );
316

    
317
  return $checkout_panes;
318
}
319

    
320
/**
321
 * Moves an order ahead to the next page via an order update and redirect.
322
 *
323
 * Redirected payment methods are responsible for calling this method when
324
 * receiving external notifications of successful payment.
325
 *
326
 * @param $order
327
 *   An order object.
328
 * @param $log
329
 *   Optional log message to use when updating the order status in conjunction
330
 *   with the redirect to the next checkout page.
331
 */
332
function commerce_payment_redirect_pane_next_page($order, $log = '') {
333
  // Load the order status object for the current order.
334
  $order_status = commerce_order_status_load($order->status);
335

    
336
  if ($order_status['state'] == 'checkout' && $order_status['checkout_page'] == 'payment') {
337
    $payment_page = commerce_checkout_page_load($order_status['checkout_page']);
338
    $next_page = $payment_page['next_page'];
339

    
340
    $order = commerce_order_status_update($order, 'checkout_' . $next_page, FALSE, NULL, $log);
341

    
342
    // Inform modules of checkout completion if the next page is Completed.
343
    if ($next_page == 'complete') {
344
      commerce_checkout_complete($order);
345
    }
346
  }
347
}
348

    
349
/**
350
 * Moves an order back to the previous page via an order update and redirect.
351
 *
352
 * Redirected payment methods are responsible for calling this method when
353
 * receiving external notifications of failed payment.
354
 *
355
 * @param $order
356
 *   An order object.
357
 * @param $log
358
 *   Optional log message to use when updating the order status in conjunction
359
 *   with the redirect to the previous checkout page.
360
 */
361
function commerce_payment_redirect_pane_previous_page($order, $log = '') {
362
  // Load the order status object for the current order.
363
  $order_status = commerce_order_status_load($order->status);
364

    
365
  if ($order_status['state'] == 'checkout' && $order_status['checkout_page'] == 'payment') {
366
    $payment_page = commerce_checkout_page_load($order_status['checkout_page']);
367
    $prev_page = $payment_page['prev_page'];
368

    
369
    $order = commerce_order_status_update($order, 'checkout_' . $prev_page, FALSE, NULL, $log);
370
  }
371
}
372

    
373
/**
374
 * Implements hook_commerce_payment_totals_row_info().
375
 */
376
function commerce_payment_commerce_payment_totals_row_info($totals, $order) {
377
  $rows = array();
378

    
379
  if (count($totals) == 0) {
380
    // Add a row for the remaining balance on the order.
381
    if ($order) {
382
      $balance = commerce_payment_order_balance($order, $totals);
383

    
384
      $rows[] = array(
385
        'data' => array(
386
          array('data' => t('Order balance'), 'class' => array('label')),
387
          array('data' => commerce_currency_format($balance['amount'], $balance['currency_code']), 'class' => array('balance')),
388
        ),
389
        'class' => array('order-balance'),
390
        'weight' => 10,
391
      );
392
    }
393
  }
394
  elseif (count($totals) == 1) {
395
    // Otherwise if there's only a single currency total...
396
    $currency_code = key($totals);
397

    
398
    // Add a row for the total amount paid.
399
    $rows[] = array(
400
      'data' => array(
401
        array('data' => t('Total paid'), 'class' => array('label')),
402
        array('data' => commerce_currency_format($totals[$currency_code], $currency_code), 'class' => array('total')),
403
      ),
404
      'class' => array('total-paid'),
405
      'weight' => 0,
406
    );
407

    
408
    // Add a row for the remaining balance on the order.
409
    if ($order) {
410
      // @todo Fix for when there's a FALSE $balance.
411
      $balance = commerce_payment_order_balance($order, $totals);
412

    
413
      $rows[] = array(
414
        'data' => array(
415
          array('data' => t('Order balance'), 'class' => array('label')),
416
          array('data' => commerce_currency_format($balance['amount'], $balance['currency_code']), 'class' => array('balance')),
417
        ),
418
        'class' => array('order-balance'),
419
        'weight' => 10,
420
      );
421
    }
422
  }
423
  else {
424
    $weight = 0;
425

    
426
    foreach ($totals as $currency_code => $amount) {
427
      $rows[] = array(
428
        'data' => array(
429
          array('data' => t('Total paid (@currency_code)', array('@currency_code' => $currency_code)), 'class' => array('label')),
430
          array('data' => commerce_currency_format($amount, $currency_code), 'class' => array('total')),
431
        ),
432
        'class' => array('total-paid', 'total-' . $currency_code),
433
        'weight' => $weight++,
434
      );
435
    }
436
  }
437

    
438
  return $rows;
439
}
440

    
441
/**
442
 * Implements hook_commerce_payment_transaction_insert().
443
 *
444
 * When a new payment transaction is inserted that is already completed, check
445
 * the order balance and invoke a Rules event if the order is paid in full.
446
 */
447
function commerce_payment_commerce_payment_transaction_insert($transaction) {
448
  // If the inserted transaction was marked as a success and placed against a
449
  // valid order...
450
  if ($transaction->status == COMMERCE_PAYMENT_STATUS_SUCCESS &&
451
    $order = commerce_order_load($transaction->order_id)) {
452
    // Then check to make sure the event hasn't been invoked for this order.
453
    if (empty($order->data['commerce_payment_order_paid_in_full_invoked'])) {
454
      // Check the order balance and invoke the event.
455
      $balance = commerce_payment_order_balance($order);
456

    
457
      if ($balance['amount'] <= 0) {
458
        // Invoke the event including a hook of the same name.
459
        rules_invoke_all('commerce_payment_order_paid_in_full', $order, $transaction);
460

    
461
        // Update the order's data array to indicate this just happened.
462
        $order->data['commerce_payment_order_paid_in_full_invoked'] = TRUE;
463

    
464
        // Save the updated order.
465
        commerce_order_save($order);
466
      }
467
    }
468
  }
469
}
470

    
471
/**
472
 * Implements hook_commerce_payment_transaction_update().
473
 *
474
 * When an existing transaction is updated from an incomplete status to
475
 * completed, check the order balance and invoke a Rules event if the order is
476
 * paid in full.
477
 */
478
function commerce_payment_commerce_payment_transaction_update($transaction) {
479
  commerce_payment_commerce_payment_transaction_insert($transaction);
480
}
481

    
482
/**
483
 * Implements hook_views_api().
484
 */
485
function commerce_payment_views_api() {
486
  return array(
487
    'api' => 3,
488
    'path' => drupal_get_path('module', 'commerce_payment') . '/includes/views',
489
  );
490
}
491

    
492
/**
493
 * Returns an array of payment methods defined by enabled modules.
494
 *
495
 * @return
496
 *   An associative array of payment method objects keyed by the method_id.
497
 */
498
function commerce_payment_methods() {
499
  $payment_methods = &drupal_static(__FUNCTION__);
500

    
501
  // If the payment methods haven't been defined yet, do so now.
502
  if (!isset($payment_methods)) {
503
    $payment_methods = array();
504

    
505
    // Build the payment methods array, including module names for the purpose
506
    // of including files if necessary.
507
    foreach (module_implements('commerce_payment_method_info') as $module) {
508
      foreach (module_invoke($module, 'commerce_payment_method_info') as $method_id => $payment_method) {
509
        $payment_method['method_id'] = $method_id;
510
        $payment_method['module'] = $module;
511
        $payment_methods[$method_id] = $payment_method;
512
      }
513
    }
514

    
515
    drupal_alter('commerce_payment_method_info', $payment_methods);
516

    
517
    foreach ($payment_methods as $method_id => &$payment_method) {
518
      $defaults = array(
519
        'method_id' => $method_id,
520
        'base' => $method_id,
521
        'title' => '',
522
        'description' => '',
523
        'active' => FALSE,
524
        'checkout' => TRUE,
525
        'terminal' => TRUE,
526
        'offsite' => FALSE,
527
        'offsite_autoredirect' => FALSE,
528
        'callbacks' => array(),
529
        'file' => '',
530
      );
531

    
532
      $payment_method += $defaults;
533

    
534
      // Default the display title to the title if necessary.  The display title
535
      // is used in instances where the payment method has an official name used
536
      // as the title (i.e. PayPal WPS) but a different method of display on
537
      // selection forms (like some text and a set of images).
538
      if (empty($payment_method['display_title'])) {
539
        $payment_method['display_title'] = $payment_method['title'];
540
      }
541

    
542
      // Default the short title to the title if necessary.  Like the display
543
      // title, the short title is an alternate way of displaying the title to
544
      // the user consisting of plain text but with unnecessary information
545
      // stripped off.  The payment method title might be PayPal WPS as it
546
      // distinguishes itself from other PayPal payment services, but you would
547
      // only want to display PayPal to the customer as their means of payment.
548
      if (empty($payment_methods[$method_id]['short_title'])) {
549
        $payment_method['short_title'] = $payment_method['title'];
550
      }
551

    
552
      // Merge in default callbacks.
553
      foreach (array('settings_form', 'submit_form', 'submit_form_validate', 'submit_form_submit', 'redirect_form', 'redirect_form_back', 'redirect_form_validate', 'redirect_form_submit') as $callback) {
554
        if (!isset($payment_method['callbacks'][$callback])) {
555
          $payment_method['callbacks'][$callback] = $payment_method['base'] . '_' . $callback;
556
        }
557
      }
558
    }
559
  }
560

    
561
  return $payment_methods;
562
}
563

    
564
/**
565
 * Resets the cached list of payment methods.
566
 */
567
function commerce_payment_methods_reset() {
568
  $payment_methods = &drupal_static('commerce_payment_methods');
569
  $payment_methods = NULL;
570
  entity_info_cache_clear();
571
}
572

    
573
/**
574
 * Returns a payment method array.
575
 *
576
 * @param $method_id
577
 *   The ID of the payment method to return.
578
 *
579
 * @return
580
 *   The fully loaded payment method object or FALSE if not found.
581
 */
582
function commerce_payment_method_load($method_id) {
583
  $payment_methods = commerce_payment_methods();
584
  return isset($payment_methods[$method_id]) ? $payment_methods[$method_id] : FALSE;
585
}
586

    
587
/**
588
 * Returns the title of any or all payment methods.
589
 *
590
 * @param $title
591
 *   String indicating which title to return, either 'title', 'display_title',
592
 *     or 'short_title'.
593
 * @param $method
594
 *   Optional parameter specifying the payment method whose title to return.
595
 *
596
 * @return
597
 *   Either an array of all payment method titles keyed by the machine name or a
598
 *   string containing the title for the specified type. If a type is specified
599
 *   that does not exist, this function returns FALSE.
600
 */
601
function commerce_payment_method_get_title($title = 'title', $method = NULL) {
602
  $payment_methods = commerce_payment_methods();
603

    
604
  // Return a method title if specified and it exists.
605
  if (!empty($method)) {
606
    if (isset($payment_methods[$method])) {
607
      return $payment_methods[$method][$title];
608
    }
609
    else {
610
      // Return FALSE if it does not exist.
611
      return FALSE;
612
    }
613
  }
614

    
615
  // Otherwise turn the array values into the specified title only.
616
  foreach ($payment_methods as $key => $value) {
617
    $payment_methods[$key] = $value[$title];
618
  }
619

    
620
  return $payment_methods;
621
}
622

    
623
/**
624
 * Wraps commerce_payment_method_get_title() for the Entity module.
625
 */
626
function commerce_payment_method_options_list() {
627
  return commerce_payment_method_get_title();
628
}
629

    
630
/**
631
 * Returns the specified callback for the given payment method if it exists.
632
 *
633
 * @param $payment_method
634
 *   The payment method object.
635
 * @param $callback
636
 *   The callback function to return, one of:
637
 *   - settings_form
638
 *   - submit_form
639
 *   - submit_form_validate
640
 *   - submit_form_submit
641
 *   - redirect_form
642
 *   - redirect_form_back
643
 *   - redirect_form_validate
644
 *   - redirect_form_submit
645
 *
646
 * @return
647
 *   A string containing the name of the callback function or FALSE if it could
648
 *     not be found.
649
 */
650
function commerce_payment_method_callback($payment_method, $callback) {
651
  // Include the payment method file if specified.
652
  if (!empty($payment_method['file'])) {
653
    $parts = explode('.', $payment_method['file']);
654
    module_load_include(array_pop($parts), $payment_method['module'], implode('.', $parts));
655
  }
656

    
657
  // If the specified callback function exists, return it.
658
  if (!empty($payment_method['callbacks'][$callback]) &&
659
      function_exists($payment_method['callbacks'][$callback])) {
660
    return $payment_method['callbacks'][$callback];
661
  }
662

    
663
  // Otherwise return FALSE.
664
  return FALSE;
665
}
666

    
667
/**
668
 * Returns a payment method instance ID given a payment method ID and the Rule
669
 *   containing an enabling action with settings.
670
 *
671
 * @param $method_id
672
 *   The ID of the payment method.
673
 * @param $rule
674
 *   The Rules configuration object used to provide settings for the method.
675
 *
676
 * @return
677
 *   A string used as the payment method instance ID.
678
 */
679
function commerce_payment_method_instance_id($method_id, $rule) {
680
  $parts = array($method_id, $rule->name);
681
  return implode('|', $parts);
682
}
683

    
684
/**
685
 * Returns a payment method instance array which includes the settings specific
686
 * to the context of the instance.
687
 *
688
 * @param $instance_id
689
 *   A payment method instance ID in the form of a pipe delimited string
690
 *     containing the method_id and the enabling Rule's name.
691
 *
692
 * @return
693
 *   The payment method instance object which is identical to the payment method
694
 *     object with the addition of the settings array.
695
 */
696
function commerce_payment_method_instance_load($instance_id) {
697
  // Return FALSE if there is no pipe delimeter in the instance ID.
698
  if (strpos($instance_id, '|') === FALSE) {
699
    return FALSE;
700
  }
701

    
702
  // Explode the method key into its component parts.
703
  list($method_id, $rule_name) = explode('|', $instance_id);
704

    
705
  // Return FALSE if we didn't receive a proper instance ID.
706
  if (empty($method_id) || empty($rule_name)) {
707
    return FALSE;
708
  }
709

    
710
  // First load the payment method and add the instance ID.
711
  $payment_method = commerce_payment_method_load($method_id);
712
  $payment_method['instance_id'] = $instance_id;
713

    
714
  // Then load the Rule configuration that enables the method.
715
  $rule = rules_config_load($rule_name);
716

    
717
  // Return FALSE if it couldn't be loaded.
718
  if (empty($rule)) {
719
    return FALSE;
720
  }
721

    
722
  // Iterate over its actions to find one with the matching element ID and pull
723
  // its settings into the payment method object.
724
  $payment_method['settings'] = array();
725

    
726
  foreach ($rule->actions() as $action) {
727
    if (is_callable(array($action, 'getElementName')) && $action->getElementName() == 'commerce_payment_enable_' . $method_id) {
728
      if (is_array($action->settings['payment_method']) && !empty($action->settings['payment_method']['settings'])) {
729
        $payment_method['settings'] = $action->settings['payment_method']['settings'];
730
      }
731
    }
732
  }
733

    
734
  return $payment_method;
735
}
736

    
737
/**
738
 * Returns an array of transaction payment status objects for the defined
739
 *   payment statuses.
740
 *
741
 * This function invokes hook_commerce_payment_transaction_status_info() so
742
 * other payment modules can define statuses if necessary. However, it doesn't
743
 * allow for altering so that existing payment methods cannot be unset. It still
744
 * does perform an array merge in such a way that the properties for the three
745
 * core statuses defined by this module may be overridden if the same keys are
746
 * used in another module's implementation of the info hook.
747
 */
748
function commerce_payment_transaction_statuses() {
749
  $transaction_statuses = &drupal_static(__FUNCTION__);
750

    
751
  // If the statuses haven't been defined yet, do so now.
752
  if (!isset($transaction_statuses)) {
753
    $transaction_statuses = module_invoke_all('commerce_payment_transaction_status_info');
754

    
755
    $transaction_statuses += array(
756
      COMMERCE_PAYMENT_STATUS_PENDING => array(
757
        'status' => COMMERCE_PAYMENT_STATUS_PENDING,
758
        'title' => t('Pending'),
759
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-pending.png',
760
        'total' => FALSE,
761
      ),
762
      COMMERCE_PAYMENT_STATUS_SUCCESS => array(
763
        'status' => COMMERCE_PAYMENT_STATUS_SUCCESS,
764
        'title' => t('Success'),
765
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-success.png',
766
        'total' => TRUE,
767
      ),
768
      COMMERCE_PAYMENT_STATUS_FAILURE => array(
769
        'status' => COMMERCE_PAYMENT_STATUS_FAILURE,
770
        'title' => t('Failure'),
771
        'icon' => drupal_get_path('module', 'commerce_payment') . '/theme/icon-failure.png',
772
        'total' => FALSE,
773
      ),
774
    );
775
  }
776

    
777
  return $transaction_statuses;
778
}
779

    
780
/**
781
 * Returns an options list of payment transaction statuses.
782
 */
783
function commerce_payment_transaction_status_options_list() {
784
  // Build an array of payment transaction status options.
785
  $options = array();
786

    
787
  foreach(commerce_payment_transaction_statuses() as $payment_transaction_status) {
788
    $options[$payment_transaction_status['status']] = $payment_transaction_status['title'];
789
  }
790

    
791
  return $options;
792
}
793

    
794
/**
795
 * Themes the icon representing a payment transaction status.
796
 */
797
function theme_commerce_payment_transaction_status_icon($variables) {
798
  $transaction_status = $variables['transaction_status'];
799
  return theme('image', array('path' => $transaction_status['icon'], 'alt' => $transaction_status['title'], 'title' => $transaction_status['title'], 'attributes' => array('class' => drupal_html_class($transaction_status['status']))));
800
}
801

    
802
/**
803
 * Themes a text value related to a payment transaction status.
804
 */
805
function theme_commerce_payment_transaction_status_text($variables) {
806
  $transaction_status = $variables['transaction_status'];
807

    
808
  return '<span class="' . drupal_html_class($transaction_status['status']) . '">' . $variables['text'] . '</span>';
809
}
810

    
811
/**
812
 * Returns the payment transaction status object for the specified status.
813
 *
814
 * @param $status
815
 *   The transaction status string.
816
 *
817
 * @return
818
 *   An object containing the transaction status information or FALSE if the
819
 *     requested status is not found.
820
 */
821
function commerce_payment_transaction_status_load($status) {
822
  $transaction_statuses = commerce_payment_transaction_statuses();
823
  return !empty($transaction_statuses[$status]) ? $transaction_statuses[$status] : FALSE;
824
}
825

    
826
/**
827
 * Returns an initialized payment transaction object.
828
 *
829
 * @param $method_id
830
 *   The method_id of the payment method for the transaction.
831
 *
832
 * @return
833
 *   A transaction object with all default fields initialized.
834
 */
835
function commerce_payment_transaction_new($method_id = '', $order_id = 0) {
836
  return entity_get_controller('commerce_payment_transaction')->create(array(
837
    'payment_method' => $method_id,
838
    'order_id' => $order_id,
839
  ));
840
}
841

    
842
/**
843
 * Saves a payment transaction.
844
 *
845
 * @param $transaction
846
 *   The full transaction object to save.
847
 *
848
 * @return
849
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
850
 */
851
function commerce_payment_transaction_save($transaction) {
852
  return entity_get_controller('commerce_payment_transaction')->save($transaction);
853
}
854

    
855
/**
856
 * Loads a payment transaction by ID.
857
 */
858
function commerce_payment_transaction_load($transaction_id) {
859
  $transactions = commerce_payment_transaction_load_multiple(array($transaction_id), array());
860
  return $transactions ? reset($transactions) : FALSE;
861
}
862

    
863
/**
864
 * Loads multiple payment transaction by ID or based on a set of matching conditions.
865
 *
866
 * @see entity_load()
867
 *
868
 * @param $transaction_ids
869
 *   An array of transaction IDs.
870
 * @param $conditions
871
 *   An array of conditions on the {commerce_payment_transaction} table in the
872
 *     form 'field' => $value.
873
 * @param $reset
874
 *   Whether to reset the internal transaction loading cache.
875
 *
876
 * @return
877
 *   An array of transaction objects indexed by transaction_id.
878
 */
879
function commerce_payment_transaction_load_multiple($transaction_ids = array(), $conditions = array(), $reset = FALSE) {
880
  return entity_load('commerce_payment_transaction', $transaction_ids, $conditions, $reset);
881
}
882

    
883
/**
884
 * Deletes a payment transaction by ID.
885
 *
886
 * @param $transaction_id
887
 *   The ID of the transaction to delete.
888
 *
889
 * @return
890
 *   TRUE on success, FALSE otherwise.
891
 */
892
function commerce_payment_transaction_delete($transaction_id) {
893
  return commerce_payment_transaction_delete_multiple(array($transaction_id));
894
}
895

    
896
/**
897
 * Deletes multiple payment transactions by ID.
898
 *
899
 * @param $transaction_ids
900
 *   An array of transaction IDs to delete.
901
 *
902
 * @return
903
 *   TRUE on success, FALSE otherwise.
904
 */
905
function commerce_payment_transaction_delete_multiple($transaction_ids) {
906
  return entity_get_controller('commerce_payment_transaction')->delete($transaction_ids);
907
}
908

    
909
/**
910
 * Determines access for a variety of operations on payment transactions.
911
 *
912
 * @param $op
913
 *   The operation being performed, one of view, update, create, or delete.
914
 * @param $transaction
915
 *   The payment transaction to check.
916
 * @param $account
917
 *   The user account attempting the operation; defaults to the current user.
918
 *
919
 * @return
920
 *   TRUE or FALSE indicating access for the operation.
921
 */
922
function commerce_payment_transaction_access($op, $transaction, $account = NULL) {
923
  if (isset($transaction->order_id)) {
924
    $order = commerce_order_load($transaction->order_id);
925
    if (!$order) {
926
      return FALSE;
927
    }
928
  }
929
  else {
930
    $order = NULL;
931
  }
932

    
933
  return commerce_payment_transaction_order_access($op, $order, $account);
934
}
935

    
936
/**
937
 * Determines access for a variety of operations for payment transactions on a given order.
938
 *
939
 * @param $op
940
 *   The payment transaction operation being performed, one of view, update, create, or delete.
941
 * @param $order
942
 *   The order to check against (optional if $op == 'create').
943
 * @param $account
944
 *   The user account attempting the operation; defaults to the current user.
945
 *
946
 * @return
947
 *   TRUE or FALSE indicating access for the operation.
948
 */
949
function commerce_payment_transaction_order_access($op, $order, $account = NULL) {
950
  global $user;
951

    
952
  if (empty($account)) {
953
    $account = clone($user);
954
  }
955

    
956
  // Grant administrators access to do anything.
957
  if (user_access('administer payments', $account)) {
958
    return TRUE;
959
  }
960

    
961
  switch ($op) {
962
    // Creating new payment transactions.
963
    case 'create':
964
      if (user_access('create payments', $account)) {
965
        // We currently allow any user to create any payment transaction,
966
        // regardless of the order, because entity_access() doesn't give us a
967
        // way to discriminate on the order.
968
        // @todo: find a way to prevent creating a payment transaction if the
969
        // user doesn't have access to the order.
970
        if (!isset($order) || commerce_order_access('update', $order, $account)) {
971
          return TRUE;
972
        }
973
      }
974
      break;
975

    
976
    // Viewing payment transactions.
977
    case 'view':
978
      if (user_access('view payments', $account)) {
979
        if (commerce_order_access('view', $order, $account)) {
980
          return TRUE;
981
        }
982
      }
983
      break;
984

    
985
    case 'update':
986
      if (user_access('update payments', $account)) {
987
        if (commerce_order_access('view', $order, $account)) {
988
          return TRUE;
989
        }
990
      }
991
      break;
992

    
993
    case 'delete':
994
      if (user_access('delete payments', $account)) {
995
        if (commerce_order_access('update', $order, $account)) {
996
          return TRUE;
997
        }
998
      }
999
      break;
1000
  }
1001

    
1002
  return FALSE;
1003
}
1004

    
1005
/**
1006
 * Implements hook_query_TAG_alter().
1007
 *
1008
 * Implement access control on payment transaction. This is different from other
1009
 * entities because the access to a payment transaction is partially delegated
1010
 * to its order.
1011
 */
1012
function commerce_payment_query_commerce_payment_transaction_access_alter(QueryAlterableInterface $query) {
1013
  // Read the meta-data from the query.
1014
  if (!$account = $query->getMetaData('account')) {
1015
    global $user;
1016
    $account = $user;
1017
  }
1018

    
1019
  // If the user has the administration permission, nothing to do.
1020
  if (user_access('administer payments', $account)) {
1021
    return;
1022
  }
1023

    
1024
  // Join the payment transaction to their orders.
1025
  if (user_access('view payments', $account)) {
1026
    $tables = &$query->getTables();
1027

    
1028
    // Look for an existing commerce_order table.
1029
    foreach ($tables as $table) {
1030
      if ($table['table'] === 'commerce_order') {
1031
        $order_alias = $table['alias'];
1032
        break;
1033
      }
1034
    }
1035

    
1036
    // If not found, attempt a join against the first table.
1037
    if (!isset($order_alias)) {
1038
      reset($tables);
1039
      $base_table = key($tables);
1040
      $order_alias = $query->innerJoin('commerce_order', 'co', '%alias.order_id = ' . $base_table . '.order_id');
1041
    }
1042

    
1043
    // Perform the access control on the order.
1044
    commerce_entity_access_query_alter($query, 'commerce_order', $order_alias);
1045
  }
1046
  else {
1047
    // The user has access to no payment transaction.
1048
    $query->where('1 = 0');
1049
  }
1050
}
1051

    
1052
/**
1053
 * Calculates the balance of an order by subtracting the total of all successful
1054
 *   transactions from the total of all the line items on the order.
1055
 *
1056
 * @param $order
1057
 *   The fully loaded order object whose balance should be calculated.
1058
 * @param $totals
1059
 *   Optionally submit an array of transaction totals keyed by currency code
1060
 *     with the amount as the value.
1061
 *
1062
 * @return
1063
 *   An array containing the amount and currency code representing the balance
1064
 *     of the order or FALSE if it is impossible to calculate.
1065
 */
1066
function commerce_payment_order_balance($order, $totals = array()) {
1067
  $wrapper = entity_metadata_wrapper('commerce_order', $order);
1068
  $order_total = $wrapper->commerce_order_total->value();
1069

    
1070
  // Calculate the transaction totals if not supplied.
1071
  if (empty($totals)) {
1072
    $transaction_statuses = commerce_payment_transaction_statuses();
1073

    
1074
    foreach (commerce_payment_transaction_load_multiple(array(), array('order_id' => $order->order_id)) as $transaction) {
1075
      // If the payment transaction status indicates it should include the
1076
      // current transaction in the total...
1077
      if ($transaction_statuses[$transaction->status]['total']) {
1078
        // Add the transaction to its currency's running total if it exists...
1079
        if (isset($totals[$transaction->currency_code])) {
1080
          $totals[$transaction->currency_code] += $transaction->amount;
1081
        }
1082
        else {
1083
          // Or begin a new running total for the currency.
1084
          $totals[$transaction->currency_code] = $transaction->amount;
1085
        }
1086
      }
1087
    }
1088
  }
1089

    
1090
  // Only return a balance if the totals array contains a single matching currency.
1091
  if (count($totals) == 1 && isset($totals[$order_total['currency_code']])) {
1092
    return array('amount' => $order_total['amount'] - $totals[$order_total['currency_code']], 'currency_code' => $order_total['currency_code']);
1093
  }
1094
  elseif (empty($totals)) {
1095
    return array('amount' => $order_total['amount'], 'currency_code' => $order_total['currency_code']);
1096
  }
1097
  else {
1098
    return FALSE;
1099
  }
1100
}
1101

    
1102
/**
1103
 * Returns a sorted array of payment totals table rows.
1104
 *
1105
 * @param $totals
1106
 *   An array of payment totals whose keys are currency codes and values are the
1107
 *     total amount paid in each currency.
1108
 * @param $order
1109
 *   If available, the order object to which the payments apply.
1110
 *
1111
 * @return
1112
 *   An array of table row data as expected by theme_table().
1113
 *
1114
 * @see hook_commerce_payment_totals_row_info()
1115
 */
1116
function commerce_payment_totals_rows($totals, $order) {
1117
  // Retrieve rows defined by the hook and allow other modules to alter them.
1118
  $rows = module_invoke_all('commerce_payment_totals_row_info', $totals, $order);
1119
  drupal_alter('commerce_payment_totals_row_info', $rows, $totals, $order);
1120

    
1121
  // Sort the rows by weight and return the array.
1122
  uasort($rows, 'drupal_sort_weight');
1123

    
1124
  return $rows;
1125
}
1126

    
1127
/**
1128
 * Callback for getting payment transaction properties.
1129
 *
1130
 * @see commerce_payment_entity_property_info()
1131
 */
1132
function commerce_payment_transaction_get_properties($transaction, array $options, $name) {
1133
  switch ($name) {
1134
    case 'user':
1135
      return $transaction->uid;
1136
    case 'order':
1137
      return !empty($transaction->order_id) ? $transaction->order_id : commerce_order_new();
1138
    case 'message':
1139
      if ($transaction->message) {
1140
        return t($transaction->message, is_array($transaction->message_variables) ? $transaction->message_variables : array());
1141
      }
1142
      else {
1143
        return '';
1144
      }
1145
  }
1146
}
1147

    
1148
/**
1149
 * Callback for setting payment transaction properties.
1150
 *
1151
 * @see commerce_payment_entity_property_info()
1152
 */
1153
function commerce_payment_transaction_set_properties($transaction, $name, $value) {
1154
  switch ($name) {
1155
    case 'user':
1156
      $transaction->uid = $value;
1157
      break;
1158
    case 'order':
1159
      $transaction->order_id = $value;
1160
      break;
1161
  }
1162
}