braintree_cashier-8.x-4.x-dev/src/BillableUser.php

src/BillableUser.php
<?php

namespace Drupal\braintree_cashier;

use Braintree\CreditCard;
use Braintree\PayPalAccount;
use Drupal\braintree_api\BraintreeApiService;
use Drupal\braintree_cashier\Entity\BraintreeCashierSubscriptionInterface;
use Drupal\braintree_cashier\Event\BraintreeCashierEvents;
use Drupal\braintree_cashier\Event\BraintreeCustomerCreatedEvent;
use Drupal\braintree_cashier\Event\BraintreeErrorEvent;
use Drupal\braintree_cashier\Event\PaymentMethodUpdatedEvent;
use Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher;
use Drupal\Core\Cache\Cache;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Logger\LoggerChannelInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\StringTranslation\StringTranslationTrait;
use Drupal\Core\Theme\ThemeManagerInterface;
use Drupal\message\Entity\Message;
use Drupal\user\Entity\User;
use Drupal\user\UserInterface;

/**
 * BillableUser class provides functions that apply to the user entity.
 *
 * @ingroup braintree_cashier
 */
class BillableUser {

  use StringTranslationTrait;

  /**
   * The Braintree Cashier logger channel.
   *
   * @var \Drupal\Core\Logger\LoggerChannelInterface
   */
  protected $logger;

  /**
   * The subscription entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $subscriptionStorage;

  /**
   * The user entity storage.
   *
   * @var \Drupal\Core\Entity\EntityStorageInterface
   */
  protected $userStorage;

  /**
   * The braintree cashier service.
   *
   * @var \Drupal\braintree_cashier\BraintreeCashierService
   */
  protected $bcService;

  /**
   * Event dispatcher.
   *
   * @var \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher
   */
  protected $eventDispatcher;

  /**
   * The Braintree API service.
   *
   * @var \Drupal\braintree_api\BraintreeApiService
   */
  protected $braintreeApiService;

  /**
   * Braintree cashier settings.
   *
   * @var \Drupal\Core\Config\ImmutableConfig
   */
  protected $bcConfig;

  /**
   * The theme manager.
   *
   * @var \Drupal\Core\Theme\ThemeManagerInterface
   */
  protected $themeManager;

  /**
   * The messenger service.
   *
   * @var \Drupal\Core\Messenger\MessengerInterface
   */
  protected $messenger;

  /**
   * BillableUser constructor.
   *
   * @param \Drupal\Core\Logger\LoggerChannelInterface $logger
   *   The Braintree Cashier logger channel.
   * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
   *   The entity type manager.
   * @param \Drupal\braintree_cashier\BraintreeCashierService $bcService
   *   The braintree cashier service.
   * @param \Drupal\Component\EventDispatcher\ContainerAwareEventDispatcher $eventDispatcher
   *   The container aware event dispatcher.
   * @param \Drupal\braintree_api\BraintreeApiService $braintreeApiService
   *   The Braintree API service.
   * @param \Drupal\Core\Config\ConfigFactoryInterface $configFactory
   *   The config factory.
   * @param \Drupal\Core\Theme\ThemeManagerInterface $themeManager
   *   The theme manager.
   * @param \Drupal\Core\Messenger\MessengerInterface $messenger
   *   The messenger service.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   */
  public function __construct(LoggerChannelInterface $logger, EntityTypeManagerInterface $entity_type_manager, BraintreeCashierService $bcService, ContainerAwareEventDispatcher $eventDispatcher, BraintreeApiService $braintreeApiService, ConfigFactoryInterface $configFactory, ThemeManagerInterface $themeManager, MessengerInterface $messenger) {
    $this->logger = $logger;
    $this->subscriptionStorage = $entity_type_manager->getStorage('braintree_cashier_subscription');
    $this->userStorage = $entity_type_manager->getStorage('user');
    $this->bcService = $bcService;
    $this->eventDispatcher = $eventDispatcher;
    $this->braintreeApiService = $braintreeApiService;
    $this->bcConfig = $configFactory->get('braintree_cashier.settings');
    $this->themeManager = $themeManager;
    $this->messenger = $messenger;
  }

