diff --git a/commerce_authnet.module b/commerce_authnet.module index b318530..9553d76 100644 --- a/commerce_authnet.module +++ b/commerce_authnet.module @@ -157,6 +157,7 @@ function commerce_authnet_commerce_payment_method_info() { 'display_title' => t('Credit card'), 'description' => t('Integrates Authorize.Net Advanced Integration Method for card not present CC transactions.'), 'cardonfile' => array( + 'create callback' => 'commerce_authnet_cim_cardonfile_create', 'charge callback' => 'commerce_authnet_cim_cardonfile_charge', 'update callback' => 'commerce_authnet_cim_cardonfile_update', 'delete callback' => 'commerce_authnet_cim_cardonfile_delete', @@ -735,6 +736,196 @@ function commerce_authnet_form_commerce_cardonfile_update_form_alter(&$form, &$f } /** + * Implements hook_form_FORM_ID_alter(). + * + * Adds a commerce customer billing address field to the cardonfile form. + */ +function commerce_authnet_form_commerce_cardonfile_card_form_alter(&$form, &$form_state) { + + // Make sure this is the right payment method and the cardonfile create form. + if ($form_state['card']->payment_method == 'authnet_aim' && $form_state['op'] == 'create') { + + // Hide the existing name field. + $form['credit_card']['owner']['#access'] = FALSE; + + // Create a billing profile object and add the address form. + $profile = commerce_customer_profile_new('billing', $form_state['card']->uid); + $form_state['commerce_customer_profile'] = $profile; + $form['commerce_customer_profile'] = array(); + field_attach_form('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); + + $form['commerce_customer_profile']['#weight'] = -1; + + // Add a validation callback so that we can call field_attach functions. + $form['#validate'][] = 'commerce_authnet_cim_cardonfile_create_validate'; + } +} + +/** + * Validation callback for card on file create. + */ +function commerce_authnet_cim_cardonfile_create_validate($form, &$form_state) { + $profile = $form_state['commerce_customer_profile']; + field_attach_form_validate('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); +} + +/** + * Commerce Card on File create callback. + * @param array $form + * The card on file create form. + * @param array $form_state + * The card on file create form state. + * @param array $payment_method + * The payment method for the card on file request. + * @param object $card_data + * The commerce_cardonfile entity. + * + * @return object/bool + * The updated commerce_cardonfile entity or FALSE if there was an error. + */ +function commerce_authnet_cim_cardonfile_create($form, &$form_state, $payment_method, $card_data) { + $account = user_load($card_data->uid); + + // Submit the profile to the field attach handlers. + $profile = $form_state['commerce_customer_profile']; + field_attach_submit('commerce_customer_profile', $profile, $form['commerce_customer_profile'], $form_state); + commerce_customer_profile_save($profile); + + // Format the address into the Auth.net schema. + $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile); + $billto = commerce_authnet_cim_billto_array(NULL, $profile_wrapper->commerce_customer_address->value()); + + // Build the card data to submit to Auth.net. + $number = $form_state['values']['credit_card']['number']; + $card_data->card_exp_month = $form_state['values']['credit_card']['exp_month']; + $card_data->card_exp_year = $form_state['values']['credit_card']['exp_year']; + $card_expire = $card_data->card_exp_year . '-' . $card_data->card_exp_month; + $card_code = $form_state['values']['credit_card']['code']; + $card_type = $form_state['values']['credit_card']['type']; + + // Attempt to load and existing profile id from the customers card data. + $existing_cards = commerce_cardonfile_load_multiple_by_uid($account->uid, $payment_method['instance_id']); + + // Check for a remote ID. + $remote_id = NULL; + if (!empty($existing_cards)) { + $existing_card = reset($existing_cards); + $remote_id = $existing_card->remote_id; + } + + if ($remote_id) { + + // Extract the profile id from the remote id. + list($cim_customer_profile_id, $cim_customer_payment_id) = explode('|', $remote_id); + + // Build a request to add a new payment method to an existing profile. + $api_request_data = array( + 'customerProfileId' => $cim_customer_profile_id, + 'paymentProfile' => array( + 'billTo' => $billto, + 'payment' => array( + 'creditCard' => array( + 'cardNumber' => $number, + 'expirationDate' => $card_expire, + 'cardCode' => $card_code, + ), + ), + ), + ); + + $xml_response = commerce_authnet_cim_request($payment_method, 'createCustomerPaymentProfileRequest', $api_request_data); + + } else { + + // There isn't a profile ID to extract + $cim_customer_profile_id = NULL; + + // Build a request to create a profile and add payment method to it. + $api_request_data = array( + 'profile' => array( + 'merchantCustomerId' => $account->uid, + 'description' => $billto['firstName'] . ' ' . $billto['lastName'], + 'email' => $account->mail, + 'paymentProfiles' => array( + 'billTo' => $billto, + 'payment' => array( + 'creditCard' => array( + 'cardNumber' => $number, + 'expirationDate' => $card_expire, + 'cardCode' => $card_code, + ), + ), + ), + ), + ); + + $xml_response = commerce_authnet_cim_request($payment_method, 'createCustomerProfileRequest', $api_request_data); + + } + + if ((string) $xml_response->messages->resultCode == 'Ok') { + // Build a remote ID that includes the Customer Profile ID and the + // Payment Profile ID. + if ($cim_customer_profile_id) { + $remote_id = $cim_customer_profile_id . '|' . (string) $xml_response->customerPaymentProfileId; + } else { + $remote_id = (string) $xml_response->customerProfileId . '|' . (string) $xml_response->customerPaymentProfileIdList->numericString; + } + + $card_data_wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data); + $card_data->uid = $account->uid; + $card_data->remote_id = $remote_id; + $card_data->card_type = $card_type; + $card_data->card_name = $billto['firstName'] . ' ' . $billto['lastName']; + $card_data->card_number = substr($number, -4); + $card_data->status = 1; + $card_data_wrapper->commerce_cardonfile_profile = $profile; + + return $card_data; + + } + elseif ((string) $xml_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) $xml_response->messages->message->text), 'is_numeric'); + $add_to_profile = reset($result); + + $payment_profile_id = commerce_authnet_cim_get_customer_profile_request($payment_method, $add_to_profile, $number); + + if ($payment_profile_id) { + // Build a remote ID that includes the Customer Profile ID and the new + // Payment Profile ID. + $remote_id = $add_to_profile . '|' . $payment_profile_id; + + $card_data_wrapper = entity_metadata_wrapper('commerce_cardonfile', $card_data); + $card_data->uid = $account->uid; + $card_data->remote_id = $remote_id; + $card_data->card_type = $card_type; + $card_data->card_name = $billto['firstName'] . ' ' . $billto['lastName']; + $card_data->card_number = substr($number, -4); + $card_data->status = 1; + $card_data_wrapper->commerce_cardonfile_profile = $profile; + + return $card_data; + } + else { + // Provide the user with information on the failure if it exists. + if (!empty($xml_response->messages->message->text)) { + drupal_set_message(t('Error: @error', array('@error' => (string) $xml_response->messages->message->text)), 'error'); + } + } + } + else { + // Provide the user with information on the failure if it exists. + if (!empty($xml_response->messages->message->text)) { + drupal_set_message(t('Error: @error', array('@error' => (string) $xml_response->messages->message->text)), 'error'); + } + } + + return FALSE; +} + +/** * Card on file callback: background charge payment * * @param object $payment_method @@ -931,14 +1122,19 @@ function commerce_authnet_cim_cardonfile_delete($form, &$form_state, $payment_me * * @param $order * The order object containing the billing information used for the billTo. + * @param $billing_address + * A commerce_customer_address array to use instead of the one from the order. * * @return * An array used to generate the billTo XML in CIM API requests. */ -function commerce_authnet_cim_billto_array($order) { +function commerce_authnet_cim_billto_array($order, $billing_address = NULL) { // Prepare the billing address for use in the request. - $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); + + if (!empty($order)) { + $order_wrapper = entity_metadata_wrapper('commerce_order', $order); + $billing_address = $order_wrapper->commerce_customer_billing->commerce_customer_address->value(); + } // Ensure we have a first and last name in the address. if (empty($billing_address['first_name'])) { @@ -952,6 +1148,11 @@ function commerce_authnet_cim_billto_array($order) { $billing_address['administrative_area'] = $billing_address['locality']; } + // Ensure organisation name is keyed. + if (!isset($billing_address['organisation_name'])) { + $billing_address['organisation_name'] = ''; + } + // Return the billTo array. return array( 'firstName' => substr($billing_address['first_name'], 0, 50), @@ -1124,6 +1325,45 @@ function commerce_authnet_cim_create_customer_payment_profile_request($payment_m } /** + * Submits a request to retrieve a Customer profile's payment profiles + * + * This is useful when Authorize.net returns an error code of E00039, duplicate. + * + * @param $payment_method + * @param $cim_customer_profile_id + * @param $number + * + * @return bool|string + * Returns FALSE if not payment profile found, or the matching profile ID. + */ +function commerce_authnet_cim_get_customer_profile_request($payment_method, $cim_customer_profile_id, $number) { + // Query for the profile's payment profiles. + $api_request_data = array( + 'customerProfileId' => $cim_customer_profile_id, + ); + + $xml_customer_response = commerce_authnet_cim_request($payment_method, 'getCustomerProfileRequest', $api_request_data); + + if ($xml_customer_response->messages->resultCode == 'Ok') { + $payment_profiles = $xml_customer_response->profile->paymentProfiles; + // Single profiles come back as a non-array + if (!is_array($payment_profiles)) { + $payment_profiles = array($payment_profiles); + } + + $payment_profile_id = FALSE; + foreach ($payment_profiles as $key => $payment_profile) { + if (substr($number, -4) == substr($payment_profile->payment->creditCard->cardNumber, -4)) { + $payment_profile_id = (string) $payment_profile->customerPaymentProfileId; + } + } + + return $payment_profile_id; + } + return FALSE; +} + +/** * Submits a getCustomerPaymentProfileRequest XML CIM API request to Authorize.Net. * * @param $payment_method