commerce-8.x-2.8/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
<?php
namespace Drupal\commerce_payment\Plugin\Commerce\CheckoutPane;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface;
use Drupal\commerce_checkout\Plugin\Commerce\CheckoutPane\CheckoutPaneBase;
use Drupal\commerce_payment\PaymentOption;
use Drupal\commerce_payment\PaymentOptionsBuilderInterface;
use Drupal\commerce_payment\Plugin\Commerce\PaymentGateway\SupportsStoredPaymentMethodsInterface;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Messenger\MessengerInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\Url;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides the payment information pane.
*
* @CommerceCheckoutPane(
* id = "payment_information",
* label = @Translation("Payment information"),
* default_step = "order_information",
* wrapper_element = "fieldset",
* )
*/
class PaymentInformation extends CheckoutPaneBase {
/**
* The current user.
*
* @var \Drupal\Core\Session\AccountInterface
*/
protected $currentUser;
/**
* The messenger.
*
* @var \Drupal\Core\Messenger\MessengerInterface
*/
protected $messenger;
/**
* The payment options builder.
*
* @var \Drupal\commerce_payment\PaymentOptionsBuilderInterface
*/
protected $paymentOptionsBuilder;
/**
* Constructs a new PaymentInformation object.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin_id for the plugin instance.
* @param mixed $plugin_definition
* The plugin implementation definition.
* @param \Drupal\commerce_checkout\Plugin\Commerce\CheckoutFlow\CheckoutFlowInterface $checkout_flow
* The parent checkout flow.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
* The entity type manager.
* @param \Drupal\Core\Session\AccountInterface $current_user
* The current user.
* @param \Drupal\Core\Messenger\MessengerInterface $messenger
* The messenger.
* @param \Drupal\commerce_payment\PaymentOptionsBuilderInterface $payment_options_builder
* The payment options builder.
*/
public function __construct(array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow, EntityTypeManagerInterface $entity_type_manager, AccountInterface $current_user, MessengerInterface $messenger, PaymentOptionsBuilderInterface $payment_options_builder) {
parent::__construct($configuration, $plugin_id, $plugin_definition, $checkout_flow, $entity_type_manager);
$this->currentUser = $current_user;
$this->messenger = $messenger;
$this->paymentOptionsBuilder = $payment_options_builder;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition, CheckoutFlowInterface $checkout_flow = NULL) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$checkout_flow,
$container->get('entity_type.manager'),
$container->get('current_user'),
$container->get('messenger'),
$container->get('commerce_payment.options_builder')
);
}
/**
* {@inheritdoc}
*/
public function buildPaneSummary() {
$billing_profile = $this->order->getBillingProfile();
if ($this->order->getTotalPrice()->isZero() && $billing_profile) {
// Only the billing information was collected.
$view_builder = $this->entityTypeManager->getViewBuilder('profile');
$summary = [
'#title' => $this->t('Billing information'),
'profile' => $view_builder->view($billing_profile, 'default'),
];
return $summary;
}
$summary = [];
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $this->order->get('payment_gateway')->entity;
if (!$payment_gateway) {
return $summary;
}
$payment_method = $this->order->get('payment_method')->entity;
if ($payment_method) {
$view_builder = $this->entityTypeManager->getViewBuilder('commerce_payment_method');
$summary = $view_builder->view($payment_method, 'default');
}
elseif ($billing_profile) {
$view_builder = $this->entityTypeManager->getViewBuilder('profile');
$summary = [
'payment_gateway' => [
'#markup' => $payment_gateway->getPlugin()->getDisplayLabel(),
],
'profile' => $view_builder->view($billing_profile, 'default'),
];
}
return $summary;
}
/**
* {@inheritdoc}
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
if ($this->order->getTotalPrice()->isZero()) {
// Free orders don't need payment, collect just the billing information.
$pane_form['#title'] = $this->t('Billing information');
$pane_form = $this->buildBillingProfileForm($pane_form, $form_state);
return $pane_form;
}
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
// Load the payment gateways. This fires an event for filtering the
// available gateways, and then evaluates conditions on all remaining ones.
$payment_gateways = $payment_gateway_storage->loadMultipleForOrder($this->order);
// Can't proceed without any payment gateways.
if (empty($payment_gateways)) {
$this->messenger->addError($this->noPaymentGatewayErrorMessage());
return $pane_form;
}
// Prepare the form for ajax.
$pane_form['#wrapper_id'] = Html::getUniqueId('payment-information-wrapper');
$pane_form['#prefix'] = '<div id="' . $pane_form['#wrapper_id'] . '">';
$pane_form['#suffix'] = '</div>';
// Core bug #1988968 doesn't allow the payment method add form JS to depend
// on an external library, so the libraries need to be preloaded here.
foreach ($payment_gateways as $payment_gateway) {
if ($js_library = $payment_gateway->getPlugin()->getJsLibrary()) {
$pane_form['#attached']['library'][] = $js_library;
}
}
$options = $this->paymentOptionsBuilder->buildOptions($this->order, $payment_gateways);
$option_labels = array_map(function (PaymentOption $option) {
return $option->getLabel();
}, $options);
$parents = array_merge($pane_form['#parents'], ['payment_method']);
$default_option_id = NestedArray::getValue($form_state->getUserInput(), $parents);
if ($default_option_id && isset($options[$default_option_id])) {
$default_option = $options[$default_option_id];
}
else {
$default_option = $this->paymentOptionsBuilder->selectDefaultOption($this->order, $options);
}
$pane_form['payment_method'] = [
'#type' => 'radios',
'#title' => $this->t('Payment method'),
'#options' => $option_labels,
'#default_value' => $default_option->getId(),
'#ajax' => [
'callback' => [get_class($this), 'ajaxRefresh'],
'wrapper' => $pane_form['#wrapper_id'],
],
'#access' => count($options) > 1,
];
// Add a class to each individual radio, to help themers.
foreach ($options as $option) {
$class_name = $option->getPaymentMethodId() ? 'stored' : 'new';
$pane_form['payment_method'][$option->getId()]['#attributes']['class'][] = "payment-method--$class_name";
}
// Store the options for submitPaneForm().
$pane_form['#payment_options'] = $options;
$default_payment_gateway_id = $default_option->getPaymentGatewayId();
$payment_gateway = $payment_gateways[$default_payment_gateway_id];
if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) {
$pane_form = $this->buildPaymentMethodForm($pane_form, $form_state, $default_option);
}
else {
$pane_form = $this->buildBillingProfileForm($pane_form, $form_state);
}
return $pane_form;
}
/**
* Builds the payment method form for the selected payment option.
*
* @param array $pane_form
* The pane form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
* @param \Drupal\commerce_payment\PaymentOption $payment_option
* The payment option.
*
* @return array
* The modified pane form.
*/
protected function buildPaymentMethodForm(array $pane_form, FormStateInterface $form_state, PaymentOption $payment_option) {
if ($payment_option->getPaymentMethodId() && !$payment_option->getPaymentMethodTypeId()) {
// Editing payment methods at checkout is not supported.
return $pane_form;
}
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
$payment_method = $payment_method_storage->create([
'type' => $payment_option->getPaymentMethodTypeId(),
'payment_gateway' => $payment_option->getPaymentGatewayId(),
'uid' => $this->order->getCustomerId(),
'billing_profile' => $this->order->getBillingProfile(),
]);
$pane_form['add_payment_method'] = [
'#type' => 'commerce_payment_gateway_form',
'#operation' => 'add-payment-method',
'#default_value' => $payment_method,
];
return $pane_form;
}
/**
* Builds the billing profile form.
*
* @param array $pane_form
* The pane form.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The form state of the parent form.
*
* @return array
* The modified pane form.
*/
protected function buildBillingProfileForm(array $pane_form, FormStateInterface $form_state) {
$store = $this->order->getStore();
$billing_profile = $this->order->getBillingProfile();
if (!$billing_profile) {
$billing_profile = $this->entityTypeManager->getStorage('profile')->create([
'uid' => $this->order->getCustomerId(),
'type' => 'customer',
]);
}
$pane_form['billing_information'] = [
'#type' => 'commerce_profile_select',
'#default_value' => $billing_profile,
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
];
return $pane_form;
}
/**
* Ajax callback.
*/
public static function ajaxRefresh(array $form, FormStateInterface $form_state) {
$parents = $form_state->getTriggeringElement()['#parents'];
array_pop($parents);
return NestedArray::getValue($form, $parents);
}
/**
* {@inheritdoc}
*/
public function validatePaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
if ($this->order->getTotalPrice()->isZero()) {
return;
}
$values = $form_state->getValue($pane_form['#parents']);
if (!isset($values['payment_method'])) {
$form_state->setError($complete_form, $this->noPaymentGatewayErrorMessage());
}
}
/**
* {@inheritdoc}
*/
public function submitPaneForm(array &$pane_form, FormStateInterface $form_state, array &$complete_form) {
if ($this->order->getTotalPrice()->isZero()) {
$this->order->setBillingProfile($pane_form['billing_information']['#profile']);
return;
}
$values = $form_state->getValue($pane_form['#parents']);
/** @var \Drupal\commerce_payment\PaymentOption $selected_option */
$selected_option = $pane_form['#payment_options'][$values['payment_method']];
/** @var \Drupal\commerce_payment\PaymentGatewayStorageInterface $payment_gateway_storage */
$payment_gateway_storage = $this->entityTypeManager->getStorage('commerce_payment_gateway');
/** @var \Drupal\commerce_payment\Entity\PaymentGatewayInterface $payment_gateway */
$payment_gateway = $payment_gateway_storage->load($selected_option->getPaymentGatewayId());
if (!$payment_gateway) {
return;
}
if ($payment_gateway->getPlugin() instanceof SupportsStoredPaymentMethodsInterface) {
if (!empty($selected_option->getPaymentMethodTypeId())) {
// The payment method was just created.
$payment_method = $values['add_payment_method'];
}
else {
/** @var \Drupal\commerce_payment\PaymentMethodStorageInterface $payment_method_storage */
$payment_method_storage = $this->entityTypeManager->getStorage('commerce_payment_method');
$payment_method = $payment_method_storage->load($selected_option->getPaymentMethodId());
}
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$this->order->set('payment_gateway', $payment_method->getPaymentGateway());
$this->order->set('payment_method', $payment_method);
$this->order->setBillingProfile($payment_method->getBillingProfile());
}
else {
$this->order->set('payment_gateway', $payment_gateway);
$this->order->set('payment_method', NULL);
$this->order->setBillingProfile($pane_form['billing_information']['#profile']);
}
}
/**
* Returns an error message in case there are no available payment gateways.
*
* @return \Drupal\Core\StringTranslation\TranslatableMarkup
* The error message.
*/
protected function noPaymentGatewayErrorMessage() {
if ($this->currentUser->hasPermission('administer commerce_payment_gateway')) {
$message = $this->t('There are no <a href=":url"">payment gateways</a> available for this order.', [
':url' => Url::fromRoute('entity.commerce_payment_gateway.collection')->toString(),
]);
}
else {
$message = $this->t('There are no payment gateways available for this order. Please try again later.');
}
return $message;
}
}
