I am trying to make a module that handles verisign's payflowpro gateway. I've got it working, but I think it's pretty ugly and I'm curious if anyone has any thoughts on security. I got initial code from http://ca.php.net/pfpro and used the ccard.module as my base. We have a few sites on the server but no one has access besides myself (aside from limited FTP and normal http access). I am also in the process of setting up an SSL connection for this process.
If you are going to try it, you have to change the info in the _payflowpro_process function on lines 163-166 and lines 179, 180 and 186
<?php
/*
* $Id: payflowpro.module,v 1 2006/06/29 by alynner copied from ccard by gordon
*/
function payflowpro_help($section = '') {
switch ($section) {
case 'admin/modules#description':
return t('payflowpro payment processing.<br />Dependency: payment.module');
break;
case 'admin/settings/payflowpro':
return t('Enter the required parameters that have been supplied during the signup process with payflowpro.');
break;
}
}
function payflowpro_menu($maycache) {
if ($maycache) {
$items[] = array(
'path' => 'store/payment/payflowpro',
'title' => t('Enter Credit Card Details'),
'callback' => 'payflowpro_enter_payment',
'access' => 1,
'type' => MENU_CALLBACK
);
}
return $items;
}
function payflowpro_ec_settings() {
$form = array();
$form['payflowpro_url'] = array(
'#type' => 'textfield',
'#title' => t('Credit Card Payment Page'),
'#default_value' => variable_get('payflowpro_url', url('store/payment/payflowpro', null, null, true)),
'#size' => 70,
'#maxlength' => 180,
'#desciption' => t('URL to be directed to so that the payment can be received.'),
'#required' => true
);
$form['payflowpro_thanks_url'] = array(
'#type' => 'textfield',
'#title' => t('Thank you Page'),
'#default_value' => variable_get('payflowpro_thanks_url', url('node', null, null, true)),
'#size' => 70,
'#maxlength' => 180,
'#desciption' => t('URL to be directed once the payment has been entered.'),
'#required' => true
);
return $form;
}
function payflowpro_paymentapi(&$edit, $op, $arg = '') {
switch ($op) {
case 'display name':
return t('Credit Card');
break;
case 'payment page':
if ($edit->gross > 0) {
return payment_cc_goto($edit, variable_get('payflowpro_url', url('store/payment/payflowpro', null, null, true)));
}
break;
}
}
function payflowpro_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
if ($txn->payment_method != 'payflowpro') return NULL;
switch($op) {
case 'delete':
return payment_cc_delete($edit);
break;
case 'insert':
case 'update':
return payment_cc_save($edit);
break;
}
}
/*
* Internal Functions
*/
function payflowpro_enter_payment() {
global $user;
$txnid = $_GET['txnid'];
$t = store_transaction_load($txnid);
if (($user->uid != $t->uid && !user_access('administer store')) || $t->payment_status != 1) {
drupal_access_denied();
exit();
}
return payflowpro_payment_form($t);
}
function payflowpro_payment_form($t) {
$form['billing'] = array(
'#type' => 'credit_card',
'#required' => TRUE,
'#name' => $t->address['billing']->firstname . ' ' . $t->address['billing']->lastname,
'#cvnshow' => TRUE
);
$form[] = array(
'#type' => 'submit',
'#value' => t('submit payment'),
);
$form['t'] = array(
'#type' => 'value',
'#value' => $t
);
return drupal_get_form('payflowpro_payment_form', $form);
}
function payflowpro_payment_form_validate($form_id, &$values) {
global $form_values; // use global value here so data from processing is available later
$t =& $form_values['t'];
if (!valid_credit_card($form_values) || !_payflowpro_process($form_values)) {
return payment_cc_goto($t, variable_get('payflowpro_url', url('store/payment/payflowpro', null, null, true)));
}
}
function payflowpro_payment_form_submit($form_id, &$form_values) {
if (!valid_credit_card($form_values) || !_payflowpro_process($form_values)) {
$url = url('store/history', null, null, true);
drupal_set_message(t('Sorry, your payment has not been accepted'));
return $url;
}
else {
$t =& $form_values['t'];
$t->payment_status = payment_get_status_id('completed');
$form_values['txnid'] = $t->txnid;
// check to see if there are any shippable items
$has_shippable = false;
foreach ($t->items as $p) {
if (product_is_shippable($p->nid)) {
$has_shippable = true;
break;
}
}
if (!$has_shippable) {
$form_values['workflow'] = 6;
}
store_transaction_save($t);
store_send_invoice_email($t);
$url = variable_get('payflowpro_thanks_url', url('node', null, null, true));
drupal_set_message(t('your payment has been accepted, thank you'));
return $url;
}
}
/*
* Private functions
*/
function _payflowpro_process(&$edit) {
//enter your payflow account info here
$user = "USER";
$vendor = "VENDOR";
$partner = "PARTNER";
$pwd = "PASSWORD";
$host = "test-payflow.verisign.com";
$port = "443";
$amt = $edit[t]->gross;
$acct = $edit['cardnumber'];
$expdate = $edit['expiry']['expmonth'].$edit['expiry']['expyear'];
// Adjust these next two paths appropriately; i.e. where did
// you actually put these two files?
$binary = '/path/to/pfpro';
$library = '/path/to/libpfpro.so';
$newld = dirname($library);
// this is necessary to prevent RESULT=-31 errors; make sure
// it's a *directory* that contains the cert file that came
// with the SDK, and *don't* put a trailing slash on it
putenv("PFPRO_CERT_PATH=/path/to/cert");
// This next chunk of stuff assumes you're having linker issues, and
// attempts to fix or work around them.
// Now, let's fix the linker path:
$original_ld_path = getenv("LD_LIBRARY_PATH");
$newld .= ":$original_path";
putenv("LD_LIBRARY_PATH=$newld");
// having set the linker to find the library, now call the binary:
$cmd = "\"TRXTYPE=S&TENDER=C&PWD=$pwd&USER=$user&VENDOR=$vendor";
$cmd .= "&PARTNER=$partner&ACCT=$acct&EXPDATE=$expdate&AMT=$amt";
$cmd .= "\" 30";
$result = exec("$binary $host $port $cmd", $result_array, $exit_value);
$valArray = explode('&', $result);
foreach($valArray as $val) {
$valArray2 = explode('=', $val);
$pfpro[$valArray2[0]] = $valArray2[1];
}
// To look at the array structure
//print_r($pfpro);
//echo "<br />".$cmd."<br />";
//print_r($edit);
// This should be equal to the RESULT that came back. If you
// get no result string back, but the exit_value here is 126 or 127,
// you've got linker or permission problems.
// echo "the exit value of the system call to pfpro was $exit_value";
if ($pfpro[RESULT] === '0') {
return true;
}
else {
return false;
}
}
/*
* Remove dependancy on PHP5
*/
if(!function_exists('http_build_query')) {
function http_build_query( $formdata, $numeric_prefix = null, $key = null ) {
$res = array();
foreach ((array)$formdata as $k=>$v) {
$tmp_key = urlencode(is_int($k) ? $numeric_prefix.$k : $k);
if ($key) {
$tmp_key = $key.'['.$tmp_key.']';
}
if ( is_array($v) || is_object($v) ) {
$res[] = http_build_query($v, null, $tmp_key);
} else {
$res[] = $tmp_key."=".urlencode($v);
}
}
return implode("&", $res);
}
}
I wasn't quite sure where the function payflowpro_payment_form_validate came into play because it always said payment was accepted, even if the _payflowpro_process function returned false. That's when I stuck the if statement into the payflowpro_payment_form_submit function and now it seems to do what it needs to do.
please let me know any of your thoughts, concerns, etc.
thanks
alynner
PS I wasn't sure exactly where to post this, so I put in feature requests, hope thats ok
| Comment | File | Size | Author |
|---|---|---|---|
| #12 | payflowpro_1.module | 36.86 KB | douggreen |
| #10 | payflowpro.patch_1.txt | 50.85 KB | Anonymous (not verified) |
| #7 | payflowpro.patch_0.txt | 46.1 KB | Anonymous (not verified) |
| #6 | payflowpro.patch.txt | 45.7 KB | Anonymous (not verified) |
| #5 | payflowpro_0.module | 7.22 KB | alynner |
Comments
Comment #1
alynner commentedadded as a file attachment...
Comment #2
alynner commentedI had set up a settings area for the user/partner/pwd for verisign in the settings, but I felt uneasy about the way that it goes into global variables. I'm not sure if this info could be hacked that way. I'm fine with hard coding it into the module for myself, but it might be better to put it into settings for a distributed module.
Any thoughts on this?
alynner
Comment #3
gordon commentedA couple of things,
I really don't like it exec'ing a program. It is just a payment gateway, you should be able to do this from PHP completely.
Also the settings username,password etc in the source needs to be moved to a page for entry.
Lastly, do you really need the password as you should be able to have someone make a payment with passing a password to the gateway.
Comment #4
alynner commentedThat's the thing about Payflowpro and probably why nobody has created a module for it here - you have to use the library and binary files to process it. I found the code for that part on the php.net site (http://ca3.php.net/pfpro), I'll take another look to see if I can do it without using exec.
Yes, you do have to enter the password in for the transaction, it's a requirement. I did have the user/partner/pwd on a settings page that worked, but I was concerned about having them in global variables. Should I be concerned, or is that okay? That's what I meant in comment 2.
thanks
alynner
Comment #5
alynner commentedI haven't looked into getting out of using exec() yet, but I did find that this module was putting through each transaction twice. to fix, replace line 117:
- if (!valid_credit_card($form_values) || !_payflowpro_process($form_values)) {
+ if (!valid_credit_card($form_values)) {
instead of making a patch, I just reattached the module.
Comment #6
Anonymous (not verified) commentedI needed a Payflow Pro module so I stared from the one alynner posted and basically rewrote the whole thing using the Payflow Pro docs. I need some feedback from people so that I can finish this up and make it conform to some sort of ecommerce payment module pattern.
This module is 90% complete at this point. I'll be working on this and updating it over the next couple of days.
Here is a pasting of the TODO:
* TODO:
*
* - We really need the concept of sub-transactions so that we can do auth/delayed
* pairs for AVS/CVV2 and general conformance to our merchant agreement when we
* ship products.
* - Payflow Pro transaction information should be integrated in some sort of
* display in the admin/store/transactions URL or something. This includes the
* Payflow Pro transaction results table which complicates things because there could
* be more than 1 result per original Payflow Pro transaction
* - Need to finish the confirmation page that shows the card is charged
* - Need to test what happens when we interrupt requests and the user hits submit again
* - More testing all around, some general fit and finish things
* - Create a .install file (I am lazy and tired of writing this module)
Comment #7
Anonymous (not verified) commentedUpdated to fix a PARMLIST encoding bug
Added RESPMSG output to the watchdog in the case of a transaction failure
Comment #8
patricksettle commentedSetup evanchsa's version and have had no luck, consistantly coming back with: Transaction error: 1, "User authentication failed." even after all usernames/passwords etc. are varified to be correct. Seen this module listed in several places, is the version listed in responce #7 the most recent version? Or has further development been done on this.
Would be willing to provide some financial motivation for the completion of this module, any takers?
Comment #9
Anonymous (not verified) commentedSorry, I have a new version that works with a number of changes. I'll post the patch soon, no need for any financial incentives.
Comment #10
Anonymous (not verified) commentedNew version:
- Added ability to add credit card badges that display on the payment page
- Fixed numerous bugs in how transaction information is saved
- Cleaned up the confirmation page so that it is more generic
TODO is now:
+ * - We really need the concept of sub-transactions so that we can do auth/delayed
+ * pairs for AVS/CVV2 and general conformance to our merchant agreement when we
+ * ship products.
+ * - Payflow Pro transaction information should be integrated in some sort of
+ * display in the admin/store/transactions URL or something. This includes the
+ * Payflow Pro transaction results table which complicates things because there could
+ * be more than 1 result per original Payflow Pro transaction
+ * - Need to test what happens when we interrupt requests and the user hits submit again
+ * - More testing all around, some general fit and finish things
+ * - Create a .install file (I am lazy and tired of writing this module)
Comment #11
patricksettle commentedTried the new patch, still getting Transaction error: 1, "User authentication failed." errors, Confrimed all username/password is correct. Is the password supposed to be plain text, or should it be encrypted before being added to the file?
Comment #12
douggreen commentedDoes anyone claim ownership of this ever growing module who will put it into CVS and patch appropriately?
I just discovered from PayPal that my client doesn't have PayFlow Pro as the PayPal Manager page claims, but rather has a legacy Cybercash account. So while the test transactions appear to be going through just fine, they never show up in PayPal. So with this realization, I'm not planning on doing any further work on this module. But before I do so, I wanted to contribute back what I have.
Attached is my latest module, with a few patches for storing and displaying the transaction results properly.
Comment #13
simefixing component
Comment #14
alex_b commentedGuys, is this thread obsolete?
See E-Commerce PayFlow Pro module by souvent22 here: http://drupal.org/project/payment_payflowpro
Comment #15
brmassa commentedAlexander,
To let the eC dev team focus on core modules, all new payment gateways shuold be, according our comment on http://drupal.org/node/162699, a new module. and external to eC.
We will try to give as much support as possible, but we simply cant afford develop all asked payments...
regards,
massa
Comment #16
alex_b commentedThanks for your quick reaction and clarification. I was just confused about the open patch here and the module on the other side.
And: thanks for the excellent work on the e-commerce suite.
Alex
Comment #17
(not verified) commented