Dependency: payment.module');
case 'admin/store/settings/payflowpro':
return t('Enter the required parameters that have been supplied during the signup process with payflowpro.');
}
}
function payment_payflowpro_block($op = 'list', $delta = 0) {
if($op == "list") {
$block[0]["info"] = t('PayFlow: Recurring Status');
return $block;
}
else if($op == "view") {
$block_content .= "";
$block_content .= 'Status: Subscribed
';
$block_content .= 'Days left: 16
';
$block_content .= 'Subscription will be renwed on May 16th, 2007';
$block['subject'] = t('Account status');
$block['content'] = $block_content;
}
return $block;
}
/**
* Implementation of hook_menu()
*/
function payment_payflowpro_menu($maycache) {
if ($maycache) {
$items[] = array(
'path' => 'admin/ecsettings/payflowpro',
'title' => t('PayFlow Pro Payment Gateway'),
'callback' => 'drupal_get_form',
'callback arguments' => 'payment_payflowpro_ec_settings',
'description' => t('PayFlow Pro API gateway settings.'),
'access' => user_access('administer store'));
}
$txn = store_transaction_load(arg(3));
$items[] = array(
'path' => 'store/payment/payflowpro/' . arg(3),
'title' => t('Enter Credit Card Details'),
'callback' => 'drupal_get_form',
'callback arguments' => array('payment_payflowpro_payment_form', arg(3)),
'access' => _payflowpro_txn_access_check($txn),
'type' => MENU_CALLBACK
);
// Only allow access if the user is the owner
// (or admin) and the payment is in the right status
$access = _payflowpro_txn_access_check($txn)
&& ($txn->payment_status == payment_get_status_id('payment received'));
$items[] = array(
'path' => 'store/payflowpro/confirmation/' . arg(3),
'title' => t('Order Confirmation'),
'callback' => 'payment_payflowpro_confirmation',
'callback arguments'=> arg(3),
'access' => $access,
'type' => MENU_CALLBACK
);
return $items;
}
/**
* Implementation of hook_paymentapi()
*/
function payment_payflowpro_paymentapi(&$txn, $op, $arg = 0) {
switch ($op) {
case 'display name':
$display_name = t('Credit Card');
/**
* We could do a node_load to test each
* one, but node_load is a very heavy
* operation. Lets just check the table.
*
* We check the transactoin is set becuase
* we may be still building the transaction.
*/
if(isset($txn)) {
$pids = array();
foreach($txn->items as $k => $item) {
$pids[] = $item->nid;
}
$recurring_products = db_query("SELECT nid FROM {ec_recurring_product} WHERE nid IN(%s)", implode(",", $pids));
$units = ec_recurring_get_units();
while($row = db_fetch_object($recurring_products)) {
$product = node_load(array('nid' => $row->nid));
# We know its recurring, lets say something about it
$display_name .= "
\n";
$display_name .= t('This payment will be renewed every %renew_period %renew_terms automatically.', array('%renew_period' => $product->schedule['numunits'], '%renew_terms' => $units[$product->schedule['unit']]));
}
}
return $display_name;
case 'payment page':
return payment_payflowpro_goto($txn);
}
}
/**
* Implementation of hook_ec_transactionapi()
*/
function payment_payflowpro_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
if($txn->payment_method != 'payment_payflowpro') {
return NULL;
}
//error_log("Status is: {$op}> " . print_r($txn, 1));
switch($op) {
case 'delete':
if(isset($txn->payflowpro)) {
_payflowpro_transactionapi_delete($txn);
}
payment_cc_delete($txn);
break;
case 'insert':
// The Payflow Pro information only becomes available once a transaction
// has been made. Which is AFTER the initial insert. Moved code to update area
payment_cc_save($txn);
break;
case 'update':
// First time in during a payment, we just need to record the 'effort'
if(isset($txn->payflowpro->pfpresults) && $txn->payment_status == EC_PAYMENT_RECEIVED) {
_payflowpro_store_transaction_results($txn->txnid, $txn->payflowpro->pfpresults);
}
// Update just inserts a new transaction since payflowpro already has it
_payflowpro_transactionapi_update($txn);
payment_cc_save($txn);
break;
case 'load':
// XXX: This seriously shouldn't be here, it should be handled in payment.module
$ec_credit_card = db_fetch_object(db_query("SELECT * FROM {ec_credit_card} WHERE txnid = %d", $txn->txnid));
$payflowpro = _payflowpro_transactionapi_load($txn);
$pfp_results = _payflowpro_load_transaction_results($txn->txnid);
if(is_object($payflowpro) && count($pfp_results) > 0) {
$payflowpro->pfpresults = $pfp_results;
}
$additions = array_merge(
array('payflowpro'=> $payflowpro),
array('payment'=> $ec_credit_card)
);
return $additions;
break;
default:
break;
}
}
/**
* This page is presented to the user so that they know what is going on with their order
*/
function payment_payflowpro_confirmation($txnid) {
$txn = store_transaction_load($txnid);
$txn->payment_status = payment_get_status_id('completed');
store_transaction_save($txn);
store_send_invoice_email($txn);
return theme('store_invoice', $txn, false, false, true);
}
/**
* Display the credit card form
*/
function payment_payflowpro_payment_form($txnid) {
$txn = store_transaction_load($txnid);
/*
$place_holders = array(
'%payflowpro_help'=> variable_get('payflowpro_help', payflowpro_help('payflowpro/submission/guidelines'))
);
$form['help'] = array(
'#value' => t('
%payflowpro_help
', $place_holders),
'#weight'=> -10,
);
*/
$form['billing'] = array(
'#type' => 'credit_card',
'#required' => true,
'#name' => $txn->address['billing']->firstname . ' ' . $txn->address['billing']->lastname,
'#cvnshow' => variable_get('ec_payflowpro_use_cvv2', 1),
'#prefix'=> _payflowpro_generate_accepted_cards(),
);
$form[] = array(
'#type' => 'submit',
'#value' => t('submit payment'),
);
$form['txnid'] = array(
'#type' => 'value',
'#value' => $txnid
);
$form['txn'] = array(
'#type' => 'value',
'#value' => $txn
);
$secure = 'https://';
if(variable_get('ec_payflowpro_use_secure_redirect', 1) == 0) {
$secure = 'http://';
}
$form['#action'] = str_replace('http://', $secure, url("store/payment/payflowpro/$txnid", null, null, true));
$form['#method'] = 'POST';
return $form;
}
/**
* Validation for the form 'payflowpro_payment_form'
*
* This returns you to the payment collection page if there is a problem.
* The customer's credit card is not charged (that happens in the form submit function)
*/
function payment_payflowpro_payment_form_validate($form_id, &$values) {
$txn = $values['txn'];
if(!valid_credit_card($values)) {
// XXX: Why store this now (I got this from example payment modules)?
store_transaction_save($txn);
return payment_payflowpro_goto($txn);
}
$txn->payment_status = payment_get_status_id('pending');
store_transaction_save($txn);
}
/**
* Form submission handler - the actual work horse function
*
* This is where we call out to Payflow Pro services
*/
function payment_payflowpro_payment_form_submit($form_id, &$form_values) {
$txnid = $form_values['txnid'];
if(!_payflowpro_process_submit($txnid, $form_values)) {
return payment_payflowpro_goto($form_values['txn']);
}
// Transaction is successful
$secure = 'https://';
if(variable_get('ec_payflowpro_use_secure_redirect', 1) == 0) {
$secure = 'http://';
}
$confirmation_url = str_replace('http://', $secure, url("store/payflowpro/confirmation/$txnid", NULL, NULL, TRUE));
return $confirmation_url;
}
/*
* TODO: This needs rewritten
*/
function _payflowpro_process_submit($txnid, &$form_values) {
$txn = store_transaction_load($txnid);
/*
* TODO: Technically anything that is shipable must go through as an
* authorization transaction (TRXTYPE=A) and then a subsequent
* delayed transaction (TRXTYPE=D). This authorization transaction
* reduces the purchaser's limit but the funds are not transferred until
* the subequent delayed transaction is processed.
*
* Additionally if you want to take advantage of Fraud Protection Services
* such as AVS and the CVV2 features you must submit the transactions as
* authorization/delayed pairs.
*
* What this means in practice is that an order is really a collection of
* subtransactions, one for each shippable item in the cart and one for all
* non-shippable items.
*
* This module is designed to allow such functionality but it is not implemented
* at this point in time.
*
* Patches are welcome (It is a simple feature to implement).
*/
$pfp_transaction = array(
'user'=> variable_get('ec_payflowpro_user', ''),
'vendor'=> variable_get('ec_payflowpro_vendor', ''),
'partner'=> variable_get('ec_payflowpro_partner', ''),
'pwd'=> PAYFLOWPRO_PWD,
'amt'=> trim($txn->gross),
'acct'=> $form_values['cardnumber'],
'expdate'=> $form_values['expiry']['expmonth'].$form_values['expiry']['expyear'],
'trxtype' => 'S',
//'trxtype'=> payment_payflowpro_get_trxtype('sale transaction'), -- Leave here for now for when we want to implement POS
'tender'=> payment_payflowpro_get_tender_id('credit card'),
'cvn' => $form_values['cvn']
);
# Support shipping module/api
if(isset($txn->address)) {
$pfp_transaction['name'] = $txn->address['billing']->firstname . ' ' . $txn->address['billing']->lastname;
}
drupal_set_message('There');
// We want to save CC information for this transaction
_payflowpro_save_cc($form_values);
$txn_results = _payflowpro_process($pfp_transaction);
$txn_results = (array)$txn_results;
$txn_results = (object)$txn_results;
$txn_result = (array)$txn_results->TransactionResult;
$txn_result = (object)$txn_result;
$txn_results->Vendor = '';
$txn_results->Partner = '';
$txn->payflowpro = $txn_results;
$txn->payflowpro->pfpresults = (object)((array)$txn_results->TransactionResult);
if(isset($txn_result->FAILED)) {
drupal_set_message(t('There was a technical error while processing your request. Your credit card was not charged.'));
$txn->payment_status = payment_get_status_id('failed');
store_transaction_save($txn);
return false;
}
else if($txn_result->Result != 0) {
$message = _payflowpro_code_to_string($txn_result->Result);
$place_holders = array(
'%result' => theme('placeholder', $txn_result->Result),
'%message' => theme('placeholder', $message),
'%resp_message' => theme('placeholder', $txn_result->Message)
);
watchdog('payflowpro', t('Transaction error: %result, "%message"/"%resp_message"', $place_holders));
$message = '';
if($txn_result->Result < 0) {
// XXX: This means a 'Communications error' according to the docs
$message = t('There was a technical error while processing your request. Your credit card was not charged.');
$txn->payment_status = payment_get_status_id('failed');
}
else {
// XXX: Declined or AVS failure or something non-technical
$message = _payflow_process_error_message($txn_result->Result);
$txn->payment_status = payment_get_status_id('denied');
}
store_transaction_save($txn);
die($message);
return false;
}
else {
// TODO: BIG TODO HERE - The credit card was successfully charged now what?
$txn->payment_status = payment_get_status_id('payment received');
store_transaction_save($txn);
$has_shippable = false;
foreach($txn->items as $p) {
if(product_is_shippable($p->nid)) {
$has_shippable = true;
}
else {
// TODO: Perform delayed capture transaction
// for this item as we can satisfy it immediately
}
}
// If there aren't any shippable items then this transaction is completed
if (!$has_shippable) {
$form_values['workflow'] = transaction_get_workflow_id('completed');
}
return true;
}
}
/**
* Saves and encrypts the CC information
*/
function _payflowpro_save_cc($txn_info) {
// We are going to do PK's on a per txn basis
// thus if one line is breached, ti will not comprimise the
// entier system as a whole.
$iv_size = mcrypt_get_iv_size(MCRYPT_BLOWFISH, MCRYPT_MODE_ECB);
$iv = mcrypt_create_iv($iv_size, MCRYPT_RAND);
$encrypted_credit_card = mcrypt_encrypt(MCRYPT_BLOWFISH, $txn_info['txn']->token, $txn_info['cardnumber'], MCRYPT_MODE_ECB, $iv);
$crid = db_next_id('{ec_payflowpro_tax_credit_card}_crid');
$cc_display = "";
for($i=0; $i < strlen($txn_info['cardnumber']); $i++) {
if( $i >= strlen($txn_info['cardnumber']) - 5) {
$cc_display .= $txn_info['cardnumber'][$i];
}
}
db_query("INSERT INTO {ec_payflowpro_txn_credit_card}(crid, cc_num, cc_exp_mon, cc_exp_year, cc_avs, txnid, cc_display, token) " .
"VALUES('%s', '%s', '%s', '%s', '%s', %d, '%s', '%s')",
$crid, $encrypted_credit_card, $txn_info['expiry']['expmonth'], $txn_info['expiry']['expyear'], $ccavs, $txn_info['txnid'], $cc_display, $txn_info['txn']->token);
return;
}
/*
* Redirect the user to the secure payment form
*/
function payment_payflowpro_goto($txn) {
$secure = 'https://';
if(variable_get('ec_payflowpro_use_secure_redirect', 1) == 0) {
$secure = 'http://';
}
$payment_url = str_replace('http://', $secure, url('store/payment/payflowpro/'. $txn->txnid, NULL, NULL, TRUE));
return payment_cc_goto($txn, $payment_url);
}
/**
* Implementation of hook_ec_settings()
*/
function payment_payflowpro_ec_settings() {
$form = array();
/*
* ACCOUNT INFORMATION
*/
$form['ec_payflowpro_account_info'] = array(
'#type' => 'fieldset',
'#title' => t('Account information'),
'#weight' => -6,
'#collapsible' => true,
'#collapsed' => false,
);
$vendor = variable_get('ec_payflowpro_vendor', '');
$form['ec_payflowpro_account_info']['ec_payflowpro_vendor'] = array(
'#type' => 'textfield',
'#title' => t('Vendor ID'),
'#default_value' => $vendor,
'#size' => 70,
'#maxlength' => 64,
'#description' => t('Your merchant login ID'),
'#required' => true
);
$user = variable_get('ec_payflowpro_user', '');
$form['ec_payflowpro_account_info']['ec_payflowpro_user'] = array(
'#type' => 'textfield',
'#title' => t('User ID'),
'#default_value' => $user,
'#size' => 70,
'#maxlength' => 180,
'#description' => t('The user ID used to process transactions, this should be the same as your vendor ID unless you have authorized more than 1 user to process transactions'),
'#required' => true
);
$partner = variable_get('ec_payflowpro_partner', '');
$form['ec_payflowpro_account_info']['ec_payflowpro_partner'] = array(
'#type' => 'textfield',
'#title' => t('Partner ID'),
'#default_value' => $partner,
'#size' => 70,
'#maxlength' => 180,
'#description' => t('The partner ID of the Payflow Pro resller'),
'#required' => true
);
/*
* SERVER INFORMATION
*/
$form['ec_payflowpro_server_info'] = array(
'#type' => 'fieldset',
'#title' => t('Account information'),
'#weight' => -4,
'#collapsible' => TRUE,
'#collapsed' => FALSE,
);
$mode = variable_get('ec_payflowpro_tx_mode', 'test');
$form['ec_payflowpro_server_info']['ec_payflowpro_tx_mode'] = array(
'#type' => 'radios',
'#title' => t('Transaction mode'),
'#default_value' => $mode,
'#required'=> true,
'#options' => array('test'=> t('Test'), 'live'=> t('Live')),
'#description'=> t('The transaction mode, either test or live.'),
);
$test_server = variable_get('ec_payflowpro_test_server', 'test-payflow.ppv.paypal.com');
$form['ec_payflowpro_server_info']['ec_payflowpro_test_server'] = array(
'#type' => 'textfield',
'#title' => t('Test server'),
'#default_value' => $test_server,
'#size' => 70,
'#maxlength' => 64,
'#description' => t('Test server hostname.'),
'#required' => true
);
$test_server_port = variable_get('ec_payflowpro_test_server_port', '443');
$form['ec_payflowpro_server_info']['ec_payflowpro_test_server_port'] = array(
'#type' => 'textfield',
'#title' => t('Test server port'),
'#default_value' => $test_server_port,
'#size' => 7,
'#maxlength' => 5,
'#description' => t('Test server port. If you don\'t know it is best to leave this value set to the default'),
'#required' => true
);
$live_server = variable_get('ec_payflowpro_live_server', 'payflow.ppv.paypal.com');
$form['ec_payflowpro_server_info']['ec_payflowpro_live_server'] = array(
'#type' => 'textfield',
'#title' => t('Live server'),
'#default_value' => $live_server,
'#size' => 70,
'#maxlength' => 64,
'#description' => t('Live server hostname'),
'#required' => true
);
$live_server_port = variable_get('ec_payflowpro_live_server_port', '443');
$form['ec_payflowpro_server_info']['ec_payflowpro_live_server_port'] = array(
'#type' => 'textfield',
'#title' => t('Live server port'),
'#default_value' => $live_server_port,
'#size' => 7,
'#maxlength' => 5,
'#description' => t('Live server port. If you don\'t know it is best to leave this value set to the default'),
'#required' => true
);
/*
* TRANSACTION OPTIONS
*/
$form['ec_payflowpro_transaction_options'] = array(
'#type' => 'fieldset',
'#title' => t('Transaction Options'),
'#weight' => -4,
'#collapsible' => true,
'#collapsed' => false,
);
$secure = variable_get('ec_payflowpro_use_secure_redirect', 1);
$form['ec_payflowpro_transaction_options']['ec_payflowpro_use_secure_redirect'] = array(
'#type' => 'checkbox',
'#title' => t('Force secure form'),
'#default_value' => $secure,
'#description' => t('Redirect to a secure URL so that the payment can be received. Make sure you are in test mode if you turn this off'),
);
$avs = variable_get('ec_payflowpro_use_avs', 0);
$form['ec_payflowpro_transaction_options']['ec_payflowpro_use_avs'] = array(
'#type' => 'checkbox',
'#title' => t('Use Address Verification System'),
'#default_value' => $avs,
'#description' => t('Turn on Address Verification System for all transactions. XXX: NOT IMPLEMENTED'),
);
$cvv2 = variable_get('ec_payflowpro_use_cvv2', 0);
$form['ec_payflowpro_transaction_options']['ec_payflowpro_use_cvv2'] = array(
'#type' => 'checkbox',
'#title' => t('Use CVV2/CVC2/CID Codes'),
'#default_value' => $cvv2,
'#description' => t('Turn on use of CVV2/CVC2/CID codes for all transactions. XXX: NOT IMPLEMENTED'),
);
/*
* PAYFLOWPRO SDK INFORMATION
*/
$form['ec_payflowpro_sdk_information'] = array(
'#type' => 'fieldset',
'#title' => t('Payflow Pro SDK information'),
'#weight' => -2,
'#collapsible' => true,
'#collapsed' => false,
);
$binary = variable_get('ec_payflowpro_sdk_binary', '');
$form['ec_payflowpro_sdk_information']['ec_payflowpro_sdk_binary'] = array(
'#type' => 'textfield',
'#title' => t('Payflow Pro Binary'),
'#default_value' => $binary,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to Payflow Pro binary (pfpro)'),
//'#validate'=> _payflowpro_validate_sdk_binary,
'#required' => true
);
$library = variable_get('ec_payflowpro_sdk_library', '');
$form['ec_payflowpro_sdk_information']['ec_payflowpro_sdk_library'] = array(
'#type' => 'textfield',
'#title' => t('Payflow Pro Library'),
'#default_value' => $library,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to Payflow Pro library (libpfpro.so)'),
//'#validate'=> _payflowpro_validate_sdk_library,
'#required' => true
);
$certpath = variable_get('ec_payflowpro_sdk_cert_path', '');
$form['ec_payflowpro_sdk_information']['ec_payflowpro_sdk_cert_path'] = array(
'#type' => 'textfield',
'#title' => t('Payflow Pro Certificate path'),
'#default_value' => $certpath,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to Payflow Pro certificates'),
//'#validate'=> _payflowpro_validate_sdk_cert_path,
'#required' => true
);
/*
* CARD PROCESSOR INFORMATION
*/
$form['ec_payflowpro_card_processor_information'] = array(
'#type' => 'fieldset',
'#title' => t('Credit Card Processor information'),
'#weight' => -2,
'#collapsible' => true,
'#collapsed' => false,
);
$accept = variable_get('ec_payflowpro_accept_visa', 0);
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_accept_visa'] = array(
'#type' => 'checkbox',
'#title' => t('VISA'),
'#default_value' => $accept,
'#description' => t('Accept VISA Cards'),
);
$image_path = variable_get('ec_payflowpro_visa_image', '');
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_visa_image'] = array(
'#type' => 'textfield',
'#title' => t('VISA Image'),
'#default_value' => $image_path,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to VISA checkout image'),
);
$accept = variable_get('ec_payflowpro_accept_mc', 0);
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_accept_mc'] = array(
'#type' => 'checkbox',
'#title' => t('MasterCard'),
'#default_value' => $accept,
'#description' => t('Accept MasterCard'),
);
$image_path = variable_get('ec_payflowpro_mc_image', '');
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_mc_image'] = array(
'#type' => 'textfield',
'#title' => t('MasterCard Image'),
'#default_value' => $image_path,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to MasterCard checkout image'),
);
$accept = variable_get('ec_payflowpro_accept_amex', 0);
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_accept_amex'] = array(
'#type' => 'checkbox',
'#title' => t('American Express'),
'#default_value' => $accept,
'#description' => t('Accept American Express Cards'),
);
$image_path = variable_get('ec_payflowpro_amex_image', '');
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_amex_image'] = array(
'#type' => 'textfield',
'#title' => t('American Express Image'),
'#default_value' => $image_path,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to American Express checkout image'),
);
$accept = variable_get('ec_payflowpro_accept_discover', 0);
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_accept_discover'] = array(
'#type' => 'checkbox',
'#title' => t('Discover Card'),
'#default_value' => $accept,
'#description' => t('Accept Discover Card'),
);
$image_path = variable_get('ec_payflowpro_discover_image', '');
$form['ec_payflowpro_card_processor_information']['ec_payflowpro_discover_image'] = array(
'#type' => 'textfield',
'#title' => t('Discover Card Image'),
'#default_value' => $image_path,
'#size' => 70,
'#maxlength' => 255,
'#description' => t('Path to Discover Card checkout image'),
);
return system_settings_form($form);
}
function payment_payflowpro_get_status($id) {
$status = payment_payflowpro_build_status();
return $status[$id];
}
function payment_payflowpro_get_status_id($name) {
return array_search(strtolower(t($name)), payment_payflowpro_build_status());
}
/**
* Return an array of payflowpro status codes.
*/
function payment_payflowpro_build_status() {
$payflowpro_status = array (
0 => t('normal'),
1 => t('deleted'),
2 => t('failed'),
3 => t('auth'),
4 => t('settled')
);
return $payflowpro_status;
}
function payment_payflowpro_get_trxtype($id) {
$status = payment_payflowpro_build_trxtype();
return $status[$id];
}
function payment_payflowpro_get_trxtype_id($name) {
return array_search(strtolower(t($name)), payment_payflowpro_build_trxtype());
}
/**
* Return an array of payflowpro TRXTYPE codes.
*/
function payment_payflowpro_build_trxtype() {
$payflowpro_status = array (
t('sale transaction') => 'S',
t('credit') => 'C',
t('auth') => 'A' ,
t('delayed capture') => 'D',
t('void') => 'V',
t('voice authorization') => 'F',
t('inquiry') => 'I',
);
return $payflowpro_status;
}
function payment_payflowpro_get_tender($id) {
$status = payment_payflowpro_build_tender();
return $status[$id];
}
function payment_payflowpro_get_tender_id($name) {
return array_search(strtolower(t($name)), payment_payflowpro_build_tender());
}
/**
* Return an array of payflowpro TENDER codes.
*/
function payment_payflowpro_build_tender() {
$payflowpro_status = array (
'A'=> t('automated clearinghouse'),
'C'=> t('credit card'),
'D'=> t('pinless debit'),
'E'=> t('electronic check'),
'K'=> t('telecheck'),
'P'=> t('paypal'),
);
return $payflowpro_status;
}
/*
* INTERNAL FUNCTIONS
*
*
*
*/
function _payflowpro_transaction_summary() {
return '';
}
/*
* Trivial access checking for hook_menu and elsewhere
*/
function _payflowpro_txn_access_check($txn) {
global $user;
if ($user->uid != $txn->uid && !user_access('administer store')) {
return false;
}
return true;
}
/*
* transactionapi helper functions
* (mostly to keep the case statement from getting unmaintainable)
*/
function _payflowpro_transactionapi_insert($txn) {
global $user;
$address_info = $txn->address['billing'];
$address = $address_info->street1 . ' ' . $address_info->street2;
$id = db_next_id('{ec_payflowpro_txn}_ec_pfp_id');
$pfp_query = "INSERT INTO {ec_payflowpro_txn}(ec_pfp_id, txnid, amt, comment1, comment2, custref, name, street, tender, trxtype, zip, status, workflow, payment_status) " .
"VALUES(%d, %d, %f, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', %d, %d, %d)";
$args = array();
$args[] = $id;
$args[] = $txn->txnid;
$args[] = $txn->gross;
$args[] = "Invoice $txn->txnid";
$args[] = "";
$args[] = $user->uid;
$args[] = $txn->name;
$args[] = $address;
$args[] = 'C'; // Always credit card for now
$args[] = 'S'; // Sale type always for now
$args[] = $address_info->zip;
$args[] = $txn->payflowpro->TransactionResult->Result;
$args[] = $txn->workflow;
$args[] = $txn->payment_status;
db_query($pfp_query, $args);
}
/*
* Normally this would update the transaction information
* but since we submitted this information to the Payflow Pro system
* we really should just treat this as a new transaction
*
* If we have updated this to "shipped", we then need to capture the auth,
* verify that we have captured the auth, and post an error msg if we
* have not captured the uath.
*/
function _payflowpro_transactionapi_update($txn) {
_payflowpro_transactionapi_insert($txn);
return;
$query = 'SELECT txnid FROM {ec_payflowpro_txn} WHERE txnid = %d';
if(db_num_rows(db_query($query, $txn->txnid)) == 0) {
_payflowpro_transactionapi_insert($txn);
}
else {
$status = '';
if($txn->workflow == 1) {
//$status = 'R';
}
$pfp_query = "UPDATE {ec_payflowpro_txn} SET amt = %f, custref = %d, name='%s', tender='C', trxtype='%s', zip='%s', status=%d WHERE txnid = %d";
$args = array();
$args[] = $txn->gross;
$args[] = $txn->uid;
$args[] = $txn->payflowpro->name;
$args[] = $txn->payflowpro->trxtype;
$args[] = $txn->payflowpro->zip;
$args[] = $txn->payflowpro->status;
$args[] = $txn->payflowpro->txnid;
db_query($pfp_query, $args);
}
}
/*
* We don't really delete the transaction because it was sent through
* the Payflow Pro system and it is useful to have the information hanging
* around (i.e. the customer disputes the transaction)
*/
function _payflowpro_transactionapi_delete($txn) {
$query = 'UPDATE {ec_payflowpro_txn} SET status = %d WHERE txnid = %d';
return db_query($query, payflowpro_get_status('deleted'), $txn->txnid);
}
/*
* Load the transaction information submitted to the Payflow Pro system
*/
function _payflowpro_transactionapi_load($txn) {
$database_map = _payflowpro_transaction_database_map($txn);
$projection = implode(',', array_keys($database_map['types']));
$query_string = 'SELECT ' . $projection . ' FROM {ec_payflowpro_txn} WHERE txnid = %d';
return db_fetch_object(db_query($query_string, $txn->txnid));
}
/*
* Store Payflow Pro transaction results
*/
function _payflowpro_store_transaction_results($txnid, $pfp_results) {
#$database_map = _payflowpro_results_database_map($pfp_results);
$pfp_id = db_next_id('{ec_payflowpro_result}_ec_pfp_id');
$query = "INSERT INTO {ec_payflowpro_result}(ec_pfp_id, txtime, txnid, pnref, result, cvv2match, respmsg, " .
"authcode, avsaddr, avszip, iavs) " .
"VALUES(%d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s')";
$args = array($pfp_id, time(), $txnid, $pfp_results->PNRef, $pfp_results->Result,
$pfp_results->CVV2, $pfp_results->Message, $pfp_results->AuthCode, $pfp_results->AVSAddr, $pfp_results->AVSZip, $pfp_results->IAVS);
db_query($query, $args);
#$pfp_query = 'INSERT INTO {ec_payflowpro_result} ('. implode(', ', array_keys($database_map['types'])) .') VALUES ('. implode(', ', $database_map['types']) .')';
#db_query($pfp_query, $database_map['values']);
/*
db_query($query, $args);
# Audit this transation
# Audit this transaction
$audit_query = "INSERT INTO {ec_payflowpro_audit}(ec_pfp_id, txtime, pnref, result, cvv2match, respmsg, authcode, avsaddr, avszip, iavs) " .
"VALUES(%d, %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s')";
db_query($audit_query, $pfp_id, $pfp_results->TXNTIME, $pfp_results->PNRef, $pfp_results->Result,
$pfp_results->CVV2, $pfp_results->Message, $pfp_results->AuthCode, $pfp_results->AVSAddr, $pfp_results->AVSZip, $pfp_results->IAVS);
*/
return;
}
function _payflowpro_load_transaction_results($txnid) {
$database_map = _payflowpro_results_database_map(array());
$transaction_results = array();
$pfp_query = 'SELECT ' . implode(',', array_keys($database_map['types'])) . ' FROM {ec_payflowpro_result} WHERE txnid = %d ORDER BY txtime DESC';
$results = db_query($pfp_query, $txnid);
while($txr = db_fetch_object($results)) {
$transaction_results[] = $txr;
}
return $transaction_results;
}
/*
* Mapping function for Payflow Pro transaction data
*/
function _payflowpro_transaction_database_map($txn) {
$pfp_txn = (object)$txn->payflowpro;
$types = array(
'txnid'=> '%d',
'amt'=> '%f',
'comment1'=> '"%s"',
'comment2'=> '"%s"',
'currency'=> '"%s"',
'custref'=> '"%s"',
'name'=> '"%s"',
'street'=> '"%s"',
'tender'=> '"%s"',
'trxtype'=> '"%s"',
'zip'=> '"%s"',
'status'=> '%d',
);
$values = array(
'txnid'=> $txn->txnid,
'amt'=> $pfp_txn->amt,
'comment1'=> $pfp_txn->comment1,
'comment2'=> $pfp_txn->comment2,
'currency'=> $pfp_txn->currency,
'custref'=> $pfp_txn->custref,
'name'=> $pfp_txn->name,
'street'=> $pfp_txn->street,
'tender'=> $pfp_txn->tender,
'trxtype'=> $pfp_txn->trxtype,
'zip'=> $pfp_txn->zip,
'status'=> $pfp_txn->status,
);
return array('values'=> $values, 'types'=> $types);
}
/*
* Mapping function for Payflow Pro result data
*/
function _payflowpro_results_database_map($pfp_results) {
$pfp_results = (object)$pfp_results;
$types = array(
'txtime'=> '%d',
'txnid'=> '%d',
'pnref'=> '"%s"',
'result'=> '%d',
'cvv2match'=> '"%s"',
'respmsg'=> '"%s"',
'authcode'=> '"%s"',
'avsaddr'=> '"%s"',
'avszip'=> '"%s"',
'iavs'=> '"%s"',
);
$values = array(
'txtime'=> $pfp_results->txtime,
'txnid'=> $pfp_results->txnid,
'pnref'=> $pfp_results->pnref,
'result'=> $pfp_results->result,
'cvv2match'=> $pfp_results->cvv2match,
'respmsg'=> $pfp_results->respmsg,
'authcode'=> $pfp_results->authcode,
'avsaddr'=> $pfp_results->avsaddr,
'avszip'=> $pfp_results->avszip,
'iavs'=> $pfp_results->iavs,
);
return array('values'=> $values, 'types'=> $types);
}
function _payflowpro_generate_accepted_cards() {
$card_string = '';
foreach(array('discover', 'amex', 'mc', 'visa') as $card_type) {
if(variable_get("ec_payflowpro_accept_$card_type", 0)) {
$path = variable_get("ec_payflowpro_${card_type}_image", '');
$alt_string = t("We accept $card_type");
$card_string .= "
";
}
}
return '' . $card_string . '
';
}
function _payflowpro_remove_invalid_chars($value) {
return str_repalce(PAYFLOWPRO_INVALID_CHARS, " ", $value);
}
/*
* Build a PARMLIST string
*
* @see https://www.paypal.com/en_US/pdf/PayflowPro_Guide.pdf, p25 "PARMLIST Syntax Guidelines"
*/
function _payflowpro_build_parmlist($transaction) {
$parmlist = implode('&', array_map('_payflowpro_construct_parmlist_entry', array_keys($transaction), array_values($transaction)));
return '"' . $parmlist . '"';
}
function _payflowpro_construct_parmlist_entry($key, $value) {
$value_len = strlen($value);
// INFO: return KEY[VALUE_LENGTH]=VALUE
if(strpos($value, PAYFLOWPRO_ESCAPE_CHARS)) {
return strtoupper($key) . '[' . $value_len . ']=' . $value;
}
return strtoupper($key) . '=' . $value;
}
/*
* Parses a result string
*
* @see https://www.paypal.com/en_US/pdf/PayflowPro_Guide.pdf, p25 "PARMLIST Syntax Guidelines"
*/
function _payflowpro_parse_results($result_string) {
// Terminate the result string with an & just to make parsing easier
$work_string = $result_string . '&';
$results = array();
$matches = array();
$full_parse = true;
// XXX: SAE - I don't like this $full_parse busniess but I don't feel like fixing it
while(preg_match("/^([^&\[\]]+)(\[\d+\])?=/", $work_string, $matches) && $full_parse) {
// XXX: Should we check to see if the result key is alpha-numeric?
$key = $matches[1];
/*
* Begin to calculate the length of the result string for this key as
* this will be removed from $work_string
*/
$result_str_len = strlen($key);
$value = '';
$value_len = 0;
if(count($matches) == 3 && strlen($matches[2]) > 0) {
$result_str_len += strlen($matches[2]);
$value_len = ltrim(rtrim($matches[2], "]"), "[");
}
// Count the = sign in KEY([\d+])?=
$result_str_len++;
if($value_len > 0) {
$value = substr($work_string, $result_str_len, $value_len);
}
else {
if(!preg_match("/([^&]+)&/", substr($work_string, $result_str_len), $matches)) {
$full_parse = false;
watchdog('payflowpro', t('Error parsing Payflow Pro string, data not fully parsed: ') . substr($work_string, $result_str_len));
}
$value = $matches[1];
$value_len = strlen($value);
}
/*
* Add the length of the value to the result string length
* and the add 1 to handle the & after the result/value pair
*/
$result_str_len = $result_str_len + $value_len + 1;
// Remove this result item from the work string
$work_string = substr($work_string, $result_str_len);
$results[$key] = $value;
}
if(strlen($work_string) > 0 || !$full_parse) {
watchdog('payflowpro', t('Error parsing Payflow Pro string, data not fully parsed: ') . $work_string);
}
return $results;
}
function _xml_wrap($tag, $string) {
if(trim($string) == '')
return "<$tag/>";
else
return "<$tag>$string$tag>";
}
/**
* TODO: This should really be its own class in which you
* add information to, and then request the compiled XML
* document to send.
*
* Doing xml lets us break down a transaction to its basic elements.
* We have, a minnimum:
* - Tender
* - Amount
* - Login info
*
* This defaults to a simple Credit Card Sale transaction
*
* TODO:
* We need simple types and more granular control.
* Simple types:
* - Invoice
* - Billto
* - Shipto
*/
function _payflowpro_get_xml_template($type = 'S', $args = array('tender_type' => 'C')) {
$xml_header = '
%VENDOR
%PARTNER';
$xml_footer = '
%USERNAME
%USERPASS
';
$transaction = '';
# Tender Section
if($args['tender_type'] == 'C') {
$card = '';
#$card .= _xml_wrap('CardType', '%CARDTYPE'); // Need a function to detect card type
$card .= _xml_wrap('CardType', '');
$card .= _xml_wrap('CardNum', '%CARDNUM');
$card .= _xml_wrap('ExpDate', '%EXPDATE');
$card .= _xml_wrap('NameOnCard', '');
if(isset($args['cvn'])) {
$card .= _xml_wrap('CVNum', '%CVN');
}
$tender = _xml_wrap('Card', $card);
}
// Now build the inner part
if($type == 'S') {
// See if we need to add CVS
$transaction = "
%TAXINC
%AMT
$tender
";
}
// Auth a card
else if($type == 'A') {
$transaction = "
%TAXINC
%AMT
$tender
";
}
// Caputre/Delayred Cature
else if($type == 'D') {
$transaction = "" .
"%PNREF" .
"";
}
// Void Auth
else if($type == 'V') {
}
# Credit Customer
else if($type == 'C') {
}
// Recurring Billing section
else if($type == 'A') {
$recurring = "
$tender
%RECURRING_PROFILE_NAME
%AMT
%RECURRING_START_DATE
%RECURRING_TERMS
%RECURRING_PERIOD
%EMAIL
Sale
12
123 4th street
San Jose
CA
95032
";
}
else if($type == 'I') {
$recurring = "
%RECURRING_PROFILE_NAME
";
}
else if($type == 'C') {
}
else if($type == 'U') {
}
return $xml_header . $transaction . $xml_footer;
}
function _payflowpro_process_xml($transaction) {
// Now build the XML request
// For now we are only doing Credit Cards and not taking anything else
// because we're not implementing a POS (Point-of-Sale)
if($transaction['trxtype'] == 'A') {
$pnref = db_result(db_query("SELECT pnref FROM {ec_payflowpro_result} WHERE txnid = %d", $transaction['txnid']));
// Recurring Transaction Args
$txn_args['%RECURRING_PROFILE_NAME'] = $transaction['profile_name'];
$txn_arg['%RECURRING_PROFILE_ID'] = $transaction['profile_id'];
// Grab the template to use
$xml_request = _payflowpro_get_xml_template($transaction['trxtype'], array('PNREF' => TRUE));
}
# Assume Sale by default
else {
# Initialize the required elements
$txn_args = array('%VENDOR' => null, '%PARTNER' =>null, '%TAXINC' => 'FALSE', '%AMT' => null, '%CARDTYPE' => null, '%CARDNUM' => null, '%EXPDATE' => null,
'%UESRNAME' => null, '%USERPASS' => null);
$txn_args['%VENDOR'] = $transaction['vendor'];
$txn_args['%PARTNER'] = $transaction['partner'];
$txn_args['%USERNAME'] = $transaction['user'];
$txn_args['%USERPASS'] = $transaction['pwd'];
$txn_args['%AMT'] = $transaction['amt'];
$txn_args['%CARDNUM'] = $transaction['acct'];
$txn_args['%EXPDATE'] = '20' . substr($transaction['expdate'], 2) . substr($transaction['expdate'], 0, 2);
$txn_args['%TENDERCODE'] = $transaction['tender'];
$txn_args['%TXNTYPE'] = $transaction['trxtype'];
$txn_args['%CVN'] = trim($transaction['cvn']);
$xml_args = array('tender_type' => 'C');
if(variable_get('ec_payflowpro_use_cvv2', 0)) {
$xml_args['cvn'] = TRUE;
}
$xml_request = _payflowpro_get_xml_template('S', array('tender_type' => 'C', 'cvn' => TRUE));
}
$xml_request = strtr($xml_request, $txn_args);
// Send the transaction
$txn_result = _payflowpro_send_transaction($xml_request);
return $txn_result;
}
function _payflowpro_send_transaction($xml_request) {
$binary = variable_get('ec_payflowpro_sdk_binary', '');
$library = variable_get('ec_payflowpro_sdk_library', '');
$certpath = variable_get('ec_payflowpro_sdk_cert_path', '');
$server = variable_get('ec_payflowpro_test_server', 'test-payflow.ppv.paypal.com');
$server_port = variable_get('ec_payflowpro_test_server_port', '443');
$mode = variable_get('ec_payflowpro_tx_mode', 'test');
if($mode != 'test') {
$server = variable_get('ec_payflowpro_live_server', 'payflow.ppv.paypal.com');
$server_port = variable_get('ec_payflowpro_live_server_port', '443');
}
$reponse = NULL;
// We have to use these servers for the cURL method below
if ($mode == 'test') {
$server = 'pilot-payflowpro.verisign.com';
}
else {
$server = 'payflowpro.verisign.com';
}
$request_id = sha1($xml_request . time());
$user_agent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)";
$headers[] = "Content-Type: text/xml"; // either text/namevalue or text/xml
$headers[] = "X-VPS-Timeout: 30";
$headers[] = "X-VPS-VIT-OS-Name: Linux"; // Name of your Operating System (OS)
$headers[] = "X-VPS-VIT-OS-Version: RHEL 4"; // OS Version
$headers[] = "X-VPS-VIT-Client-Type: PHP/cURL"; // Language you are using
$headers[] = "X-VPS-VIT-Client-Version: 1.0"; // For your info
$headers[] = "X-VPS-VIT-Client-Architecture: x86"; // For your info
$headers[] = "X-VPS-VIT-Client-Certification-Id: 33baf5893fc2123d8b191d2d011b7fdc"; // This header requirement will be removed
$headers[] = "X-VPS-VIT-Integration-Product: Drupal eCommerce"; // For your info, would populate with application name
$headers[] = "X-VPS-VIT-Integration-Version: 3.0"; // Application version
$headers[] = "X-VPS-Request-ID: " . $request_id;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://' . $server . '/transaction:443/');
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch, CURLOPT_USERAGENT, $user_agent);
curl_setopt($ch, CURLOPT_HEADER, FALSE);
curl_setopt($ch, CURLOPT_POST, TRUE);
curl_setopt($ch, CURLOPT_POSTFIELDS, $xml_request);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
//curl_setopt($ch, CURLOPT_CAPATH, $certpath);
//curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, FALSE);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, FALSE);
curl_setopt($ch, CURLOPT_FORBID_REUSE, TRUE);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, FALSE);
$response = curl_exec($ch);
if (!$response) {
watchdog('payflowpro', 'Connecting to PayFlow server failed: ' . curl_error($ch), WATCHDOG_ERROR);
}
curl_close($ch);
// Place the xml response in an object
$xml_response = simplexml_load_string($response);
$return_code = $xml_response->ResponseData->TransactionResults->TransactionResult->Result;
$txn_result = $xml_response->ResponseData->TransactionResults;
# Check the return code to see if we have an error
if((int)$return_code < 0) {
watchdog('payflowpro', t('Transaction threw error.
' .
'Check your PayflowPro Account.
' .
'Error Code: %return_code ' .
'Error Message: %error_msg', array('%return_code' => $return_code, '%error_msg' => _payflowpro_error_code_to_string($return_code))));
}
else {
$txn_result->TXTIME = time();
$txn_result->return_code = (int)$return_code;
}
return $txn_result;
}
function _payflowpro_process($transaction) {
return _payflowpro_process_xml($transaction);
}
/*
* Simplify the upper layers by making lower case and uppercase keys work the same
*/
function _payflowpro_normalize_keys($pfpro_results) {
foreach($pfpro_results as $key=> $value) {
$pfpro_results[strtolower($key)] = $value;
$pfpro_results[strtoupper($key)] = $value;
}
return $pfpro_results;
}
function _payflowpro_com_error() {
drupal_set_message(t('There seems to be an error with you COM install.'));
drupal_goto('node');
}
/**
* This function single handidly takes care of
* interaction with the PFP recurring-billing
* api.
* Notes:
* - All profiles are saved using the user's username
*/
function _payflowpro_recurring_api($username, $action, $options = NULL) {
if($options == NULL) {
$options = array();
}
switch($action) {
# Add a user
case 'A':
$xml_template = _payflowpro_get_xml_template('A');
break;
# Modify a user
case 'M':
break;
# Inquiry about a user
case 'I':
break;
default:
return NULL;
}
return;
}
function payment_payflowpro_recurringapi($op, $arg1 = "", $arg2 = "", $arg3 = "" ) {
switch($op) {
case 'on expiry':
break;
case 'on purchase':
// TODO Use a variable here to let the admin choose
// if they want to integrate with PFP's recurring
// payments.
/**
* $arg1 = txn (as an array)
*/
$txn = $arg1;
# Grab the PNREF for this
$txn_result = db_fetch_object(db_query("SELECT * FROM {ec_payflowpro_result} WHERE txnid = %d", $txn['txnid']));
# Throw this PNREF to our function and
# as it to create a recurring payment
# Check to see if they already have aprofile
$profile = db_fetch_object(db_query("SELECT * FROM {ec_payflowpro_recurring} WHERE uid = %d", $txn['uid']));
# If we don't have a profile, add one
if($profile->uid != $txn['uid']) {
$result = _payflowpro_recurring_api('A', (object)$txn);
}
break;
case 'cron report':
break;
case 'get previous purchase':
break;
case 'renewal links':
break;
case 'expiry schedule changed':
break;
}
/*
error_log("Fired: $op");
error_log("Arg1: " . print_r($arg1, 1));
error_log("Arg2: " . print_r($arg2, 1));
error_log("Arg3: " . print_r($arg3, 1));
*/
return;
}
/* Payflow Pro test credit card numbers
American Express 378282246310005
American Express 371449635398431
Amex Corporate 378734493671000
Australian BankCard 5610591081018250
Diners Club 30569309025904
Diners Club 38520000023237
Discover 6011111111111117
Discover 6011000990139424
JCB 3530111333300000
JCB 3566002020360505
MasterCard 5555555555554444
MasterCard 5105105105105100
Visa 4111111111111111
Visa 4012888888881881
Visa 4222222222222
*/
function _get_test_xml() {
$xml = '
Souvent22
PayPal
123 4th street
San Jose
CA
95032
USA
1.23
5105105105105100
200912
Joe Smith
WebAPI
password
';
return $xml;
}
?>