diff --git a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
index e53b0f9..a24515f 100644
--- a/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
+++ b/modules/checkout/src/Plugin/Commerce/CheckoutPane/BillingInformation.php
@@ -33,18 +33,13 @@ class BillingInformation extends CheckoutPaneBase implements CheckoutPaneInterfa
*/
public function buildPaneForm(array $pane_form, FormStateInterface $form_state, array &$complete_form) {
$store = $this->order->getStore();
- $billing_profile = $this->order->getBillingProfile();
- if (!$billing_profile) {
- $profile_storage = $this->entityTypeManager->getStorage('profile');
- $billing_profile = $profile_storage->create([
- 'type' => 'customer',
- 'uid' => $this->order->getCustomerId(),
- ]);
- }
$pane_form['profile'] = [
'#type' => 'commerce_profile_select',
- '#default_value' => $billing_profile,
+ '#title' => $this->t('Select an address'),
+ '#profile' => $this->getBillingProfile(),
+ '#profile_type' => 'customer',
+ '#profile_uid' => $this->order->getCustomerId(),
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
];
@@ -59,4 +54,40 @@ class BillingInformation extends CheckoutPaneBase implements CheckoutPaneInterfa
$this->order->setBillingProfile($pane_form['profile']['#profile']);
}
+ /**
+ * Gets the billing profile.
+ *
+ * @return \Drupal\profile\Entity\ProfileInterface
+ * The billing profile.
+ */
+ protected function getBillingProfile() {
+ if ($billing_profile = $this->order->getBillingProfile()) {
+ return $billing_profile;
+ }
+ // If we still don't have a profile, assign the default profile with
+ // fallback to most recently created.
+ if (!$billing_profile && \Drupal::currentUser()->isAuthenticated()) {
+ $query = $this->entityTypeManager
+ ->getStorage('profile')
+ ->getQuery();
+ $result = $query
+ ->condition('uid', \Drupal::currentUser()->id())
+ ->sort('is_default')
+ ->sort('created', 'DESC')
+ ->execute();
+ if ($result) {
+ $billing_profile = $this->entityTypeManager
+ ->getStorage('profile')
+ ->loadRevision(reset($result));
+ }
+ }
+ if (!$billing_profile) {
+ $billing_profile = $this->entityTypeManager->getStorage('profile')->create([
+ 'type' => 'customer',
+ 'uid' => $this->order->getCustomerId(),
+ ]);
+ }
+ return $billing_profile;
+ }
+
}
diff --git a/modules/order/src/Element/ProfileSelect.php b/modules/order/src/Element/ProfileSelect.php
index 6aacf1c..13ffc45 100644
--- a/modules/order/src/Element/ProfileSelect.php
+++ b/modules/order/src/Element/ProfileSelect.php
@@ -3,9 +3,12 @@
namespace Drupal\commerce_order\Element;
use Drupal\commerce\Element\CommerceElementTrait;
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\NestedArray;
use Drupal\Core\Entity\Entity\EntityFormDisplay;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Render\Element\RenderElement;
+use Drupal\Core\Url;
use Drupal\profile\Entity\ProfileInterface;
/**
@@ -15,7 +18,9 @@ use Drupal\profile\Entity\ProfileInterface;
* @code
* $form['billing_profile'] = [
* '#type' => 'commerce_profile_select',
- * '#default_value' => $profile,
+ * '#profile' => $profile,
+ * '#profile_type' => 'customer',
+ * '#profile_uid' => \Drupal::currentUser()->id(),
* '#default_country' => 'FR',
* '#available_countries' => ['US', 'FR'],
* ];
@@ -24,6 +29,10 @@ use Drupal\profile\Entity\ProfileInterface;
* $form['billing_profile']['#profile']. Due to Drupal core limitations the
* profile can't be accessed via $form_state->getValue('billing_profile').
*
+ * Note:
+ * This element always behaves as required. For optional behavior add
+ * a checkbox above the element that hides it on #ajax.
+ *
* @RenderElement("commerce_profile_select")
*/
class ProfileSelect extends RenderElement {
@@ -36,20 +45,31 @@ class ProfileSelect extends RenderElement {
public function getInfo() {
$class = get_class($this);
return [
+ '#tree' => TRUE,
+ '#title' => t('Select a profile'),
+ // The element doesn't use #default_value / #value. Use #profile instead.
+ '#default_value' => NULL,
+ // The preselected profile. A profile entity, or NULL.
+ '#profile' => NULL,
+ // Needed for creating new profiles, since #profile is not always given.
+ '#profile_type' => NULL,
+ '#profile_uid' => 0,
+ // Whether profiles should always be loaded in the latest revision.
+ // Disable when editing historical data, such as placed orders.
+ '#profile_latest_revision' => TRUE,
// The country to select if the address widget doesn't have a default.
'#default_country' => NULL,
// A list of country codes. If empty, all countries will be available.
'#available_countries' => [],
+ // Display options to administer profiles.
+ '#operation_buttons' => TRUE,
- // The profile entity operated on. Required.
- '#default_value' => NULL,
'#process' => [
[$class, 'attachElementSubmit'],
- [$class, 'processForm'],
+ [$class, 'processElement'],
],
'#element_validate' => [
[$class, 'validateElementSubmit'],
- [$class, 'validateForm'],
],
'#commerce_element_submit' => [
[$class, 'submitForm'],
@@ -59,28 +79,23 @@ class ProfileSelect extends RenderElement {
}
/**
- * Builds the element form.
+ * Validates the element properties.
*
* @param array $element
- * The form element being processed.
- * @param \Drupal\Core\Form\FormStateInterface $form_state
- * The current state of the form.
- * @param array $complete_form
- * The complete form structure.
+ * The form element.
*
* @throws \InvalidArgumentException
- * Thrown when #default_value is empty or not an entity, or when
- * #available_countries is not an array of country codes.
- *
- * @return array
- * The processed form element.
+ * Thrown if an element property is invalid, or empty but required.
*/
- public static function processForm(array $element, FormStateInterface $form_state, array &$complete_form) {
- if (empty($element['#default_value'])) {
- throw new \InvalidArgumentException('The commerce_profile_select element requires the #default_value property.');
+ public static function validateElementProperties(array $element) {
+ if (empty($element['#profile_type'])) {
+ throw new \InvalidArgumentException('The commerce_profile_select #profile_type property must be provided.');
}
- elseif (isset($element['#default_value']) && !($element['#default_value'] instanceof ProfileInterface)) {
- throw new \InvalidArgumentException('The commerce_profile_select #default_value property must be a profile entity.');
+ if (isset($element['#profile']) && !($element['#profile'] instanceof ProfileInterface)) {
+ throw new \InvalidArgumentException('The commerce_profile_select #profile property must be a profile entity.');
+ }
+ if (!empty($element['#profile_uid']) && !is_numeric($element['#profile_uid'])) {
+ throw new \InvalidArgumentException('The commerce_profile_select #profile_uid property must be a numeric entity ID.');
}
if (!is_array($element['#available_countries'])) {
throw new \InvalidArgumentException('The commerce_profile_select #available_countries property must be an array.');
@@ -91,8 +106,121 @@ class ProfileSelect extends RenderElement {
$element['#default_country'] = NULL;
}
}
+ }
+
+ /**
+ * Builds the element form.
+ *
+ * @param array $element
+ * The form element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * The processed form element.
+ */
+ public static function processElement(array $element, FormStateInterface $form_state) {
+ self::validateElementProperties($element);
+ if (count(self::loadProfiles($element)) > 0) {
+ $element = self::addSelectList($element, $form_state);
+ if (!empty($element['#operation_buttons'])) {
+ $element = self::addModalLinks($element, $form_state);
+ }
+ }
+ else {
+ $element = self::addInlineForm($element, $form_state);
+ }
+
+ return $element;
+ }
+
+ /**
+ * Prepare options for select list and modal links.
+ *
+ * @param array $element
+ * The form element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * Array of element, profile options and selected option.
+ */
+ protected static function prepareOptions(array $element, FormStateInterface $form_state) {
+ $element_state = self::getElementState($element['#parents'], $form_state);
+ // If the provided profile is in a previous revision, make sure it stays
+ // available as an option when $element['#profile'] gets changed.
+ if (empty($element['#profile_latest_revision']) && empty($element_state['fixed_option'])) {
+ if (!empty($element['#profile']) && !$element['#profile']->isDefaultRevision()) {
+ $element_state['fixed_option'] = [
+ 'id' => self::buildOptionId($element['#profile'], $element['#profile_latest_revision']),
+ 'label' => $element['#profile']->label() . ' ' . t('(Original)'),
+ ];
+ }
+ }
+ $profiles = self::loadProfiles($element);
+ $profile_options = [];
+ foreach ($profiles as $profile) {
+ $option_id = self::buildOptionId($profile, $element['#profile_latest_revision']);
+ $profile_options[$option_id] = $profile->label();
+ }
+ $user_input = $form_state->getUserInput();
+ $selected_option = NestedArray::getValue($user_input, array_merge($element['#parents'], ['profile_selection']));
+ if (!empty($selected_option)) {
+ // Keep the last selection in element state, so that the element knows
+ // where to return the customer upon cancelling form mode.
+ $element_state['selected_option'] = $selected_option;
+ }
+ elseif (!empty($element_state['selected_option'])) {
+ $selected_option = $element_state['selected_option'];
+ }
+ elseif ($default_profile = self::selectDefaultProfile($element, $profiles)) {
+ $selected_option = self::buildOptionId($default_profile, $element['#profile_latest_revision']);
+ }
+ /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
+ $profile_storage = \Drupal::entityTypeManager()->getStorage('profile');
+ $id_parts = explode('-', $selected_option);
+ if (!empty($id_parts[1])) {
+ $element['#profile'] = $profile_storage->loadRevision($id_parts[1]);
+ }
+ elseif (!empty($id_parts[0])) {
+ $element['#profile'] = $profile_storage->load($id_parts[0]);
+ }
+ else {
+ $element['#profile'] = $profile_storage->create([
+ 'type' => $element['#profile_type'],
+ 'uid' => $element['#profile_uid'],
+ ]);
+ }
+ // Persist any changes made to $element_state.
+ self::setElementState($element['#parents'], $form_state, $element_state);
+
+ return [
+ $element,
+ $profile_options,
+ $selected_option,
+ ];
+ }
- $element['#profile'] = $element['#default_value'];
+ /**
+ * Provide render array for an inline form.
+ *
+ * @param array $element
+ * The form element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * Render array for an inline form.
+ */
+ protected static function addInlineForm(array $element, FormStateInterface $form_state) {
+ /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
+ $profile_storage = \Drupal::entityTypeManager()->getStorage('profile');
+ if (empty($element['#profile'])) {
+ $element['#profile'] = $profile_storage->create([
+ 'type' => $element['#profile_type'],
+ 'uid' => $element['#profile_uid'],
+ ]);
+ }
$form_display = EntityFormDisplay::collectRenderDisplay($element['#profile'], 'default');
$form_display->buildForm($element['#profile'], $element, $form_state);
if (!empty($element['address']['widget'][0])) {
@@ -108,26 +236,120 @@ class ProfileSelect extends RenderElement {
$widget_element['address']['#available_countries'] = $element['#available_countries'];
}
}
+ return $element;
+ }
+
+ /**
+ * Provide render array for select list.
+ *
+ * @param array $element
+ * The form element being processed.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * Render array for an inline form.
+ */
+ protected static function addSelectList(array $element, FormStateInterface $form_state) {
+ list($element, $profile_options, $selected_option) = self::prepareOptions($element, $form_state);
+ $id_prefix = implode('-', $element['#parents']);
+ $wrapper_id = Html::getUniqueId("$id_prefix-ajax-wrapper");
+ // Pass the id along to other methods.
+ $element['#wrapper_id'] = $wrapper_id;
+ $view_builder = \Drupal::entityTypeManager()->getViewBuilder('profile');
+ $element['profile_selection'] = [
+ '#type' => 'select',
+ '#title' => $element['#title'],
+ '#options' => $profile_options,
+ '#default_value' => $selected_option,
+ '#ajax' => [
+ 'callback' => [get_called_class(), 'ajaxRefresh'],
+ 'wrapper' => $wrapper_id,
+ ],
+ '#limit_validation_errors' => [],
+ '#weight' => -1,
+ '#access' => !empty($profile_options),
+ ];
+ $element['profile'] = $view_builder->view($element['#profile'], 'default');
+ $element['#prefix'] = "
";
+ $element['#suffix'] = '
';
return $element;
}
/**
- * Validates the element form.
+ * Provide render array for modal links.
*
* @param array $element
- * The form element.
+ * The form element being processed.
* @param \Drupal\Core\Form\FormStateInterface $form_state
* The current state of the form.
*
- * @throws \Exception
- * Thrown if button-level #validate handlers are detected on the parent
- * form, as a protection against buggy behavior.
+ * @return array
+ * Render array for an inline form.
*/
- public static function validateForm(array &$element, FormStateInterface $form_state) {
- $form_display = EntityFormDisplay::collectRenderDisplay($element['#profile'], 'default');
- $form_display->extractFormValues($element['#profile'], $element, $form_state);
- $form_display->validateFormValues($element['#profile'], $element, $form_state);
+ protected static function addModalLinks(array $element, FormStateInterface $form_state) {
+ $url = Url::fromRoute('entity.profile.type.user_profile_form.add', ['user' => $element['#profile']->getOwnerId(), 'profile_type' => $element['#profile_type']]);
+ $url->mergeOptions(['query' => ['destination' => \Drupal::service('path.current')->getPath()]]);
+ $element['new'] = [
+ '#type' => 'link',
+ '#url' => $url,
+ '#title' => t('Add new profile'),
+ '#attributes' => [
+ 'class' => [
+ 'use-ajax',
+ 'button',
+ ],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => json_encode([
+ 'width' => 700,
+ ]),
+ ],
+ ];
+ if (!$element['#profile']->isNew()) {
+ $url = Url::fromRoute('entity.profile.edit_form', ['profile' => $element['#profile']->id()]);
+ $url->mergeOptions(['query' => ['destination' => \Drupal::service('path.current')->getPath()]]);
+ $element['edit'] = [
+ '#type' => 'link',
+ '#url' => $url,
+ '#title' => t('Edit profile'),
+ '#attributes' => [
+ 'class' => [
+ 'use-ajax',
+ 'button',
+ ],
+ 'data-dialog-type' => 'modal',
+ 'data-dialog-options' => json_encode([
+ 'width' => 700,
+ ]),
+ ],
+ ];
+ }
+ $element['#attached']['library'][] = 'core/drupal.dialog.ajax';
+
+ return $element;
+ }
+
+ /**
+ * Loads related profiles.
+ *
+ * @param array $element
+ * The form element being processed.
+ *
+ * @return \Drupal\profile\ProfileStorageInterface[]
+ * A list of loaded profiles.
+ */
+ protected static function loadProfiles(array $element) {
+ /** @var \Drupal\profile\ProfileStorageInterface $profile_storage */
+ $profile_storage = \Drupal::entityTypeManager()->getStorage('profile');
+ /** @var \Drupal\user\UserStorageInterface $user_storage */
+ $user_storage = \Drupal::entityTypeManager()->getStorage('user');
+ $user = $element['#profile_uid'] ? $user_storage->load($element['#profile_uid']) : NULL;
+ $profiles = [];
+ if ($user) {
+ $profiles = $profile_storage->loadMultipleByUser($user, $element['#profile_type'], TRUE);
+ }
+ return $profiles;
}
/**
@@ -144,4 +366,92 @@ class ProfileSelect extends RenderElement {
$element['#profile']->save();
}
+ /**
+ * Ajax callback.
+ */
+ public static function ajaxRefresh(array &$form, FormStateInterface $form_state) {
+ $triggering_element = $form_state->getTriggeringElement();
+ $element = NestedArray::getValue($form, array_slice($triggering_element['#array_parents'], 0, -1));
+ return $element;
+ }
+
+ /**
+ * Builds the option ID for the given profile.
+ *
+ * @param \Drupal\profile\Entity\ProfileInterface $profile
+ * The profile.
+ * @param bool $use_latest_revision
+ * (optional) Use the latest revision, defaults to TRUE.
+ *
+ * @return string
+ * The option ID.
+ */
+ public static function buildOptionId(ProfileInterface $profile, $use_latest_revision = TRUE) {
+ $option_id = $profile->id();
+ if (!$use_latest_revision) {
+ $option_id .= '-' . $profile->getRevisionId();
+ }
+ return $option_id;
+ }
+
+ /**
+ * Selects the default profile for the given element.
+ *
+ * @param array $element
+ * The element.
+ * @param array $profiles
+ * The available profiles.
+ *
+ * @return \Drupal\profile\Entity\ProfileInterface|null
+ * The default profile, or NULL if none found.
+ */
+ public static function selectDefaultProfile(array $element, array $profiles) {
+ $default_profile = NULL;
+ if (!empty($element['#profile'])) {
+ $default_profile = $element['#profile'];
+ }
+ elseif (!empty($profiles)) {
+ $default_profile = reset($profiles);
+ foreach ($profiles as $profile) {
+ if ($profile->isDefault()) {
+ $default_profile = $profile;
+ break;
+ }
+ }
+ }
+ return $default_profile;
+ }
+
+ /**
+ * Sets the element state.
+ *
+ * @param array $parents
+ * The element parents.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ * @param array $element_state
+ * The element state.
+ */
+ public static function setElementState(array $parents, FormStateInterface $form_state, array $element_state) {
+ $parents = array_merge(['element_state', '#parents'], $parents);
+ NestedArray::setValue($form_state->getStorage(), $parents, $element_state);
+ }
+
+ /**
+ * Gets the element state.
+ *
+ * @param array $parents
+ * The element parents.
+ * @param \Drupal\Core\Form\FormStateInterface $form_state
+ * The current state of the form.
+ *
+ * @return array
+ * The element state.
+ */
+ public static function getElementState(array $parents, FormStateInterface $form_state) {
+ $parents = array_merge(['element_state', '#parents'], $parents);
+ $element_state = (array) NestedArray::getValue($form_state->getStorage(), $parents);
+ return $element_state;
+ }
+
}
diff --git a/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php b/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
index 2d0987c..fecdcc8 100644
--- a/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
+++ b/modules/order/src/Plugin/Field/FieldWidget/BillingProfileWidget.php
@@ -3,13 +3,10 @@
namespace Drupal\commerce_order\Plugin\Field\FieldWidget;
use Drupal\Component\Utility\NestedArray;
-use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldDefinitionInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Field\WidgetBase;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
-use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Plugin implementation of 'commerce_billing_profile'.
@@ -22,50 +19,7 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
* }
* )
*/
-class BillingProfileWidget extends WidgetBase implements ContainerFactoryPluginInterface {
-
- /**
- * The entity type manager.
- *
- * @var \Drupal\Core\Entity\EntityTypeManagerInterface
- */
- protected $entityTypeManager;
-
- /**
- * Constructs a new BillingProfileWidget object.
- *
- * @param string $plugin_id
- * The plugin_id for the widget.
- * @param mixed $plugin_definition
- * The plugin implementation definition.
- * @param \Drupal\Core\Field\FieldDefinitionInterface $field_definition
- * The definition of the field to which the widget is associated.
- * @param array $settings
- * The widget settings.
- * @param array $third_party_settings
- * Any third party settings.
- * @param \Drupal\Core\Entity\EntityTypeManagerInterface $entity_type_manager
- * The entity type manager.
- */
- public function __construct($plugin_id, $plugin_definition, FieldDefinitionInterface $field_definition, array $settings, array $third_party_settings, EntityTypeManagerInterface $entity_type_manager) {
- parent::__construct($plugin_id, $plugin_definition, $field_definition, $settings, $third_party_settings);
-
- $this->entityTypeManager = $entity_type_manager;
- }
-
- /**
- * {@inheritdoc}
- */
- public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
- return new static(
- $plugin_id,
- $plugin_definition,
- $configuration['field_definition'],
- $configuration['settings'],
- $configuration['third_party_settings'],
- $container->get('entity_type.manager')
- );
- }
+class BillingProfileWidget extends WidgetBase {
/**
* {@inheritdoc}
@@ -75,20 +29,14 @@ class BillingProfileWidget extends WidgetBase implements ContainerFactoryPluginI
$order = $items[$delta]->getEntity();
$store = $order->getStore();
- if (!$items[$delta]->isEmpty()) {
- $profile = $items[$delta]->entity;
- }
- else {
- $profile = $this->entityTypeManager->getStorage('profile')->create([
- 'type' => 'customer',
- 'uid' => $order->getCustomerId(),
- ]);
- }
-
$element['#type'] = 'fieldset';
$element['profile'] = [
'#type' => 'commerce_profile_select',
- '#default_value' => $profile,
+ '#title' => $this->t('Select an address'),
+ '#profile' => $items[$delta]->entity,
+ '#profile_type' => 'customer',
+ '#profile_uid' => $order->getCustomerId(),
+ '#profile_latest_revision' => $order->getState()->value == 'draft',
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
];
diff --git a/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml b/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml
new file mode 100644
index 0000000..466dd4c
--- /dev/null
+++ b/modules/order/tests/modules/commerce_order_test/commerce_order_test.routing.yml
@@ -0,0 +1,9 @@
+commerce_order_test.profile_select_form:
+ path: '/commerce_order_test/profile_select_test_form'
+ defaults:
+ _form: '\Drupal\commerce_order_test\Form\ProfileSelectTestForm'
+ _title: 'Profile select test form'
+ requirements:
+ _access: 'TRUE'
+ options:
+ no_cache: TRUE
diff --git a/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php b/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php
new file mode 100644
index 0000000..1c59cb8
--- /dev/null
+++ b/modules/order/tests/modules/commerce_order_test/src/Form/ProfileSelectTestForm.php
@@ -0,0 +1,43 @@
+ 'commerce_profile_select',
+ '#profile_type' => 'customer',
+ '#profile_uid' => \Drupal::currentUser()->id(),
+ '#available_countries' => ['FR', 'DE', 'HU', 'RS', 'US'],
+ ];
+ $form['submit'] = [
+ '#type' => 'submit',
+ '#value' => $this->t('Submit'),
+ ];
+
+ return $form;
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function submitForm(array &$form, FormStateInterface $form_state) {
+ $profile = $form['profile']['#profile'];
+ drupal_set_message($this->t('Profile selected: :label', [':label' => $profile->label()]));
+ }
+
+}
diff --git a/modules/order/tests/src/Functional/OrderAdminTest.php b/modules/order/tests/src/Functional/OrderAdminTest.php
index 9476000..104a404 100644
--- a/modules/order/tests/src/Functional/OrderAdminTest.php
+++ b/modules/order/tests/src/Functional/OrderAdminTest.php
@@ -6,6 +6,7 @@ use Drupal\commerce_order\Adjustment;
use Drupal\commerce_order\Entity\Order;
use Drupal\commerce_price\Price;
use Drupal\profile\Entity\Profile;
+use Drupal\Tests\commerce\FunctionalJavascript\JavascriptTestTrait;
/**
* Tests the commerce_order entity forms.
@@ -14,28 +15,14 @@ use Drupal\profile\Entity\Profile;
*/
class OrderAdminTest extends OrderBrowserTestBase {
- /**
- * The profile to test against.
- *
- * @var \Drupal\profile\Entity\Profile
- */
- protected $billingProfile;
+ use JavascriptTestTrait;
/**
* {@inheritdoc}
*/
- protected function setUp() {
- parent::setUp();
- \Drupal::service('module_installer')->install(['profile']);
-
- $profile_values = [
- 'type' => 'customer',
- 'uid' => 1,
- 'status' => 1,
- ];
- $this->billingProfile = Profile::create($profile_values);
- $this->billingProfile->save();
- }
+ public static $modules = [
+ 'profile',
+ ];
/**
* Tests creating/editing an Order.
@@ -43,6 +30,9 @@ class OrderAdminTest extends OrderBrowserTestBase {
* @group failing
*/
public function testCreateOrder() {
+ $assert = $this->assertSession();
+ $page = $this->getSession()->getPage();
+
// Create an order through the add form.
$this->drupalGet('/admin/commerce/orders');
$this->getSession()->getPage()->clickLink('Create a new order');
@@ -55,7 +45,8 @@ class OrderAdminTest extends OrderBrowserTestBase {
// Check the integrity of the edit form.
$this->assertSession()->statusCodeEquals(200);
- $this->assertSession()->fieldExists('billing_profile[0][profile][address][0][address][given_name]');
+ $input = $page->find('named', ['id_or_name', 'profile[address][0][address][country_code]']);
+ $this->assertNotEmpty($input, sprintf('%s was not found', 'country_code'));
$this->assertSession()->fieldExists('order_items[form][inline_entity_form][purchased_entity][0][target_id]');
$this->assertSession()->fieldExists('order_items[form][inline_entity_form][quantity][0][value]');
$this->assertSession()->fieldExists('order_items[form][inline_entity_form][unit_price][0][amount][number]');
@@ -91,16 +82,26 @@ class OrderAdminTest extends OrderBrowserTestBase {
'order_items[form][inline_entity_form][entities][0][form][unit_price][0][amount][number]' => '1.11',
];
$this->submitForm($edit, 'Update order item');
- $edit = [
- 'billing_profile[0][profile][address][0][address][given_name]' => 'John',
- 'billing_profile[0][profile][address][0][address][family_name]' => 'Smith',
- 'billing_profile[0][profile][address][0][address][address_line1]' => '123 street',
- 'billing_profile[0][profile][address][0][address][postal_code]' => '94043',
- 'billing_profile[0][profile][address][0][address][locality]' => 'Mountain View',
- 'billing_profile[0][profile][address][0][address][administrative_area]' => 'CA',
- ];
+
// There is no adjustment - the order should save successfully.
- $this->submitForm($edit, 'Save');
+ $input = $page->find('named', ['id_or_name', 'profile[address][0][address][country_code]']);
+ $this->assertNotEmpty($input, sprintf('%s was not found', 'country_code'));
+ $input->setValue('US');
+ $this->waitForAjaxToFinish();
+ $address = [
+ 'postal_code' => '53177',
+ 'locality' => 'Milwaukee',
+ 'address_line1' => 'Pabst Blue Ribbon Dr',
+ 'administrative_area' => 'WI',
+ 'given_name' => 'Frederick',
+ 'family_name' => 'Pabst',
+ ];
+ foreach ($address as $key => $value) {
+ $input = $page->find('named', ['id_or_name', "profile[address][0][address][$key]"]);
+ $this->assertNotEmpty($input, sprintf('%s was not found', $key));
+ $input->setValue($value);
+ }
+ $this->submitForm([], 'Save');
$this->assertSession()->pageTextContains('The order has been successfully saved.');
// Use an adjustment that is not locked by default.
diff --git a/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php b/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php
new file mode 100644
index 0000000..5daf2a0
--- /dev/null
+++ b/modules/order/tests/src/FunctionalJavascript/ProfileSelectTest.php
@@ -0,0 +1,337 @@
+ 'HU',
+ 'given_name' => 'Gustav',
+ 'family_name' => 'Mahler',
+ 'address_line1' => 'Teréz körút 7',
+ 'locality' => 'Budapest',
+ 'postal_code' => '1067',
+ ];
+
+ /**
+ * Profile address values.
+ *
+ * @var array
+ */
+ protected $address2 = [
+ 'country_code' => 'DE',
+ 'given_name' => 'Johann Sebastian',
+ 'family_name' => 'Bach',
+ 'address_line1' => 'Thomaskirchhof 15',
+ 'locality' => 'Leipzig',
+ 'postal_code' => '04109',
+ ];
+
+ /**
+ * The profile storage.
+ *
+ * @var \Drupal\profile\ProfileStorageInterface
+ */
+ protected $profileStorage;
+
+ /**
+ * {@inheritdoc}
+ */
+ public static $modules = [
+ 'commerce_order',
+ 'commerce_order_test',
+ ];
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function setUp() {
+ parent::setUp();
+
+ $this->profileStorage = $this->container->get('entity_type.manager')->getStorage('profile');
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function getAdministratorPermissions() {
+ return [
+ 'view own customer profile',
+ 'update own customer profile',
+ ] + parent::getAdministratorPermissions();
+ }
+
+ /**
+ * Tests creating a profile as an authenticated user.
+ */
+ public function testAnonymous() {
+ $page = $this->getSession()->getPage();
+ $assert = $this->assertSession();
+
+ // Create a profile for a different user to make sure it's not shown.
+ $profile1 = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'uid' => $this->adminUser->id(),
+ 'address' => $this->address1,
+ ]);
+
+ $this->drupalLogout();
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $assert->pageTextContains('Profile select test form');
+ $assert->pageTextNotContains('Select a profile');
+ $input = $page->find('named', ['id_or_name', 'profile[address][0][address][country_code]']);
+ $this->assertNotEmpty($input, sprintf('%s was not found', 'country_code'));
+ $input->setValue($this->address1['country_code']);
+ $assert->assertWaitOnAjaxRequest();
+ foreach ($this->address1 as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $input = $page->find('named', ['id_or_name', "profile[address][0][address][$key]"]);
+ $this->assertNotEmpty($input, sprintf('%s was not found', $key));
+ $input->setValue($value);
+ }
+ $this->submitForm([], 'Submit');
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile = $this->profileStorage->load(1);
+ $assert->responseContains(sprintf('Profile selected: %s', $profile->label()));
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile->get('address')->first();
+ $this->assertEquals($this->address1['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address1['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address1['family_name'], $address->getFamilyName());
+ $this->assertEquals($this->address1['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($this->address1['locality'], $address->getLocality());
+ $this->assertEquals($this->address1['postal_code'], $address->getPostalCode());
+ }
+
+ /**
+ * Tests selecting a profile as an authenticated user.
+ */
+ public function testAuthenticated() {
+ $page = $this->getSession()->getPage();
+ $assert = $this->assertSession();
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile1 */
+ $profile1 = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'uid' => $this->adminUser->id(),
+ 'address' => $this->address1,
+ ]);
+ $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'uid' => $this->adminUser->id(),
+ 'address' => $this->address2,
+ 'is_default' => TRUE,
+ ]);
+
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $assert->pageTextContains('Profile select test form');
+ $assert->fieldExists('Select a profile');
+ // The last created profile should be selected by default.
+ $assert->pageTextContains($this->address2['locality']);
+
+ $page->fillField('Select a profile', $profile1->id());
+ $assert->assertWaitOnAjaxRequest();
+ $assert->pageTextContains($this->address1['locality']);
+ $this->submitForm([], 'Submit');
+ $assert->responseContains(sprintf('Profile selected: %s', $profile1->label()));
+
+ $this->profileStorage->resetCache([$profile1->id()]);
+ $profile1 = $this->profileStorage->load($profile1->id());
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile1->get('address')->first();
+ // Assert that field values have not changed.
+ $this->assertEquals($this->address1['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address1['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address1['family_name'], $address->getFamilyName());
+ $this->assertEquals($this->address1['address_line1'], $address->getAddressLine1());
+ $this->assertEquals($this->address1['locality'], $address->getLocality());
+ $this->assertEquals($this->address1['postal_code'], $address->getPostalCode());
+
+ $profiles = $this->profileStorage->loadMultipleByUser($this->adminUser, 'customer', TRUE);
+ $this->assertCount(2, $profiles);
+ }
+
+ /**
+ * Tests creating the initial profile as an authenticated user.
+ */
+ public function testAuthenticatedCreate() {
+ $page = $this->getSession()->getPage();
+ $assert = $this->assertSession();
+
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $assert->pageTextContains('Profile select test form');
+ $assert->pageTextNotContains('Select a profile');
+ $page->fillField('profile[address][0][address][country_code]', $this->address1['country_code']);
+ $assert->assertWaitOnAjaxRequest();
+
+ foreach ($this->address1 as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $input = $page->find('named', ['id_or_name', "profile[address][0][address][$key]"]);
+ $this->assertNotEmpty($input, sprintf('%s was not found', $key));
+ $input->setValue($value);
+ }
+ $this->submitForm([], 'Submit');
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile1 */
+ $profile1 = $this->profileStorage->load(1);
+ $assert->responseContains(sprintf('Profile selected: %s', $profile1->label()));
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address1 */
+ $address1 = $profile1->get('address')->first();
+ $this->assertEquals($this->address1['country_code'], $address1->getCountryCode());
+ $this->assertEquals($this->address1['given_name'], $address1->getGivenName());
+ $this->assertEquals($this->address1['family_name'], $address1->getFamilyName());
+ $this->assertEquals($this->address1['address_line1'], $address1->getAddressLine1());
+ $this->assertEquals($this->address1['locality'], $address1->getLocality());
+ $this->assertEquals($this->address1['postal_code'], $address1->getPostalCode());
+
+ $profiles = $this->profileStorage->loadMultipleByUser($this->adminUser, 'customer', TRUE);
+ $this->assertCount(1, $profiles);
+
+ $this->clickLink('Add new profile');
+ $this->assertNotEmpty($assert->waitForElementVisible('css', '.ui-dialog'));
+ $assert->elementContains('css', 'span.ui-dialog-title', 'Create Customer');
+ $input = $page->find('named', ['id_or_name', 'address[0][address][country_code]']);
+ $this->assertNotEmpty($input, sprintf('%s was not found', 'country_code'));
+ $input->setValue($this->address2['country_code']);
+ $this->waitForAjaxToFinish();
+ foreach ($this->address2 as $key => $value) {
+ if ($key == 'country_code') {
+ continue;
+ }
+ $input = $page->find('named', ['id_or_name', "address[0][address][$key]"]);
+ $this->assertNotEmpty($input, sprintf('%s was not found', $key));
+ $input->setValue($value);
+ }
+ $this->clickModalSubmit('Save and make default');
+ $this->submitForm([], 'Submit');
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile */
+ $profile2 = $this->profileStorage->load(2);
+ $assert->responseContains(sprintf('Profile selected: %s', $profile2->label()));
+
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address2 */
+ $address2 = $profile2->get('address')->first();
+ $this->assertEquals($this->address2['country_code'], $address2->getCountryCode());
+ $this->assertEquals($this->address2['given_name'], $address2->getGivenName());
+ $this->assertEquals($this->address2['family_name'], $address2->getFamilyName());
+ $this->assertEquals($this->address2['address_line1'], $address2->getAddressLine1());
+ $this->assertEquals($this->address2['locality'], $address2->getLocality());
+ $this->assertEquals($this->address2['postal_code'], $address2->getPostalCode());
+
+ $profiles = $this->profileStorage->loadMultipleByUser($this->adminUser, 'customer', TRUE);
+ $this->assertCount(2, $profiles);
+ }
+
+ /**
+ * Tests editing a profile as an authenticated user.
+ *
+ * @group debug
+ */
+ public function testAuthenticatedEdit() {
+ $page = $this->getSession()->getPage();
+ $assert = $this->assertSession();
+
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile1 */
+ $profile1 = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'uid' => $this->adminUser->id(),
+ 'address' => $this->address1,
+ ]);
+ $profile1->save();
+ /** @var \Drupal\profile\Entity\ProfileInterface $profile2 */
+ $profile2 = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'uid' => $this->adminUser->id(),
+ 'address' => $this->address2,
+ 'is_default' => TRUE,
+ ]);
+ $profile2->save();
+
+ $this->drupalGet(Url::fromRoute('commerce_order_test.profile_select_form'));
+ $assert->pageTextContains('Profile select test form');
+ $assert->fieldExists('Select a profile');
+ // The last created profile should be selected by default.
+ $assert->pageTextContains($this->address2['locality']);
+ $this->clickLink('Edit profile');
+
+ $this->assertNotEmpty($assert->waitForElementVisible('css', '.ui-dialog'));
+ $assert->elementContains('css', 'span.ui-dialog-title', sprintf('Edit %s', $this->address2['address_line1']));
+ foreach ($this->address2 as $key => $value) {
+ $input = $page->find('named', ['id_or_name', "address[0][address][$key]"]);
+ $this->assertEquals($value, $input->getValue());
+ }
+ $input = $page->find('named', ['id_or_name', 'address[0][address][address_line1]']);
+ $input->setValue('Andrássy út 22');
+ $this->clickModalSubmit('Save');
+ $this->submitForm([], 'Submit');
+
+ $this->profileStorage->resetCache([$profile2->id()]);
+ $profile2 = $this->profileStorage->load($profile2->id());
+ /** @var \Drupal\address\Plugin\Field\FieldType\AddressItem $address */
+ $address = $profile2->get('address')->first();
+ $assert->responseContains(sprintf('Profile selected: %s', $profile2->label()));
+ // Assert that all but street1 field values have not changed.
+ $this->assertEquals($this->address2['country_code'], $address->getCountryCode());
+ $this->assertEquals($this->address2['given_name'], $address->getGivenName());
+ $this->assertEquals($this->address2['family_name'], $address->getFamilyName());
+ $this->assertEquals('Andrássy út 22', $address->getAddressLine1());
+ $this->assertEquals($this->address2['locality'], $address->getLocality());
+ $this->assertEquals($this->address2['postal_code'], $address->getPostalCode());
+
+ $profiles = $this->profileStorage->loadMultipleByUser($this->adminUser, 'customer', TRUE);
+ $this->assertCount(2, $profiles);
+ }
+
+ /**
+ * Click a modal submit button.
+ *
+ * @param string $text
+ * The button text.
+ *
+ * @todo: Switch to using NodeElement::click() on the button or
+ * NodeElement::submit() on the form when #2831506 is fixed.
+ *
+ * @see https://www.drupal.org/node/2831506
+ */
+ protected function clickModalSubmit($text) {
+ $this->waitForAjaxToFinish();
+ $condition = <<getSession()->wait(10000, $condition);
+ $condition = <<getSession()->wait(10000, $condition);
+ }
+
+}
diff --git a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
index a91aabe..92be4b9 100644
--- a/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
+++ b/modules/payment/src/Plugin/Commerce/CheckoutPane/PaymentInformation.php
@@ -260,17 +260,13 @@ class PaymentInformation extends CheckoutPaneBase {
*/
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,
+ '#title' => $this->t('Select an address'),
+ '#profile' => $this->order->getBillingProfile(),
+ '#profile_type' => 'customer',
+ '#profile_uid' => $this->order->getCustomerId(),
'#default_country' => $store->getAddress()->getCountryCode(),
'#available_countries' => $store->getBillingCountries(),
];
diff --git a/modules/payment/src/PluginForm/PaymentMethodAddForm.php b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
index 44c6c69..616d6a4 100644
--- a/modules/payment/src/PluginForm/PaymentMethodAddForm.php
+++ b/modules/payment/src/PluginForm/PaymentMethodAddForm.php
@@ -6,7 +6,6 @@ use Drupal\commerce_payment\CreditCard;
use Drupal\commerce_payment\Exception\DeclineException;
use Drupal\commerce_payment\Exception\PaymentGatewayException;
use Drupal\Core\Form\FormStateInterface;
-use Drupal\profile\Entity\Profile;
class PaymentMethodAddForm extends PaymentGatewayFormBase {
@@ -54,16 +53,6 @@ class PaymentMethodAddForm extends PaymentGatewayFormBase {
/** @var \Drupal\commerce_payment\Entity\PaymentMethodInterface $payment_method */
$payment_method = $this->entity;
- /** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
- $billing_profile = $payment_method->getBillingProfile();
- if (!$billing_profile) {
- /** @var \Drupal\profile\Entity\ProfileInterface $billing_profile */
- $billing_profile = Profile::create([
- 'type' => 'customer',
- 'uid' => $payment_method->getOwnerId(),
- ]);
- }
-
if ($order = $this->routeMatch->getParameter('commerce_order')) {
$store = $order->getStore();
}
@@ -76,7 +65,9 @@ class PaymentMethodAddForm extends PaymentGatewayFormBase {
$form['billing_information'] = [
'#parents' => array_merge($form['#parents'], ['billing_information']),
'#type' => 'commerce_profile_select',
- '#default_value' => $billing_profile,
+ '#title' => t('Select an address'),
+ '#profile_type' => 'customer',
+ '#profile_uid' => $payment_method->getOwnerId(),
'#default_country' => $store ? $store->getAddress()->getCountryCode() : NULL,
'#available_countries' => $store ? $store->getBillingCountries() : [],
];
diff --git a/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php b/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
index 099b70e..ce9b2f2 100644
--- a/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
+++ b/modules/payment/tests/src/FunctionalJavascript/PaymentCheckoutTest.php
@@ -34,6 +34,13 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
protected $product;
/**
+ * The profile.
+ *
+ * @var \Drupal\profile\Entity\ProfileInterface
+ */
+ protected $adminProfile;
+
+ /**
* A non-reusable order payment method.
*
* @var \Drupal\commerce_payment\Entity\PaymentMethodInterface
@@ -148,7 +155,7 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
]);
$gateway->save();
- $profile = $this->createEntity('profile', [
+ $this->adminProfile = $this->createEntity('profile', [
'type' => 'customer',
'address' => [
'country_code' => 'US',
@@ -167,11 +174,11 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
'payment_gateway' => 'onsite',
'card_type' => 'visa',
'card_number' => '1111',
- 'billing_profile' => $profile,
+ 'billing_profile' => $this->adminProfile,
'reusable' => TRUE,
'expires' => strtotime('2028/03/24'),
]);
- $payment_method->setBillingProfile($profile);
+ $payment_method->setBillingProfile($this->adminProfile);
$payment_method->save();
$this->orderPaymentMethod = $this->createEntity('commerce_payment_method', [
@@ -236,7 +243,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$this->drupalGet('checkout/1');
$radio_button = $page->findField('Example');
$this->assertNull($radio_button);
- $this->assertSession()->fieldExists('payment_information[billing_information][address][0][address][postal_code]');
+ $this->createScreenshot();
+ $this->assertEquals($page->findField('Select an address')->getValue(), $this->adminProfile->id());
}
/**
@@ -273,6 +281,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests checkout with a new payment method.
*/
public function testCheckoutWithNewPaymentMethod() {
+ $profile = $this->createUserProfile();
+
// Test the 'capture' setting of PaymentProcess while here.
/** @var \Drupal\commerce_checkout\Entity\CheckoutFlow $checkout_flow */
$checkout_flow = CheckoutFlow::load('default');
@@ -289,17 +299,12 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
$this->submitForm([
'payment_information[add_payment_method][payment_details][number]' => '4012888888881881',
'payment_information[add_payment_method][payment_details][expiration][month]' => '02',
'payment_information[add_payment_method][payment_details][expiration][year]' => '2020',
'payment_information[add_payment_method][payment_details][security_code]' => '123',
- 'payment_information[add_payment_method][billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[add_payment_method][billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[add_payment_method][billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[add_payment_method][billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[add_payment_method][billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[add_payment_method][billing_information][address][0][address][postal_code]' => '10001',
], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Visa ending in 1881');
@@ -327,6 +332,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests that a declined payment does not complete checkout.
*/
public function testCheckoutWithDeclinedPaymentMethod() {
+ $profile = $this->createUserProfile();
+
$this->drupalGet($this->product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
$this->drupalGet('checkout/1');
@@ -334,17 +341,14 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->createScreenshot();
$this->submitForm([
'payment_information[add_payment_method][payment_details][number]' => '4111111111111111',
'payment_information[add_payment_method][payment_details][expiration][month]' => '02',
'payment_information[add_payment_method][payment_details][expiration][year]' => '2020',
'payment_information[add_payment_method][payment_details][security_code]' => '123',
- 'payment_information[add_payment_method][billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[add_payment_method][billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[add_payment_method][billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[add_payment_method][billing_information][address][0][address][locality]' => 'Somewhere',
- 'payment_information[add_payment_method][billing_information][address][0][address][administrative_area]' => 'WI',
- 'payment_information[add_payment_method][billing_information][address][0][address][postal_code]' => '53140',
], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Visa ending in 1111');
@@ -365,6 +369,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests checkout with an off-site gateway (POST redirect method).
*/
public function testCheckoutWithOffsiteRedirectPost() {
+ $profile = $this->createUserProfile();
+
$this->drupalGet($this->product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
$this->drupalGet('checkout/1');
@@ -372,14 +378,9 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Example');
$this->assertSession()->pageTextContains('Johnny Appleseed');
@@ -403,6 +404,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* page in order to proceed to the gateway.
*/
public function testCheckoutWithOffsiteRedirectPostManual() {
+ $profile = $this->createUserProfile();
+
$payment_gateway = PaymentGateway::load('offsite');
$payment_gateway->getPlugin()->setConfiguration([
'redirect_method' => 'post_manual',
@@ -417,14 +420,9 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Example');
$this->assertSession()->pageTextContains('Johnny Appleseed');
@@ -453,6 +451,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests checkout with an off-site gateway (GET redirect method).
*/
public function testCheckoutWithOffsiteRedirectGet() {
+ $profile = $this->createUserProfile();
+
// Checkout must work when the off-site gateway is alone, and the
// radio button hidden.
$onsite_gateway = PaymentGateway::load('onsite');
@@ -473,14 +473,9 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$this->submitForm([], 'Add to cart');
$this->drupalGet('checkout/1');
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Example');
$this->assertSession()->pageTextContains('Johnny Appleseed');
@@ -503,6 +498,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* The off-site form throws an exception, simulating an API fail.
*/
public function testFailedCheckoutWithOffsiteRedirectGet() {
+ $profile = $this->createUserProfile();
+
$payment_gateway = PaymentGateway::load('offsite');
$payment_gateway->getPlugin()->setConfiguration([
'redirect_method' => 'get',
@@ -517,14 +514,9 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'FAIL',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Example');
$this->assertSession()->pageTextContains('Johnny FAIL');
@@ -546,6 +538,8 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests checkout with a manual gateway.
*/
public function testCheckoutWithManual() {
+ $profile = $this->createUserProfile();
+
$this->drupalGet($this->product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
$this->drupalGet('checkout/1');
@@ -553,14 +547,9 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$radio_button->click();
$this->waitForAjaxToFinish();
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Payment information');
$this->assertSession()->pageTextContains('Example');
$this->assertSession()->pageTextContains('Johnny Appleseed');
@@ -582,6 +571,7 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
* Tests a free order, where only the billing information is collected.
*/
public function testFreeOrder() {
+ $profile = $this->createUserProfile();
$this->drupalGet($this->product->toUrl()->toString());
$this->submitForm([], 'Add to cart');
@@ -595,17 +585,12 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
]));
$order->save();
+ $this->getSession()->getPage()->fillField('Select an address', $profile->id());
+ $this->waitForAjaxToFinish();
$this->drupalGet('checkout/1');
$this->assertSession()->pageTextContains('Billing information');
$this->assertSession()->pageTextNotContains('Payment information');
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+ $this->submitForm([], 'Continue to review');
$this->assertSession()->pageTextContains('Billing information');
$this->assertSession()->pageTextNotContains('Payment information');
@@ -617,4 +602,26 @@ class PaymentCheckoutTest extends CommerceBrowserTestBase {
$this->assertSession()->pageTextContains('Your order number is 1. You can view your order on your account page when logged in.');
}
+ /**
+ * Create user profile.
+ *
+ * @return \Drupal\profile\Entity\ProfileInterface
+ */
+ function createUserProfile() {
+ $profile = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'address' => [
+ 'country_code' => 'US',
+ 'postal_code' => '10001',
+ 'locality' => 'New York City',
+ 'address_line1' => '123 New York Drive',
+ 'administrative_area' => 'NY',
+ 'given_name' => 'Johnny',
+ 'family_name' => 'Appleseed',
+ ],
+ 'uid' => $this->adminUser->id(),
+ ]);
+ return $profile;
+ }
+
}
diff --git a/modules/promotion/tests/src/FunctionalJavascript/CouponRedemptionPaneTest.php b/modules/promotion/tests/src/FunctionalJavascript/CouponRedemptionPaneTest.php
index 8cbf9a3..7376133 100644
--- a/modules/promotion/tests/src/FunctionalJavascript/CouponRedemptionPaneTest.php
+++ b/modules/promotion/tests/src/FunctionalJavascript/CouponRedemptionPaneTest.php
@@ -34,6 +34,13 @@ class CouponRedemptionPaneTest extends CommerceBrowserTestBase {
protected $cartManager;
/**
+ * The profile.
+ *
+ * @var \Drupal\profile\Entity\ProfileInterface
+ */
+ protected $profile;
+
+ /**
* The promotion.
*
* @var \Drupal\commerce_promotion\Entity\PromotionInterface
@@ -125,7 +132,7 @@ class CouponRedemptionPaneTest extends CommerceBrowserTestBase {
]);
$onsite_gateway->save();
- $profile = $this->createEntity('profile', [
+ $this->profile = $this->createEntity('profile', [
'type' => 'customer',
'address' => [
'country_code' => 'US',
@@ -283,24 +290,31 @@ class CouponRedemptionPaneTest extends CommerceBrowserTestBase {
* Tests that adding/removing coupons does not submit other panes.
*/
public function testCheckoutSubmit() {
+ $page = $this->getSession()->getPage();
// Start checkout, and enter billing information.
$this->drupalGet(Url::fromRoute('commerce_checkout.form', ['commerce_order' => $this->cart->id()]));
$this->getSession()->getPage()->findField('Example')->check();
$this->waitForAjaxToFinish();
- $this->submitForm([
- 'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
- 'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
- 'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
- 'payment_information[billing_information][address][0][address][locality]' => 'New York City',
- 'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
- 'payment_information[billing_information][address][0][address][postal_code]' => '10001',
- ], 'Continue to review');
+
+ $this->assertEquals($page->findField('Select a profile')->getValue(), $this->profile->id());
+ $this->submitForm([], 'Continue to review');
// Go back and edit the billing information, but don't submit it.
$this->getSession()->getPage()->clickLink('Go back');
- $address_prefix = 'payment_information[billing_information][address][0][address]';
- $this->getSession()->getPage()->fillField($address_prefix . '[given_name]', 'John');
- $this->getSession()->getPage()->fillField($address_prefix . '[family_name]', 'Smith');
+ $profile = $this->createEntity('profile', [
+ 'type' => 'customer',
+ 'address' => [
+ 'country_code' => 'US',
+ 'postal_code' => '10001',
+ 'locality' => 'New York City',
+ 'address_line1' => '123 New York Drive',
+ 'administrative_area' => 'NY',
+ 'given_name' => 'Johnny',
+ 'family_name' => 'Appleseed',
+ ],
+ 'uid' => $this->adminUser->id(),
+ ]);
+ $this->getSession()->getPage()->fillField('Select a profile', $profile->id());
// Add a coupon.
$coupons = $this->promotion->getCoupons();