  /**
   * Updates the payment method for the provided user entity.
   *
   * Deletes the previous payment method so that only one is kept on file.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   * @param string $nonce
   *   The payment method nonce from the Braintree Drop-in UI.
   * @param string $device_data
   *   Device data collected by the Drop-in UI for Advanced Fraud Management.
   *
   * @return bool
   *   A boolean indicating whether the update was successful.
   */
  public function updatePaymentMethod(User $user, $nonce, $device_data) {

    $customer = $this->asBraintreeCustomer($user);

    $payload = [
      'customerId' => $customer->id,
      'paymentMethodNonce' => $nonce,
      'deviceData' => $device_data,
      'options' => [
        'makeDefault' => true,
        'verifyCard' => true,
      ],
    ];
    $result = $this->braintreeApiService->getGateway()->paymentMethod()->create($payload);

    if (!$result->success) {
      $this->logger->error('Error creating payment method: ' . $result->message);
      $event = new BraintreeErrorEvent($user, $result->message, $result);
      $this->eventDispatcher->dispatch(BraintreeCashierEvents::BRAINTREE_ERROR, $event);
      if (!empty($result->creditCardVerification)) {
        $credit_card_verification = $result->creditCardVerification;
        if ($credit_card_verification->status == 'processor_declined') {
          $this->bcService->handleProcessorDeclined($credit_card_verification->processorResponseCode, $credit_card_verification->processorResponseText);
        }
        if ($credit_card_verification->status == 'gateway_rejected') {
          $this->bcService->handleGatewayRejected($credit_card_verification->gatewayRejectionReason);
        }
      }
      else {
        $this->messenger->addError($this->t('Error: @message', ['@message' => $result->message]));
      }
      return FALSE;
    }

    if ($this->bcConfig->get('prevent_duplicate_payment_methods') && !$this->preventDuplicatePaymentMethods($user, $result->paymentMethod)) {
      return FALSE;
    }

    $this->updateSubscriptionsToPaymentMethod($user, $result->paymentMethod->token);
    $this->removeNonDefaultPaymentMethods($user);

    $payment_method_type = get_class($result->paymentMethod);

    $event = new PaymentMethodUpdatedEvent($user, $payment_method_type);
    $this->eventDispatcher->dispatch(BraintreeCashierEvents::PAYMENT_METHOD_UPDATED, $event);

    return TRUE;
  }

  /**
   * Gets the Braintree customer.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user entity.
   *
   * @return \Braintree\Customer
   *   The Braintree customer object.
   *
   * @throws \Braintree\Exception\NotFound
   *   The Braintree not found exception.
   */
  public function asBraintreeCustomer(UserInterface $user) {
    return $this->braintreeApiService->getGateway()->customer()->find($this->getBraintreeCustomerId($user));
  }

  /**
   * Gets the Braintree customer ID.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user entity.
   *
   * @return string
   *   The Braintree customer ID.
   */
  public function getBraintreeCustomerId(UserInterface $user) {
    return $user->get('braintree_customer_id')->value;
  }

  /**
   * Updates all subscriptions to use the payment method with the given token.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   * @param string $token
   *   The payment method token.
   */
  public function updateSubscriptionsToPaymentMethod(User $user, $token) {
    foreach ($this->getSubscriptions($user) as $subscription_entity) {
      /* @var $subscription_entity \Drupal\braintree_cashier\Entity\BraintreeCashierSubscriptionInterface */
      $this->braintreeApiService->getGateway()->subscription()->update(
        $subscription_entity->getBraintreeSubscriptionId(), [
          'paymentMethodToken' => $token,
        ]);
    }
  }

