You are here

class Braintree3DSReview in Commerce Braintree 8

Adds 3DS authentication for Braintree vaulted/stored payment methods.

This checkout pane is required for 3DS functionality. It ensures that the last step in the checkout performs authentication. If the customer's card is not enrolled in 3DS then the form will submit as normal. Otherwise a modal will appear for the customer to authenticate.

Plugin annotation


@CommerceCheckoutPane(
  id = "braintree_3ds_review",
  label = @Translation("Braintree 3DS review"),
  default_step = "review",
  wrapper_element = "container",
)

Hierarchy

Expanded class hierarchy of Braintree3DSReview

File

src/Plugin/Commerce/CheckoutPane/Braintree3DSReview.php, line 31

Namespace

Drupal\commerce_braintree\Plugin\Commerce\CheckoutPane
View source
class Braintree3DSReview extends CheckoutPaneBase {

  /**
   * The logger.
   *
   * @var \Psr\Log\LoggerInterface
   */
  protected $logger;

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
    $instance = parent::create($container, $configuration, $plugin_id, $plugin_definition, $checkout_flow);
    $instance
      ->setLogger($container
      ->get('logger.channel.commerce_payment'));
    return $instance;
  }

  /**
   * Sets the logger.
   *
   * @param \Psr\Log\LoggerInterface $logger
   *   The new logger.
   *
   * @return $this
   */
  public function setLogger(LoggerInterface $logger) {
    $this->logger = $logger;
    return $this;
  }

  /**
   * {@inheritdoc}
   */
  public function isVisible() {

    // Only display for reusable/vaulted 3DS Braintree payment methods.
    if ($this->order
      ->get('payment_method')
      ->isEmpty() || $this->order
      ->get('payment_gateway')
      ->isEmpty() || !$this->order
      ->get('payment_gateway')->entity) {
      return FALSE;
    }

    /** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
    $payment_gateway = $this->order
      ->get('payment_gateway')->entity;
    if (!$payment_gateway
      ->getPlugin() instanceof HostedFieldsInterface) {
      return FALSE;
    }
    $configuration = $payment_gateway
      ->getPlugin()
      ->getConfiguration();
    if (empty($configuration['3d_secure'])) {
      return FALSE;
    }
    $payment_method = $this->order
      ->get('payment_method')->entity;
    if (!$payment_method
      ->isReusable() || $payment_method
      ->getType()
      ->getPluginId() !== 'credit_card') {
      return FALSE;
    }
    return TRUE;
  }

  /**
   * {@inheritdoc}
   */
  public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {

    /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
    $payment_method = $this->order
      ->get('payment_method')->entity;

    /** @var \Drupal\commerce_braintree\Plugin\Commerce\PaymentGateway\HostedFieldsInterface $braintree_plugin */
    $braintree_plugin = $this->order
      ->get('payment_gateway')->entity
      ->getPlugin();

    // 3DS nonces are single-use, so a new nonce must be generated from the
    // stored payment method and authenticated for use in payment transaction.
    try {
      $result = $braintree_plugin
        ->createPaymentMethodNonce($payment_method
        ->getRemoteId());
      $pane_form['#attached']['library'][] = 'commerce_braintree/checkout-review';
      $amount = Calculator::trim($this->order
        ->getBalance()
        ->getNumber());
      $pane_form['#attached']['drupalSettings']['commerceBraintree'] = [
        'clientToken' => $braintree_plugin
          ->generateClientToken(),
        'formId' => $complete_form['#id'],
        'amount' => $amount,
        'nonce' => $result->paymentMethodNonce->nonce,
        'bin' => $result->paymentMethodNonce->details['bin'],
        'email' => $this->order
          ->getEmail(),
      ];

      // Unused non-hidden element included to ensure pane is built.
      $pane_form['payment_errors'] = [
        '#type' => 'markup',
        '#markup' => '<div id="payment-errors"></div>',
        '#weight' => -200,
      ];

      // Populated by the JS library.
      $pane_form['payment_method_nonce'] = [
        '#type' => 'hidden',
        '#attributes' => [
          'class' => [
            'braintree-nonce',
          ],
        ],
      ];
    } catch (\Braintree\Exception $e) {
      ErrorHelper::handleException($e);
    } catch (PaymentGatewayException $e) {
      $this->logger
        ->error($e
        ->getMessage());
      $message = $this
        ->t('We encountered an unexpected error processing your payment method. Please try again later.');
      $this
        ->messenger()
        ->addError($message);
      $this->checkoutFlow
        ->redirectToStep($this
        ->getErrorStepId());
    }
    $cacheability = new CacheableMetadata();
    $cacheability
      ->addCacheableDependency($this->order);
    $cacheability
      ->setCacheMaxAge(0);
    $cacheability
      ->applyTo($pane_form);
    return $pane_form;
  }

  /**
   * {@inheritdoc}
   */
  public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $error_step_id = $this
      ->getErrorStepId();
    $values = $form_state
      ->getValue($pane_form['#parents']);
    if (empty($values['payment_method_nonce'])) {
      $this->logger
        ->error('Missing payment method nonce.');
      $message = $this
        ->t('We encountered an unexpected error processing your payment method. Please try again later.');
      $this
        ->messenger()
        ->addError($message);
      $this->checkoutFlow
        ->redirectToStep($error_step_id);
    }
    $braintree_plugin = $this->order
      ->get('payment_gateway')->entity
      ->getPlugin();
    $configuration = $braintree_plugin->configuration;
    try {
      $paymentMethodNonce = $braintree_plugin
        ->findPaymentMethodNonce($values['payment_method_nonce']);
      $result = $paymentMethodNonce->threeDSecureInfo;
      $required = isset($configuration['3d_secure']) && $configuration['3d_secure'] == 'required';
      ErrorHelper::handleErrors3ds($result, $required);
    } catch (\Braintree\Exception $e) {
      ErrorHelper::handleException($e);
    } catch (PaymentGatewayException $e) {
      $this->logger
        ->error($e
        ->getMessage());
      $message = $this
        ->t('We encountered an unexpected error processing your payment method. Please try again later.');
      $this
        ->messenger()
        ->addError($message);
      $this->checkoutFlow
        ->redirectToStep($error_step_id);
    }
  }

  /**
   * {@inheritdoc}
   */
  public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
    $values = $form_state
      ->getValue($pane_form['#parents']);

    /** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
    $payment_method = $this->order
      ->get('payment_method')->entity;

    // The payment method nonce should be used for this one time purchase and
    // the previous tokenized payment method should be kept for future
    // purchases.
    $three_d_payment_method = $payment_method
      ->createDuplicate();
    $three_d_payment_method
      ->setRemoteId($values['payment_method_nonce']);
    $three_d_payment_method
      ->setReusable(FALSE);
    $three_d_payment_method
      ->save();
    $this->order
      ->set('payment_method', $payment_method);
  }

  /**
   * Gets the step ID that the customer should be sent to on error.
   *
   * @return string
   *   The error step ID.
   */
  protected function getErrorStepId() {

    // Default to the step that contains the PaymentInformation pane.
    $step_id = $this->checkoutFlow
      ->getPane('payment_information')
      ->getStepId();
    if ($step_id == '_disabled') {

      // Can't redirect to the _disabled step. This could mean that isVisible()
      // was overridden to allow Braintree3DSReview to be used without a
      // payment_information pane, but this method was not modified.
      throw new \RuntimeException('Cannot get the step ID for the payment_information pane. The pane is disabled.');
    }
    return $step_id;
  }

}

