Skip to content

Commit 2fc6f36

Browse files
committed
AC-14049: Missing Billing Address Error in Admin Dashboard When Creating Order via REST API with Only Payment Information
1 parent 3920c2b commit 2fc6f36

File tree

4 files changed

+574
-0
lines changed

4 files changed

+574
-0
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Plugin\Model;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Sales\Api\Data\OrderInterface;
12+
use Magento\Sales\Model\OrderRepository;
13+
14+
/**
15+
* Plugin for OrderRepository to add comprehensive order validation
16+
*/
17+
class OrderRepositoryPlugin
18+
{
19+
/**
20+
* Validate order before save
21+
*
22+
* @param OrderRepository $subject
23+
* @param OrderInterface $entity
24+
* @return array
25+
* @throws LocalizedException
26+
*/
27+
public function beforeSave(OrderRepository $subject, OrderInterface $entity): array
28+
{
29+
$this->validateBillingAddress($entity);
30+
$this->validateOrderItems($entity);
31+
return [$entity];
32+
}
33+
34+
/**
35+
* Validate billing address exists and has required fields
36+
*
37+
* @param OrderInterface $entity
38+
* @throws LocalizedException
39+
*/
40+
private function validateBillingAddress(OrderInterface $entity): void
41+
{
42+
$billingAddress = $entity->getBillingAddress();
43+
if (!$billingAddress ||
44+
!$billingAddress->getFirstname() ||
45+
!$billingAddress->getLastname()) {
46+
throw new LocalizedException(__('Please provide billing address for the order.'));
47+
}
48+
}
49+
50+
/**
51+
* Validate order has items
52+
*
53+
* @param OrderInterface $entity
54+
* @throws LocalizedException
55+
*/
56+
private function validateOrderItems(OrderInterface $entity): void
57+
{
58+
$items = $entity->getItems();
59+
60+
if (!$items || count($items) === 0) {
61+
throw new LocalizedException(__('Please specify order items.'));
62+
}
63+
64+
// Validate each item has required data
65+
foreach ($items as $item) {
66+
if (!$item->getProductId() || !$item->getSku()) {
67+
throw new LocalizedException(__('Order items must have valid product information.'));
68+
}
69+
}
70+
}
71+
}
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
<?php
2+
/**
3+
* Copyright 2025 Adobe
4+
* All Rights Reserved.
5+
*/
6+
declare(strict_types=1);
7+
8+
namespace Magento\Sales\Test\Unit\Plugin\Model;
9+
10+
use Magento\Framework\Exception\LocalizedException;
11+
use Magento\Sales\Api\Data\OrderAddressInterface;
12+
use Magento\Sales\Api\Data\OrderInterface;
13+
use Magento\Sales\Api\Data\OrderItemInterface;
14+
use Magento\Sales\Model\OrderRepository;
15+
use PHPUnit\Framework\MockObject\MockObject;
16+
use PHPUnit\Framework\TestCase;
17+
use Magento\Sales\Plugin\Model\OrderRepositoryPlugin;
18+
19+
/**
20+
* Unit test for OrderRepositoryPlugin
21+
*/
22+
class OrderRepositoryPluginTest extends TestCase
23+
{
24+
/**
25+
* @var OrderRepositoryPlugin
26+
*/
27+
private $plugin;
28+
29+
/**
30+
* @var OrderRepository|MockObject
31+
*/
32+
private $orderRepository;
33+
34+
/**
35+
* @var OrderInterface|MockObject
36+
*/
37+
private $order;
38+
39+
/**
40+
* @var OrderAddressInterface|MockObject
41+
*/
42+
private $billingAddress;
43+
44+
/**
45+
* @var OrderItemInterface|MockObject
46+
*/
47+
private $orderItem;
48+
49+
protected function setUp(): void
50+
{
51+
$this->plugin = new OrderRepositoryPlugin();
52+
$this->orderRepository = $this->createMock(OrderRepository::class);
53+
$this->order = $this->createMock(OrderInterface::class);
54+
$this->billingAddress = $this->createMock(OrderAddressInterface::class);
55+
$this->orderItem = $this->createMock(OrderItemInterface::class);
56+
}
57+
58+
/**
59+
* Test successful validation with valid order
60+
*/
61+
public function testBeforeSaveWithValidOrder(): void
62+
{
63+
// Setup billing address
64+
$this->billingAddress->method('getFirstname')->willReturn('John');
65+
$this->billingAddress->method('getLastname')->willReturn('Doe');
66+
$this->billingAddress->method('getStreet')->willReturn(['123 Main St']);
67+
$this->billingAddress->method('getCity')->willReturn('City');
68+
$this->billingAddress->method('getCountryId')->willReturn('US');
69+
70+
// Setup order item
71+
$this->orderItem->method('getProductId')->willReturn(1);
72+
$this->orderItem->method('getSku')->willReturn('simple-product');
73+
$this->orderItem->method('getQtyOrdered')->willReturn(2);
74+
75+
// Setup order
76+
$this->order->method('getBillingAddress')->willReturn($this->billingAddress);
77+
$this->order->method('getItems')->willReturn([$this->orderItem]);
78+
79+
$result = $this->plugin->beforeSave($this->orderRepository, $this->order);
80+
81+
$this->assertEquals([$this->order], $result);
82+
}
83+
84+
/**
85+
* Test validation fails with missing billing address
86+
*/
87+
public function testBeforeSaveWithMissingBillingAddress(): void
88+
{
89+
$this->order->method('getBillingAddress')->willReturn(null);
90+
91+
$this->expectException(LocalizedException::class);
92+
$this->expectExceptionMessage('Please provide billing address for the order.');
93+
94+
$this->plugin->beforeSave($this->orderRepository, $this->order);
95+
}
96+
97+
/**
98+
* Test validation fails with incomplete billing address
99+
*/
100+
public function testBeforeSaveWithIncompleteBillingAddress(): void
101+
{
102+
$this->billingAddress->method('getFirstname')->willReturn('');
103+
$this->billingAddress->method('getLastname')->willReturn('Doe');
104+
105+
$this->order->method('getBillingAddress')->willReturn($this->billingAddress);
106+
107+
$this->expectException(LocalizedException::class);
108+
$this->expectExceptionMessage('Please provide billing address for the order.');
109+
110+
$this->plugin->beforeSave($this->orderRepository, $this->order);
111+
}
112+
113+
/**
114+
* Test validation fails with missing order items
115+
*/
116+
public function testBeforeSaveWithMissingItems(): void
117+
{
118+
// Setup valid billing address
119+
$this->billingAddress->method('getFirstname')->willReturn('John');
120+
$this->billingAddress->method('getLastname')->willReturn('Doe');
121+
$this->billingAddress->method('getStreet')->willReturn(['123 Main St']);
122+
$this->billingAddress->method('getCity')->willReturn('City');
123+
$this->billingAddress->method('getCountryId')->willReturn('US');
124+
125+
$this->order->method('getBillingAddress')->willReturn($this->billingAddress);
126+
$this->order->method('getItems')->willReturn([]);
127+
128+
$this->expectException(LocalizedException::class);
129+
$this->expectExceptionMessage('Please specify order items.');
130+
131+
$this->plugin->beforeSave($this->orderRepository, $this->order);
132+
}
133+
}

app/code/Magento/Sales/etc/webapi_rest/di.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,9 @@
2626
<plugin name="add_existing_product_options"
2727
type="Magento\Sales\Plugin\Model\ResourceModel\Order\Relation\AddExistingItemProductOptions"/>
2828
</type>
29+
<type name="Magento\Sales\Model\OrderRepository">
30+
<plugin name="validate_billing_address_on_save"
31+
type="Magento\Sales\Plugin\Model\OrderRepositoryPlugin"
32+
sortOrder="10"/>
33+
</type>
2934
</config>

0 commit comments

Comments
 (0)