  /**
   * Gets the subscription entities for a user.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user entity.
   * @param bool $active
   *   Whether to return only subscriptions that are currently active.
   *
   * @return \Drupal\Core\Entity\EntityInterface[]
   *   An array of subscription entities.
   */
  public function getSubscriptions(UserInterface $user, $active = TRUE) {
    $query = $this->subscriptionStorage->getQuery();
    $query->condition('subscribed_user.target_id', $user->id());
    if ($active) {
      $query->condition('status', BraintreeCashierSubscriptionInterface::ACTIVE);
    }
    $result = $query->execute();
    if (!empty($result)) {
      return $this->subscriptionStorage->loadMultiple($result);
    }
    return [];
  }

  /**
   * Remove non-default payment methods.
   *
   * This keeps the Drop-in UI simple since otherwise all payment methods are
   * always shown.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   *
   * @throws \Braintree\Exception\NotFound
   *   The Braintree not found exception.
   */
  public function removeNonDefaultPaymentMethods(User $user) {
    $customer = $this->asBraintreeCustomer($user);
    $default_payment_method = $customer->defaultPaymentMethod();
    foreach ($customer->paymentMethods as $paymentMethod) {
      if ($paymentMethod->token != $default_payment_method->token) {
        $this->braintreeApiService->getGateway()->paymentMethod()->delete($paymentMethod->token);
      }
    }
  }

  /**
   * Gets a Braintree payment method.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   *
   * @return \Braintree\CreditCard|\Braintree\PayPalAccount|\Braintree\ApplePayCard|\Braintree\AndroidPayCard
   *   The Braintree payment method object, or FALSE if none found.
   *
   * @throws \Braintree\Exception\NotFound
   *   The Braintree not found exception.
   */
  public function getPaymentMethod(User $user) {
    $customer = $this->asBraintreeCustomer($user);
    return $customer->defaultPaymentMethod();
  }

  /**
   * Creates a new Braintree customer.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   * @param string $nonce
   *   The payment method nonce from the Drop-in UI.
   * @param string $device_data
   *   Device data collected by the Drop-in UI for Advanced Fraud Management.
   *
   * @return \Braintree\Customer|bool
   *   The Braintree customer object.
   */
  public function createAsBraintreeCustomer(User $user, $nonce, $device_data) {
    $result = $this->braintreeApiService->getGateway()->customer()->create([
      'firstName' => $user->getAccountName(),
      'email' => $user->getEmail(),
      'paymentMethodNonce' => $nonce,
      'deviceData' => $device_data,
      'creditCard' => [
        'options' => [
          'makeDefault' => true,
          'verifyCard' => true,
        ],
      ]
    ]);

    if (!$result->success) {
      $this->logger->error('Error creating Braintree customer: ' . $result->message);
      $event = new BraintreeErrorEvent($user, $result->message, $result);
      $this->eventDispatcher->dispatch(BraintreeCashierEvents::BRAINTREE_ERROR, $event);
      if (!empty($result->creditCardVerification)) {
        $credit_card_verification = $result->creditCardVerification;
        if ($credit_card_verification->status == 'processor_declined') {
          $this->bcService->handleProcessorDeclined($credit_card_verification->processorResponseCode, $credit_card_verification->processorResponseText);
        }
        if ($credit_card_verification->status == 'gateway_rejected') {
          $this->bcService->handleGatewayRejected($credit_card_verification->gatewayRejectionReason);
        }
        return FALSE;
      }
      $this->messenger->addError($this->t('Card declined: @message', ['@message' => $result->message]));
      return FALSE;
    }

    // Check for duplicate payment methods.
    if ($this->bcConfig->get('prevent_duplicate_payment_methods')) {
      foreach ($result->customer->paymentMethods as $payment_method) {
        if (!$this->preventDuplicatePaymentMethods($user, $payment_method)) {
          $this->braintreeApiService->getGateway()->customer()->delete($result->customer->id);
          return FALSE;
        }
      }
    }

    $this->logger->notice('A new Braintree Customer has been created with Braintree Customer ID: %id', [
      '%id' => $result->customer->id,
    ]);

    $user->set('braintree_customer_id', $result->customer->id);
    $user->save();

    $event = new BraintreeCustomerCreatedEvent($user);
    $this->eventDispatcher->dispatch(BraintreeCashierEvents::BRAINTREE_CUSTOMER_CREATED, $event);

    // Invalidate the local tasks cache to make the "Invoices" task appear when
    // viewed by other users such as administrators.
    $theme_machine_name = $this->themeManager->getActiveTheme()->getName();
    Cache::invalidateTags(['config:block.block.' . $theme_machine_name . '_local_tasks']);

    return $result->customer;
  }