Members

Namesort descending Modifiers Type Description Overrides
Braintree3DSReview::$logger protected property The logger.
Braintree3DSReview::buildPaneForm public function Builds the pane form. Overrides CheckoutPaneInterface::buildPaneForm
Braintree3DSReview::create public static function Creates an instance of the plugin. Overrides CheckoutPaneBase::create
Braintree3DSReview::getErrorStepId protected function Gets the step ID that the customer should be sent to on error.
Braintree3DSReview::isVisible public function Determines whether the pane is visible. Overrides CheckoutPaneBase::isVisible
Braintree3DSReview::setLogger public function Sets the logger.
Braintree3DSReview::submitPaneForm public function Handles the submission of an pane form. Overrides CheckoutPaneBase::submitPaneForm
Braintree3DSReview::validatePaneForm public function Validates the pane form. Overrides CheckoutPaneBase::validatePaneForm
CheckoutPaneBase::$checkoutFlow protected property The parent checkout flow.
CheckoutPaneBase::$entityTypeManager protected property The entity type manager.
CheckoutPaneBase::$order protected property The current order.
CheckoutPaneBase::buildConfigurationForm public function Form constructor. Overrides PluginFormInterface::buildConfigurationForm 6
CheckoutPaneBase::buildConfigurationSummary public function Builds a summary of the pane configuration. Overrides CheckoutPaneInterface::buildConfigurationSummary 5
CheckoutPaneBase::buildPaneSummary public function Builds a summary of the pane values. Overrides CheckoutPaneInterface::buildPaneSummary 3
CheckoutPaneBase::calculateDependencies public function Calculates dependencies for the configured plugin. Overrides DependentPluginInterface::calculateDependencies
CheckoutPaneBase::defaultConfiguration public function Gets default configuration for this plugin. Overrides ConfigurableInterface::defaultConfiguration 6
CheckoutPaneBase::getConfiguration public function Gets this plugin's configuration. Overrides ConfigurableInterface::getConfiguration
CheckoutPaneBase::getDisplayLabel public function Gets the pane display label. Overrides CheckoutPaneInterface::getDisplayLabel
CheckoutPaneBase::getId public function Gets the pane ID. Overrides CheckoutPaneInterface::getId
CheckoutPaneBase::getLabel public function Gets the pane label. Overrides CheckoutPaneInterface::getLabel
CheckoutPaneBase::getStepId public function Gets the pane step ID. Overrides CheckoutPaneInterface::getStepId
CheckoutPaneBase::getWeight public function Gets the pane weight. Overrides CheckoutPaneInterface::getWeight
CheckoutPaneBase::getWrapperElement public function Gets the pane wrapper element. Overrides CheckoutPaneInterface::getWrapperElement
CheckoutPaneBase::setConfiguration public function Sets the configuration for this plugin instance. Overrides ConfigurableInterface::setConfiguration
CheckoutPaneBase::setOrder public function Sets the current order. Overrides CheckoutPaneInterface::setOrder
CheckoutPaneBase::setStepId public function Sets the pane step ID. Overrides CheckoutPaneInterface::setStepId
CheckoutPaneBase::setWeight public function Sets the pane weight. Overrides CheckoutPaneInterface::setWeight
CheckoutPaneBase::submitConfigurationForm public function Form submission handler. Overrides PluginFormInterface::submitConfigurationForm 6
CheckoutPaneBase::validateConfigurationForm public function Form validation handler. Overrides PluginFormInterface::validateConfigurationForm
CheckoutPaneBase::__construct public function Constructs a new CheckoutPaneBase object. Overrides PluginBase::__construct 6
DependencySerializationTrait::$_entityStorages protected property An array of entity type IDs keyed by the property name of their storages.
DependencySerializationTrait::$_serviceIds protected property An array of service IDs keyed by property name used for serialization.
DependencySerializationTrait::__sleep public function 1
DependencySerializationTrait::__wakeup public function 2
MessengerTrait::$messenger protected property The messenger. 29
MessengerTrait::messenger public function Gets the messenger. 29
MessengerTrait::setMessenger public function Sets the messenger.
PluginBase::$configuration protected property Configuration information passed into the plugin. 1
PluginBase::$pluginDefinition protected property The plugin implementation definition. 1
PluginBase::$pluginId protected property The plugin_id.
PluginBase::DERIVATIVE_SEPARATOR constant A string which is used to separate base plugin IDs from the derivative ID.
PluginBase::getBaseId public function Gets the base_plugin_id of the plugin instance. Overrides DerivativeInspectionInterface::getBaseId
PluginBase::getDerivativeId public function Gets the derivative_id of the plugin instance. Overrides DerivativeInspectionInterface::getDerivativeId
PluginBase::getPluginDefinition public function Gets the definition of the plugin implementation. Overrides PluginInspectionInterface::getPluginDefinition 3
PluginBase::getPluginId public function Gets the plugin_id of the plugin instance. Overrides PluginInspectionInterface::getPluginId
PluginBase::isConfigurable public function Determines if the plugin is configurable.
StringTranslationTrait::$stringTranslation protected property The string translation service. 1
StringTranslationTrait::formatPlural protected function Formats a string containing a count of items.
StringTranslationTrait::getNumberOfPlurals protected function Returns the number of plurals supported by a given language.
StringTranslationTrait::getStringTranslation protected function Gets the string translation service.
StringTranslationTrait::setStringTranslation public function Sets the string translation service to use. 2
StringTranslationTrait::t protected function Translates a string to the current language or to a given language.