Skip to content

Commit 7938ec6

Browse files
bojanzbojanz
bojanz
authored andcommitted
Issue #2902408 by chrisrockwell, bojanz: Applying a coupon saves the other checkout panes
1 parent 6e000f9 commit 7938ec6

File tree

2 files changed

+96
-6
lines changed

2 files changed

+96
-6
lines changed

modules/promotion/tests/src/FunctionalJavascript/CouponRedemptionPaneTest.php

+57-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,19 @@ protected function setUp() {
102102
$this->promotion->save();
103103

104104
/** @var \Drupal\commerce_payment\Entity\PaymentGateway $gateway */
105-
$gateway = PaymentGateway::create([
105+
$offsite_gateway = PaymentGateway::create([
106+
'id' => 'offsite',
107+
'label' => 'Off-site',
108+
'plugin' => 'example_offsite_redirect',
109+
'configuration' => [
110+
'redirect_method' => 'post',
111+
'payment_method_types' => ['credit_card'],
112+
],
113+
]);
114+
$offsite_gateway->save();
115+
116+
/** @var \Drupal\commerce_payment\Entity\PaymentGateway $gateway */
117+
$onsite_gateway = PaymentGateway::create([
106118
'id' => 'onsite',
107119
'label' => 'On-site',
108120
'plugin' => 'example_onsite',
@@ -111,7 +123,7 @@ protected function setUp() {
111123
'payment_method_types' => ['credit_card'],
112124
],
113125
]);
114-
$gateway->save();
126+
$onsite_gateway->save();
115127

116128
$profile = $this->createEntity('profile', [
117129
'type' => 'customer',
@@ -267,4 +279,47 @@ public function testCheckoutWithMainSubmit() {
267279
$this->assertEquals(new Price('899.10', 'USD'), $this->cart->getTotalPrice());
268280
}
269281

282+
/**
283+
* Tests that adding/removing coupons does not submit other panes.
284+
*/
285+
public function testCheckoutSubmit() {
286+
// Start checkout, and enter billing information.
287+
$this->drupalGet(Url::fromRoute('commerce_checkout.form', ['commerce_order' => $this->cart->id()]));
288+
$this->getSession()->getPage()->findField('Example')->check();
289+
$this->waitForAjaxToFinish();
290+
$this->submitForm([
291+
'payment_information[billing_information][address][0][address][given_name]' => 'Johnny',
292+
'payment_information[billing_information][address][0][address][family_name]' => 'Appleseed',
293+
'payment_information[billing_information][address][0][address][address_line1]' => '123 New York Drive',
294+
'payment_information[billing_information][address][0][address][locality]' => 'New York City',
295+
'payment_information[billing_information][address][0][address][administrative_area]' => 'NY',
296+
'payment_information[billing_information][address][0][address][postal_code]' => '10001',
297+
], 'Continue to review');
298+
299+
// Go back and edit the billing information, but don't submit it.
300+
$this->getSession()->getPage()->clickLink('Go back');
301+
$address_prefix = 'payment_information[billing_information][address][0][address]';
302+
$this->getSession()->getPage()->fillField($address_prefix . '[given_name]', 'John');
303+
$this->getSession()->getPage()->fillField($address_prefix . '[family_name]', 'Smith');
304+
305+
// Add a coupon.
306+
$coupons = $this->promotion->getCoupons();
307+
$coupon = reset($coupons);
308+
$page = $this->getSession()->getPage();
309+
$page->fillField('Coupon code', $coupon->getCode());
310+
$page->pressButton('Apply coupon');
311+
$this->waitForAjaxToFinish();
312+
$this->assertSession()->pageTextContains($coupon->getCode());
313+
$this->assertSession()->fieldNotExists('Coupon code');
314+
$this->assertSession()->buttonNotExists('Apply coupon');
315+
316+
// Refresh the page and ensure the billing information hasn't been modified.
317+
$this->drupalGet(Url::fromRoute('commerce_checkout.form', ['commerce_order' => $this->cart->id(), 'step' => 'order_information']));
318+
$page = $this->getSession()->getPage();
319+
$given_name_field = $page->findField('payment_information[billing_information][address][0][address][given_name]');
320+
$family_name_field = $page->findField('payment_information[billing_information][address][0][address][family_name]');
321+
$this->assertEquals($given_name_field->getValue(), 'Johnny');
322+
$this->assertEquals($family_name_field->getValue(), 'Appleseed');
323+
}
324+
270325
}

src/Element/CommerceElementTrait.php

+39-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace Drupal\commerce\Element;
44

5+
use Drupal\Component\Utility\NestedArray;
56
use Drupal\Core\Form\FormStateInterface;
67
use Drupal\Core\Render\Element;
78

@@ -71,21 +72,55 @@ public static function validateElementSubmit(array &$element, FormStateInterface
7172
* step of validation, allowing thrown exceptions to be converted into form
7273
* errors.
7374
*
74-
* @param array $element
75-
* The form element.
75+
* @param array &$form
76+
* The form.
7677
* @param \Drupal\Core\Form\FormStateInterface $form_state
7778
* The form state.
7879
*/
79-
public static function executeElementSubmitHandlers(array &$element, FormStateInterface $form_state) {
80+
public static function executeElementSubmitHandlers(array &$form, FormStateInterface $form_state) {
8081
if (!$form_state->isSubmitted() || $form_state->hasAnyErrors()) {
8182
// The form wasn't submitted (#ajax in progress) or failed validation.
8283
return;
8384
}
85+
// A submit button might need to process only a part of the form.
86+
// For example, the "Apply coupon" button at checkout should apply coupons,
87+
// but not save the payment information. Use #limit_validation_errors
88+
// as a guideline for which parts of the form to submit.
89+
$triggering_element = $form_state->getTriggeringElement();
90+
if (isset($triggering_element['#limit_validation_errors']) && $triggering_element['#limit_validation_errors'] !== FALSE) {
91+
// #limit_validation_errors => [], the button cares about nothing.
92+
if (empty($triggering_element['#limit_validation_errors'])) {
93+
return;
94+
}
8495

96+
foreach ($triggering_element['#limit_validation_errors'] as $limit_validation_errors) {
97+
$element = NestedArray::getValue($form, $limit_validation_errors);
98+
if (!$element) {
99+
// The element will be empty if #parents don't match #array_parents,
100+
// the case for IEF widgets. In that case just submit everything.
101+
$element = &$form;
102+
}
103+
self::doExecuteSubmitHandlers($element, $form_state);
104+
}
105+
}
106+
else {
107+
self::doExecuteSubmitHandlers($form, $form_state);
108+
}
109+
}
110+
111+
/**
112+
* Calls the #commerce_element_submit callbacks recursively.
113+
*
114+
* @param array &$element
115+
* The current element.
116+
* @param \Drupal\Core\Form\FormStateInterface $form_state
117+
* The form state.
118+
*/
119+
public static function doExecuteSubmitHandlers(&$element, FormStateInterface $form_state) {
85120
// Recurse through all children.
86121
foreach (Element::children($element) as $key) {
87122
if (!empty($element[$key])) {
88-
static::executeElementSubmitHandlers($element[$key], $form_state);
123+
static::doExecuteSubmitHandlers($element[$key], $form_state);
89124
}
90125
}
91126

0 commit comments

Comments
 (0)