  /**
   * Sets the user-provided invoice billing information.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity with the provided billing information.
   * @param string $billing_information
   *   The billing information.
   *
   * @return \Drupal\user\Entity\User
   *   The user entity.
   */
  public function setInvoiceBillingInformation(User $user, $billing_information) {
    $user->set('invoice_billing_information', $billing_information);
    $user->save();
    return $user;
  }

  /**
   * Gets the user-provided invoice billing information for the user.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   *
   * @return string
   *   The billing information markup.
   */
  public function getInvoiceBillingInformation(User $user) {
    return check_markup($user->get('invoice_billing_information')->value, $user->get('invoice_billing_information')->format);
  }

  /**
   * Gets the user's billing information as plain text.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity.
   *
   * @return mixed
   *   The plain text billing information.
   */
  public function getRawInvoiceBillingInformation(User $user) {
    return $user->get('invoice_billing_information')->value;
  }

  /**
   * Generate client token for the Drop-in UI for the provided user entity.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user entity which might have a Braintree customer ID.
   * @param int $version
   *   The Braintree API version.
   *
   * @see https://developers.braintreepayments.com/reference/request/client-token/generate/php#version
   *   For documentation about the version.
   *
   * @return string
   *   The Braintree Client Token.
   */
  public function generateClientToken(User $user = NULL, $version = 3) {
    $version = $this->sanitizeVersion($version);
    try {
      $payload = [
        'version' => $version,
      ];
      if ($user !== NULL && !empty($this->getBraintreeCustomerId($user))) {
        $payload['customerId'] = $this->getBraintreeCustomerId($user);
        $payload['options'] = [
          'makeDefault' => TRUE,
        ];
      }
      return $this->braintreeApiService->getGateway()->clientToken()->generate($payload);
    }
    catch (\InvalidArgumentException $e) {
      // The customer id provided probably doesn't exist with Braintree.
      $this->logger->error('InvalidArgumentException occurred in generateClientToken: ' . $e->getMessage());
      $this->messenger->addError($this->t('Our payment processor reported the following error: %error. Please contact the site administrator.', [
        '%error' => $e->getMessage(),
      ]));
    }
    catch (\Exception $e) {
      // There was probably an API error of some kind. Either API credentials
      // are not configured properly, or there's an issue with Braintree.
      $this->logger->error('Exception in generateClientToken(): ' . $e->getMessage());
      $this->messenger->addError($this->t('Our payment processor reported the following error: %error. Please try reloading the page.', ['%error' => $e->getMessage()]));
    }
  }

  /**
   * Sanitizes the version number.
   *
   * @param int $version
   *   The Braintree API version.
   *
   * @return int
   *   A version which is guaranteed to be valid.
   */
  private function sanitizeVersion($version) {
    if (!in_array($version, [1, 2, 3])) {
      $version = 3;
    }
    return $version;
  }

  /**
   * Gets the form API array for the Braintree Drop-in UI element.
   *
   * This function permits updating the version of the drop-in UI in only one
   * place since the drop-in UI is used in multiple forms.
   *
   * @param \Drupal\Core\Entity\EntityInterface $user
   *   The user account for which to generate the Drop-in UI.
   *
   * @return array
   *   The Drop-in UI form element.
   */
  public function getDropinUiFormElement(EntityInterface $user = NULL) {
    $element = [
      '#type' => 'html_tag',
      '#tag' => 'script',
      '#attributes' => [
        'src' => 'https://js.braintreegateway.com/web/dropin/1.11.0/js/dropin.min.js',
        'data-braintree-dropin-authorization' => $this->generateClientToken($user),
      ],
    ];
    if ($this->bcConfig->get('accept_paypal')) {
      $element['#attributes']['data-paypal.flow'] = 'vault';
    }
    return $element;
  }

