/[drupal]/contributions/modules/uc_protx_vsp_direct/uc_protx_vsp_direct.module
ViewVC logotype

Contents of /contributions/modules/uc_protx_vsp_direct/uc_protx_vsp_direct.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.2 - (show annotations) (download) (as text)
Mon May 11 14:56:58 2009 UTC (6 months, 2 weeks ago) by longwave
Branch: MAIN
CVS Tags: HEAD
Branch point for: DRUPAL-6--1
Changes since 1.1: +563 -276 lines
File MIME type: text/x-php
Copy latest DRUPAL-5 to HEAD.
1 <?php
2 // $Id: uc_protx_vsp_direct.module,v 1.1.2.10 2009/05/11 14:05:55 longwave Exp $
3
4 /**
5 * @file
6 * Protx VSP Direct payment gateway module for Ubercart.
7 *
8 * Developed by solarian (http://drupal.org/user/166738).
9 * Incorporating suggestions from hanoii (http://drupal.org/user/23157).
10 * Implementing v2.23 of the VSP Direct Protocol.
11 * http://www.sagepay.com/documents/sagepayDirectProtocolandIntegrationGuideline.pdf
12 *
13 * Note: Originally the Protx interface was called the "Verified Payment System"
14 * and was referred to by the acronym "VPS", but now it's called "Veri-Secure Payment"
15 * and therefore has a different acronym, "VSP".
16 * Data fields and URLs, however, still partly use the "VPS" acronym.
17 *
18 * This software is licenced under the GPLv2.
19 *
20 */
21
22 define('UC_PROTX_VSP_DIRECT_PROTOCOL_VERSION', '2.23');
23
24 function _uc_protx_vsp_direct_comments_to_protx($order_id) {
25 $comments = uc_order_comments_load($order_id, TRUE);
26
27 if (is_array($comments)) {
28 foreach ( $comments as $comment ) {
29 $subpatterns = array();
30 preg_match('/VendorTxCode: (.*)/', $comment->message, $subpatterns);
31 if ( $subpatterns[1] ) {
32 $protx['vendortxcode'] = $subpatterns[1];
33 }
34 $subpatterns = array();
35 preg_match('/VPSTxId: (.*)/', $comment->message, $subpatterns);
36 if ( $subpatterns[1] ) {
37 $protx['vpstxid'] = $subpatterns[1];
38 }
39
40 $subpatterns = array();
41 preg_match('/SecurityKey: (.*)/', $comment->message, $subpatterns);
42 if ( $subpatterns[1] ) {
43 $protx['securitykey'] = $subpatterns[1];
44 }
45
46 $subpatterns = array();
47 $str = t('Transaction authenticated');
48 preg_match("/$str/", $comment->message, $subpatterns);
49 if ( $subpatterns[0] ) {
50 $protx['authenticated'] = TRUE;
51 }
52
53 $subpatterns = array();
54 $str = t('Transaction authorized');
55 preg_match("/$str/", $comment->message, $subpatterns);
56 if ( $subpatterns[0] ) {
57 $protx['authorized'] = TRUE;
58 }
59 }
60 }
61
62 //print_r($protx);
63 return $protx;
64 }
65
66 /*******************************************************************************
67 * Hook Functions (Drupal)
68 ******************************************************************************/
69
70 /**
71 * Implementation of hook_menu().
72 */
73 function uc_protx_vsp_direct_menu($may_cache) {
74 $items = array();
75
76 // add css
77 if (!$may_cache) {
78 drupal_add_css(drupal_get_path('module', 'uc_protx_vsp_direct') .'/uc_protx_vsp_direct.css', 'module', 'all', FALSE);
79 }
80
81 if ($may_cache) {
82 $items[] = array(
83 'path' => 'uc_protx_vsp_direct/3DSecure',
84 'callback' => 'uc_protx_vsp_direct_3DSecure',
85 'access' => TRUE,
86 'type' => MENU_CALLBACK,
87 );
88 $items[] = array(
89 'path' => 'uc_protx_vsp_direct/3DSecure_callback',
90 'callback' => 'uc_protx_vsp_direct_3DSecure_callback',
91 'access' => TRUE,
92 'type' => MENU_CALLBACK,
93 );
94 $items[] = array(
95 'path' => 'uc_protx_vsp_direct/3DSecure_waitingPage',
96 'callback' => 'uc_protx_vsp_direct_3DSecure_waitingPage',
97 'access' => TRUE,
98 'type' => MENU_CALLBACK,
99 );
100 $items[] = array(
101 'path' => 'uc_protx_vsp_direct/3DSecure_complete',
102 'callback' => 'uc_protx_vsp_direct_3DSecure_complete',
103 'access' => TRUE,
104 'type' => MENU_CALLBACK,
105 );
106 }
107 else {
108 if (is_numeric(arg(3))) {
109 $items[] = array(
110 'path' => 'admin/store/orders/'. arg(3) .'/uc_protx_vsp_direct_auth',
111 'title' => t('Protx authorization terminal: Order @order_id', array('@order_id' => arg(3))),
112 'description' => t('Authorize apayment for order @order_id.', array('@order_id' => arg(3))),
113 'callback' => 'uc_protx_vsp_direct_auth_page',
114 'callback arguments' => array(arg(3)),
115 'access' => user_access('authorize credit cards'),
116 'type' => MENU_CALLBACK
117 );
118 }
119 }
120
121 return $items;
122 }
123
124
125 /**
126 * Implementation of hook_form_alter().
127 */
128 function uc_protx_vsp_direct_form_alter($form_id, &$form) { // This just adds the Protx logo.
129 if ($form_id == 'uc_payment_gateways_form' && isset($form['uc_pg_protx_vsp_direct']['uc_pg_protx_vsp_direct_enabled']['#title'])) {
130 $form['uc_pg_protx_vsp_direct']['uc_pg_protx_vsp_direct_enabled']['#title'] .= '<img src="'. base_path() . drupal_get_path('module', 'uc_protx_vsp_direct') .'/img/protxtrans150_75.gif" alt="" id="uc_protx_vsp_direct_protx" />';
131 }
132 }
133
134 /*******************************************************************************
135 * Hook Functions (Ubercart)
136 ******************************************************************************/
137
138 function uc_protx_vsp_direct_payment_gateway() {
139
140 $gateways[] = array(
141 'id' => 'protx_vsp_direct',
142 'title' => t('Protx VSP Direct'),
143 'description' => t('Process credit card payments using Protx VSP Direct.'),
144 'settings' => 'uc_protx_vsp_direct_settings_form',
145 'credit' => 'uc_protx_vsp_direct_charge',
146 );
147
148 return $gateways;
149 }
150
151 /**
152 * Implementation of hook_store_status().
153 */
154 function uc_protx_vsp_direct_store_status() {
155 // Throw up an error row if encryption has not been setup yet.
156 $vendor = variable_get('uc_protx_vsp_direct_vendor', '');
157 if ($vendor) {
158 $statuses[] = array(
159 'status' => 'ok',
160 'title' => t('Protx Vendor mame'),
161 'desc' => t('You have properly set a vendor name for protx: %vendor.', array('%vendor' => $vendor)),
162 );
163 }
164 else {
165 $statuses[] = array(
166 'status' => 'error',
167 'title' => t('Protx Vendor mame'),
168 'desc' => t('Protx VSP Direct module has not been configured yet. Please configure its settings from the !settings, under the Protx VPS Direct collapsed box.', array('!settings' => l(t('Payment gateways section of Ubercart'), 'admin/store/settings/payment/edit/gateways'))),
169 );
170 }
171
172 $server = variable_get('uc_protx_vsp_direct_server', 2);
173 if ($server < 2) {
174 $statuses[] = array(
175 'status' => 'warning',
176 'title' => t('Protx Server'),
177 'desc' => t('Protx VSP Direct is not configured to use the Live server. No real transaction will be processed. You can change it in the !settings, under the Protx VPS Direct collapsed box. (Currently set to %server)', array('!settings' => l(t('Payment gateways section of Ubercart'), 'admin/store/settings/payment/edit/gateways'), '%server' => $server == 1 ? t('Test server') : t('VSP Simulator'))),
178 );
179 }
180 else {
181 $statuses[] = array(
182 'status' => 'ok',
183 'title' => t('Protx Server'),
184 'desc' => t('Protx VSP Direct is configured to use the Live server. Transactions will be processed normally.'),
185 );
186 }
187
188 return $statuses;
189 }
190
191 /*******************************************************************************
192 * Callback Functions, Forms, and Tables
193 ******************************************************************************/
194
195 /**
196 * Callback for payment gateway settings.
197 */
198 function uc_protx_vsp_direct_settings_form() {
199
200 if (!extension_loaded('curl')) {
201 drupal_set_message(t('The Protx VSP Direct payment gateway requires the cURL PHP extension to be loaded.'), 'error');
202 }
203
204 if (variable_get('uc_credit_validate_numbers', FALSE)) {
205 drupal_set_message(t('Message from Protx_VSP_Direct module: Credit card number validation is broken in Ubercart 1.0. You are advised to turn it off in "Payment Methods->Credit card settings"'), 'error');
206 }
207
208 $form['uc_protx_vsp_direct_vendor'] = array(
209 '#type' => 'textfield',
210 '#title' => t('Vendor Login Name'),
211 '#default_value' => variable_get('uc_protx_vsp_direct_vendor', ''),
212 '#description' => t(''),
213 '#size' => 15,
214 '#maxlength' => 15,
215 );
216
217 $form['uc_protx_vsp_direct_server'] = array(
218 '#type' => 'radios',
219 '#title' => t('Protx Server'),
220 '#options' => array(t('VSP Simulator'), t('Test Server'), t('Live System')),
221 '#default_value' => variable_get('uc_protx_vsp_direct_server', 2),
222 '#description' => t(''),
223 );
224
225 $options = array();
226 $options['PAYMENT'] = t('PAYMENT');
227 $options['AUTHENTICATE'] = t('AUTHENTICATE');
228 $form['uc_protx_vsp_direct_transaction'] = array(
229 '#type' => 'radios',
230 '#title' => t('Transaction type'),
231 '#options' => $options,
232 '#default_value' => variable_get('uc_protx_vsp_direct_transaction', 'PAYMENT'),
233 '#description' => t('Select what type of transaction type should be used when sending the payment to the protx server. So far just PAYMENT and AUTHENTICATE are supported and DEFERRED is currently missing. In case of choosing AUTHENTICATE, authorization of the card should be done manually using the protx server.'),
234 );
235
236 $form['uc_protx_vsp_direct_iframe'] = array(
237 '#type' => 'checkbox',
238 '#title' => t('Use inline frames for 3D-Secure'),
239 '#default_value' => variable_get('uc_protx_vsp_direct_iframe', 1),
240 '#description' => t("<strong>For 3D-Secure transactions only.</strong> Using an inline frame allows the transaction to appear as if it's all taking place at this store, but it's not W3C standards compliant if you're using the normal (for Drupal) XHTML Strict Document Type Declaration. However, this is not really a problem in practice, or you can change your theme's DTD (e.g., to a Frameset DTD) if you prefer. If this setting is turned off, it will be obvious to the user that he is being redirected to a different server to complete the transaction, rather like Protx VSP Form."),
241 );
242
243 $form['uc_protx_vsp_direct_send_extra'] = array(
244 '#type' => 'checkbox',
245 '#title' => t('Send additional transaction data'),
246 '#default_value' => variable_get('uc_protx_vsp_direct_send_extra', 0),
247 '#description' => t('Protx offer to store extra info such as the customer&rsquo;s personal info and delivery address, the contents of their order and their IP address. Some of this information is used for fraud screening purposes.'),
248 );
249
250 $form['uc_protx_vsp_direct_info_cards'] = array(
251 '#type' => 'markup',
252 '#value' => theme('uc_protx_vsp_direct_cards'),
253 );
254
255 $form['#validate'] = array('uc_protx_vsp_direct_settings_validate' => Array());
256 return $form;
257 }
258
259
260 function uc_protx_vsp_direct_settings_validate($form_values) {
261 $field = 'uc_protx_vsp_direct_vendor';
262 $vendor = strlen($form_values[$field]['#post'][$field]);
263 if ($vendor==0 || $vendor>15) {
264 form_set_error($field, t('Please enter a valid Vendor Login Name.'));
265 }
266 }
267
268
269 /*******************************************************************************
270 * Module and Helper Functions
271 ******************************************************************************/
272 function uc_protx_vsp_direct_charge($order_id, $amount, $data) {
273
274 if (!extension_loaded('curl')) {
275 drupal_set_message(t('The Protx VSP Direct payment gateway requires the cURL PHP extension to be loaded.'), 'error');
276 }
277
278 global $user;
279 $order = uc_order_load($order_id);
280 $result = array(
281 'success' => FALSE,
282 'comment' => '', // For uc_payment_receipts if success == TRUE
283 'message' => '', // For watchdog() if success == FALSE; sent to drupal_set_message() if not default payment gateway
284 'uid' => $user->uid,
285 );
286 $data['Vendor'] = variable_get('uc_protx_vsp_direct_vendor', '');
287 if ($data['Vendor']=='' || strlen($data['Vendor'])>15) {
288 $result['message'] = t('Invalid Vendor Login Name. Authorization Request could not be sent.');
289 return $result;
290 }
291
292 // ----------------------------------------
293 // Set HTTPS URL
294 $url = uc_protx_vsp_direct_url('registration', variable_get('uc_protx_vsp_direct_server', 2));
295 // ----------------------------------------
296
297 // ----------------------------------------
298 // Validate Card Type data from select list.
299 // 'VISA', 'MC', 'DELTA', 'SOLO', 'MAESTRO', 'UKE', 'AMEX', 'DC' or 'JCB'
300 $searches = array(
301 '`.*?delta.*`i' => 'DELTA',// Visa Delta needs to take precedence in this list over ordinary Visa
302 '`.*?electron.*`i' => 'UKE', // Visa Electron needs to take precedence in this list over ordinary Visa
303 '`.*?visa.*`i' => 'VISA',
304 '`.*?master[ ]*card.*`i' => 'MC',
305 '`.*?solo.*`i' => 'SOLO',
306 '`.*?(maestro|switch).*`i' => 'MAESTRO',
307 '`.*?(amex|american[ ]*express).*`i' => 'AMEX',
308 '`.*?diner.*`i' => 'DC',
309 '`.*?jcb.*`i' => 'JCB',
310 );
311
312 $data['CardType'] = '';
313 foreach ($searches as $pattern => $replace) {
314 if (preg_match($pattern, $order->payment_details['cc_type'])) {
315 $data['CardType'] = $replace;
316 break;
317 }
318 }
319 if ($data['CardType'] == '') {
320 $result['message'] = t('Cannot find card type "%CardType".', array('%CardType' => $order->payment_details['cc_type']));
321 return $result;
322 }
323 // ----------------------------------------
324
325
326 // ----------------------------------------
327 // Build the basic set of data for this transaction.
328 $max = 0;
329 foreach ($order->products as $product) { // The 100-character description perhaps ought to consist of the most expensive item.
330 if ( ($product->price * $product->qty) > $max ) {
331 $data['description'] = $product->title;
332 }
333 $max = max($max, $product->price);
334 }
335 if (count($order->products)>1) {
336 $appendix .= ' [etc.] - '. count($order->products) .' products';
337 }
338 $data['description'] = substr($data['description'], 0, 100-strlen($appendix)) . $appendix;
339
340 // We must do a basic sanity check on the card number because Credit Card module might not do any at all (as of Ubercart RC5).
341 if (!ctype_digit($order->payment_details['cc_number'])) {
342 $result['message'] = t('Invalid card number.');
343 return $result;
344 }
345
346 $delivery_country = uc_get_country_data(array('country_id' => $order->delivery_country));
347 $billing_country = uc_get_country_data(array('country_id' => $order->billing_country));
348
349 $transaction = array(
350 'VPSProtocol' => UC_PROTX_VSP_DIRECT_PROTOCOL_VERSION,
351 'TxType' => variable_get('uc_protx_vsp_direct_transaction', 'PAYMENT'),
352 'Vendor' => $data['Vendor'],
353 'VendorTxCode' => md5( time() . $user->uid . $order->order_id . rand() ), // This must be unique to the vendor.
354 'Amount' => sprintf('%01.2f', $amount),
355 'Currency' => variable_get('uc_currency_code', 'GBP'), // This is a UK-based payment gateway, hence GBP default.
356 'Description' => $data['description'],
357 'CardHolder' => substr($order->payment_details['cc_owner'], 0, 50),
358 'CardNumber' => $order->payment_details['cc_number'],
359 'ExpiryDate' => sprintf('%02d', $order->payment_details['cc_exp_month']) . substr($order->payment_details['cc_exp_year'], -2),
360 'CV2' => $order->payment_details['cc_cvv'],
361 'CardType' => $data['CardType'],
362 'BillingSurname' => substr($order->billing_last_name, 0, 20),
363 'BillingFirstnames' => substr($order->billing_first_name, 0, 20),
364 'BillingAddress1' => substr($order->billing_street1, 0, 100),
365 'BillingAddress2' => substr($order->billing_street2, 0, 100),
366 'BillingCity' => substr($order->billing_city, 0, 40),
367 'BillingPostCode' => substr($order->billing_postal_code, 0, 10),
368 'BillingCountry' => $billing_country[0]['country_iso_code_2'],
369 'BillingPhone' => substr($order->billing_phone, 0, 20),
370 'DeliverySurname' => substr($order->delivery_last_name, 0, 20),
371 'DeliveryFirstnames' => substr($order->delivery_first_name, 0, 20),
372 'DeliveryAddress1' => substr($order->delivery_street1, 0, 100),
373 'DeliveryAddress2' => substr($order->delivery_street2, 0, 100),
374 'DeliveryCity' => substr($order->delivery_city, 0, 40),
375 'DeliveryPostCode' => substr($order->delivery_postal_code, 0, 10),
376 'DeliveryCountry' => $delivery_country[0]['country_iso_code_2'],
377 'DeliveryPhone' => substr($order->delivery_phone, 0, 20),
378 'GiftAidPayment' => '0',
379 'ApplyAVSCV2' => '0',
380 'Apply3DSecure' => '0',
381 'AccountType' => 'E',
382 );
383 // ----------------------------------------
384
385
386 // ----------------------------------------
387 // Extra fields which need to be patched into credit card module -- StartDate, IssueNumber.
388 if (isset($order->payment_details['cc_start_month']) && isset($order->payment_details['cc_start_year'])) {
389 $month = $order->payment_details['cc_start_month'];
390 $year = $order->payment_details['cc_start_year'];
391 if (is_numeric($month) && is_numeric($year) && $month>0 && $month<13 && strlen($year)==4) {
392 $transaction = array_merge($transaction, array( 'StartDate' => sprintf('%02d', $month) . substr($year, -2) ));
393 }
394 }
395
396 if (isset($order->payment_details['cc_issue'])) { // N.B. issue number must be stored as string, since '4' is different from '04'.
397 $issue = $order->payment_details['cc_issue'];
398 if (is_numeric($issue) && (strlen($issue)==1 || strlen($issue)==2)) {
399 $transaction = array_merge($transaction, array( 'IssueNumber' => $order->payment_details['cc_issue'] ));
400 }
401 }
402 // ----------------------------------------
403
404
405
406 // ----------------------------------------
407 // Collect extra information for Protx if this option is checked.
408 if (variable_get('uc_protx_vsp_direct_send_extra', 0)) {
409
410 $data['CustomerName'] = rtrim($order->delivery_first_name .' '. $order->delivery_last_name .', '. $order->delivery_company, ', ');
411 if ($data['CustomerName'] == '') {
412 $data['CustomerName'] = rtrim($order->billing_first_name .' '. $order->billing_last_name .', '. $order->billing_company, ', ');
413 }
414
415 // The Basket field is <=7,500 characters.
416 // The first line is just the total of lines in the basket, followed by a colon.
417 // The final line should be shipping, tax, etc., and any items that can't be included in under 7,500 chars.
418 // The final line has no final colon
419 $basket['strlen'] = 0;
420 $basket['subtotal'] = 0;
421
422 // The number of lines of items, inc. the extra one for shipping, tax and any items that don't fit in under 7,500 chars:
423 $basket['lines'] = count($order->products) + 1;
424
425 foreach ($order->products as $x => $product) {
426 $item_total = $product->price * $product->qty;
427 $basket['amounts'][] = $item_total;
428 $basket['items'][] = str_replace(
429 ':', ' - ', $product->title)
430 .':'. $product->qty
431 .':' // Net
432 .':' // Tax
433 .':'. sprintf('%01.2f', $product->price) // Gross
434 .':'. sprintf('%01.2f', $item_total)
435 .':'
436 ;
437 $basket['strlen'] += strlen(end($basket['items']));
438 $basket['subtotal'] += $item_total;
439 }
440
441 $basket['finalLine']['amount'] = $amount - $basket['subtotal'];// At this stage this will probably just be tax & shipping.
442 $basket['finalLine']['text'] = '[Other items, shipping and taxes]:::::';
443
444 // The final line has no final colon, but we need one to complete the first line.
445 while ( strlen($basket['lines'] .':') + $basket['strlen'] > (7500 - strlen($basket['finalLine']['text'] . $basket['finalLine']['amount'])) ) {
446 $basket['strlen'] -= strlen(array_pop($basket['items']));
447 $basket['lines']--;
448 $basket['finalLine']['amount'] += array_pop($basket['amounts']);
449 }
450
451 $data['Basket'] = $basket['lines'] .':';
452 foreach ($basket['items'] as $item) {
453 $data['Basket'] .= $item;
454 }
455 $data['Basket'] .= $basket['finalLine']['text'] . $basket['finalLine']['amount'];
456
457
458 if (getenv('HTTP_CLIENT_IP')) {
459 $data['ClientIPAddress'] = getenv('HTTP_CLIENT_IP');
460 }
461 elseif (getenv('HTTP_X_FORWARDED_FOR')) {
462 $data['ClientIPAddress'] = getenv('HTTP_X_FORWARDED_FOR');
463 }
464 elseif (getenv('HTTP_X_FORWARDED')) {
465 $data['ClientIPAddress'] = getenv('HTTP_X_FORWARDED');
466 }
467 elseif (getenv('HTTP_FORWARDED_FOR')) {
468 $data['ClientIPAddress'] = getenv('HTTP_FORWARDED_FOR');
469 }
470 elseif (getenv('HTTP_FORWARDED')) {
471 $data['ClientIPAddress'] = getenv('HTTP_FORWARDED');
472 }
473 else {
474 $data['ClientIPAddress'] = $_SERVER['REMOTE_ADDR'];
475 }
476
477 $transaction = array_merge(
478 $transaction,
479 array(
480 'CustomerName' => $data['CustomerName'],
481 'ContactNumber' => $order->billing_phone,
482 //'ContactFax' => $order->, // This value is not available as of Ubercart RC5.
483 'CustomerEMail' => $order->primary_email,
484 'Basket' => $data['Basket'],
485 'ClientIPAddress' => $data['ClientIPAddress'],
486 )
487 );
488 }
489 // ----------------------------------------
490
491 // ----------------------------------------
492 // Put all this data into an HTTPS POST request
493 $post = '';
494 foreach ($transaction as $name => $value) {
495 //$post .= urlencode(iconv('UTF-8', 'ISO-8859-1', $name)) . '=' . urlencode(iconv('UTF-8', 'ISO-8859-1', $value)) . '&';
496 $post .= urlencode($name) .'='. urlencode($value) .'&';
497 }
498 $post = substr($post, 0, -1);
499
500 list($response, $http_response_code, $curl_error) = uc_protx_vsp_direct_curl($url, $post);
501
502 if ($curl_error!='') {
503 $result['message'] = t('Message from PHP cURL: @curlError', array('@curlError' => $curl_error));
504 return $result;
505 }
506
507 if ($http_response_code!=200) {
508 $result['message'] = t('The request met with HTTP response code @code', array('@code' => $http_response_code));
509 return $result;
510 }
511 // ----------------------------------------
512
513 // store a comment with some protx useful data of the transaction being saved
514 $comment = t(
515 'Transaction sent.<br />VendorTxCode: @VendorTxCode',
516 array(
517 '@VendorTxCode' => $transaction['VendorTxCode'],
518 )
519 );
520 uc_order_comment_save($order_id, $user->uid, $comment);
521
522 // ----------------------------------------
523 // Now make sense of the response.
524
525 if ($response['Status']=='3DAUTH') {
526 $_SESSION['3dsecure']['ACSURL'] = $response['ACSURL'];
527 $_SESSION['3dsecure']['MD'] = $response['MD'];
528 $_SESSION['3dsecure']['PAReq'] = $response['PAReq'];
529 drupal_goto('uc_protx_vsp_direct/3DSecure');
530 }
531 else {
532 uc_protx_vsp_direct_response($response, $result);
533 }
534
535 if ($result['success'] == TRUE) {
536 uc_order_comment_save($order_id, $user->uid, $result['comment']);
537 }
538
539 return $result;
540 }
541
542 function uc_protx_vsp_direct_3DSecure() {
543
544 if (empty($_SESSION['3dsecure']) || !isset($_SESSION['cart_order'])) {
545 drupal_set_message(t('Credit card 3D-Secure authorization could not be completed.'), 'error');
546 drupal_goto('cart/checkout');
547 }
548
549 $term_url = url('uc_protx_vsp_direct/3DSecure_callback', NULL, NULL, TRUE);
550
551 // Note the different case of "PaReq" here in the HTML element name:
552 if (variable_get('uc_protx_vsp_direct_iframe', 1)) {
553 drupal_add_js("window.onload = function() { document.forms['uc_protx_vsp_direct_3dsecure'].submit(); }", 'inline');
554 $output =
555 '<form name="uc_protx_vsp_direct_3dsecure" method="post" action="'. $_SESSION['3dsecure']['ACSURL'] .'" target="Secure3D">
556 <p>
557 <input type="hidden" name="MD" value="'. $_SESSION['3dsecure']['MD'] .'" />
558 <input type="hidden" name="PaReq" value="'. $_SESSION['3dsecure']['PAReq'] .'" />
559 <input type="hidden" name="TermUrl" value="'. $term_url .'" />
560 </p>
561 <noscript>
562 <p>
563 <input type="submit" value="Click here to continue" />
564 </p>
565 </noscript>
566 </form>
567 <iframe name="Secure3D" id="Secure3D" src="'. url('uc_protx_vsp_direct/3DSecure_waitingPage') .'">
568 </iframe>'
569 ;
570 return $output;
571 }
572 else {
573 $output =
574 '<?xml version="1.0" encoding="utf-8"?>
575 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
576 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
577 <head>
578 <title>3D-Secure</title>
579 <script type="text/javascript">window.onload = function() { document.forms[0].submit(); }</script>
580 </head>
581 <body>
582 <form method="post" action="'. $_SESSION['3dsecure']['ACSURL'] .'">
583 <p>
584 <input type="hidden" name="MD" value="'. $_SESSION['3dsecure']['MD'] .'" />
585 <input type="hidden" name="PaReq" value="'. $_SESSION['3dsecure']['PAReq'] .'" />
586 <input type="hidden" name="TermUrl" value="'. $term_url .'" />
587 </p>
588 <noscript>
589 <p>
590 <input type="submit" value="Click here to continue" />
591 </p>
592 </noscript>
593 </form>
594 </body>
595 </html>'
596 ;
597 echo $output;
598 exit;
599 }
600 }
601
602 function uc_protx_vsp_direct_3DSecure_callback() {
603
604 // Note the different case of "PaReq" in the $_POST version:
605 if (!isset($_POST['MD']) || !isset($_POST['PaRes']) || !isset($_SESSION['3dsecure']['MD'])
606 || !isset($_SESSION['cart_order']) || $_POST['MD'] != $_SESSION['3dsecure']['MD']) {
607 drupal_set_message(t('Credit card 3D-Secure authorization could not be completed.'), 'error');
608 drupal_goto('cart/checkout');
609 }
610
611 $order = uc_order_load($_SESSION['cart_order']);
612 if ($order === FALSE) {
613 drupal_goto('cart/checkout');
614 }
615 $amount = $order->amount;
616
617 $url = uc_protx_vsp_direct_url('3d-secure', variable_get('uc_protx_vsp_direct_server', 2));
618 $post = 'MD='. $_POST['MD'] .'&PARes='. $_POST['PaRes'];
619 list($response, $http_response_code, $curl_error) = uc_protx_vsp_direct_curl($url, $post);
620
621 if ($curl_error!='') {
622 watchdog('uc_protx_vsp_direct', t('Payment failed: Message from PHP cURL: @curlError', array('@curlError' => $curl_error)), WATCHDOG_ERROR);
623 drupal_set_message(t('Credit card 3D-Secure authorization could not be completed.'), 'error');
624 drupal_goto('cart/checkout');
625 }
626
627 if ($http_response_code!=200) {
628 watchdog('uc_protx_vsp_direct', t('Payment failed: The 3D-Secure callback request met with HTTP response code @code', array('@code' => $http_response_code)), WATCHDOG_WARNING);
629 return;
630 }
631
632 $result['success'] = FALSE;
633 uc_protx_vsp_direct_response($response, $result);
634
635 unset($_SESSION['3dsecure']);
636
637 if ($result['success'] == TRUE) {
638 uc_payment_enter($order->order_id, 'credit', $order->order_total, $order->uid, '', $result['comment']);
639 uc_order_comment_save($order->order_id, $order->uid, $result['comment']);
640 $_SESSION['do_complete'] = TRUE;
641 $redirect = 'cart/checkout/complete';
642 }
643 else {
644 watchdog('uc_protx_vsp_direct', t('Payment failed: @message', array('@message' => $result['message'])), WATCHDOG_WARNING);
645 drupal_set_message(variable_get('uc_credit_fail_message', t('We were unable to process your credit card payment. Please verify your card details and try again. If the problem persists, contact us to complete your order.')), 'error');
646 $redirect = 'cart/checkout';
647 }
648
649 if (variable_get('uc_protx_vsp_direct_iframe', 1)) {
650 $output =
651 '<html><head><title></title></head><body onload="document.forms[0].submit();"><form name="uc_protx_vsp_direct_3dsecure" method="post" action="'. url($redirect) .'" target="_top"></body></html>
652 <noscript>
653 <input type="submit" value="Please click here to continue." />
654 </noscript>
655 </form>'
656 ;
657 echo $output;
658 exit;
659 }
660 else {
661 drupal_goto($redirect);
662 }
663 }
664
665 function uc_protx_vsp_direct_curl($url, $post) {
666 $ch = curl_init($url);
667 curl_setopt_array(
668 $ch,
669 array(
670 CURLOPT_HEADER => FALSE,
671 // If Protx are sending a redirect this should really be handled manually
672 CURLOPT_FOLLOWLOCATION => FALSE,
673 CURLOPT_FRESH_CONNECT => TRUE,
674 CURLOPT_POST => TRUE,
675 CURLOPT_RETURNTRANSFER => TRUE,
676 CURLOPT_SSL_VERIFYPEER => FALSE,
677 CURLOPT_TRANSFERTEXT => TRUE,
678 CURLOPT_VERBOSE => FALSE,
679 CURLOPT_CONNECTTIMEOUT => 60,
680 CURLOPT_SSL_VERIFYHOST => 2,
681 CURLOPT_POSTFIELDS => $post,
682 )
683 );
684
685 $raw_response = curl_exec($ch);
686 $http_response_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
687 $curl_error = curl_error($ch);
688
689 $response_strings = explode("\r\n", $raw_response);
690
691 foreach ($response_strings as $str) {
692 list($name, $value) = explode('=', $str, 2); // This strange limit method is required because sometimes the '=' character is also part of the data.
693 if ($name!='') $response[$name] = $value;
694 }
695
696 curl_close($ch);
697 return array($response, $http_response_code, $curl_error);
698 }
699
700 function uc_protx_vsp_direct_url($method, $server) {
701
702 $servers = array(
703 // Ordinary method, or for first stage of 3D-Secure method
704 'registration' => array(
705 0 => 'https://test.sagepay.com/Simulator/VSPDirectGateway.asp',
706 1 => 'https://test.sagepay.com/gateway/service/vspdirect-register.vsp',
707 2 => 'https://live.sagepay.com/gateway/service/vspdirect-register.vsp',
708 ),
709 'authorize' => array(
710 0 => 'https://test.sagepay.com/Simulator/VSPServerGateway.asp?Service=VendorAuthoriseTx',
711 1 => 'https://test.sagepay.com/gateway/service/authorise.vsp',
712 2 => 'https://live.sagepay.com/gateway/service/authorise.vsp',
713 ),
714 // 3D-Secure method, used only when $response['Status']=='3DAUTH'
715 '3d-secure' => array(
716 0 => 'https://test.sagepay.com/Simulator/VSPDirectCallback.asp',
717 1 => 'https://test.sagepay.com/gateway/service/direct3dcallback.vsp',
718 2 => 'https://live.sagepay.com/gateway/service/direct3dcallback.vsp',
719 ),
720 );
721
722 return $servers[$method][$server];
723 }
724
725 function uc_protx_vsp_direct_3DSecure_waitingPage() {
726 echo '<html><head><title></title></head><body><p style="font-family: Verdana; color: #AAA; font-weight: bold">Please wait to be redirected to your card issuer for authorization...</p></body></html>';
727 }
728
729 function uc_protx_vsp_direct_response($response, &$result) {
730
731 switch ($response['Status']) {
732
733 case 'AUTHENTICATED':
734 case 'OK':
735 if ( $response['Status'] == 'AUTHENTICATED' ) {
736 $str = t('Transaction authenticated');
737 }
738 else {
739 $str = t('Transaction authorized');
740 }
741 $result['message'] = '';
742 $result['comment'] = t(
743 '@response_str.
744 <br />VPSTxId: @VPSTxId
745 <br />SecurityKey: @SecurityKey
746 <br />TxAuthNo: @TxAuthNo
747 <br />AVSCV2: @AVSCV2
748 <br /><em>(AddressResult: @AddressResult | PostCodeResult: @PostCodeResult | CV2Result: @CV2Result)</em>
749 <br />3D-Secure Status: @3DSecureStatus',
750 array(
751 '@VPSTxId' => $response['VPSTxId'],
752 '@SecurityKey' => $response['SecurityKey'],
753 '@TxAuthNo' => $response['TxAuthNo'],
754 '@AVSCV2' => $response['AVSCV2'],
755 '@AddressResult' => $response['AddressResult'],
756 '@PostCodeResult' => $response['PostCodeResult'],
757 '@CV2Result' => $response['CV2Result'],
758 '@3DSecureStatus' => $response['3DSecureStatus'],
759 '@response_str' => $str,
760 )
761 );
762
763 if ($response['3DSecureStatus']=='OK') {
764 $result['comment'] .= t('<br />CAVV: @CAVV', array('@CAVV' => $response['CAVV']));
765 }
766
767 $result['success'] = TRUE;
768 break;
769
770 case 'NOTAUTHED':
771 $result['message'] = t('The transaction was not authorised by the acquiring bank.');
772 break;
773
774 case 'REJECTED':
775 $result['message'] = t('The VSP System rejected the transaction because of the rules you have set on your Protx account. The message was: %StatusDetail', array('%StatusDetail' => $response['StatusDetail']));
776 break;
777
778 case 'INVALID':
779 case 'MALFORMED':
780 case 'ERROR':
781 $result['message'] = $response['StatusDetail'];
782 break;
783
784 case 'REGISTERED':
785 default:
786 // This should never happen.
787 $result['message'] = t('Protx responded with a status code that indicates acceptance of the request, but which is not implemented by this module: @code @StatusDetail', array('@code' => $response['Status'], '@StatusDetail' => $response['StatusDetail']));
788 break;
789 }
790
791 }
792
793 /**
794 * Implementation of hook_order_pane().
795 */
796 function uc_protx_vsp_direct_order_pane() {
797 $panes[] = array(
798 'id' => 'uc_protx_vsp_direct',
799 'callback' => 'uc_protx_vsp_direct_order_pane_callback',
800 'title' => t('Protx'),
801 'desc' => t('Display protx authorization button.'),
802 'class' => 'pos-left',
803 'weight' => 5,
804 'show' => array('view'), //'edit', 'customer', 'invoice', 'customer'),
805 );
806
807 return $panes;
808 }
809
810 /**
811 * Handle the Payment order pane.
812 */
813 function uc_protx_vsp_direct_order_pane_callback($op, $arg1) {
814 switch ($op) {
815 case 'view':
816 $order = $arg1;
817 $protx = _uc_protx_vsp_direct_comments_to_protx($order->order_id);
818 if ( $protx['authenticated'] && !$protx['authorized'] ) {
819 $output = drupal_get_form('uc_protx_vsp_direct_order_pane_view_form', $order);
820 }
821 return $output;
822 }
823 }
824
825 function uc_protx_vsp_direct_order_pane_view_form($order) {
826 $form['order_id'] = array(
827 '#type' => 'hidden',
828 '#value' => $order->order_id,
829 );
830
831 $form['submit'] = array(
832 '#type' => 'submit',
833 '#value' => t('Authorize card'),
834 );
835
836 return $form;
837 }
838
839 function uc_protx_vsp_direct_order_pane_view_form_submit($form_id, $form_values) {
840 return 'admin/store/orders/'. $form_values['order_id'] .'/uc_protx_vsp_direct_auth';
841 }
842
843 function uc_protx_vsp_direct_perm() {
844 return array(
845 'authorize credit cards',
846 );
847 }
848
849 function uc_protx_vsp_direct_auth_page($order_id) {
850 $comments = uc_order_comments_load($order_id, TRUE);
851 $output .= tapir_get_table('op_admin_comments_view_table', $comments);
852 $output .= drupal_get_form('uc_protx_vsp_direct_auth_form', $order_id);
853
854 return $output;
855 }
856
857 function uc_protx_vsp_direct_auth_form($order_id) {
858 $form['order_id'] = array(
859 '#type' => 'value',
860 '#value' => $order_id,
861 );
862
863 $form['submit'] = array(
864 '#type' => 'submit',
865 '#value' => t('Authorize card'),
866 );
867
868 return $form;
869 }
870
871 function uc_protx_vsp_direct_auth_form_submit($form_id, $form_values) {
872 global $user;
873
874 $order_id = $form_values['order_id'];
875 $order = uc_order_load($order_id);
876 $protx = _uc_protx_vsp_direct_comments_to_protx($order_id);
877
878 $transaction = array(
879 'VPSProtocol' => UC_PROTX_VSP_DIRECT_PROTOCOL_VERSION,
880 'TxType' => 'AUTHORISE',
881 'Vendor' => variable_get('uc_protx_vsp_direct_vendor', ''),
882 'VendorTxCode' => md5( time() . $user->uid . $order->order_id . rand() ), // This must be unique to the vendor.
883 'Amount' => sprintf('%01.2f', $order->order_total),
884 'Currency' => variable_get('uc_currency_code', 'GBP'), // This is a UK-based payment gateway, hence GBP default.
885 'Description' => t('Authorizing order @orderid.', array('@orderid' => $order_id)),
886 // VPSTxId of the authenticate transaction against which the authorisation is required.
887 'RelatedVPSTxId' => $protx['vpstxid'],
888 // VendorTxCode of the authenticate transaction against which the authorisation is require
889 'RelatedVendorTxCode' => $protx['vendortxcode'],
890 // The SecurityKey of the authenticate transaction sent back by the VSP System when the transaction was registered.
891 'RelatedSecurityKey' => $protx['securitykey'],
892 // (optional) Using this flag you can fine tune the AVS/CV2 checks and rule set you’ve defined at a transaction level. This is useful in circumstances where direct and trusted customer contact has been established and you wish to override the default security checks.
893 'ApplyAVSCV2' => '0',
894 );
895
896 // Put all this data into an HTTPS POST request
897 $post = '';
898 foreach ($transaction as $name => $value) {
899 //$post .= urlencode(iconv('UTF-8', 'ISO-8859-1', $name)) . '=' . urlencode(iconv('UTF-8', 'ISO-8859-1', $value)) . '&';
900 $post .= urlencode($name) .'='. urlencode($value) .'&';
901 }
902 $post = substr($post, 0, -1);
903
904 $url = uc_protx_vsp_direct_url('authorize', variable_get('uc_protx_vsp_direct_server', 2));
905 list($response, $http_response_code, $curl_error) = uc_protx_vsp_direct_curl($url, $post);
906
907 if ($curl_error!='') {
908 watchdog('uc_protx_vsp_direct', t('Message from PHP cURL: @curlError', array('@curlError' => $curl_error)), WATCHDOG_ERROR);
909 drupal_set_message(t('Credit card authorization could not be completed.'), 'error');
910 return 'admin/store/orders/'. $order_id .'/uc_protx_vsp_direct_auth';
911 }
912
913 if ($http_response_code!=200) {
914 watchdog('uc_protx_vsp_direct', t('The request met with HTTP response code @code', array('@code' => $http_response_code)), WATCHDOG_ERROR);
915 drupal_set_message(t('Credit card authorization could not be completed.'), 'error');
916 return 'admin/store/orders/'. $order_id .'/uc_protx_vsp_direct_auth';
917 }
918
919 uc_protx_vsp_direct_response($response, $result);
920
921 if ( $result['success'] ) {
922 drupal_set_message(t('The transaction was succesfully authorised by the acquiring bank.'));
923 uc_order_comment_save($order_id, $user->uid, $result['message']);
924 }
925 else {
926 drupal_set_message($result['message'], 'error');
927 }
928
929 return 'admin/store/orders/'. $order_id .'/uc_protx_vsp_direct_auth';
930 }
931
932 /**
933 * Outputs the cards used in the configuration fieldset
934 * @return <string>
935 */
936 function theme_uc_protx_vsp_direct_cards() {
937 $img_path = base_path() . drupal_get_path('module', 'uc_protx_vsp_direct') .'/img';
938 $img_path_cards = base_path() . drupal_get_path('module', 'uc_protx_vsp_direct') .'/img/protx_cards';
939
940 $output = <<<CARDS
941 <div class="uc_protx_vsp_direct_cards">
942 <img src="$img_path_cards/mastercard normal.gif" alt="Mastercard" />
943 <img src="$img_path_cards/visa.gif" alt="Visa" />
944 <img src="$img_path_cards/delta.gif" alt="Visa Debit" />
945 <img src="$img_path_cards/electron.gif" alt="Electron" />
946 <img src="$img_path_cards/amexsmall.gif" alt="American Express" />
947 </div>
948 <div class="uc_protx_vsp_direct_cards">
949 <img src="$img_path_cards/maestro.gif" alt="Maestro" />
950 <img src="$img_path_cards/solo.gif" alt="Solo" />
951 <img src="$img_path_cards/dinersclublogo125_26.gif" alt="Diners" />
952 <img src="$img_path_cards/jcb.gif" alt="JCB" />
953 </div>
954 <div class="uc_protx_vsp_direct_cards">
955 <p>This gateway supports 3D-Secure:</p>
956 <img src="$img_path/vbv_logo24.gif" alt="Verified by Visa" />
957 <img src="$img_path/msc_logo24.gif" alt="Mastercard SecureCode" />
958 </div>
959 CARDS;
960
961 return $output;
962 }

  ViewVC Help
Powered by ViewVC 1.1.2