View source
<?php
namespace Drupal\payment\Element;
use Drupal\Component\Utility\Html;
use Drupal\Component\Utility\NestedArray;
use Drupal\Component\Utility\Random;
use Drupal\Core\Ajax\AjaxResponse;
use Drupal\Core\Ajax\OpenModalDialogCommand;
use Drupal\Core\Ajax\ReplaceCommand;
use Drupal\Core\Datetime\DateFormatter;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Entity\EntityStorageInterface;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Render\Element;
use Drupal\Core\Render\Element\FormElement;
use Drupal\Core\Render\Element\FormElementInterface;
use Drupal\Core\Render\RendererInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\Core\StringTranslation\TranslationInterface;
use Drupal\Core\Utility\LinkGeneratorInterface;
use Drupal\currency\FormElementCallbackTrait;
use Drupal\payment\Entity\Payment;
use Drupal\payment\Entity\PaymentInterface;
use Drupal\payment\Plugin\Payment\Method\PaymentExecutionPaymentMethodManager;
use Drupal\plugin\Plugin\Plugin\PluginSelector\PluginSelectorManagerInterface;
use Drupal\plugin\PluginDiscovery\LimitedPluginDiscoveryDecorator;
use Drupal\plugin\PluginType\PluginTypeInterface;
use Symfony\Component\HttpFoundation\RequestStack;
abstract class PaymentReferenceBase extends FormElement implements FormElementInterface, ContainerFactoryPluginInterface {
use FormElementCallbackTrait;
const KEY_VALUE_TTL = 3600;
protected $currentUser;
protected $dateFormatter;
protected $linkGenerator;
protected $paymentMethodType;
protected $paymentStorage;
protected $pluginSelectorManager;
protected $random;
protected $renderer;
protected $requestStack;
public function __construct(array $configuration, $plugin_id, array $plugin_definition, RequestStack $request_stack, EntityStorageInterface $payment_storage, TranslationInterface $string_translation, DateFormatter $date_formatter, LinkGeneratorInterface $link_generator, RendererInterface $renderer, AccountInterface $current_user, PluginSelectorManagerInterface $plugin_selector_manager, PluginTypeInterface $payment_method_type, Random $random) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->currentUser = $current_user;
$this->dateFormatter = $date_formatter;
$this->linkGenerator = $link_generator;
$this->paymentMethodType = $payment_method_type;
$this->paymentStorage = $payment_storage;
$this->pluginSelectorManager = $plugin_selector_manager;
$this->random = $random;
$this->renderer = $renderer;
$this->requestStack = $request_stack;
$this->stringTranslation = $string_translation;
}
public function getInfo() {
$plugin_id = $this
->getPluginId();
return array(
'#default_value' => NULL,
'#limit_allowed_plugin_ids' => NULL,
'#plugin_selector_id' => NULL,
'#process' => [
[
get_class($this),
'instantiate#process#' . $plugin_id,
],
],
'#prototype_payment' => NULL,
'#queue_category_id' => NULL,
'#queue_owner_id' => NULL,
);
}
public function elementValidate(array &$element, FormStateInterface $form_state, array &$form) {
$plugin_selector = $this
->getPluginSelector($element, $form_state);
$plugin_selector
->validateSelectorForm($element['container']['payment_form']['payment_method'], $form_state);
$entity_form_display = $this
->getEntityFormDisplay($element, $form_state);
$payment = $this
->getPayment($element, $form_state);
$entity_form_display
->extractFormValues($payment, $element['container']['payment_form'], $form_state);
$entity_form_display
->validateFormValues($payment, $element['container']['payment_form'], $form_state);
}
public function process(array &$element, FormStateInterface $form_state, array $form) {
$element['#available_payment_id'] = NULL;
$element['#element_validate'] = [
[
$this,
'elementValidate',
],
];
$element['#theme_wrappers'] = array(
'form_element',
);
$element['#tree'] = TRUE;
if (!is_int($element['#default_value']) && !is_null($element['#default_value'])) {
throw new \InvalidArgumentException('#default_value must be an integer or NULL, but ' . gettype($element['#default_value']) . ' was given.');
}
if (!is_null($element['#limit_allowed_plugin_ids']) && !is_array($element['#limit_allowed_plugin_ids'])) {
throw new \InvalidArgumentException('#limit_allowed_plugin_ids must be an array or NULL, but ' . gettype($element['#limit_allowed_plugin_ids']) . ' was given.');
}
if (!is_string($element['#queue_category_id'])) {
throw new \InvalidArgumentException('#queue_category_id must be a string, but ' . gettype($element['#queue_category_id']) . ' was given.');
}
if (!is_int($element['#queue_owner_id'])) {
throw new \InvalidArgumentException('#queue_owner_id must be an integer, but ' . gettype($element['#queue_owner_id']) . ' was given.');
}
if (!is_string($element['#plugin_selector_id'])) {
throw new \InvalidArgumentException('#plugin_selector_id must be a string, but ' . gettype($element['#plugin_selector_id']) . ' was given.');
}
if (!$element['#prototype_payment'] instanceof PaymentInterface) {
throw new \InvalidArgumentException('#prototype_payment must implement \\Drupal\\payment\\Entity\\PaymentInterface.');
}
if (!$element['#default_value']) {
$payment_ids = $this
->getPaymentQueue()
->loadPaymentIds($element['#queue_category_id'], $element['#queue_owner_id']);
$element['#available_payment_id'] = $payment_ids ? reset($payment_ids) : NULL;
}
$ajax_wrapper_id = Html::getClass('payment_reference-' . $element['#name']);
$element['container'] = array(
'#attached' => [
'drupalSettings' => [
'PaymentReferencePaymentAvailable' => [
$ajax_wrapper_id => $element['#default_value'] || $element['#available_payment_id'],
],
],
'library' => [
'payment/payment_reference',
],
],
'#id' => $ajax_wrapper_id,
'#type' => 'container',
);
$element['container']['payment_form'] = $this
->buildPaymentForm($element, $form_state);
$element['container']['payment_form']['#access'] = !$element['#default_value'] && !$element['#available_payment_id'];
$element['container']['payment_view'] = $this
->buildPaymentView($element, $form_state);
$element['container']['payment_view']['#access'] = $element['#default_value'] || $element['#available_payment_id'];
$element['container']['refresh'] = $this
->buildRefreshButton($element, $form_state);
return $element;
}
protected function buildPaymentForm(array $element, FormStateInterface $form_state) {
$build = array(
'#parents' => array_merge($element['#parents'], array(
'container',
'payment_form',
)),
'#type' => 'container',
);
$plugin_selector = $this
->getPluginSelector($element, $form_state);
$selected_payment_method = $plugin_selector
->getSelectedPlugin();
$build['line_items'] = array(
'#payment_line_items' => $this
->getPayment($element, $form_state),
'#type' => 'payment_line_items_display',
);
$build['payment_method'] = $plugin_selector
->buildSelectorForm([], $form_state);
if ($selected_payment_method && !$selected_payment_method
->getPaymentExecutionResult()
->isCompleted()) {
$this
->disableChildren($build['payment_method']);
}
$this
->getEntityFormDisplay($element, $form_state)
->buildForm($this
->getPayment($element, $form_state), $build, $form_state);
$build['pay_link'] = $this
->buildCompletePaymentLink($element, $form_state);
$build['pay_link']['#access'] = !$selected_payment_method || !$selected_payment_method
->getPaymentExecutionResult()
->isCompleted();
$build['pay_link']['#process'] = array(
array(
get_class($this),
'processMaxWeight',
),
);
$plugin_id = $this
->getPluginId();
$build['pay_button'] = array(
'#ajax' => array(
'callback' => [
get_class(),
'instantiate#ajaxPay#' . $plugin_id,
],
),
'#limit_validation_errors' => array(
array_merge($element['#parents'], array(
'container',
'payment_form',
)),
),
'#submit' => [
[
get_class(),
'instantiate#pay#' . $plugin_id,
],
],
'#type' => 'submit',
'#value' => $this
->t('Pay'),
'#process' => array(
array(
get_class($this),
'processMaxWeight',
),
),
);
return $build;
}
public static function processMaxWeight(array &$element, FormStateInterface $form_state, array $form) {
$parent_element = NestedArray::getValue($form, array_slice($element['#array_parents'], 0, -1));
$weights = [];
foreach (Element::children($parent_element) as $sibling_key) {
$weights[] = isset($parent_element[$sibling_key]['#weight']) ? $parent_element[$sibling_key]['#weight'] : 0;
}
$element['#weight'] = max($weights) + 1;
return $element;
}
protected function buildRefreshButton(array $element, FormStateInterface $form_state) {
$plugin_selector = $this
->getPluginSelector($element, $form_state);
$class = array(
'payment_reference-refresh-button',
);
$selected_payment_method = $plugin_selector
->getSelectedPlugin();
if (!$element['#default_value'] && $element['#available_payment_id'] && (!$selected_payment_method || !$selected_payment_method
->getPaymentExecutionResult()
->isCompleted())) {
$class[] = 'payment-reference-hidden';
}
$plugin_id = $this
->getPluginId();
$build = array(
'#ajax' => array(
'callback' => [
get_class(),
'instantiate#ajaxRefresh#' . $plugin_id,
],
'event' => 'mousedown',
'wrapper' => $element['container']['#id'],
),
'#attached' => [
'library' => [
'payment/payment_reference',
],
],
'#attributes' => array(
'class' => $class,
),
'#limit_validation_errors' => [],
'#submit' => array(
array(
$this->pluginDefinition['class'],
'refresh',
),
),
'#type' => 'submit',
'#value' => $this
->t('Re-check available payments'),
);
return $build;
}
protected function buildPaymentView(array $element, FormStateInterface $form_state) {
$payment_id = $element['#default_value'] ?: $element['#available_payment_id'];
$payment = $payment_id ? $this->paymentStorage
->load($payment_id) : NULL;
$build = [];
if ($payment) {
$currency = $payment
->getCurrency();
$status = $payment
->getPaymentStatus();
$status_definition = $status
->getPluginDefinition();
$build = array(
'#empty' => $this
->t('There are no line items.'),
'#header' => array(
$this
->t('Amount'),
$this
->t('Status'),
$this
->t('Last updated'),
),
'#type' => 'table',
);
$build[0]['amount'] = array(
'#markup' => $currency
->formatAmount($payment
->getAmount()),
);
$build[0]['status'] = array(
'#markup' => $status_definition['label'],
);
$build[0]['updated'] = array(
'#markup' => $this->dateFormatter
->format($status
->getCreated()),
);
if ($payment
->access('view')) {
$build['#header'][] = $this
->t('Operations');
$build[0]['view'] = array(
'#markup' => $this
->t('<a href="@url" target="_blank">View payment details</a> (opens in a new window)', array(
'@url' => $payment
->toUrl()
->toString(),
)),
);
}
}
return $build;
}
protected function buildCompletePaymentLink(array $element, FormStateInterface $form_state) {
$plugin_selector = $this
->getPluginSelector($element, $form_state);
$payment_method = $plugin_selector
->getSelectedPlugin();
$build = [];
if ($payment_method && !$payment_method
->getPayment()
->isNew()) {
$build['message'] = array(
'#markup' => $this
->t('@payment_method_label requires the payment to be completed in a new window.', array(
'@payment_method_label' => $payment_method
->getPluginDefinition()['label'],
)),
);
$build['link'] = array(
'#attributes' => array(
'class' => array(
'button',
'payment-reference-complete-payment-link',
),
'target' => '_blank',
),
'#url' => $payment_method
->getPayment()
->toUrl('complete'),
'#title' => $this
->t('Complete payment'),
'#type' => 'link',
);
}
return $build;
}
protected function disableChildren(array &$elements) {
foreach (Element::children($elements) as $child_key) {
$elements[$child_key]['#disabled'] = TRUE;
$this
->disableChildren($elements[$child_key]);
}
}
public function pay(array $form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
$root_element_parents = array_slice($triggering_element['#array_parents'], 0, -3);
$root_element = NestedArray::getValue($form, $root_element_parents);
$plugin_selector = $this
->getPluginSelector($root_element, $form_state);
$plugin_selector
->submitSelectorForm($root_element['container']['payment_form']['payment_method'], $form_state);
$payment = $this
->getPayment($root_element, $form_state);
$this
->getEntityFormDisplay($root_element, $form_state)
->extractFormValues($payment, $root_element['container']['payment_form'], $form_state);
$payment_method = $plugin_selector
->getSelectedPlugin();
$payment
->setPaymentMethod($payment_method);
$payment
->save();
$result = $payment
->execute();
if (!$result
->isCompleted() && !$this->requestStack
->getCurrentRequest()
->isXmlHttpRequest()) {
$url = $payment
->toUrl('complete');
$url
->setOption('attributes', [
'target' => '_blank',
]);
$link = $this->linkGenerator
->generate($this
->t('Complete payment (opens in a new window).'), $url);
$this
->messenger()
->addMessage($link);
}
$form_state
->setRebuild();
}
public function ajaxPay(array &$form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
$root_element_parents = array_slice($triggering_element['#array_parents'], 0, -3);
$root_element = NestedArray::getValue($form, $root_element_parents);
$response = new AjaxResponse();
$response
->addCommand(new ReplaceCommand('#' . $root_element['container']['#id'], $this->renderer
->render($root_element['container'])));
$selected_payment_method = $this
->getPluginSelector($root_element, $form_state)
->getSelectedPlugin();
if (!$selected_payment_method
->getPaymentExecutionResult()
->isCompleted()) {
$link = $this
->buildCompletePaymentLink($root_element, $form_state);
$response
->addCommand(new OpenModalDialogCommand($this
->t('Complete payment'), $this->renderer
->render($link)));
}
return $response;
}
public static function refresh(array $form, FormStateInterface $form_state) {
$form_state
->setRebuild();
}
public function ajaxRefresh(array &$form, FormStateInterface $form_state) {
$triggering_element = $form_state
->getTriggeringElement();
$root_element_parents = array_slice($triggering_element['#array_parents'], 0, -2);
$root_element = NestedArray::getValue($form, $root_element_parents);
$response = new AjaxResponse();
$response
->addCommand(new ReplaceCommand('#' . $root_element['container']['#id'], $this->renderer
->render($root_element['container'])));
return $response;
}
protected function getPluginSelector(array $element, FormStateInterface $form_state) {
$key = 'payment_reference.element.payment_reference.plugin_selector.' . $element['#name'];
if (!$form_state
->has($key)) {
$plugin_selector = $this->pluginSelectorManager
->createInstance($element['#plugin_selector_id']);
$payment_method_discovery = $this->paymentMethodType
->getPluginManager();
if (!is_null($element['#limit_allowed_plugin_ids'])) {
$payment_method_discovery = (new LimitedPluginDiscoveryDecorator($payment_method_discovery))
->setDiscoveryLimit($element['#limit_allowed_plugin_ids']);
}
$payment_method_manager = new PaymentExecutionPaymentMethodManager($this
->getPayment($element, $form_state), $this->currentUser, $this->paymentMethodType
->getPluginManager(), $payment_method_discovery);
$plugin_selector
->setSelectablePluginType($this->paymentMethodType);
$plugin_selector
->setSelectablePluginDiscovery($payment_method_manager);
$plugin_selector
->setSelectablePluginFactory($payment_method_manager);
$plugin_selector
->setRequired($element['#required']);
$form_state
->set($key, $plugin_selector);
}
return $form_state
->get($key);
}
protected function getPayment(array $element, FormStateInterface $form_state) {
$key = 'payment_reference.element.payment_reference.payment';
if (!$form_state
->has($key)) {
$prototype_payment = $element['#prototype_payment'];
$payment = $prototype_payment
->createDuplicate();
$form_state
->set($key, $payment);
}
return $form_state
->get($key);
}
protected function getEntityFormDisplay(array $element, FormStateInterface $form_state) {
$key = 'payment_reference.element.payment_reference.entity_form_display.' . $element['#name'];
if (!$form_state
->has($key)) {
$entity_form_display = EntityFormDisplay::collectRenderDisplay($this
->getPayment($element, $form_state), 'payment_reference');
$form_state
->set($key, $entity_form_display);
}
return $form_state
->get($key);
}
protected abstract function getPaymentQueue();
public static function valueCallback(&$element, $input, FormStateInterface $form_state) {
if ($element['#default_value']) {
return $element['#default_value'];
}
else {
$element_info_manager = \Drupal::service('plugin.manager.element_info');
$element_plugin = $element_info_manager
->createInstance($element['#type']);
$payment_ids = $element_plugin
->getPaymentQueue()
->loadPaymentIds($element['#queue_category_id'], $element['#queue_owner_id']);
return $payment_ids ? (int) reset($payment_ids) : NULL;
}
}
}