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

Contents of /contributions/modules/payment_ach/payment_ach.module

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


Revision 1.1 - (show annotations) (download) (as text)
Thu Sep 21 22:45:22 2006 UTC (3 years, 2 months ago) by budda
Branch: MAIN
CVS Tags: HEAD
File MIME type: text/x-php
Initial check in of ACH Payment module for Drupal eCommerce system.
1 <?php
2 // $Id $
3 /* ACHDirect Payment Gateway for E-Commerce Payment API Module
4 * $Id:
5 *
6 * Developed by Mike Carter <mike@buddasworld.co.uk>
7 *
8 */
9
10 include 'payment_ach.theme';
11
12 define(PAYMENT_ACH_TRANSACTION_SALE, 10);
13 define(PAYMENT_ACH_TRANSACTION_VOID, 14);
14 define(PAYMENT_ACH_TRANSACTION_PREAUTH, 15);
15
16 define(PAYMENT_ACH_RESPONSE_TYPE_APPROVAL, 'A');
17 define(PAYMENT_ACH_RESPONSE_TYPE_DECLINED, 'D');
18 define(PAYMENT_ACH_RESPONSE_TYPE_ERROR, 'E');
19
20 define(PAYMENT_ACH_INVALID_MERCH_OR_PASSWD, 'E10');
21 define(PAYMENT_ACH_BAD_IP_ADDR, 'E90');
22 define(PAYMENT_ACH_INTERNAL_ERROR, 'E99');
23
24
25 /**
26 * Implementation of hook_menu().
27 */
28 function payment_ach_menu($may_cache) {
29
30 $items = array();
31
32 if ($may_cache) {
33 $items[] = array('path' => 'payment_ach/form', 'title' => t('Credit Card Payment'),
34 'callback' => 'payment_ach_form', 'access' => true, 'type' => MENU_CALLBACK);
35 }
36 return $items;
37 }
38
39 /**
40 * Implementation of hook_help().
41 */
42 function payment_ach_help($section = 'admin/help#payment_ach') {
43 switch ($section) {
44 case 'admin/modules#description':
45 return t('Process payments using %url. Dependency: payment.module', array('%url' => l('ACH Direct', 'http://www.achdirect.com/')));
46
47 case 'admin/settings/payment_ach':
48 return t("You need to have an ACH Direct merchant account in order to use this module. See %url for more information.", array('%url' => l('ACH Direct', 'http://www.achdirect.com/')));
49 }
50
51 if(strpos($section, 'payment_ach/form') === 0) {
52 return variable_get('payment_ach_help', t("Do not submit this form twice, or you may be double billed!"));
53 }
54 }
55
56 function payment_ach_ec_settings() {
57 $form['payment_ach_help'] = array(
58 '#type' => 'textarea',
59 '#title' => t('Explanation or submission guidelines'),
60 '#default_value' => variable_get('payment_ach_help', payment_ach_help('payment_ach/form_submit_guidlines')),
61 '#cols' => 70,
62 '#rows' => 5,
63 '#description' => t('This text will be displayed at the top of the credit card submission form.'),
64 );
65
66
67 $form['payment_ach_merchantid'] = array(
68 '#type' => 'textfield',
69 '#title' => t('Merchant ID'),
70 '#default_value' => variable_get('payment_ach_merchantid', ''),
71 '#required' => TRUE,
72 '#size' => 40,
73 '#maxlength' => 6,
74 '#description' => t("Enter your merchant 6-digit Merchant ID."),
75 );
76
77 $form['payment_ach_password'] = array(
78 '#type' => 'textfield',
79 '#title' => t('Merchant Password'),
80 '#default_value' => variable_get('payment_ach_password', ''),
81 '#size' => 40,
82 '#maxlength' => 128,
83 '#required' => TRUE,
84 '#description' => t("Enter your merchant password."),
85 );
86
87 $options = array('https://www.paymentsgateway.net/cgi-bin/postauth.pl' => t('ACH Live server'), 'https://www.paymentsgateway.net/cgi-bin/posttest.pl' => t('ACH Test server'));
88 $form['payment_ach_url'] = array(
89 '#type' => 'select',
90 '#title' => t('Which Processing Server'),
91 '#options' => $options,
92 '#multiple' => FALSE,
93 '#default_value' => variable_get('payment_ach_url', 'https://www.paymentsgateway.net/cgi-bin/posttest.pl'),
94 '#size' => 1,
95 '#description' => t('Select which payment processing server should handle the transactions.'),
96 );
97
98 $form['payment_ach_use_https'] = array(
99 '#type' => 'checkbox',
100 '#return_value' => 1,
101 '#title' => t('Use HTTPS encryption for payment form page'),
102 '#default_value' => variable_get('payment_ach_use_https', 1),
103 '#disabled' => TRUE,
104 '#description' => t("For the security of your customers it is advised this remains enabled. Use only when testing on a development server which doesn't have SSL configured."),
105 );
106
107 $form['payment_ach_success_url'] = array(
108 '#type' => 'textfield',
109 '#title' => t('Successful payment URL'),
110 '#default_value' => variable_get('payment_ach_success_url', 'node'),
111 '#size' => 70,
112 '#maxlength' => 180,
113 '#description' => t("This is the destination to which you would like to send your customers when their payment has been successfully completed. The URL must be a Drupal system path. If you are not using clean URLs, specify the part after '?q='. If unsure, specify 'node'."),
114 );
115 $form['payment_ach_decline_url'] = array(
116 '#type' => 'textfield',
117 '#title' => t('Declined card payment URL'),
118 '#default_value' => variable_get('payment_ach_decline_url', 'node'),
119 '#size' => 70,
120 '#maxlength' => 180,
121 '#description' => t("This is the destination to which you would like to send your customers to when card payment is declined by the gateway. The URL must be a Drupal system path. If you are not using clean URLs, specify the part after '?q='. If unsure, specify 'node'."),
122 );
123 $form['payment_ach_error_url'] = array(
124 '#type' => 'textfield',
125 '#title' => t('Failed payment processing URL'),
126 '#default_value' => variable_get('payment_ach_error_url', 'node'),
127 '#size' => 70,
128 '#maxlength' => 180,
129 '#description' => t("This is the destination to which you would like to send your customers to when something goes wrong with the processing gateway. This could include a phone number to contact you to place the order over the phone if the gateway is down. The URL must be a Drupal system path. If you are not using clean URLs, specify the part after '?q='. If unsure, specify 'node'."),
130 );
131
132 /*
133 if(!$_SERVER['HTTPS']) {
134 form_set_error('payment_ach_use_https', t('This server does not appear to be HTTPS enabled.'));
135 $form['payment_ach_use_https']['#attributes'] = array('disabled' => 'disabled');
136 $form['payment_ach_use_https']['#default_value'] = 0;
137 }
138 */
139
140 return $form;
141 }
142
143 /**
144 * Implementation of hook_paymentapi().
145 */
146 function payment_ach_paymentapi(&$txn, $op) {
147
148 switch ($op) {
149 case 'display name':
150 return t('Pay with credit card');
151
152 case 'payment page':
153 return payment_ach_goto($txn);
154
155 }
156 }
157
158 /**
159 * Implementation of hook_ec_transactionapi().
160 */
161 function payment_ach_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
162 if ($txn->payment_method != 'payment_ach') return NULL;
163
164 switch ($op) {
165 case 'load':
166 $txn->payment = db_fetch_object(db_query("SELECT * FROM {ec_payment_ach} WHERE txnid = %d", $txn->txnid));
167 break;
168 case 'insert':
169 case 'update':
170 payment_ach_save($txn);
171 break;
172 case 'delete':
173 payment_ach_delete($txn);
174 break;
175 }
176 }
177
178 function payment_ach_delete($txn) {
179 db_query('DELETE FROM {ec_payment_ach} WHERE txnid = %d', $txn->txnid);
180 }
181
182 /**
183 * Called immediately after the user has clicked the checkout button.
184 *
185 * Redirect the user to the secure server to collect credit card information.
186 */
187 function payment_ach_goto($txn) {
188
189 global $base_url;
190
191 $payment_url = url('payment_ach/form/'. $txn->txnid, NULL, NULL, TRUE);
192 if(variable_get('payment_ach_use_https', 1) == 1) {
193 $payment_url = str_replace('http:', 'https:', $payment_url);
194 }
195
196 /* I can't use drupal_goto() because I possibly need to go to a https */
197 header('Location: ' . $payment_url);
198 exit();
199 }
200
201
202 /**
203 * Build the credit card form.
204 */
205 function payment_ach_form($txnid) {
206 global $user, $base_url;
207
208 $t = store_transaction_load($txnid);
209
210 //Make sure the user owns the transaction or is admin.
211 if ($user->uid != $t->uid && $user->uid != 1) {
212 return drupal_access_denied();
213 }
214
215 // Make sure the user is connected via SSL
216 if (!$_SERVER['HTTPS'] && (variable_get('payment_ach_use_https', 1) == 1)) {
217 return drupal_access_denied();
218 }
219
220 // Make sure all relative urls on the page use https:
221 if(variable_get('payment_ach_use_https', 1) == 1) {
222 $base_url = str_replace('http:', 'https:', $base_url);
223 $form['#action'] = str_replace('http://', 'https://', url("payment_ach/form/$txnid", NULL, NULL, TRUE));
224 }
225
226 if ($t->items) {
227 foreach ($t->items as $p) {
228 $product = product_load($p);
229 $subtotal += $p->qty * $p->price;
230 $items[] = t('%order of <b>%title</b> at %price each', array('%order' => format_plural($p->qty, '1 order', '%count orders'), '%title' => $p->title, '%price' => payment_format($product->price))). "\n";
231 }
232 }
233
234 $form['items'] = array('#value' => theme('item_list', $items, t('Your items')). '</p>');
235
236 // Prepare the values of the form fields.
237 $years = array('' => '--') + drupal_map_assoc(range(date('Y'), date('Y')+15));
238 $months = array('' => '--') + drupal_map_assoc(array('01', '02', '03', '04', '05', '06', '07', '08', '09', '10', '11', '12'));
239
240 $form['details'] = array(
241 '#type' => 'fieldset',
242 '#title' => t('Card details'),
243 );
244
245 $form['details']['ecom_payment_card_name'] = array(
246 '#type' => 'textfield',
247 '#title' => t('Card Holder Name'),
248 '#default_value' => $t->address['billing']->firstname . ' ' . $t->address['billing']->lastname,
249 '#size' => 50,
250 '#maxlength' => 100,
251 '#required' => TRUE,
252 );
253
254 $form['details']['ecom_payment_card_type'] = array(
255 '#type' => 'select',
256 '#title' => t('Card Type'),
257 '#options' => array('' => t('Please Choose'), 'VISA' => 'Visa', 'MAST' => 'Master Card', 'DISC' => 'Discover Card', 'AMER' => 'American Express', 'JCB' => 'JCB', 'DINE' => 'Diners Club'),
258 '#size' => 1,
259 '#required' => TRUE,
260 '#theme' => 'payment_ach_select',
261 );
262
263 $form['details']['ecom_payment_card_number'] = array(
264 '#type' => 'textfield',
265 '#title' => t('Card Number'),
266 '#size' => 21,
267 '#maxlength' => 21,
268 '#description' => null,
269 '#required' => TRUE,
270 );
271 $form['details']['ecom_payment_card_verification'] = array(
272 '#type' => 'textfield',
273 '#title' => t('Card Verification Value (CV2/CVV)'),
274 '#size' => 5,
275 '#maxlength' => 3,
276 '#description' => t('A three digit number on the signature strip of your card.'),
277 '#required' => FALSE,
278 );
279 $form['expiry_date'] = array(
280 '#type' => 'fieldset',
281 '#title' => t('Expiration Date'),
282 //'#prefix' => '<div class="container-inline">',
283 //'#suffix' => '</div>'
284 );
285 $form['expiry_date']['ecom_payment_card_expdate_month'] = array(
286 '#type' => 'select',
287 '#title' => t('Month'),
288 '#default_value' => '',
289 '#options' => $months,
290 '#description' => null,
291 '#extra' => 0,
292 '#multiple' => false,
293 '#required' => true,
294 );
295 $form['expiry_date']['ecom_payment_card_expdate_year'] = array(
296 '#type' => 'select',
297 '#title' => t('Year'),
298 '#default_value' => '',
299 '#options' => $years,
300 '#description' => null,
301 '#extra' => 0,
302 '#collapsible' => TRUE,
303 '#required' => true,
304 );
305
306
307 $form['txnid'] = array(
308 '#type' => 'value',
309 '#value' => $txnid,
310 );
311
312 $form['submit'] = array(
313 '#type' => 'submit',
314 '#value' => t('Place your order'),
315 );
316
317 theme_add_style(drupal_get_path('module', 'payment_ach') . '/style.css', 'screen');
318
319 return drupal_get_form('payment_ach_process', $form);
320 }
321
322 /**
323 * Ensure the integrity of the user-submitted data.
324 */
325 function payment_ach_process_validate($form_id, $edit) {
326 $errors = array();
327
328 if (!is_numeric($edit['ecom_payment_card_number'])) {
329 form_set_error('ecom_payment_card_number', t('Error in credit card number. Please make sure it is typed correctly.'));
330 }
331 }
332
333 function payment_ach_save($txn) {
334 if (is_numeric($txn->txnid) && $txn->pg_trace_number) {
335 if (db_result(db_query("SELECT COUNT(txnid) FROM {ec_payment_ach} WHERE txnid = %d", $txn->txnid))) {
336 db_query("UPDATE {ec_payment_ach} SET trace = '%s', amount = '%f' WHERE txnid = %d", $txn->pg_trace_number, $txn->gross, $txn->txnid);
337 }
338 else {
339 db_query("INSERT INTO {ec_payment_ach} (txnid, trace, amount) VALUES (%d, '%s', '%f')", $txn->txnid, $txn->pg_trace_number, $txn->gross);
340 }
341 }
342 }
343
344 /**
345 * Send the HTTPS POST request and process the returned data.
346 */
347 function payment_ach_process_submit($form_id, $edit) {
348 global $user;
349
350 $t = store_transaction_load($edit['txnid']);
351
352 //Make sure the user owns the transaction or is admin.
353 if ($user->uid != $t->uid && $user->uid != 1) {
354 return drupal_access_denied();
355 }
356
357 //Make sure the user is connected via SSL
358 if (!$_SERVER['HTTPS'] && variable_get('payment_ach_use_https', 1) == 1) {
359 drupal_access_denied();
360 return;
361 }
362
363 $param = array( 'pg_merchant_id' => variable_get('payment_ach_merchantid', ''),
364 'pg_password' => variable_get('payment_ach_password', ''),
365 'pg_transaction_type' => PAYMENT_ACH_TRANSACTION_SALE,
366 'pg_total_amount' => $t->gross,
367 'pg_consumer_id' => '',
368 'pg_customer_ip_address' => $_SERVER['X_HTTP_VIA'] ? $_SERVER['X_HTTP_VIA'] : $_SERVER['REMOTE_ADDR'],
369 //'pg_avs_method' => '',
370 'ecom_payment_card_type' => $edit['ecom_payment_card_type'],
371 'ecom_payment_card_name'=> $edit['ecom_payment_card_name'],
372 'ecom_payment_card_number'=> $edit['ecom_payment_card_number'],
373 'ecom_payment_card_expdate_month'=> $edit['ecom_payment_card_expdate_month'],
374 'ecom_payment_card_expdate_year'=> $edit['ecom_payment_card_expdate_year'],
375 'ecom_payment_card_verification' => $edit['ecom_payment_card_verification'],
376 'ecom_consumerorderid' => $t->txnid,
377 'ecom_billto_postal_name_first' => $t->address['billing']->firstname,
378 'ecom_billto_postal_name_last' => $t->address['billing']->lastname,
379 'ecom_billto_postal_street_line1' => $t->address['billing']->street1,
380 'ecom_billto_postal_street_line2' => $t->address['billing']->street2,
381 'ecom_billto_postal_city' => $t->address['billing']->city,
382 'ecom_billto_postal_stateprov' => $t->address['billing']->state,
383 'ecom_billto_postal_postalcode' => $t->address['billing']->zip,
384 'ecom_billto_postal_countrycode' => store_get_country($t->address['billing']->country),
385 'ecom_billto_telecom_phone_number' => $t->address['billing']->telephone,
386 'ecom_billto_online_email' => $t->mail ? $t->mail : $user->mail,
387 );
388
389 $uri = '';
390 foreach ($param as $k => $v) {
391 $uri .= '&'. $k. '='. urlencode($v);
392 }
393 $uri = trim($uri, '&');
394
395
396 global $base_url;
397 $payment_url = str_replace('http://', 'https://', url('payment_ach/form/'. $edit['txnid'], NULL, NULL, TRUE));
398
399 // Start CURL session
400 $ch = curl_init();
401 curl_setopt($ch, CURLOPT_URL, variable_get('payment_ach_url', ''));
402 curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
403 curl_setopt($ch, CURLOPT_NOPROGRESS, 1);
404 curl_setopt($ch, CURLOPT_VERBOSE, 0);
405 curl_setopt($ch, CURLOPT_FOLLOWLOCATION,0);
406 curl_setopt($ch, CURLOPT_POST, 1);
407 curl_setopt($ch, CURLOPT_POSTFIELDS, $uri);
408 curl_setopt($ch, CURLOPT_TIMEOUT, 120);
409 curl_setopt($ch, CURLOPT_USERAGENT, "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0)");
410 curl_setopt($ch, CURLOPT_REFERER, $payment_url);
411 curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
412
413 $buffer = curl_exec($ch);
414 curl_close($ch);
415
416 $return = preg_split("/[\n]+/", $buffer);
417
418 foreach($return as $value) {
419 $p = explode('=', $value);
420 $result[$p[0]] = $p[1];
421 }
422
423 $error_codes = array(
424 'F01' => t('Required field is missing'),
425 'F03' => t('Name is not recognized'),
426 'F04' => t('Value is not allowed'),
427 'F05' => t('Field is repeated in message'),
428 'F07' => t('Fields cannot both be present')
429 );
430
431
432 switch ($result['pg_response_type']) {
433
434 case PAYMENT_ACH_RESPONSE_TYPE_APPROVAL: // Credit card successfully charged
435 $edit = (array) store_transaction_load($edit['txnid']);
436
437 // Ensure the transaction receipt matches what we sent for processing
438 if($edit['gross'] == $result['pg_total_amount'] &&
439 $edit['txnid'] == $result['ecom_consumerorderid']) {
440 $edit['payment_status'] = payment_get_status_id('completed');
441 $edit['payment_method'] = 'payment_ach';
442 $edit['pg_trace_number'] = $result['pg_trace_number'];
443
444 $is_new = (db_result(db_query('SELECT COUNT(txnid) FROM {ec_payment_ach} WHERE txnid = %d', $edit['txnid']))) ? false : true;
445 $txnid = store_transaction_save($edit);
446
447 if ($is_new && $txnid) {
448 // Compose and send confirmation email to the user
449 store_send_invoice_email($txnid);
450 }
451
452 // Time for a success message
453 $url = url(variable_get('payment_ach_success_url', 'node'), NULL, NULL, TRUE);
454 if(variable_get('payment_ach_use_https', 1) == 1) {
455 $url = str_replace('http:', 'https:', $url);
456 }
457 header("Location: $url");
458 exit();
459 }
460
461
462 break;
463
464 CASE PAYMENT_ACH_RESPONSE_TYPE_DECLINED:
465 // Mark this transaction as a payment failure
466 $t->payment_status = payment_get_status_id('denied');
467 store_transaction_save($t);
468
469 watchdog('payment ach', t('Payment declined for transaction %txnid. %error', array('%txnid' => $t->txnid, '%error' => '')), WATCHDOG_NOTICE, l('view', 'admin/store/transaction/edit/'.$t->txnid));
470
471 //redirect to decline error?
472 $url = url(variable_get('payment_ach_decline_url', 'node'), NULL, NULL, TRUE);
473 if(variable_get('payment_ach_use_https', 1) == 1) {
474 $url = str_replace('http:', 'https:', $url);
475 }
476 header("Location: $url");
477 break;
478
479 CASE PAYMENT_ACH_RESPONSE_TYPE_ERROR: // Credit card error: card was not charged.
480 default:
481 // Mark this transaction as a payment failure
482 $t->payment_status = payment_get_status_id('failed');
483 store_transaction_save($t);
484
485 // Parse error string
486 $error_description = strtr($result['pg_response_description'], $error_codes);
487
488 drupal_set_message(t('An error with the payment gateway has occured.'), 'error');
489 watchdog('payment ach', t('Processing error for transaction %txnid. %error', array('%txnid' => $t->txnid, '%error' => $error_description)), WATCHDOG_ERROR, l('view', 'admin/store/transaction/edit/'.$t->txnid));
490
491 // This is a major problem so user cannot re-submit the payment details
492 $url = url(variable_get('payment_ach_error_url', 'node'), NULL, NULL, TRUE);
493 if(variable_get('payment_ach_use_https', 1) == 1) {
494 $url = str_replace('http:', 'https:', $url);
495 }
496 header("Location: $url");
497 exit();
498 break;
499 }
500
501 }

  ViewVC Help
Powered by ViewVC 1.1.2