diff -uNr a/payflowpro/payflowpro.module b/payflowpro/payflowpro.module
--- a/payflowpro/payflowpro.module 1969-12-31 19:00:00.000000000 -0500
+++ b/payflowpro/payflowpro.module 2006-08-21 01:17:37.000000000 -0400
@@ -0,0 +1,1367 @@
+Dependency: payment.module');
+ case 'admin/store/settings/payflowpro':
+ return t('Enter the required parameters that have been supplied during the signup process with payflowpro.');
+ }
+}
+
+
+/**
+ * Implementation of hook_menu()
+ */
+function payflowpro_menu($maycache) {
+ if ($maycache) {
+ $items[] = array(
+ 'path' => 'admin/store/payment/payflowpro',
+ 'title' => t('Payflow Pro Transaction Summary'),
+ 'callback' => '_payflowpro_transaction_summary',
+ 'access' => user_access('administer store'),
+ 'type' => MENU_CALLBACK
+ );
+ }
+ else {
+ if(is_numeric(arg(3))) {
+ $txn = store_transaction_load(arg(3));
+
+ $items[] = array(
+ 'path' => 'store/payment/payflowpro/' . arg(3),
+ 'title' => t('Enter Credit Card Details'),
+ 'callback' => 'payflowpro_payment_form',
+ 'callback arguments'=> 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' => 'payflowpro_confirmation',
+ 'callback arguments'=> arg(3),
+ 'access' => $access,
+ 'type' => MENU_CALLBACK
+ );
+
+ }
+ }
+ return $items;
+}
+
+
+/**
+ * Implementation of hook_paymentapi()
+ */
+function payflowpro_paymentapi(&$txn, $op, $arg = 0) {
+ switch ($op) {
+ case 'display name':
+ return t('Credit Card');
+ case 'on checkout':
+ // var_dump($txn);
+ break;
+ case 'form':
+ // var_dump($txn);
+ break;
+ case 'review':
+ // var_dump($txn);
+ break;
+ case 'payment page':
+ return payflowpro_goto($txn);
+ }
+}
+
+
+/**
+ * Implementation of hook_ec_transactionapi()
+ */
+function payflowpro_ec_transactionapi(&$txn, $op, $a3 = NULL, $a4 = NULL) {
+ if($txn->payment_method != 'payflowpro') {
+ return NULL;
+ }
+
+ switch($op) {
+ case 'delete':
+ _payflowpro_transactionapi_delete($txn);
+ payment_cc_delete($txn);
+ break;
+ case 'insert':
+ _payflowpro_transactionapi_insert($txn);
+ payment_cc_save($txn);
+ break;
+ case 'update':
+ _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, $a3, $a4);
+ return array_merge($payflowpro, array('payment'=> $ec_credit_card));
+ default:
+ break;
+ }
+}
+
+
+/**
+ * This page is presented to the user so that they know what is going on with their order
+ */
+function payflowpro_confirmation($txnid) {
+ $txn = store_transaction_load($txnid);
+
+ $txn->payment_status = payment_get_status_id('completed');
+
+ // store_send_invoice_email($txn);
+
+ store_transaction_save($txn);
+ // print_r($txn);
+}
+
+
+/**
+ * Display the credit card form
+ */
+function payflowpro_payment_form($txnid, $edit = array()) {
+
+ $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)
+ );
+*/
+
+ $form['billing'] = array(
+ '#type' => 'credit_card',
+ '#required' => TRUE,
+ '#name' => $txn->address['billing']->firstname . ' ' . $txn->address['billing']->lastname,
+ '#cvnshow' => TRUE
+ );
+
+ $form[] = array(
+ '#type' => 'submit',
+ '#value' => t('submit payment'),
+ );
+
+ $form['txnid'] = array(
+ '#type' => 'value',
+ '#value' => $txn->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 drupal_get_form('payflowpro_payment_form', $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 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 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 payflowpro_payment_form_submit($form_id, &$form_values) {
+ $txnid = $form_values['txnid'];
+
+ if(!_payflowpro_process_submit($txnid, $form_values)) {
+ return 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(
+ 'amt'=> trim($txn->gross),
+ 'acct'=> $form_values['cardnumber'],
+ 'expdate'=> $form_values['expiry']['expmonth'].$form_values['expiry']['expyear'],
+ 'trxtype'=> payflowpro_get_trxtype_id('sale transaction'),
+ 'tender'=> payflowpro_get_tender_id('credit card'),
+ 'user'=> variable_get('ec_payflowpro_user', ''),
+ 'vendor'=> variable_get('ec_payflowpro_vendor', ''),
+ 'partner'=> variable_get('ec_payflowpro_partner', ''),
+ 'pwd'=> PAYFLOWPRO_PWD,
+ );
+
+ $pfp_results = _payflowpro_process($pfp_transaction);
+
+ // XXX: Remove sensitive information here
+ unset($pfp_transaction['acct']);
+ unset($pfp_transaction['user']);
+ unset($pfp_transaction['vendor']);
+ unset($pfp_transaction['partner']);
+ unset($pfp_transaction['pwd']);
+
+ $txn->payflowpro = (object)$pfp_transaction;
+
+ if(isset($pfp_results['return_code'])) {
+ 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($pfp_results['RESULT'] != 0) {
+ $message = _payflowpro_code_to_string($pfp_results['RESULT']);
+
+ $place_holders = array(
+ '%result' => theme('placeholder', $pfp_results['RESULT']),
+ '%message' => theme('placeholder', $message),
+ '%resp_message' => theme('placeholder', $pfp_results['RESPMSG'])
+ );
+
+ watchdog('payflowpro', t('Transaction error: %result, "%message"/"%resp_message"', $place_holders));
+
+ $message = '';
+
+ if($pfp_results['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($pfp_results['RESULT']);
+ $txn->payment_status = payment_get_status_id('denied');
+ }
+
+ _payflowpro_store_transaction_results($pfp_results);
+ store_transaction_save($txn);
+
+ drupal_set_message($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');
+
+ _payflowpro_store_transaction_results($pfp_results);
+ 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;
+ }
+}
+
+
+/*
+ * Redirect the user to the secure payment form
+ */
+function 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 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
+ );
+
+ return $form;
+}
+
+
+function payflowpro_get_status($id) {
+ $status = payflowpro_build_status();
+ return $status[$id];
+}
+
+function payflowpro_get_status_id($name) {
+ return array_search(strtolower(t($name)), payflowpro_build_status());
+}
+
+/**
+ * Return an array of payflowpro status codes.
+ */
+function payflowpro_build_status() {
+ $payflowpro_status = array (
+ 0 => t('normal'),
+ 1 => t('deleted'),
+ );
+
+ return $payflowpro_status;
+}
+
+
+function payflowpro_get_trxtype($id) {
+ $status = payflowpro_build_trxtype();
+ return $status[$id];
+}
+
+function payflowpro_get_trxtype_id($name) {
+ return array_search(strtolower(t($name)), payflowpro_build_trxtype());
+}
+
+/**
+ * Return an array of payflowpro TRXTYPE codes.
+ */
+function payflowpro_build_trxtype() {
+ $payflowpro_status = array (
+ 'S'=> t('sale transaction'),
+ 'C'=> t('credit'),
+ 'A'=> t('authorization'),
+ 'D'=> t('delayed capture'),
+ 'V'=> t('void'),
+ 'F'=> t('voice authorization'),
+ 'I'=> t('inquiry'),
+ );
+
+ return $payflowpro_status;
+}
+
+
+function payflowpro_get_tender($id) {
+ $status = payflowpro_build_tender();
+ return $status[$id];
+}
+
+function payflowpro_get_tender_id($name) {
+ return array_search(strtolower(t($name)), payflowpro_build_tender());
+}
+
+/**
+ * Return an array of payflowpro TENDER codes.
+ */
+function 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) {
+
+ // The Payflow Pro information only becomes available once a transaction
+ // has been made.
+ if(!isset($txn->payflowpro)) {
+ return;
+ }
+
+ $database_map = _payflowpro_transaction_database_map($txn->payflowpro);
+
+ $pfp_query = 'INSERT INTO {ec_payflowpro_txn} ('. implode(', ', array_keys($database_map['types'])) .') VALUES ('. implode(', ', $database_map['types']) .')';
+ db_query($pfp_query, $database_map['values']);
+}
+
+
+/*
+ * 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
+ */
+function _payflowpro_transactionapi_update($txn) {
+}
+
+
+/*
+ * 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) {
+ if(!isset($txn->payflowpro)) {
+ return;
+ }
+
+ $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';
+
+ $additions = db_fetch_object(db_query($query_string, $tnx->txnid));
+ return array('payflowpro'=> $additions);
+}
+
+
+/*
+ * Store Payflow Pro transaction results
+ */
+function _payflowpro_store_transaction_results($pfp_results) {
+ $database_map = _payflowpro_results_database_map($pfp_results);
+
+ $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']);
+}
+
+
+/*
+ * Mapping function for Payflow Pro transaction data
+ */
+function _payflowpro_transaction_database_map($pfp_txn) {
+ $pfp_txn = (object)$pfp_txn;
+
+ $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'=> $pfp_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_validate_required_sale_fields($transaction) {
+ global $PAYFLOWPRO_SALE_REQUIRED_FIELDS;
+
+ $transaction_keys = array_keys($transaction);
+
+ foreach($PAYFLOWPRO_SALE_REQUIRED_FIELDS as $key) {
+ if(!in_array($key, $transaction_keys)) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+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 _payflowpro_process($transaction) {
+
+ $binary = variable_get('ec_payflowpro_sdk_binary', '');
+ $library = variable_get('ec_payflowpro_sdk_library', '');
+ $certpath = variable_get('ec_payflowpro_sdk_cert_path', '');
+
+ /*
+ * This next chunk of stuff assumes you're having linker issues, and
+ * attempts to fix or work around them.
+ */
+ $newld = dirname($library);
+ $original_ld_path = getenv("LD_LIBRARY_PATH");
+ $newld .= ":$original_path";
+ putenv("LD_LIBRARY_PATH=$newld");
+
+ /*
+ * 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=$certpath");
+
+ // pfpro PHP extension
+ if(function_exists('pfpro_process')) {
+
+ // XXX: I am not sure whether case matters in the keys, so I'll play it safe
+ $_trx = array();
+ foreach($transaction as $key=> $value) {
+ $_trx[strtoupper($key)] = $value;
+ }
+
+ $pfpro = pfpro_process($_trx);
+ $pfpro['TXTIME'] = time();
+
+ return _payflowpro_normalize_keys($pfpro);
+ }
+
+ $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');
+ }
+
+ $cmd = escapeshellarg(_payflowpro_build_parmlist($transaction));
+
+ $return_code = null;
+
+ // XXX: $cmd_output is left unset to prevent output from being captured
+ $result_string = exec("$binary $server $server_port $cmd", $cmd_output, $return_code);
+
+ if(strlen($result_string) == 0) {
+
+ $place_holders = array(
+ '%return_code' => theme('placeholder', $return_code),
+ );
+
+ watchdog('payflowpro', t('exec returned error code: %result. Check pfpro library and binary paths.', $place_holders));
+ return array('return_code'=> $return_code);
+ }
+
+ $pfpro = _payflowpro_parse_results($result_string);
+
+ // Do this explicitly
+ $pfpro['RESULT'] = (int)$pfpro['RESULT'];
+ $pfpro['TXTIME'] = time();
+
+ return _payflowpro_normalize_keys($pfpro);
+}
+
+
+/*
+ * 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;
+}
+
+
+/*
+ * There are some error messages that are helpful to the user while others need to be
+ * a bit more vauge so that the user gets enough information to make a good choice about
+ * what to do next.
+ *
+ * @see _payflow_process_submit for usage
+ */
+function _payflow_process_error_message($result_code) {
+ switch($result_code) {
+ case 12:
+ case 23:
+ case 24:
+ case 50:
+ return _payflowpro_code_to_string($result_code);
+ case 112:
+ return t('Transaction declined, your address is not on file with your credit card company. Your credit card was not charged.');
+ case 114:
+ return t('Transaction declined, security code does not match. Your credit card was not charged.');
+ case 117:
+ return t('Transaction declined, your address is not on file with your credit card company. Your credit card was not charged.');
+ default:
+ return t('Transaction declined. Your credit card was not charged');
+ }
+}
+
+
+function _payflowpro_code_to_string($result_code) {
+ $result_code = (int)$result_code;
+
+ if($result_code < 0) {
+ return _payflowpro_error_code_to_string($result_code);
+ }
+
+ return _payflowpro_transaction_code_to_string($result_code);
+}
+
+function _payflowpro_error_code_to_string($error_code) {
+ switch($error_code) {
+ case -1:
+ return t('Failed to connect to host');
+ case -2:
+ return t('Failed to resolve hostname');
+ case -5:
+ return t('Failed to initialize SSL context');
+ case -6:
+ return t('Parameter list format error: & in name');
+ case -7:
+ return t('Parameter list format error: invalid [ ] name length clause');
+ case -8:
+ return t('SSL failed to connect to host');
+ case -9:
+ return t('SSL read failed');
+ case -10:
+ return t('SSL write failed');
+ case -11:
+ return t('Proxy authorization failed');
+ case -12:
+ return t('Timeout waiting for response');
+ case -13:
+ return t('Select failure');
+ case -14:
+ return t('Too many connections');
+ case -15:
+ return t('Failed to set socket options');
+ case -20:
+ return t('Proxy read failed');
+ case -21:
+ return t('Proxy write failed');
+ case -22:
+ return t('Failed to initialize SSL certificate');
+ case -23:
+ return t('Host address not specified');
+ case -24:
+ return t('Invalid transaction type');
+ case -25:
+ return t('Failed to create a socket');
+ case -26:
+ return t('Failed to initialize socket layer');
+ case -27:
+ return t('Parameter list format error: invalid [ ] name length clause');
+ case -28:
+ return t('Parameter list format error: name');
+ case -29:
+ return t('Failed to initialize SSL connection');
+ case -30:
+ return t('Invalid timeout value');
+ case -31:
+ return t('The certificate chain did not validate, no local certificate found');
+ case -32:
+ return t('The certificate chain did not validate, common name did not match URL');
+ case -40:
+ return t('Unexpected Request ID found in request.');
+ case -41:
+ return t('Required Request ID not found in request');
+ case -42:
+ return t('Required Response ID not found in request');
+ case -43:
+ return t('Unexpected Response ID found in request');
+ case -44:
+ return t('Response ID not found in the response received from the server');
+ case -99:
+ return t('Out of memory');
+ case -100:
+ return t('Parameter list cannot be empty');
+ case -103:
+ return t('Context initialization failed');
+ case -104:
+ return t('Unexpected transaction state');
+ case -105:
+ return t('Invalid name value pair request');
+ case -106:
+ return t('Invalid response format');
+ case -107:
+ return t('This XMLPay version is not supported');
+ case -108:
+ return t('The server certificate chain did not validate');
+ case -109:
+ return t('Unable to do logging');
+ case -111:
+ return t('The following error occurred while initializing from message file: ');
+ case -113:
+ return t('Unable to round and truncate the currency value simultaneously');
+ default:
+ return t('Unknown communications error');
+ }
+}
+
+function _payflowpro_transaction_code_to_string($result_code) {
+ switch($result_code) {
+ case 0:
+ return t('Approved');
+ case 1:
+ return t('User authentication failed.');
+ case 2:
+ return t('Invalid tender type. Your merchant bank account does not support the following credit card type that was submitted.');
+ case 3:
+ return t('Invalid transaction type. Transaction type is not appropriate for this transaction. For example, you cannot credit an authorization-only transaction.');
+ case 4:
+ return t('Invalid amount format. Use the format: “#####.##” Do not include currency symbols or commas.');
+ case 5:
+ return t('Invalid merchant information. Processor does not recognize your merchant account information. Contact your bank account acquirer to resolve this problem.');
+ case 6:
+ return t('Invalid or unsupported currency code');
+ case 7:
+ return t('Field format error. Invalid information entered. See RESPMSG.');
+ case 8:
+ return t('Not a transaction server');
+ case 9:
+ return t('Too many parameters or invalid stream');
+ case 10:
+ return t('Too many line items');
+ case 11:
+ return t('Client time-out waiting for response');
+ case 12:
+ return t('Declined. Check the credit card number, expiration date, and transaction information to make sure they were entered correctly. If this does not resolve the problem, have the customer call their card issuing bank to resolve.');
+ case 13:
+ return t('Referral. Transaction cannot be approved electronically but can be approved with a verbal authorization. Contact your merchant bank to obtain an authorization and submit a manual Voice Authorization transaction.');
+ case 14:
+ return t('Invalid Client Certification ID. Check the HTTP header. If the tag, X-VPS-VIT-CLIENT-CERTIFICATION-ID, is missing, RESULT code 14 is returned.');
+ case 19:
+ return t('Original transaction ID not found. The transaction ID you entered for this transaction is not valid. See RESPMSG.');
+ case 20:
+ return t('Cannot find the customer reference number');
+ case 22:
+ return t('Invalid ABA number');
+ case 23:
+ return t('Invalid account number. Check credit card number and re-submit.');
+ case 24:
+ return t('Invalid expiration date. Check and re-submit.');
+ case 25:
+ return t('Invalid Host Mapping. You are trying to process a tender type such as Discover Card, but you are not set up with your merchant bank to accept this card type.');
+ case 26:
+ return t('Invalid vendor account');
+ case 27:
+ return t('Insufficient partner permissions');
+ case 28:
+ return t('Insufficient user permissions');
+ case 29:
+ return t('Invalid XML document. This could be caused by an unrecognized XML tag or a bad XML format that cannot be parsed by the system.');
+ case 30:
+ return t('Duplicate transaction');
+ case 31:
+ return t('Error in adding the recurring profile');
+ case 32:
+ return t('Error in modifying the recurring profile');
+ case 33:
+ return t('Error in canceling the recurring profile');
+ case 34:
+ return t('Error in forcing the recurring profile');
+ case 35:
+ return t('Error in reactivating the recurring profile');
+ case 36:
+ return t('OLTP Transaction failed');
+ case 37:
+ return t('Invalid recurring profile ID');
+ case 50:
+ return t('Insufficient funds available in account');
+ case 99:
+ return t('General error. See RESPMSG.');
+ case 100:
+ return t('Transaction type not supported by host');
+ case 101:
+ return t('Time-out value too small');
+ case 102:
+ return t('Processor not available');
+ case 103:
+ return t('Error reading response from host');
+ case 104:
+ return t('Timeout waiting for processor response. Try your transaction again.');
+ case 105:
+ return t('Credit error. Make sure you have not already credited this transaction, or that this transaction ID is for a creditable transaction. (For example, you cannot credit an authorization.)');
+ case 106:
+ return t('Host not available');
+ case 107:
+ return t('Duplicate suppression time-out');
+ case 108:
+ return t('Void error. See RESPMSG. Make sure the transaction ID entered has not already been voided. If not, then look at the Transaction Detail screen for this transaction to see if it has settled. (The Batch field is set to a number greater than zero if the transaction has been settled). If the transaction has already settled, your only recourse is a reversal (credit a payment or submit a payment for a credit).');
+ case 109:
+ return t('Time-out waiting for host response');
+ case 111:
+ return t('Capture error. Either an attempt to capture a transaction that is not an authorization transaction type, or an attempt to capture an authorization transaction that has already been captured.');
+ case 112:
+ return t('Failed AVS check. Address and ZIP code do not match. An authorization may still exist on the cardholder’s account.');
+ case 113:
+ return t('Merchant sale total will exceed the sales cap with current transaction. ACH transactions only.');
+ case 114:
+ return t('Card Security Code (CSC) Mismatch. An authorization may still exist on the cardholder’s account.');
+ case 115:
+ return t('System busy, try again later');
+ case 116:
+ return t('VPS Internal error. Failed to lock terminal number');
+ case 117:
+ return t('Failed merchant rule check. One or more of the following three failures occurred: 1) An attempt was made to submit a transaction that failed to meet the security settings specified on the Payflow Pro Java SDK Security Settings page. If the transaction exceeded the Maximum Amount security setting, then no values are returned for AVS or CSC. 2) AVS validation failed. The AVS return value should appear in the RESPMSG. 3) CSC validation failed. The CSC return value should appear in the RESPMSG.');
+ case 118:
+ return t('Invalid keywords found in string fields');
+ case 122:
+ return t('Merchant sale total will exceed the credit cap with current transaction. ACH transactions only.');
+ case 125:
+ return t('Fraud Protection Services Filter — Declined by filters');
+ case 126:
+ return t('Fraud Protection Services Filter — Flagged for review by filters');
+ case 127:
+ return t('Fraud Protection Services Filter — Not processed by filters');
+ case 128:
+ return t('Fraud Protection Services Filter — Declined by merchant after being flagged for review by filters');
+ case 131:
+ return t('Version 1 Payflow Pro SDK client no longer supported. Upgrade to the most recent version of the Payflow Pro client.');
+ case 150:
+ return t('Issuing bank timed out');
+ case 151:
+ return t('Issuing bank unavailable');
+ case 1000:
+ return t('Generic host error. This is a generic message returned by your credit card processor. The RESPMSG will contain more information describing the error.');
+ case 1001:
+ return t('Buyer Authentication Service unavailable');
+ case 1002:
+ return t('Buyer Authentication Service — Transaction timeout');
+ case 1003:
+ return t('Buyer Authentication Service — Invalid client version');
+ case 1004:
+ return t('Buyer Authentication Service — Invalid timeout value');
+ case 1011:
+ return t('Buyer Authentication Service unavailable');
+ case 1012:
+ return t('Buyer Authentication Service unavailable');
+ case 1013:
+ return t('Buyer Authentication Service unavailable');
+ case 1014:
+ return t('Buyer Authentication Service — Merchant is not enrolled for Buyer Authentication Service (3-D Secure).');
+ case 1016:
+ return t('Buyer Authentication Service — 3-D Secure error response received. Instead of receiving a PARes response to a Validate Authentication transaction, an error response was received.');
+ case 1017:
+ return t('Buyer Authentication Service — 3-D Secure error response is invalid. An error response is received and the response is not well formed for a Validate Authentication transaction.');
+ case 1021:
+ return t('Buyer Authentication Service — Invalid card type');
+ case 1022:
+ return t('Buyer Authentication Service — Invalid or missing currency code');
+ case 1023:
+ return t('Buyer Authentication Service — merchant status for 3D secure is invalid');
+ case 1041:
+ return t('Buyer Authentication Service — Validate Authentication failed: missing or invalid PARES');
+ case 1042:
+ return t('Buyer Authentication Service — Validate Authentication failed: PARES format is invalid');
+ case 1043:
+ return t('Buyer Authentication Service — Validate Authentication failed: Cannot find successful Verify Enrollment');
+ case 1044:
+ return t('Buyer Authentication Service — Validate Authentication failed: Signature validation failed for PARES');
+ case 1045:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid amount in PARES');
+ case 1046:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid acquirer in PARES');
+ case 1047:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid Merchant ID in PARES');
+ case 1048:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid card number in PARES');
+ case 1049:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid currency code in PARES');
+ case 1050:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid XID in PARES');
+ case 1051:
+ return t('Buyer Authentication Service — Validate Authentication failed: Mismatched or invalid order date in PARES');
+ case 1052:
+ return t('Buyer Authentication Service — Validate Authentication failed: This PARES was already validated for a previous Validate Authentication transaction');
+ default:
+ return t('Unknown transaction code');
+ }
+}
+
+
+/* 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
+ */
+?>
diff -uNr a/payflowpro/payflowpro.sql b/payflowpro/payflowpro.sql
--- a/payflowpro/payflowpro.sql 1969-12-31 19:00:00.000000000 -0500
+++ b/payflowpro/payflowpro.sql 2006-08-20 22:02:33.000000000 -0400
@@ -0,0 +1,44 @@
+--- $Id: payflowpro.sql,v 1.6 2006/08/21 02:02:33 evanchsa Exp $
+---
+--- Table the records the non-sensitive information of a Payflow Pro transaction.
+--- This information is suitable for sale, authorization, delayed and inquiry
+--- transactions.
+---
+DROP TABLE IF EXISTS `ec_payflowpro_txn`;
+CREATE TABLE `ec_payflowpro_txn` (
+`ec_pfp_id` int(10) unsigned NOT NULL auto_increment,
+`txnid` int(10) unsigned NOT NULL,
+`amt` float (11,2) NOT NULL,
+`comment1` varchar(128),
+`comment2` varchar(128),
+`currency` varchar(3),
+`custref` varchar(12),
+`name` varchar(30),
+`street` varchar(30),
+`tender` char(1) NOT NULL,
+`trxtype` char(1) NOT NULL,
+`zip` varchar(9),
+`status` tinyint(2) DEFAULT 0 NOT NULL,
+ PRIMARY KEY (`ec_pfp_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+
+
+---
+--- Table that records the results of a PayFlow Pro transaction
+---
+DROP TABLE IF EXISTS `ec_payflowpro_result`;
+CREATE TABLE `ec_payflowpro_result` (
+`ec_pfp_id` int(10) unsigned NOT NULL,
+`txtime` int(11) NOT NULL,
+`txnid` int(10) unsigned NOT NULL,
+`pnref` varchar(12) NOT NULL,
+`result` int(11) NOT NULL,
+`cvv2match` char(1),
+`respmsg` varchar(255) NOT NULL,
+`authcode` varchar(6) NOT NULL,
+`avsaddr` char(1),
+`avszip` char(1),
+`iavs` char(1),
+ KEY `ec_payflowpro_txn` (`ec_pfp_id`)
+) ENGINE=MyISAM DEFAULT CHARSET=utf8;
+