  /**
   * Check whether a payment method is in use by a different account.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user account that wishes to own the payment method.
   * @param mixed $payment_method
   *   The payment method object.
   *
   * @return array|bool
   *   An array of other uids that have this payment method, or FALSE if
   *   no other account is using this payment method.
   */
  public function isDuplicatePaymentMethod(User $user, $payment_method) {
    $query = $this->userStorage->getQuery();
    if ($payment_method instanceof CreditCard) {
      $identifier = $payment_method->uniqueNumberIdentifier;
    }
    if ($payment_method instanceof PayPalAccount) {
      $identifier = $payment_method->email;
    }
    $query->condition('payment_method_identifier', $identifier);
    $uids = $query->execute();
    if (empty($uids) || (\count($uids) === 1 && \in_array($user->id(), $uids, TRUE))) {
      // Either no user owns this payment method, or only the given user does.
      return FALSE;
    }
    return $uids;
  }

  /**
   * Records the payment method identifier on the user entity.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user account that wishes to own the payment method.
   * @param mixed $payment_method
   *   The payment method object.
   *
   * @return bool
   *   A boolean indicating whether the identifier was successfully recorded.
   */
  public function recordPaymentMethodIdentifier(User $user, $payment_method) {
    if ($payment_method instanceof CreditCard) {
      $identifier = $payment_method->uniqueNumberIdentifier;
    }
    if ($payment_method instanceof PayPalAccount) {
      $identifier = $payment_method->email;
    }
    if (empty($identifier)) {
      return FALSE;
    }
    $user->set('payment_method_identifier', $identifier);
    $user->save();
    return TRUE;
  }

  /**
   * Prevent duplicate payment methods.
   *
   * @param \Drupal\user\Entity\User $user
   *   The user account that wishes to own the payment method.
   * @param mixed $payment_method
   *   The payment method object.
   *
   * @return bool
   *   A boolean indicating success with preventing duplicate payment methods.
   */
  public function preventDuplicatePaymentMethods(User $user, $payment_method) {
    if (!empty($uids = $this->isDuplicatePaymentMethod($user, $payment_method))) {
      $this->braintreeApiService->getGateway()->paymentMethod()->delete($payment_method->token);
      $message = Message::create([
        'template' => 'duplicate_payment_method',
        'uid' => $user->id(),
        'field_duplicate_user' => $uids,
      ]);
      $message->save();
      $this->messenger->addError($this->bcConfig->get('duplicate_payment_method_message'));
      $this->logger->error('Duplicate payment method. User account uids with this payment method: %uids',
        ['%uids' => print_r($uids, TRUE)]);
      return FALSE;
    }
    if (!$this->recordPaymentMethodIdentifier($user, $payment_method)) {
      $this->braintreeApiService->getGateway()->paymentMethod()->delete($payment_method->token);
      $message = $this->t('There was a problem with your payment method. Please try again, or contact a site administrator');
      $this->messenger->addError($message);
      $this->logger->error($message);
      return FALSE;
    }
    return TRUE;
  }

  /**
   * Update the vaulted email address stored in Braintree for a given user.
   *
   * The email address in Braintree will be set to the current email of the
   * provided user entity. Before utilizing this function, ensure that the user
   * entity is already vaulted in Braintree by checking for a Braintree customer
   * ID.
   *
   * @param \Drupal\user\UserInterface $user
   *   The user entity for which to update the email address.
   */
  public function updateVaultedEmail(UserInterface $user) {
    $gateway = $this->braintreeApiService->getGateway();
    $gateway->customer()->update($this->getBraintreeCustomerId($user), [
      'email' => $user->getEmail(),
    ]);
    $this->logger->notice('Updated email address in Braintree vault to @new_email', [
      '@new_email' => $user->getEmail(),
    ]);
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc