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 (!empty($balance) && $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
|
}
|