You are here

protected function AcceptJs::doCreatePaymentMethod in Commerce Authorize.Net 8

Creates the payment method on the gateway.

This handles customer and payment profile creation, along with logic to handle Authorize.net's duplicate profile detection.

@link https://developer.authorize.net/api/reference/features/customer_profiles...

@todo Rename to customer profile @todo Make a method for just creating payment profile on existing profile.

Parameters

\Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method: The payment method.

array $payment_details: The gateway-specific payment details.

Return value

array The payment method information returned by the gateway. Notable keys:

  • token: The remote ID.

Credit card specific keys:

  • card_type: The card type.
  • last4: The last 4 digits of the credit card number.
  • expiration_month: The expiration month.
  • expiration_year: The expiration year.
1 call to AcceptJs::doCreatePaymentMethod()
AcceptJs::createPaymentMethod in src/Plugin/Commerce/PaymentGateway/AcceptJs.php
@todo Needs kernel test

File

src/Plugin/Commerce/PaymentGateway/AcceptJs.php, line 607

Class

AcceptJs
Provides the Accept.js payment gateway.

Namespace

Drupal\commerce_authnet\Plugin\Commerce\PaymentGateway

Code

protected function doCreatePaymentMethod(PaymentMethodInterface $payment_method, array $payment_details) {
  $owner = $payment_method
    ->getOwner();
  $customer_profile_id = NULL;
  $customer_data = [];
  if ($owner && !$owner
    ->isAnonymous()) {
    $customer_profile_id = $this
      ->getRemoteCustomerId($owner);
    if (empty($customer_profile_id)) {
      $customer_profile_id = $this
        ->getPaymentMethodCustomerId($payment_method);
    }
    $customer_data['email'] = $owner
      ->getEmail();
  }
  if ($customer_profile_id) {
    $payment_profile = $this
      ->buildCustomerPaymentProfile($payment_method, $payment_details, $customer_profile_id);
    $request = new CreateCustomerPaymentProfileRequest($this->authnetConfiguration, $this->httpClient);
    $request
      ->setCustomerProfileId($customer_profile_id);
    $request
      ->setPaymentProfile($payment_profile);
    $response = $request
      ->execute();
    if ($response
      ->getResultCode() != 'Ok') {
      $this
        ->logResponse($response);
      $error = $response
        ->getMessages()[0];
      switch ($error
        ->getCode()) {
        case 'E00039':
          if (!isset($response->customerPaymentProfileId)) {
            throw new InvalidResponseException('Duplicate payment profile ID, however could not get existing ID.');
          }
          break;
        case 'E00040':

          // The customer record ID is invalid, remove it.
          // @note this should only happen in development scenarios.
          $this
            ->setRemoteCustomerId($owner, NULL);
          $owner
            ->save();
          throw new InvalidResponseException('The customer record could not be found');
        default:
          throw new InvalidResponseException($error
            ->getText());
      }
    }
    $payment_profile_id = $response->customerPaymentProfileId;
    $validation_direct_response = explode(',', $response->validationDirectResponse);
  }
  else {
    $request = new CreateCustomerProfileRequest($this->authnetConfiguration, $this->httpClient);
    if ($owner
      ->isAuthenticated()) {
      $profile = new Profile([
        // @todo how to allow altering.
        'merchantCustomerId' => $owner
          ->id(),
        'email' => $owner
          ->getEmail(),
      ]);
    }
    else {
      $profile = new Profile([
        // @todo how to allow altering.
        'merchantCustomerId' => $owner
          ->id() . '_' . $this->time
          ->getRequestTime(),
        'email' => $payment_details['customer_email'],
      ]);
    }
    $request
      ->setProfile($profile);

    // Due to their being a possible duplicate record for the customer
    // profile, we cannot attach the payment profile in the initial request.
    // If we did, it would invalidate the token generated by Accept.js and
    // not allow us to reconcile duplicate payment methods.
    //
    // So we do not attach a payment profile and run two requests, and make
    // sure no validation mode is executed.
    $request
      ->setValidationMode(NULL);
    $response = $request
      ->execute();
    if ($response
      ->getResultCode() == 'Ok') {
      $customer_profile_id = $response->customerProfileId;
    }
    else {

      // Handle duplicate.
      if ($response
        ->getMessages()[0]
        ->getCode() == 'E00039') {
        $result = array_filter(explode(' ', $response
          ->getMessages()[0]
          ->getText()), 'is_numeric');
        $customer_profile_id = reset($result);
      }
      else {
        $this
          ->logResponse($response);
        throw new InvalidResponseException("Unable to create customer profile.");
      }
    }
    $payment_profile = $this
      ->buildCustomerPaymentProfile($payment_method, $payment_details, $customer_profile_id);
    $request = new CreateCustomerPaymentProfileRequest($this->authnetConfiguration, $this->httpClient);
    $request
      ->setCustomerProfileId($customer_profile_id);
    $request
      ->setPaymentProfile($payment_profile);
    $response = $request
      ->execute();
    if ($response
      ->getResultCode() != 'Ok') {

      // If the error is not due to duplicates, log and error.
      if ($response
        ->getMessages()[0]
        ->getCode() != 'E00039') {
        $this
          ->logResponse($response);
        throw new InvalidResponseException("Unable to create payment profile for existing customer");
      }
    }
    $payment_profile_id = $response->customerPaymentProfileId;
    $validation_direct_response = explode(',', $response->validationDirectResponse);
    if ($owner
      ->isAuthenticated()) {
      $this
        ->setRemoteCustomerId($owner, $customer_profile_id);
      $owner
        ->save();
    }
  }

  // The result in validationDirectResponse does not properly escape any
  // delimiter characters in the response data. So if the address, name, or
  // email have "," the key for the card type is offset.
  //
  // We know the card type is after the XXXX#### masked card number.
  $card_type_key = 51;
  foreach ($validation_direct_response as $key => $value) {
    if (!empty($value) && strpos($value, 'XXXX') !== FALSE) {
      $card_type_key = $key + 1;
    }
  }
  $remote_id = $owner
    ->isAuthenticated() ? $payment_profile_id : $customer_profile_id . '|' . $payment_profile_id;
  return [
    'remote_id' => $remote_id,
    'card_type' => $validation_direct_response[$card_type_key],
    'last4' => $payment_details['last4'],
    'expiration_month' => $payment_details['expiration_month'],
    'expiration_year' => $payment_details['expiration_year'],
  ];
}