diff --git a/commerce_authnet.module b/commerce_authnet.module index d54d1e2..b62f7ec 100644 --- a/commerce_authnet.module +++ b/commerce_authnet.module @@ -161,6 +161,16 @@ function commerce_authnet_commerce_payment_method_info() { 'update callback' => 'commerce_authnet_cim_cardonfile_update', 'delete callback' => 'commerce_authnet_cim_cardonfile_delete', ), + 'file' => 'includes/commerce_authnet_payment.cc.inc', + ); + + $payment_methods['authnet_echeck'] = array( + 'base' => 'commerce_authnet_echeck', + 'title' => t('Authorize.Net AIM - eCheck.Net'), + 'short_title' => t('Authorize.Net eCheck'), + 'display_title' => t('eCheck'), + 'description' => t('Integrates Authorize.Net Advanced Integration Method for eCheck transactions.'), + 'file' => 'includes/commerce_authnet_payment.echeck.inc', ); return $payment_methods; @@ -184,218 +194,20 @@ function commerce_authnet_aim_default_settings() { } /** - * Payment method callback: settings form. - */ -function commerce_authnet_aim_settings_form($settings = NULL) { - module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); - - // Merge default settings into the stored settings array. - $settings = (array) $settings + commerce_authnet_aim_default_settings(); - - $form = array(); - - $form['login'] = array( - '#type' => 'textfield', - '#title' => t('API Login ID'), - '#description' => t('Your API Login ID is different from the username you use to login to your Authorize.Net account. Once you login, browse to your Account tab and click the API Login ID and Transaction Key link to find your API Login ID. If you are using a new Authorize.Net account, you may still need to generate an ID.'), - '#default_value' => $settings['login'], - '#required' => TRUE, - ); - $form['tran_key'] = array( - '#type' => 'textfield', - '#title' => t('Transaction Key'), - '#description' => t('Your Transaction Key can be found on the same screen as your API Login ID. However, it will not be readily displayed. You must answer your security question and submit a form to see your Transaction Key.'), - '#default_value' => $settings['tran_key'], - '#required' => TRUE, - ); - $form['txn_mode'] = array( - '#type' => 'radios', - '#title' => t('Transaction mode'), - '#description' => t('Adjust to live transactions when you are ready to start processing real payments.') . '
' . t('Only specify a developer test account if you login to your account through https://test.authorize.net.'), - '#options' => array( - AUTHNET_TXN_MODE_LIVE => t('Live transactions in a live account'), - AUTHNET_TXN_MODE_LIVE_TEST => t('Test transactions in a live account'), - AUTHNET_TXN_MODE_DEVELOPER => t('Developer test account transactions'), - ), - '#default_value' => $settings['txn_mode'], - ); - $form['txn_type'] = array( - '#type' => 'radios', - '#title' => t('Default credit card transaction type'), - '#description' => t('The default will be used to process transactions during checkout.'), - '#options' => array( - COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'), - COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'), - ), - '#default_value' => $settings['txn_type'], - ); - - $form['card_types'] = array( - '#type' => 'checkboxes', - '#title' => t('Limit accepted credit cards to the following types'), - '#description' => t('If you want to limit acceptable card types, you should only select those supported by your merchant account.') . '
' . t('If none are checked, any credit card type will be accepted.'), - '#options' => commerce_payment_credit_card_types(), - '#default_value' => $settings['card_types'], - ); - - // CIM support in conjunction with AIM requires the Card on File module. - if (module_exists('commerce_cardonfile')) { - $form['cardonfile'] = array( - '#type' => 'checkbox', - '#title' => t('Enable Card on File functionality with this payment method using Authorize.Net CIM.'), - '#description' => t('This requires an Authorize.Net account upgraded to include support for CIM (Customer Information Manager).'), - '#default_value' => $settings['cardonfile'], - ); - - $form['continuous'] = array( - '#type' => 'checkbox', - '#title' => t('Use continuous authority transactions.'), - '#description' => t('A continuous authority merchant account will be required.'), - '#default_value' => $settings['continuous'], - ); - } - - else { - $form['cardonfile'] = array( - '#type' => 'markup', - '#markup' => t('To enable Card on File funcitionality download and install the Card on File module.'), - ); - } - - $form['email_customer'] = array( - '#type' => 'checkbox', - '#title' => t('Tell Authorize.net to e-mail the customer a receipt based on your account settings.'), - '#default_value' => $settings['email_customer'], - ); - $form['log'] = array( - '#type' => 'checkboxes', - '#title' => t('Log the following messages for debugging'), - '#options' => array( - 'request' => t('API request messages'), - 'response' => t('API response messages'), - ), - '#default_value' => $settings['log'], - ); - - return $form; -} - -/** - * Payment method callback: checkout form. - */ -function commerce_authnet_aim_submit_form($payment_method, $pane_values, $checkout_pane, $order) { - module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); - - // Prepare the fields to include on the credit card form. - $fields = array( - 'code' => '', - ); - - // Add the credit card types array if necessary. - if (isset($payment_method['settings']['card_types'])) { - $card_types = array_diff(array_values($payment_method['settings']['card_types']), array(0)); - - if (!empty($card_types)) { - $fields['type'] = $card_types; - } - } - - return commerce_payment_credit_card_form($fields); -} - -/** - * Payment method callback: checkout form validation. - */ -function commerce_authnet_aim_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) { - // If the customer specified a card on file, skip the normal validation. - if (module_exists('commerce_cardonfile') && !empty($payment_method['settings']['cardonfile']) && - !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { - return; - } - - module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); - - // Validate the credit card fields. - $settings = array( - 'form_parents' => array_merge($form_parents, array('credit_card')), - ); - - if (!commerce_payment_credit_card_validate($pane_values['credit_card'], $settings)) { - return FALSE; - } -} - -/** - * Payment method callback: checkout form submission. + * Build the order details required to make a request. + * + * @param stdClass $order + * The order entity that the payment is being made for. + * + * @return array + * Array of order data for the Authorize.Net request. */ -function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { - // If the customer specified payment using a card on file, attempt that now - // and simply return the result. - if (module_exists('commerce_cardonfile') && $payment_method['settings']['cardonfile'] && - !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { - return commerce_authnet_cim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge); - } - - // Determine the credit card type if possible for use in later code. - if (!empty($pane_values['credit_card']['number'])) { - module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); - $card_type = commerce_payment_validate_credit_card_type($pane_values['credit_card']['number'], array_keys(commerce_payment_credit_card_types())); - } - - // If the charge amount is 0... - if ($charge['amount'] == 0) { - // Prevent the transaction except under limited circumstances. - $prevent_transaction = TRUE; - - // Allow 0 amount authorizations on Visa cards. - if ($payment_method['settings']['txn_type'] == COMMERCE_CREDIT_AUTH_ONLY && $card_type == 'visa') { - $prevent_transaction = FALSE; - } - - // If the transaction should still be prevented... - if ($prevent_transaction) { - // Create a transaction to log the skipped transaction and display a - // helpful message to the customer. - $transaction = commerce_payment_transaction_new('authnet_aim', $order->order_id); - $transaction->amount = $charge['amount']; - $transaction->currency_code = $charge['currency_code']; - $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; - $transaction->message = t('Invalid @amount transaction not attempted.', array('@amount' => commerce_currency_format($charge['amount'], $charge['currency_code']))); - commerce_payment_transaction_save($transaction); - - drupal_set_message('We encountered an error processing your transaction. Please contact us to resolve the issue.', 'error'); - return FALSE; - } - } - +function commerce_authnet_aim_request_order_details($order) { $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - // Get the default transaction type from the payment method settings. - $txn_type = $payment_method['settings']['txn_type']; - - // If txn_type has been specified in the pane values array, such as through - // the special select element we alter onto the payment terminal form, use - // that instead. - if (!empty($pane_values['txn_type'])) { - $txn_type = $pane_values['txn_type']; - } - - // Build a name-value pair array for this transaction. - $nvp = array( - 'x_type' => commerce_authnet_txn_type($txn_type), - 'x_method' => 'CC', - 'x_amount' => number_format(commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']), 2, '.', ''), - 'x_currency_code' => $charge['currency_code'], - 'x_card_num' => $pane_values['credit_card']['number'], - 'x_exp_date' => $pane_values['credit_card']['exp_month'] . $pane_values['credit_card']['exp_year'], - ); - - if (isset($pane_values['credit_card']['code'])) { - $nvp['x_card_code'] = $pane_values['credit_card']['code']; - } - // Build a description for the order. $description = array(); + // Descriptions come from products, though not all environments have them. So check first. if (function_exists('commerce_product_line_item_types')) { foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) { @@ -405,8 +217,7 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p } } - // Add additional transaction invormation to the request array. - $nvp += array( + $details = array( // Order Information 'x_invoice_num' => $order->order_number, 'x_description' => substr(implode(', ', $description), 0, 255), @@ -427,7 +238,7 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p $billing_address['last_name'] = implode(' ', $name_parts); } - $nvp += array( + $details += array( // Customer Billing Address 'x_first_name' => substr($billing_address['first_name'], 0, 50), 'x_last_name' => substr($billing_address['last_name'], 0, 50), @@ -439,194 +250,8 @@ function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $p 'x_country' => $billing_address['country'], ); } - else { - $billing_address = array(); - } - - // Submit the request to Authorize.Net. - $response = commerce_authnet_aim_request($payment_method, $nvp); - - // Prepare a transaction object to log the API response. - $transaction = commerce_payment_transaction_new('authnet_aim', $order->order_id); - $transaction->instance_id = $payment_method['instance_id']; - $transaction->remote_id = $response[6]; - $transaction->amount = $charge['amount']; - $transaction->currency_code = $charge['currency_code']; - $transaction->payload[REQUEST_TIME] = $response; - - // If we didn't get an approval response code... - if ($response[0] != '1') { - // Create a failed transaction with the error message. - $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; - } - else { - // Set the transaction status based on the type of transaction this was. - switch ($txn_type) { - case COMMERCE_CREDIT_AUTH_ONLY: - $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; - break; - - case COMMERCE_CREDIT_AUTH_CAPTURE: - $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; - break; - - case COMMERCE_CREDIT_CAPTURE_ONLY: - $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; - break; - } - } - - // Store the type of transaction in the remote status. - $transaction->remote_status = $response[11]; - - // Build a meaningful response message. - $message = array( - '' . commerce_authnet_reverse_txn_type($response[11]) . '', - '' . ($response[0] != '1' ? t('REJECTED') : t('ACCEPTED')) . ': ' . check_plain($response[3]), - t('AVS response: @avs', array('@avs' => commerce_authnet_avs_response($response[5]))), - ); - - // Add the CVV response if enabled. - if (isset($nvp['x_card_code'])) { - $message[] = t('CVV match: @cvv', array('@cvv' => commerce_authnet_cvv_response($response[38]))); - } - - $transaction->message = implode('
', $message); - // Save the transaction information. - commerce_payment_transaction_save($transaction); - - // If the payment failed, display an error and rebuild the form. - if ($response[0] != '1') { - drupal_set_message(t('We received the following error processing your card. Please enter your information again or try a different card.'), 'error'); - drupal_set_message(check_plain($response[3]), 'error'); - return FALSE; - } - - // If Card on File storage is enabled via CIM and the form says to store data... - if (module_exists('commerce_cardonfile') && !empty($payment_method['settings']['cardonfile']) && - !empty($pane_values['credit_card']['cardonfile_store']) && $pane_values['credit_card']['cardonfile_store']) { - // Build a payment details array for the credit card. - $payment_details = array( - 'cardNumber' => $pane_values['credit_card']['number'], - 'expirationDate' => $pane_values['credit_card']['exp_year'] . '-' . $pane_values['credit_card']['exp_month'], - ); - - if (isset($pane_values['credit_card']['code'])) { - $payment_details['cardCode'] = $pane_values['credit_card']['code']; - } - - // First look to see if we already have cards on file for the user. - $stored_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $payment_method['instance_id']); - $add_to_profile = NULL; - - // If we didn't find any, attempt to make a new Customer Profile now. - if (empty($stored_cards)) { - // Submit a CIM request to create the Customer Profile. - if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { - // If the Customer Profile creation was a success, store the new card on - // file data locally. - if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the - // Payment Profile ID. - $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; - - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); - } - elseif ((string) $response->messages->message->code == 'E00039') { - // But if a Customer Profile already existed for this user, attempt - // instead to add this card as a new Payment Profile to it. - $result = array_filter(explode(' ', (string) $response->messages->message->text), 'is_numeric'); - $add_to_profile = reset($result); - } - } - } - else { - // Extract the user's Customer Profile ID from the first card's remote ID. - $card_data = reset($stored_cards); - list($cim_customer_profile_id, $cim_payment_profile_id) = explode('|', $card_data->remote_id); - - // Attempt to add the card as a new payment profile to this Customer Profile. - $add_to_profile = $cim_customer_profile_id; - } - - // Attempt to add the card to an existing Customer Profile if specified. - if (!empty($add_to_profile)) { - $response = commerce_authnet_cim_create_customer_payment_profile_request($payment_method, $add_to_profile, $order, $payment_details); - - // If the Payment Profile creation was a success, store the new card on - // file data locally. - if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the new - // Payment Profile ID. - $remote_id = $add_to_profile . '|' . (string) $response->customerPaymentProfileId; - - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Payment Profile added to Customer Profile @profile_id for user @uid.', array('@profile_id' => $add_to_profile, '@uid' => $order->uid)); - } - elseif (!empty($card_data) && (string) $response->messages->message->code == 'E00040') { - // But if we could not find a customer profile, assume the existing - // customer profile ID we had is no longer valid and deactivate the card - // data that resulted in the error. - $card_data->status = 0; - commerce_cardonfile_save($card_data); - - // Submit a CIM request to create the Customer Profile. - if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { - // If the Customer Profile creation was a success, store the new card on - // file data locally. - if ((string) $response->messages->resultCode == 'Ok') { - // Build a remote ID that includes the Customer Profile ID and the - // Payment Profile ID. - $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; - - $card_data = commerce_cardonfile_new(); - $card_data->uid = $order->uid; - $card_data->payment_method = $payment_method['method_id']; - $card_data->instance_id = $payment_method['instance_id']; - $card_data->remote_id = $remote_id; - $card_data->card_type = !empty($card_type) ? $card_type : 'card'; - $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; - $card_data->card_number = substr($pane_values['credit_card']['number'], -4); - $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; - $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; - $card_data->status = 1; - - // Save and log the creation of the new card on file. - commerce_cardonfile_save($card_data); - watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); - } - } - } - } - } + return array_filter($details); } /** @@ -1480,23 +1105,3 @@ function commerce_authnet_avs_response($code) { return '-'; } - -/** - * Returns the message text for a CVV match. - */ -function commerce_authnet_cvv_response($code) { - switch ($code) { - case 'M': - return t('Match'); - case 'N': - return t('No Match'); - case 'P': - return t('Not Processed'); - case 'S': - return t('Should have been present'); - case 'U': - return t('Issuer unable to process request'); - } - - return '-'; -} diff --git a/includes/commerce_authnet_payment.cc.inc b/includes/commerce_authnet_payment.cc.inc new file mode 100644 index 0000000..fa45e62 --- /dev/null +++ b/includes/commerce_authnet_payment.cc.inc @@ -0,0 +1,423 @@ + 'textfield', + '#title' => t('API Login ID'), + '#description' => t('Your API Login ID is different from the username you use to login to your Authorize.Net account. Once you login, browse to your Account tab and click the API Login ID and Transaction Key link to find your API Login ID. If you are using a new Authorize.Net account, you may still need to generate an ID.'), + '#default_value' => $settings['login'], + '#required' => TRUE, + ); + $form['tran_key'] = array( + '#type' => 'textfield', + '#title' => t('Transaction Key'), + '#description' => t('Your Transaction Key can be found on the same screen as your API Login ID. However, it will not be readily displayed. You must answer your security question and submit a form to see your Transaction Key.'), + '#default_value' => $settings['tran_key'], + '#required' => TRUE, + ); + $form['txn_mode'] = array( + '#type' => 'radios', + '#title' => t('Transaction mode'), + '#description' => t('Adjust to live transactions when you are ready to start processing real payments.') . '
' . t('Only specify a developer test account if you login to your account through https://test.authorize.net.'), + '#options' => array( + AUTHNET_TXN_MODE_LIVE => t('Live transactions in a live account'), + AUTHNET_TXN_MODE_LIVE_TEST => t('Test transactions in a live account'), + AUTHNET_TXN_MODE_DEVELOPER => t('Developer test account transactions'), + ), + '#default_value' => $settings['txn_mode'], + ); + $form['txn_type'] = array( + '#type' => 'radios', + '#title' => t('Default credit card transaction type'), + '#description' => t('The default will be used to process transactions during checkout.'), + '#options' => array( + COMMERCE_CREDIT_AUTH_CAPTURE => t('Authorization and capture'), + COMMERCE_CREDIT_AUTH_ONLY => t('Authorization only (requires manual or automated capture after checkout)'), + ), + '#default_value' => $settings['txn_type'], + ); + + $form['card_types'] = array( + '#type' => 'checkboxes', + '#title' => t('Limit accepted credit cards to the following types'), + '#description' => t('If you want to limit acceptable card types, you should only select those supported by your merchant account.') . '
' . t('If none are checked, any credit card type will be accepted.'), + '#options' => commerce_payment_credit_card_types(), + '#default_value' => $settings['card_types'], + ); + + // CIM support in conjunction with AIM requires the Card on File module. + if (module_exists('commerce_cardonfile')) { + $form['cardonfile'] = array( + '#type' => 'checkbox', + '#title' => t('Enable Card on File functionality with this payment method using Authorize.Net CIM.'), + '#description' => t('This requires an Authorize.Net account upgraded to include support for CIM (Customer Information Manager).'), + '#default_value' => $settings['cardonfile'], + ); + + $form['continuous'] = array( + '#type' => 'checkbox', + '#title' => t('Use continuous authority transactions.'), + '#description' => t('A continuous authority merchant account will be required.'), + '#default_value' => $settings['continuous'], + ); + } + + else { + $form['cardonfile'] = array( + '#type' => 'markup', + '#markup' => t('To enable Card on File functionality download and install the Card on File module.'), + ); + } + + $form['email_customer'] = array( + '#type' => 'checkbox', + '#title' => t('Tell Authorize.net to e-mail the customer a receipt based on your account settings.'), + '#default_value' => $settings['email_customer'], + ); + $form['log'] = array( + '#type' => 'checkboxes', + '#title' => t('Log the following messages for debugging'), + '#options' => array( + 'request' => t('API request messages'), + 'response' => t('API response messages'), + ), + '#default_value' => $settings['log'], + ); + + return $form; +} + +/** + * Payment method callback: checkout form. + */ +function commerce_authnet_aim_submit_form($payment_method, $pane_values, $checkout_pane, $order) { + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); + + // Prepare the fields to include on the credit card form. + $fields = array( + 'code' => '', + ); + + // Add the credit card types array if necessary. + if (isset($payment_method['settings']['card_types'])) { + $card_types = array_diff(array_values($payment_method['settings']['card_types']), array(0)); + + if (!empty($card_types)) { + $fields['type'] = $card_types; + } + } + + return commerce_payment_credit_card_form($fields); +} + +/** + * Payment method callback: checkout form validation. + */ +function commerce_authnet_aim_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) { + // If the customer specified a card on file, skip the normal validation. + if (module_exists('commerce_cardonfile') && !empty($payment_method['settings']['cim_cardonfile']) && + !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { + return; + } + + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); + + // Validate the credit card fields. + $settings = array( + 'form_parents' => array_merge($form_parents, array('credit_card')), + ); + + if (!commerce_payment_credit_card_validate($pane_values['credit_card'], $settings)) { + return FALSE; + } +} + +/** + * Payment method callback: checkout form submission. + */ +function commerce_authnet_aim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { + // If the customer specified payment using a card on file, attempt that now + // and simply return the result. + if (module_exists('commerce_cardonfile') && $payment_method['settings']['cim_cardonfile'] && + !empty($pane_values['cardonfile']) && $pane_values['cardonfile'] !== 'new') { + return commerce_authnet_cim_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge); + } + + // Determine the credit card type if possible for use in later code. + if (!empty($pane_values['credit_card']['number'])) { + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.credit_card'); + $card_type = commerce_payment_validate_credit_card_type($pane_values['credit_card']['number'], array_keys(commerce_payment_credit_card_types())); + } + + // If the charge amount is 0... + if ($charge['amount'] == 0) { + // Prevent the transaction except under limited circumstances. + $prevent_transaction = TRUE; + + // Allow 0 amount authorizations on Visa cards. + if ($payment_method['settings']['txn_type'] == COMMERCE_CREDIT_AUTH_ONLY && $card_type == 'visa') { + $prevent_transaction = FALSE; + } + + // If the transaction should still be prevented... + if ($prevent_transaction) { + // Create a transaction to log the skipped transaction and display a + // helpful message to the customer. + $transaction = commerce_payment_transaction_new('authnet_aim', $order->order_id); + $transaction->amount = $charge['amount']; + $transaction->currency_code = $charge['currency_code']; + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + $transaction->message = t('Invalid @amount transaction not attempted.', array('@amount' => commerce_currency_format($charge['amount'], $charge['currency_code']))); + commerce_payment_transaction_save($transaction); + + drupal_set_message('We encountered an error processing your transaction. Please contact us to resolve the issue.', 'error'); + return FALSE; + } + } + + // Get the default transaction type from the payment method settings. + $txn_type = $payment_method['settings']['txn_type']; + + // If txn_type has been specified in the pane values array, such as through + // the special select element we alter onto the payment terminal form, use + // that instead. + if (!empty($pane_values['txn_type'])) { + $txn_type = $pane_values['txn_type']; + } + + // Build a name-value pair array for this transaction. + $nvp = array( + 'x_type' => commerce_authnet_txn_type($txn_type), + 'x_method' => 'CC', + 'x_amount' => number_format(commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']), 2, '.', ''), + 'x_currency_code' => $charge['currency_code'], + 'x_card_num' => $pane_values['credit_card']['number'], + 'x_exp_date' => $pane_values['credit_card']['exp_month'] . $pane_values['credit_card']['exp_year'], + ); + + if (isset($pane_values['credit_card']['code'])) { + $nvp['x_card_code'] = $pane_values['credit_card']['code']; + } + + // Add additional transaction information to the request array. + $nvp += commerce_authnet_aim_request_order_details($order); + + // Submit the request to Authorize.Net. + $response = commerce_authnet_aim_request($payment_method, $nvp); + + // Prepare a transaction object to log the API response. + $transaction = commerce_payment_transaction_new('authnet_aim', $order->order_id); + $transaction->instance_id = $payment_method['instance_id']; + $transaction->remote_id = $response[6]; + $transaction->amount = $charge['amount']; + $transaction->currency_code = $charge['currency_code']; + $transaction->payload[REQUEST_TIME] = $response; + + // If we didn't get an approval response code... + if ($response[0] != '1') { + // Create a failed transaction with the error message. + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + } + else { + // Set the transaction status based on the type of transaction this was. + switch ($txn_type) { + case COMMERCE_CREDIT_AUTH_ONLY: + $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; + break; + + case COMMERCE_CREDIT_AUTH_CAPTURE: + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + break; + + case COMMERCE_CREDIT_CAPTURE_ONLY: + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + break; + } + } + + // Store the type of transaction in the remote status. + $transaction->remote_status = $response[11]; + + // Build a meaningful response message. + $message = array( + '' . commerce_authnet_reverse_txn_type($response[11]) . '', + '' . ($response[0] != '1' ? t('REJECTED') : t('ACCEPTED')) . ': ' . check_plain($response[3]), + t('AVS response: @avs', array('@avs' => commerce_authnet_avs_response($response[5]))), + ); + + // Add the CVV response if enabled. + if (isset($nvp['x_card_code'])) { + $message[] = t('CVV match: @cvv', array('@cvv' => commerce_authnet_cvv_response($response[38]))); + } + + $transaction->message = implode('
', $message); + + // Save the transaction information. + commerce_payment_transaction_save($transaction); + + // If the payment failed, display an error and rebuild the form. + if ($response[0] != '1') { + drupal_set_message(t('We received the following error processing your card. Please enter your information again or try a different card.'), 'error'); + drupal_set_message(check_plain($response[3]), 'error'); + return FALSE; + } + + // If Card on File storage is enabled via CIM and the form says to store data... + if (module_exists('commerce_cardonfile') && !empty($payment_method['settings']['cim_cardonfile']) && + !empty($pane_values['credit_card']['cardonfile_store']) && $pane_values['credit_card']['cardonfile_store']) { + // Build a payment details array for the credit card. + $payment_details = array( + 'cardNumber' => $pane_values['credit_card']['number'], + 'expirationDate' => $pane_values['credit_card']['exp_year'] . '-' . $pane_values['credit_card']['exp_month'], + ); + + if (isset($pane_values['credit_card']['code'])) { + $payment_details['cardCode'] = $pane_values['credit_card']['code']; + } + + // First look to see if we already have cards on file for the user. + $stored_cards = commerce_cardonfile_load_multiple_by_uid($order->uid, $payment_method['instance_id']); + $add_to_profile = NULL; + + // If we didn't find any, attempt to make a new Customer Profile now. + if (empty($stored_cards)) { + // Submit a CIM request to create the Customer Profile. + if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { + // If the Customer Profile creation was a success, store the new card on + // file data locally. + if ((string) $response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the + // Payment Profile ID. + $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; + + $card_data = commerce_cardonfile_new(); + $card_data->uid = $order->uid; + $card_data->payment_method = $payment_method['method_id']; + $card_data->instance_id = $payment_method['instance_id']; + $card_data->remote_id = $remote_id; + $card_data->card_type = !empty($card_type) ? $card_type : 'card'; + $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; + $card_data->card_number = substr($pane_values['credit_card']['number'], -4); + $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; + $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; + $card_data->status = 1; + + // Save and log the creation of the new card on file. + commerce_cardonfile_save($card_data); + watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); + } + elseif ((string) $response->messages->message->code == 'E00039') { + // But if a Customer Profile already existed for this user, attempt + // instead to add this card as a new Payment Profile to it. + $result = array_filter(explode(' ', (string) $response->messages->message->text), 'is_numeric'); + $add_to_profile = reset($result); + } + } + } + else { + // Extract the user's Customer Profile ID from the first card's remote ID. + $card_data = reset($stored_cards); + list($cim_customer_profile_id, $cim_payment_profile_id) = explode('|', $card_data->remote_id); + + // Attempt to add the card as a new payment profile to this Customer Profile. + $add_to_profile = $cim_customer_profile_id; + } + + // Attempt to add the card to an existing Customer Profile if specified. + if (!empty($add_to_profile)) { + $response = commerce_authnet_cim_create_customer_payment_profile_request($payment_method, $add_to_profile, $order, $payment_details); + + // If the Payment Profile creation was a success, store the new card on + // file data locally. + if ((string) $response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the new + // Payment Profile ID. + $remote_id = $add_to_profile . '|' . (string) $response->customerPaymentProfileId; + + $card_data = commerce_cardonfile_new(); + $card_data->uid = $order->uid; + $card_data->payment_method = $payment_method['method_id']; + $card_data->instance_id = $payment_method['instance_id']; + $card_data->remote_id = $remote_id; + $card_data->card_type = !empty($card_type) ? $card_type : 'card'; + $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; + $card_data->card_number = substr($pane_values['credit_card']['number'], -4); + $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; + $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; + $card_data->status = 1; + + // Save and log the creation of the new card on file. + commerce_cardonfile_save($card_data); + watchdog('commerce_authnet', 'CIM Payment Profile added to Customer Profile @profile_id for user @uid.', array('@profile_id' => $add_to_profile, '@uid' => $order->uid)); + } + elseif (!empty($card_data) && (string) $response->messages->message->code == 'E00040') { + // But if we could not find a customer profile, assume the existing + // customer profile ID we had is no longer valid and deactivate the card + // data that resulted in the error. + $card_data->status = 0; + commerce_cardonfile_save($card_data); + + // Submit a CIM request to create the Customer Profile. + if ($response = commerce_authnet_cim_create_customer_profile_request($payment_method, $order, $payment_details)) { + // If the Customer Profile creation was a success, store the new card on + // file data locally. + if ((string) $response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the + // Payment Profile ID. + $remote_id = (string) $response->customerProfileId . '|' . (string) $response->customerPaymentProfileIdList->numericString; + + $card_data = commerce_cardonfile_new(); + $card_data->uid = $order->uid; + $card_data->payment_method = $payment_method['method_id']; + $card_data->instance_id = $payment_method['instance_id']; + $card_data->remote_id = $remote_id; + $card_data->card_type = !empty($card_type) ? $card_type : 'card'; + $card_data->card_name = !empty($billing_address['name_line']) ? $billing_address['name_line'] : ''; + $card_data->card_number = substr($pane_values['credit_card']['number'], -4); + $card_data->card_exp_month = $pane_values['credit_card']['exp_month']; + $card_data->card_exp_year = $pane_values['credit_card']['exp_year']; + $card_data->status = 1; + + // Save and log the creation of the new card on file. + commerce_cardonfile_save($card_data); + watchdog('commerce_authnet', 'CIM Customer Profile @profile_id created and saved to user @uid.', array('@profile_id' => (string) $response->customerProfileId, '@uid' => $order->uid)); + } + } + } + } + } +} + +/** + * Returns the message text for a CVV match. + */ +function commerce_authnet_cvv_response($code) { + switch ($code) { + case 'M': + return t('Match'); + case 'N': + return t('No Match'); + case 'P': + return t('Not Processed'); + case 'S': + return t('Should have been present'); + case 'U': + return t('Issuer unable to process request'); + } + + return '-'; +} diff --git a/includes/commerce_authnet_payment.echeck.inc b/includes/commerce_authnet_payment.echeck.inc new file mode 100644 index 0000000..83396c0 --- /dev/null +++ b/includes/commerce_authnet_payment.echeck.inc @@ -0,0 +1,175 @@ + '', + 'tran_key' => '', + 'txn_mode' => AUTHNET_TXN_MODE_LIVE_TEST, + 'email_customer' => FALSE, + 'log' => array('request' => '0', 'response' => '0'), + ); + + $form['login'] = array( + '#type' => 'textfield', + '#title' => t('API Login ID'), + '#description' => t('Where this can be found.'), + '#default_value' => $settings['login'], + '#required' => TRUE, + ); + + $form['tran_key'] = array( + '#type' => 'textfield', + '#title' => t('Transaction Key'), + '#description' => t('Where this can be found.'), + '#default_value' => $settings['tran_key'], + '#required' => TRUE, + ); + + $form['txn_mode'] = array( + '#type' => 'radios', + '#title' => t('Transaction mode'), + '#description' => t('Adjust to live transactions when you are ready to start processing real payments.') . '
' . t('Only specify a developer test account if you login to your account through https://test.authorize.net.'), + '#options' => array( + AUTHNET_TXN_MODE_LIVE => t('Live transactions in a live account'), + AUTHNET_TXN_MODE_LIVE_TEST => t('Test transactions in a live account'), + AUTHNET_TXN_MODE_DEVELOPER => t('Developer test account transactions'), + ), + '#default_value' => $settings['txn_mode'], + ); + + $form['email_customer'] = array( + '#type' => 'checkbox', + '#title' => t('Tell Authorize.net to e-mail the customer a receipt based on your account settings.'), + '#default_value' => $settings['email_customer'], + ); + + $form['log'] = array( + '#type' => 'checkboxes', + '#title' => t('Log the following messages for debugging'), + '#options' => array( + 'request' => t('API request messages'), + 'response' => t('API response messages'), + ), + '#default_value' => $settings['log'], + ); + + return $form; +} + +/** + * Payment method callback: checkout form. + */ +function commerce_authnet_echeck_submit_form($payment_method, $pane_values, $checkout_pane, $order) { + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.echeck'); + + $fields = drupal_map_assoc(array('bank_name', 'acct_name')); + $fields['type'] = drupal_map_assoc(array('checking', 'business_checking', 'savings')); + + $form = commerce_payment_echeck_form($fields); + $form['echeck']['bank_name']['#maxlength'] = 50; + $form['echeck']['acct_name']['#maxlength'] = 50; + + return $form; +} + +/** + * Payment method callback: checkout form validation. + */ +function commerce_authnet_echeck_submit_form_validate($payment_method, $pane_form, $pane_values, $order, $form_parents = array()) { + module_load_include('inc', 'commerce_payment', 'includes/commerce_payment.echeck'); + + // Validate the credit card fields. + $settings = array( + 'form_parents' => array_merge($form_parents, array('echeck')), + ); + + if (!commerce_payment_echeck_validate($pane_values['echeck'], $settings)) { + return FALSE; + } +} + +/** + * Payment method callback: checkout form submission. + */ +function commerce_authnet_echeck_submit_form_submit($payment_method, $pane_form, $pane_values, $order, $charge) { + // Build a name-value pair array for this transaction. + $nvp = array( + 'x_method' => 'ECHECK', + 'x_bank_aba_code' => $pane_values['echeck']['aba_code'], + 'x_bank_acct_num' => $pane_values['echeck']['acct_num'], + 'x_bank_acct_type' => str_replace('', '_', strtoupper($pane_values['echeck']['type'])), + 'x_bank_name' => substr($pane_values['echeck']['bank_name'], 0, 50), + 'x_bank_acct_name' => substr($pane_values['echeck']['acct_name'], 0, 50), + 'x_echeck_type' => 'WEB', + 'x_recurring_billing' => 'FALSE', + 'x_amount' => commerce_currency_amount_to_decimal($charge['amount'], $charge['currency_code']), + ); + + // Add additional transaction invormation to the request array. + $nvp += commerce_authnet_aim_request_order_details($order); + + // Submit the request to Authorize.Net. + $response = commerce_authnet_aim_request($payment_method, $nvp); + + // Prepare a transaction object to log the API response. + $transaction = commerce_payment_transaction_new($payment_method['method_id'], $order->order_id); + $transaction->instance_id = $payment_method['instance_id']; + $transaction->remote_id = $response[6]; + $transaction->amount = $charge['amount']; + $transaction->currency_code = $charge['currency_code']; + $transaction->payload[REQUEST_TIME] = $response; + + // Set transaction status from response code. + if ($response[0] == '1') { + $transaction->status = COMMERCE_PAYMENT_STATUS_SUCCESS; + $reason_text = t('APPROVED'); + } + elseif ($response[0] == '4') { + $transaction->status = COMMERCE_PAYMENT_STATUS_PENDING; + $reason_text = t('PENDING'); + } + else { + $transaction->status = COMMERCE_PAYMENT_STATUS_FAILURE; + $reason_text = t('REJECTED'); + } + + // Store the type of transaction in the remote status. + $transaction->remote_status = $response[11]; + + // Build a meaningful response message. + $message = array( + 'ECHECK', + '' . $reason_text . ': ' . check_plain($response[3]), + ); + + // Add the CVV response if enabled. + if (!empty($respnsee[6])) { + $message[] = t('Transaction ID: @txn_id', array('@txn_id' => $response[6])); + } + + $transaction->message = implode('
', $message); + + // Save the transaction information. + commerce_payment_transaction_save($transaction); + + // If the payment failed, display an error and rebuild the form. + if ($response[0] == '4') { + drupal_set_message(t('We received the following notice processing your eCheck. Please contact support.'), 'warning'); + drupal_set_message(check_plain($response[3]), 'warning'); + } + elseif ($response[0] != '1') { + drupal_set_message(t('We received the following error processing your eCheck. Please enter your information again or try a different account.'), 'error'); + drupal_set_message(check_plain($response[3]), 'error'); + return FALSE; + } +}