From 6d6aeea44b3efcdeba76165d317bb924d2ea1ba5 Mon Sep 17 00:00:00 2001 From: Greg Brandysiewicz Date: Tue, 11 Jun 2013 23:02:32 -0700 Subject: [PATCH] Issue #1712344 by tinker. Add uc_recurring integration: - Fix uc_authnet_recurring_renew to properly charge and enter payment. - Add create profile to order if customer select not to save card to user profile in uc_authnet_recurring_process. - Add recurring profile update page to uc_authnet_recurring_info. Other changes: Fix customer_profile_id overwrite when UC_CREDIT_REFERENCE_TXN is used in _uc_authnet_charge(). Update order messages to make clearer what profile was created/failed. Rename gateway to uc_authnet to follow module naming convention. Fix uc_authnet_payment_method_alter to only modify if it is the default active gateway. Code format fixes - whitespace, variable concation, if statements. --- uc_authnet.charge.inc | 46 +++++++------------ uc_authnet.module | 122 +++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 125 insertions(+), 43 deletions(-) diff --git a/uc_authnet.charge.inc b/uc_authnet.charge.inc index cd6ad09..6764241 100644 --- a/uc_authnet.charge.inc +++ b/uc_authnet.charge.inc @@ -5,7 +5,6 @@ * Ubercart Authorize.net Payment Gateway - Charge */ - /** * Helper to uc_authnet_charge(). * (Callback for payment gateway settings) @@ -20,7 +19,6 @@ function _uc_authnet_charge($order_id, $amount, $data) { * 3) Create a new customer profile and/or payment profile, if necessary. * 4) Send the transaction to Authorize.net, if necessary. */ - /** * Step 1: Gather information. * @@ -46,14 +44,13 @@ function _uc_authnet_charge($order_id, $amount, $data) { * -Boolean for saving a new card will be in: * $order->payment_details['save_card'] */ - // Container variables: $success = FALSE; // Keep track of our success. Default pessimism. $payment_profile_id = FALSE; // Authorize.net CIM payment profile id. + $customer_profile_id = FALSE; // Authorize.net CIM customer profile id. $transaction_id = FALSE; // AIM or CIM transaction id. $save_card = FALSE; // Whether or not to save the card as a CIM profile on the user. $save_card_on_order = FALSE; // Whether or not to save the card as a CIM profile on the order. - // Load the order. $order = uc_order_load($order_id); @@ -117,7 +114,6 @@ function _uc_authnet_charge($order_id, $amount, $data) { * If the type is UC_CREDIT_REFERENCE_TXN, our job is pretty simple: we just need to create a transaction on the CIM profile * that was saved to the order. The customer profile id will be passed in via $data['ref_id']. */ - // If the transaction type is one of Ubercart's special reference types... $reference_types = array( UC_CREDIT_REFERENCE_SET, @@ -147,7 +143,9 @@ function _uc_authnet_charge($order_id, $amount, $data) { case UC_CREDIT_REFERENCE_TXN: // Grab the customer profile id from $data['ref_id']. - $customer_profile_id = $data['ref_id']; + if ($customer_profile_id == FALSE && isset($data['ref_id'])) { + $customer_profile_id = $data['ref_id']; + }; break; // Remove a reference. @@ -184,7 +182,6 @@ function _uc_authnet_charge($order_id, $amount, $data) { * The second scenario will be triggered if a) the user doesn't want to save the card, or b) a user wasn't found to save the card to in the * first scenario. In this case, we'll save the card to a new customer profile on the order. */ - // If the customer wants to save their new card... if ($save_card) { @@ -215,7 +212,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { if (!$customer_profile) { // Build a message. - $message = t('Authorize.Net: Creating CIM profile failed.
@text', array('@text' => authnet_get_error_message())); + $message = t('Authorize.Net: Creating CIM user profile failed.
@text', array('@text' => authnet_get_error_message())); // Add an admin order comment. uc_order_comment_save($order->order_id, $user->uid, $message, 'admin'); @@ -228,7 +225,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { $success = TRUE; // Add an admin order comment. - uc_order_comment_save($order->order_id, $user->uid, t('Authorize.Net: CIM profile created - @id', array('@id' => $customer_profile->customerProfileId)), 'admin'); + uc_order_comment_save($order->order_id, $user->uid, t('Authorize.Net: CIM user profile created - @id', array('@id' => $customer_profile->customerProfileId)), 'admin'); } } @@ -248,7 +245,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { if (!$customer_profile) { // Build a message. - $message = t('Authorize.Net: Creating CIM profile failed.
@text', array('@text' => authnet_get_error_message())); + $message = t('Authorize.Net: Creating CIM order profile failed.
@text', array('@text' => authnet_get_error_message())); // Add an admin order comment. uc_order_comment_save($order->order_id, $user->uid, $message, 'admin'); @@ -261,7 +258,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { $success = TRUE; // Add an admin order comment. - uc_order_comment_save($order->order_id, $user->uid, t('Authorize.Net: CIM profile created - @id', array('@id' => $customer_profile->customerProfileId)), 'admin'); + uc_order_comment_save($order->order_id, $user->uid, t('Authorize.Net: CIM order profile created - @id', array('@id' => $customer_profile->customerProfileId)), 'admin'); /** * If the profile was successfully created, and the user didn't select 'save card', add the customer profile as a reference on the order. @@ -330,7 +327,6 @@ function _uc_authnet_charge($order_id, $amount, $data) { * * Otherwise, send the transaction to Authorize.net via AIM. */ - // Only perform a transaction if the transaction type is one of the following: $transaction_types = array( UC_CREDIT_AUTH_CAPTURE, @@ -368,8 +364,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { // Get the first payment profile's id. $payment_profile_id = $customer_profile->paymentProfiles[0]->customerPaymentProfileId; } - // We don't break here so that the payment_profile_id that was found can be charged in the next case... - + // We don't break here so that the payment_profile_id that was found can be charged in the next case... // If the transaction type is 'Authorize and Capture', 'Authorize Only' or 'Prior authorization capture', // check to see if we have a customer and payment profile. If so, send the transaction via CIM. // Otherwise, send the transaction via AIM. @@ -418,7 +413,7 @@ function _uc_authnet_charge($order_id, $amount, $data) { * @return * Returns a result array for the charge function. */ -function _uc_authnet_cim_transaction($order, $customer_profile_id, $payment_profile_id, $amount, $method, $transaction_id=NULL) { +function _uc_authnet_cim_transaction($order, $customer_profile_id, $payment_profile_id, $amount, $method, $transaction_id = NULL) { global $user; // Assemble the new transaction. @@ -546,11 +541,9 @@ function _uc_authnet_aim_transaction($order, $amount, $data) { // Build the sale data for the transaction. $sale = array( - // Merchant Information 'login' => variable_get('authnet_login_id', ''), 'tran_key' => variable_get('authnet_transaction_key', ''), - // Transaction Information 'version' => '3.1', 'type' => $method, @@ -559,17 +552,15 @@ function _uc_authnet_aim_transaction($order, $amount, $data) { // 'recurring_billing' => 'FALSE', 'amount' => $uc_price, 'card_num' => $order->payment_details['cc_number'], - 'exp_date' => $order->payment_details['cc_exp_month'] .'/'. $order->payment_details['cc_exp_year'], + 'exp_date' => $order->payment_details['cc_exp_month'] . '/' . $order->payment_details['cc_exp_year'], 'card_code' => $order->payment_details['cc_cvv'], // 'trans_id' => '', // 'auth_code' => '', 'test_request' => variable_get('authnet_sandbox', 0) ? 'TRUE' : 'FALSE', 'duplicate_window' => variable_get('uc_authnet_aim_duplicate_window', 120), - // Order Information 'invoice_num' => $order->order_id, 'description' => drupal_substr(implode(', ', $description), 0, 255), - // Customer Information 'first_name' => drupal_substr($order->billing_first_name, 0, 50), 'last_name' => drupal_substr($order->billing_last_name, 0, 50), @@ -584,7 +575,6 @@ function _uc_authnet_aim_transaction($order, $amount, $data) { 'email' => drupal_substr($order->primary_email, 0, 255), 'cust_id' => drupal_substr($order->uid, 0, 20), 'customer_ip' => drupal_substr(ip_address(), 0, 15), - // Shipping Information 'ship_to_first_name' => drupal_substr($order->delivery_first_name, 0, 50), 'ship_to_last_name' => drupal_substr($order->delivery_last_name, 0, 50), @@ -594,7 +584,6 @@ function _uc_authnet_aim_transaction($order, $amount, $data) { 'ship_to_state' => drupal_substr(uc_get_zone_code($order->delivery_zone), 0, 40), 'ship_to_zip' => drupal_substr($order->delivery_postal_code, 0, 20), 'ship_to_country' => !$delivery_country ? '' : $delivery_country[0]['country_iso_code_2'], - // Extra Information 'delim_data' => 'TRUE', 'delim_char' => '|', @@ -662,12 +651,11 @@ function _uc_authnet_aim_transaction($order, $amount, $data) { 'revision' => 'formatted-original', 'type' => 'amount', ); - $admin_comment = t('@type
@status: @message
Amount: @amount', - array( - '@type' => $types[$method], - '@status' => $success ? t('ACCEPTED') : t('REJECTED'), - '@message' => $response->response_reason_text, - '@amount' => uc_price($response->amount, $context), + $admin_comment = t('@type
@status: @message
Amount: @amount', array( + '@type' => $types[$method], + '@status' => $success ? t('ACCEPTED') : t('REJECTED'), + '@message' => $response->response_reason_text, + '@amount' => uc_price($response->amount, $context), ) ); @@ -894,4 +882,4 @@ function _uc_authnet_payment_profile_from_order($order) { ), ), ); -} \ No newline at end of file +} diff --git a/uc_authnet.module b/uc_authnet.module index 66aebcd..1ddf5f6 100644 --- a/uc_authnet.module +++ b/uc_authnet.module @@ -24,7 +24,9 @@ function uc_authnet_form_alter(&$form, &$form_state, $form_id) { // Load the order. // Bail if no order or if order doesn't have a user $order = uc_order_load($form['order_id']['#value']); - if (empty($order) || empty($order->uid)) return; + if (empty($order) || empty($order->uid)) { + return; + }; // Load the customer profile id. $customer_profile_id = authnet_cim_entity_profile_id_load('user', $order->uid); @@ -147,8 +149,8 @@ function uc_authnet_theme() { */ function uc_authnet_payment_gateway() { $gateways[] = array( - 'id' => 'authnet', - 'title' => t('Authorize.net'), + 'id' => 'uc_authnet', + 'title' => t('Authorize.net API'), 'description' => t('Process credit card payments using Authorize.net.'), 'settings' => 'uc_authnet_settings_form', 'credit' => 'uc_authnet_charge', @@ -172,13 +174,12 @@ function uc_authnet_payment_gateway() { * Implementation of hook_payment_method_alter(). */ function uc_authnet_payment_method_alter(&$methods) { - /** * Replace the callback function of the 'credit' payment method with our own. * We do this so that we can offer users the ability to choose an existing card. * See uc_authnet_payment_method_credit() below. */ - if (!empty($methods)) { + if (variable_get('uc_payment_credit_gateway', FALSE) == 'uc_authnet' && !empty($methods)) { foreach ($methods as &$method) { if ($method['id'] == 'credit') { @@ -208,7 +209,6 @@ function uc_authnet_uc_checkout_complete($order, $account) { * to see if is present in the order's list of references. If it isn't, then it should be assigned * to the order's user instead. */ - // If the order's payment method is 'credit'... if ($order->payment_method == 'credit') { @@ -234,7 +234,6 @@ function uc_authnet_uc_checkout_complete($order, $account) { * We should check to see if this fails, and if so, add an order admin comment. * If it succeeds, we don't need an order comment, because this whole piece is supposed to be invisible. */ - // Update the database record for the customer profile. db_query('UPDATE {authnet_cim_entity} SET type="user", id=%d WHERE customer_profile_id=%d', array($account->uid, $customer_profile_id)); } @@ -418,7 +417,7 @@ function uc_authnet_payment_profile_charge_submit($form, &$form_state) { drupal_set_message(t('The credit card was processed successfully. See the admin comments for more details.')); // Redirect to the order view. - $form_state['redirect'] = 'admin/store/orders/'. $form_state['values']['order_id']; + $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id']; } // If an error occurred during processing... @@ -465,7 +464,7 @@ function uc_authnet_payment_profile_authorize_submit($form, &$form_state) { drupal_set_message(t('The credit card was authorized successfully. See the admin comments for more details.')); // Redirect to the order view. - $form_state['redirect'] = 'admin/store/orders/'. $form_state['values']['order_id']; + $form_state['redirect'] = 'admin/store/orders/' . $form_state['values']['order_id']; } // If an error occurred during processing... @@ -651,7 +650,6 @@ function uc_authnet_payment_method_credit($op, &$order, $silent = FALSE) { * accesses across page loads. This is Ubercart's default expected behavior, so we're * following it. */ - // Initialize the encryption key and class. $key = uc_credit_encryption_key(); $crypt = new uc_encryption_class(); @@ -731,14 +729,11 @@ function uc_authnet_translate_txn_type($type) { UC_CREDIT_AUTH_ONLY => AUTHNET_METHOD_AUTH_ONLY, UC_CREDIT_CREDIT => AUTHNET_METHOD_CREDIT, UC_CREDIT_VOID => AUTHNET_METHOD_VOID, - // 'Setting a reference' technically involves authorizing, // and we need it to set 'log_payment' = FALSE in _uc_authnet_transaction_result(). UC_CREDIT_REFERENCE_SET => AUTHNET_METHOD_AUTH_ONLY, - // Reference transaction are 'Authorize and capture'. UC_CREDIT_REFERENCE_TXN => AUTHNET_METHOD_AUTH_CAPTURE, - /** * There is no equivalent for Ubercart's other reference transactions, at the moment. */ @@ -787,4 +782,103 @@ function uc_authnet_log_cim_authorization($order_id, $payment_profile_id, $auth_ db_query("UPDATE {uc_orders} SET data = '%s' WHERE order_id = %d", serialize($data), $order_id); return $data; -} \ No newline at end of file +} + +/* ****************************************************************************** + * uc_recurring integration + * *****************************************************************************/ + +/** + * Implements hook_recurring_info(). + */ +function uc_authnet_recurring_info() { + $items['uc_authnet'] = array( + 'name' => t('Authorize.net API (uc_authnet)'), + 'payment method' => 'credit', + 'module' => 'uc_authnet', + 'fee handler' => 'uc_authnet', + 'renew callback' => 'uc_authnet_recurring_renew', + 'process callback' => 'uc_authnet_recurring_process', + 'own handler' => FALSE, + 'saved profile' => TRUE, + 'menu' => array( + 'charge' => UC_RECURRING_MENU_DEFAULT, + 'edit' => UC_RECURRING_MENU_DEFAULT, + 'cancel' => UC_RECURRING_MENU_DEFAULT, + 'update' => array( + 'title' => 'Update billing details', + 'page arguments' => array('uc_authnet_recurring_profile_update_form'), + 'access callback' => 'uc_authnet_recurring_profile_update_access', + 'access arguments' => array(1), + ), + ), // Use the default user operation defined in uc_recurring. + ); + return $items; +} + +/** + * Process a recurring fee using CIM. + */ +function uc_authnet_recurring_process($order, &$fee) { + $fee->fee_handler = 'uc_authnet'; + + // If the customer chose not to save the card during checkout then we save + // the profile to the order because it is required for future payments. + if ($order->payment_details['checkout_save_card'] == FALSE) { + $data = array( + 'txn_type' => UC_CREDIT_REFERENCE_SET, + ); + $result = uc_authnet_charge($order->order_id, $order->total_amount, $data); + return $result['success']; + } + + return TRUE; +} + +/** + * Process a recurring fee renewal using CIM. + */ +function uc_authnet_recurring_renew($order, &$fee) { + // Get the profile ID from the order and return results from transaction. + if (isset($order->payment_details['payment_profile_id']) && $order->payment_details['payment_profile_id']) { + $data = array( + 'payment_profile_id' => $order->payment_details['payment_profile_id'], + 'txn_type' => UC_CREDIT_REFERENCE_TXN, + ); + + // Card already saved so don't save it again. See _uc_authnet_charge(). + $order->payment_details['checkout_save_card'] = FALSE; + + $result = uc_authnet_charge($order->order_id, $fee->fee_amount, $data); + if ($result['success'] == TRUE) { + uc_payment_enter($order->order_id, $order->payment_method, $order->order_total, $fee->uid, $result['data'], $result['comment']); + return TRUE; + } + } + + return FALSE; +} + +/* + * Provide a from to update recurring billing information. + */ +function uc_authnet_recurring_profile_update_form($form_state, $rfid) { + $fee = uc_recurring_fee_user_load($rfid); + $order = uc_order_load($fee->order_id); + $profile = authnet_cim_entity_payment_profile_load($order->payment_details['payment_profile_id']); + return authnet_ui_payment_profile_form($form_state, $profile['customer_profile_id'], $profile['payment_profile_id']); +} + +/* + * Access callback for update recurring billing information form. + */ +function uc_authnet_recurring_profile_update_access($account) { + global $user; + if (user_access('administer authnet payment profiles')) { + return TRUE; + } + else if (user_access('manage own authnet payment profiles') && $user->uid == $account->uid) { + return TRUE; + }; + return FALSE; +} -- 1.7.0.4