diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php index 7f88e9e52f116..895516e783efa 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/QuoteAddressFactory.php @@ -16,6 +16,7 @@ use Magento\Framework\GraphQl\Exception\GraphQlAuthorizationException; use Magento\Framework\GraphQl\Exception\GraphQlInputException; use Magento\Framework\GraphQl\Exception\GraphQlNoSuchEntityException; +use Magento\Quote\Api\Data\CartInterface; use Magento\Quote\Model\Quote\Address as QuoteAddress; use Magento\Quote\Model\Quote\AddressFactory as BaseQuoteAddressFactory; @@ -194,4 +195,21 @@ public function createBasedOnCustomerAddress(int $customerAddressId, int $custom } return $quoteAddress; } + + /** + * Create quote address based on the shipping address. + * + * @param CartInterface $quote + * @return QuoteAddress + */ + public function createBasedOnShippingAddress(CartInterface $quote): QuoteAddress + { + $shippingAddressData = $quote->getShippingAddress()->exportCustomerAddress(); + + /** @var QuoteAddress $quoteAddress */ + $quoteAddress = $this->quoteAddressFactory->create(); + $quoteAddress->importCustomerAddressData($shippingAddressData); + + return $quoteAddress; + } } diff --git a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php index 497819cbeec3b..a7d44fa104c27 100644 --- a/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php +++ b/app/code/Magento/QuoteGraphQl/Model/Cart/SetBillingAddressOnCart.php @@ -49,42 +49,46 @@ public function __construct( * @param array $billingAddressInput * @return void * @throws GraphQlAuthorizationException - * @throws GraphQlInputException - * @throws GraphQlNoSuchEntityException */ public function execute(ContextInterface $context, CartInterface $cart, array $billingAddressInput): void { + $this->checkForInputExceptions($billingAddressInput); + $customerAddressId = $billingAddressInput['customer_address_id'] ?? null; $addressInput = $billingAddressInput['address'] ?? null; + $useForShipping = $billingAddressInput['use_for_shipping'] ?? false; + $sameAsShipping = $billingAddressInput['same_as_shipping'] ?? false; - if (!$customerAddressId && !isset($billingAddressInput['address']['save_in_address_book']) && $addressInput) { + if (!$customerAddressId && $addressInput && !isset($addressInput['save_in_address_book'])) { $addressInput['save_in_address_book'] = true; } - // Need to keep this for BC of `use_for_shipping` field - $sameAsShipping = isset($billingAddressInput['use_for_shipping']) - ? (bool)$billingAddressInput['use_for_shipping'] : false; - $sameAsShipping = isset($billingAddressInput['same_as_shipping']) - ? (bool)$billingAddressInput['same_as_shipping'] : $sameAsShipping; - - $this->checkForInputExceptions($billingAddressInput); - - $addresses = $cart->getAllShippingAddresses(); - if ($sameAsShipping && count($addresses) > 1) { - throw new GraphQlInputException( - __('Using the "same_as_shipping" option with multishipping is not possible.') + if ($sameAsShipping) { + $this->validateCanUseShippingForBilling($cart); + $billingAddress = $this->quoteAddressFactory->createBasedOnShippingAddress($cart); + $useForShipping = false; + } elseif ($customerAddressId) { + $this->validateCanUseCustomerAddress($context); + $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( + (int)$customerAddressId, + (int)$context->getUserId() ); + } else { + $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); } - $billingAddress = $this->createBillingAddress($context, $customerAddressId, $addressInput); + if ($useForShipping) { + $this->validateCanUseBillingForShipping($cart); + } - $this->assignBillingAddressToCart->execute($cart, $billingAddress, $sameAsShipping); + $this->validateBillingAddress($billingAddress); + $this->assignBillingAddressToCart->execute($cart, $billingAddress, $useForShipping); } /** * Check for the input exceptions * - * @param array $billingAddressInput + * @param array|null $billingAddressInput * @throws GraphQlInputException */ private function checkForInputExceptions( @@ -92,10 +96,11 @@ private function checkForInputExceptions( ) { $customerAddressId = $billingAddressInput['customer_address_id'] ?? null; $addressInput = $billingAddressInput['address'] ?? null; + $sameAsShipping = $billingAddressInput['same_as_shipping'] ?? null; - if (null === $customerAddressId && null === $addressInput) { + if (null === $customerAddressId && null === $addressInput && empty($sameAsShipping)) { throw new GraphQlInputException( - __('The billing address must contain either "customer_address_id" or "address".') + __('The billing address must contain either "customer_address_id", "address", or "same_as_shipping".') ); } @@ -107,41 +112,79 @@ private function checkForInputExceptions( } /** - * Create billing address + * Validate that the quote is capable of using the shipping address as the billing address. * - * @param ContextInterface $context - * @param int|null $customerAddressId - * @param array $addressInput - * @return Address - * @throws GraphQlAuthorizationException + * @param CartInterface $quote * @throws GraphQlInputException - * @throws GraphQlNoSuchEntityException */ - private function createBillingAddress( - ContextInterface $context, - ?int $customerAddressId, - ?array $addressInput - ): Address { - if (null === $customerAddressId) { - $billingAddress = $this->quoteAddressFactory->createBasedOnInputData($addressInput); - } else { - if (false === $context->getExtensionAttributes()->getIsCustomer()) { - throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); - } + private function validateCanUseShippingForBilling(CartInterface $quote) + { + $shippingAddresses = $quote->getAllShippingAddresses(); - $billingAddress = $this->quoteAddressFactory->createBasedOnCustomerAddress( - (int)$customerAddressId, - (int)$context->getUserId() + if (count($shippingAddresses) > 1) { + throw new GraphQlInputException( + __('Could not use the "same_as_shipping" option, because multiple shipping addresses have been set.') ); } + + if (empty($shippingAddresses) || $shippingAddresses[0]->validate() !== true) { + throw new GraphQlInputException( + __('Could not use the "same_as_shipping" option, because the shipping address has not been set.') + ); + } + } + + /** + * Validate that the quote is capable of using the billing address as the shipping address. + * + * @param CartInterface $quote + * @throws GraphQlInputException + */ + private function validateCanUseBillingForShipping(CartInterface $quote) + { + $shippingAddresses = $quote->getAllShippingAddresses(); + + if (count($shippingAddresses) > 1) { + throw new GraphQlInputException( + __('Could not use the "use_for_shipping" option, because multiple shipping addresses have already been set.') + ); + } + } + + /** + * Validate that the currently logged-in customer is authorized to use a customer address id as the billing address. + * + * @param ContextInterface $context + * @throws GraphQlAuthorizationException + */ + private function validateCanUseCustomerAddress(ContextInterface $context) + { + if (false === $context->getExtensionAttributes()->getIsCustomer()) { + throw new GraphQlAuthorizationException(__('The current customer isn\'t authorized.')); + } + } + + /** + * Validate the billing address to be set on the cart. + * + * @param Address $billingAddress + * @return Address + * @throws GraphQlInputException + */ + private function validateBillingAddress(Address $billingAddress) + { $errors = $billingAddress->validate(); + if (true !== $errors) { - $e = new GraphQlInputException(__('Billing address errors')); + $e = new GraphQlInputException(__('An error occurred while processing the billing address.')); + foreach ($errors as $error) { $e->addError(new GraphQlInputException($error)); } + throw $e; } + return $billingAddress; } } diff --git a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls index 35d7cf082fc94..21688234dc16d 100644 --- a/app/code/Magento/QuoteGraphQl/etc/schema.graphqls +++ b/app/code/Magento/QuoteGraphQl/etc/schema.graphqls @@ -104,8 +104,8 @@ input SetBillingAddressOnCartInput { input BillingAddressInput { customer_address_id: Int address: CartAddressInput - use_for_shipping: Boolean @doc(description: "Deprecated: use `same_as_shipping` field instead") - same_as_shipping: Boolean @doc(description: "Set billing address same as shipping") + use_for_shipping: Boolean @doc(description: "Indicates whether to additionally set the shipping address based on the provided billing address") + same_as_shipping: Boolean @doc(description: "Indicates whether to set the billing address based on the existing shipping address on the cart") } input CartAddressInput { diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php index e2e9d809bf8ee..a1ad0763865a8 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Customer/SetBillingAddressOnCartTest.php @@ -75,6 +75,8 @@ protected function setUp(): void } /** + * Tests setting the billing address on a logged-in customer's cart by providing new address input information. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -101,7 +103,6 @@ public function testSetNewBillingAddress() country_code: "US" telephone: "88776655" } - same_as_shipping: true } } ) { @@ -120,20 +121,6 @@ public function testSetNewBillingAddress() } __typename } - shipping_addresses { - firstname - lastname - company - street - city - postcode - telephone - country { - code - label - } - __typename - } } } } @@ -144,14 +131,11 @@ public function testSetNewBillingAddress() $cartResponse = $response['setBillingAddressOnCart']['cart']; self::assertArrayHasKey('billing_address', $cartResponse); $billingAddressResponse = $cartResponse['billing_address']; - self::assertArrayHasKey('shipping_addresses', $cartResponse); - $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'ShippingCartAddress'); } /** - * Test case for deprecated `use_for_shipping` param. + * Tests that the "use_for_shipping" option sets the provided billing address for shipping as well. * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -229,6 +213,8 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } /** + * Tests setting the billing address on a logged-in customer's cart by providing a saved customer address. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -277,6 +263,8 @@ public function testSetBillingAddressFromAddressBook() } /** + * Tests that the billing_address output is of type BillingCartAddress. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -313,6 +301,8 @@ public function testVerifyBillingAddressType() } /** + * Tests that an error occurs when a non-existent customer_address_id is provided. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -348,6 +338,8 @@ public function testSetNotExistedBillingAddressFromAddressBook() } /** + * Tests that an error occurs when both a "customer_address_id" and "address" input are simultaneously provided. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_two_addresses.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php @@ -395,6 +387,8 @@ public function testSetNewBillingAddressAndFromAddressBookAtSameTime() } /** + * Tests that an error occurs when an address is not provided. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -406,30 +400,144 @@ public function testSetNewBillingAddressWithoutCustomerAddressIdAndAddress() $query = <<graphQlMutation($query, [], '', $this->getHeaderMap()); + } + + /** + * Tests that the "same_as_shipping" option uses the cart's existing shipping address for the billing address. + * + * Ignores the "customer_address_id" field as well as the "use_for_shipping" option when "same_as_shipping" is true. + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php + */ + public function testSetBillingAddressWithSameAsShipping() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<graphQlMutation($query, [], '', $this->getHeaderMap()); + self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); + $cartResponse = $response['setBillingAddressOnCart']['cart']; + + // Assert billing address has been set according to the cart's shipping address + self::assertArrayHasKey('billing_address', $cartResponse); + $billingAddressResponse = $cartResponse['billing_address']; + $this->assertNewAddressFieldsFromShippingAddress($billingAddressResponse); + + // Assert the shipping address is unchanged + self::assertArrayHasKey('shipping_addresses', $cartResponse); + $shippingAddressResponse = current($cartResponse['shipping_addresses']); + $this->assertNewAddressFieldsFromShippingAddress($shippingAddressResponse, 'ShippingCartAddress'); + } + + /** + * Tests that the "same_as_shipping" option cannot be used when a shipping address has not been set on the cart. + * + * @magentoApiDataFixture Magento/Customer/_files/customer.php + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetBillingAddressWithSameAsShippingWithoutShippingAddressOnCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<graphQlMutation($query, [], '', $this->getHeaderMap()); } /** + * Tests that the "same_as_shipping" option cannot be used when multi-shipping is applied. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -446,17 +554,6 @@ public function testSetNewBillingAddressWithSameAsShippingAndMultishipping() input: { cart_id: "$maskedQuoteId" billing_address: { - address: { - firstname: "test firstname" - lastname: "test lastname" - company: "test company" - street: ["test street 1", "test street 2"] - city: "test city" - region: "AZ" - postcode: "887766" - country_code: "US" - telephone: "88776655" - } same_as_shipping: true } } @@ -471,12 +568,14 @@ public function testSetNewBillingAddressWithSameAsShippingAndMultishipping() QUERY; self::expectExceptionMessage( - 'Using the "same_as_shipping" option with multishipping is not possible.' + 'Could not use the "same_as_shipping" option, because multiple shipping addresses have been set.' ); $this->graphQlMutation($query, [], '', $this->getHeaderMap()); } /** + * Tests that a logged-in customer cannot set the billing address on a guest cart. + * * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php @@ -514,6 +613,8 @@ public function testSetBillingAddressToGuestCart() } /** + * Tests that a logged-in customer cannot set the billing address on a cart they do not own. + * * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php @@ -549,6 +650,8 @@ public function testSetBillingAddressToAnotherCustomerCart() } /** + * Tests that a logged-in customer cannot use a saved customer address that is not their own. + * * _security * @magentoApiDataFixture Magento/Customer/_files/three_customers.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php @@ -584,6 +687,8 @@ public function testSetBillingAddressIfCustomerIsNotOwnerOfAddress() } /** + * Tests that an error occurs when attempting to set the billing address on a cart that does not exist. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/Customer/_files/customer_address.php */ @@ -615,6 +720,8 @@ public function testSetBillingAddressOnNonExistentCart() } /** + * Test that an error occurs when ommitting required fields in the address input. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -727,6 +834,8 @@ public function dataProviderSetWithoutRequiredParameters(): array } /** + * Tests that an error occurs when the street information exceeds the maximum number of allowed lines. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -769,6 +878,8 @@ public function testSetNewBillingAddressWithRedundantStreetLine() } /** + * Tests that setting a new billing address succeeds with a lower case "country_code". + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -827,6 +938,9 @@ public function testSetBillingAddressWithLowerCaseCountry() } /** + * Tests that setting the "save_in_address_book" option to true adds the newly set billing address to the + * customer's address book. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -894,6 +1008,9 @@ public function testSetNewBillingAddressWithSaveInAddressBook() } /** + * Tests that setting the "save_in_address_book" option to false does not add the newly set billing address + * to the customer's address book. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -961,6 +1078,8 @@ public function testSetNewBillingAddressWithNotSaveInAddressBook() } /** + * Tests that an error occurs when providing invalid address input. + * * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -1445,8 +1564,10 @@ public function testSetBillingAddressOnCartWithCorrectRegionId() * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'BillingCartAddress'): void - { + private function assertNewAddressFields( + array $addressResponse, + string $addressType = 'BillingCartAddress' + ): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], ['response_field' => 'lastname', 'expected_value' => 'test lastname'], @@ -1462,6 +1583,33 @@ private function assertNewAddressFields(array $addressResponse, string $addressT $this->assertResponseFields($addressResponse, $assertionMap); } + /** + * Verify that the fields for the specified quote address match the shipping address from the fixture. + * + * Useful for verifying scenarios with the "same_as_shipping" option. + * + * @param array $addressResponse + * @param string $addressType + */ + private function assertNewAddressFieldsFromShippingAddress( + array $addressResponse, + string $addressType = 'BillingCartAddress' + ): void { + $assertionMap = [ + ['response_field' => 'firstname', 'expected_value' => 'John'], + ['response_field' => 'lastname', 'expected_value' => 'Smith'], + ['response_field' => 'company', 'expected_value' => 'CompanyName'], + ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']], + ['response_field' => 'city', 'expected_value' => 'CityM'], + ['response_field' => 'postcode', 'expected_value' => '75477'], + ['response_field' => 'telephone', 'expected_value' => 3468676], + ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], + ['response_field' => '__typename', 'expected_value' => $addressType] + ]; + + $this->assertResponseFields($addressResponse, $assertionMap); + } + /** * Verify the all the whitelisted fields for a Address Object * @@ -1566,7 +1714,7 @@ public function testSetBillingAddressAndPlaceOrder() input: { cart_id: "$maskedQuoteId" billing_address: { - same_as_shipping: true + use_for_shipping: true address: { firstname: "test firstname" lastname: "test lastname" @@ -1649,7 +1797,7 @@ public function testSetBillingAddressWithDefaultValueOfSaveInAddressBookAndPlace input: { cart_id: "$maskedQuoteId" billing_address: { - same_as_shipping: true + use_for_shipping: true address: { firstname: "test firstname" lastname: "test lastname" diff --git a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php index d6780b311aff6..764fc3f573a2b 100644 --- a/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php +++ b/dev/tests/api-functional/testsuite/Magento/GraphQl/Quote/Guest/SetBillingAddressOnCartTest.php @@ -28,6 +28,8 @@ protected function setUp(): void } /** + * Tests setting the billing address on the cart by providing address input information. + * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php @@ -53,7 +55,6 @@ public function testSetNewBillingAddress() country_code: "US" telephone: "88776655" } - same_as_shipping: true } } ) { @@ -72,20 +73,6 @@ public function testSetNewBillingAddress() } __typename } - shipping_addresses { - firstname - lastname - company - street - city - postcode - telephone - country { - code - label - } - __typename - } } } } @@ -97,20 +84,17 @@ public function testSetNewBillingAddress() self::assertArrayHasKey('billing_address', $cartResponse); $billingAddressResponse = $cartResponse['billing_address']; $this->assertNewAddressFields($billingAddressResponse); - self::assertArrayHasKey('shipping_addresses', $cartResponse); - $shippingAddressResponse = current($cartResponse['shipping_addresses']); $this->assertNewAddressFields($billingAddressResponse); - $this->assertNewAddressFields($shippingAddressResponse, 'ShippingCartAddress'); } /** - * Test case for deprecated `use_for_shipping` param. + * Tests that the "use_for_shipping" option sets the provided billing address for shipping as well. * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewBillingAddressWithUseForShippingParameter() + public function testSetNewBillingAddressWithUseForShipping() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -181,6 +165,8 @@ public function testSetNewBillingAddressWithUseForShippingParameter() } /** + * Tests that a guest cannot set the billing address on a logged-in customer's cart. + * * _security * @magentoApiDataFixture Magento/Customer/_files/customer.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/customer/create_empty_cart.php @@ -225,6 +211,8 @@ public function testSetBillingAddressToCustomerCart() } /** + * Tests that a guest cannot use a logged-in customer's saved address as the billing address on their own cart. + * * _security * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php @@ -260,6 +248,7 @@ public function testSetBillingAddressFromAddressBook() } /** + * Tests that a guest cannot set the billing address on a cart that does not exist. */ public function testSetBillingAddressOnNonExistentCart() { @@ -299,11 +288,13 @@ public function testSetBillingAddressOnNonExistentCart() } /** + * Tests that an error is thrown when the street information exceeds the maximum number of allowed lines. + * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewBillingAddressWithoutCustomerAddressIdAndAddress() + public function testSetBillingAddressRedundantStreetLine() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -313,32 +304,41 @@ public function testSetNewBillingAddressWithoutCustomerAddressIdAndAddress() input: { cart_id: "$maskedQuoteId" billing_address: { - same_as_shipping: true + address: { + firstname: "test firstname" + lastname: "test lastname" + company: "test company" + street: ["test street 1", "test street 2", "test street 3"] + city: "test city" + region: "AL" + postcode: "887766" + country_code: "US" + telephone: "88776655" + } } } ) { cart { billing_address { - city + firstname } } } } QUERY; - self::expectExceptionMessage( - 'The billing address must contain either "customer_address_id" or "address".' - ); + self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); $this->graphQlMutation($query); } /** + * Tests that setting a new billing address succeeds with a lower case "country_code". + * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php - * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_multishipping_with_two_shipping_addresses.php */ - public function testSetNewBillingAddressWithSameAsShippingAndMultishipping() + public function testSetBillingAddressWithLowerCaseCountry() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -356,34 +356,48 @@ public function testSetNewBillingAddressWithSameAsShippingAndMultishipping() city: "test city" region: "AL" postcode: "887766" - country_code: "US" + country_code: "us" telephone: "88776655" } - same_as_shipping: true } } ) { cart { billing_address { + firstname + lastname + company + street city + postcode + telephone + country { + code + label + } + __typename } } } } QUERY; + $response = $this->graphQlMutation($query); - self::expectExceptionMessage( - 'Using the "same_as_shipping" option with multishipping is not possible.' - ); - $this->graphQlMutation($query); + self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); + $cartResponse = $response['setBillingAddressOnCart']['cart']; + self::assertArrayHasKey('billing_address', $cartResponse); + $billingAddressResponse = $cartResponse['billing_address']; + $this->assertNewAddressFields($billingAddressResponse); } /** + * Tests that an error is thrown when a billing address is not provided. + * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php */ - public function testSetNewBillingAddressRedundantStreetLine() + public function testSetBillingAddressWithoutProvidingAddress() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -393,39 +407,36 @@ public function testSetNewBillingAddressRedundantStreetLine() input: { cart_id: "$maskedQuoteId" billing_address: { - address: { - firstname: "test firstname" - lastname: "test lastname" - company: "test company" - street: ["test street 1", "test street 2", "test street 3"] - city: "test city" - region: "AL" - postcode: "887766" - country_code: "US" - telephone: "88776655" - } + use_for_shipping: true } } ) { cart { billing_address { - firstname + city } } } } QUERY; - self::expectExceptionMessage('"Street Address" cannot contain more than 2 lines.'); + self::expectExceptionMessage( + 'The billing address must contain either "customer_address_id", "address", or "same_as_shipping".' + ); $this->graphQlMutation($query); } /** + * Tests that the "same_as_shipping" option uses the cart's existing shipping address for the billing address. + * + * Ignores any address input as well as the "use_for_shipping" option when "same_as_shipping" is true. + * * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_new_shipping_address.php */ - public function testSetBillingAddressWithLowerCaseCountry() + public function testSetBillingAddressWithSameAsShipping() { $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); @@ -435,6 +446,8 @@ public function testSetBillingAddressWithLowerCaseCountry() input: { cart_id: "$maskedQuoteId" billing_address: { + same_as_shipping: true + use_for_shipping: true address: { firstname: "test firstname" lastname: "test lastname" @@ -443,7 +456,7 @@ public function testSetBillingAddressWithLowerCaseCountry() city: "test city" region: "AL" postcode: "887766" - country_code: "us" + country_code: "US" telephone: "88776655" } } @@ -464,6 +477,20 @@ public function testSetBillingAddressWithLowerCaseCountry() } __typename } + shipping_addresses { + firstname + lastname + company + street + city + postcode + telephone + country { + code + label + } + __typename + } } } } @@ -472,19 +499,100 @@ public function testSetBillingAddressWithLowerCaseCountry() self::assertArrayHasKey('cart', $response['setBillingAddressOnCart']); $cartResponse = $response['setBillingAddressOnCart']['cart']; + + // Assert billing address has been set according to the cart's shipping address self::assertArrayHasKey('billing_address', $cartResponse); $billingAddressResponse = $cartResponse['billing_address']; - $this->assertNewAddressFields($billingAddressResponse); + $this->assertNewAddressFieldsFromShippingAddress($billingAddressResponse); + + // Assert the shipping address is unchanged + self::assertArrayHasKey('shipping_addresses', $cartResponse); + $shippingAddressResponse = current($cartResponse['shipping_addresses']); + $this->assertNewAddressFieldsFromShippingAddress($shippingAddressResponse, 'ShippingCartAddress'); } /** - * Verify the all the whitelisted fields for a New Address Object + * Tests that the "same_as_shipping" option cannot be used when a shipping address has not been set on the cart. + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + */ + public function testSetBillingAddressWithSameAsShippingWithoutShippingAddressOnCart() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<graphQlMutation($query); + } + + /** + * Tests that the "same_as_shipping" option cannot be used when multi-shipping is applied. + * + * @magentoApiDataFixture Magento/GraphQl/Catalog/_files/simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/guest/create_empty_cart.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/add_simple_product.php + * @magentoApiDataFixture Magento/GraphQl/Quote/_files/set_multishipping_with_two_shipping_addresses.php + */ + public function testSetBillingAddressWithSameAsShippingAndMultishipping() + { + $maskedQuoteId = $this->getMaskedQuoteIdByReservedOrderId->execute('test_quote'); + + $query = <<graphQlMutation($query); + } + + /** + * Verify all the whitelisted fields for a New Address Object * * @param array $addressResponse * @param string $addressType */ - private function assertNewAddressFields(array $addressResponse, string $addressType = 'BillingCartAddress'): void - { + private function assertNewAddressFields( + array $addressResponse, + string $addressType = 'BillingCartAddress' + ): void { $assertionMap = [ ['response_field' => 'firstname', 'expected_value' => 'test firstname'], ['response_field' => 'lastname', 'expected_value' => 'test lastname'], @@ -499,4 +607,31 @@ private function assertNewAddressFields(array $addressResponse, string $addressT $this->assertResponseFields($addressResponse, $assertionMap); } + + /** + * Verify that the fields for the specified quote address match the shipping address from the fixture. + * + * Useful for verifying scenarios with the "same_as_shipping" option. + * + * @param array $addressResponse + * @param string $addressType + */ + private function assertNewAddressFieldsFromShippingAddress( + array $addressResponse, + string $addressType = 'BillingCartAddress' + ): void { + $assertionMap = [ + ['response_field' => 'firstname', 'expected_value' => 'John'], + ['response_field' => 'lastname', 'expected_value' => 'Smith'], + ['response_field' => 'company', 'expected_value' => 'CompanyName'], + ['response_field' => 'street', 'expected_value' => [0 => 'Green str, 67']], + ['response_field' => 'city', 'expected_value' => 'CityM'], + ['response_field' => 'postcode', 'expected_value' => '75477'], + ['response_field' => 'telephone', 'expected_value' => 3468676], + ['response_field' => 'country', 'expected_value' => ['code' => 'US', 'label' => 'US']], + ['response_field' => '__typename', 'expected_value' => $addressType] + ]; + + $this->assertResponseFields($addressResponse, $assertionMap); + } }