From f6eeed733c5b58304564fdcb2b5bccf168b7d4a9 Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Tue, 2 Jun 2026 08:05:45 +0300 Subject: [PATCH 1/8] KUSTOM-78 Skip sending shipping info when no tracking is provided --- Gateway/Command/Capture.php | 20 +- Model/Api/OrderManagement.php | 4 +- .../Gateway/Command/CaptureTest.php | 379 ++++++++++++++++++ Test/Integration/Stub/StubRequest.php | 65 +++ .../_files/invoice_with_klarna_payment.php | 62 +++ .../_files/order_with_klarna_payment.php | 150 +++++++ .../order_with_klarna_payment_rollback.php | 69 ++++ Test/Unit/Model/Api/OrderManagementTest.php | 189 +++++++-- 8 files changed, 883 insertions(+), 55 deletions(-) create mode 100644 Test/Integration/Gateway/Command/CaptureTest.php create mode 100644 Test/Integration/Stub/StubRequest.php create mode 100644 Test/Integration/_files/invoice_with_klarna_payment.php create mode 100644 Test/Integration/_files/order_with_klarna_payment.php create mode 100644 Test/Integration/_files/order_with_klarna_payment_rollback.php diff --git a/Gateway/Command/Capture.php b/Gateway/Command/Capture.php index 5a52f97..0ab98f3 100644 --- a/Gateway/Command/Capture.php +++ b/Gateway/Command/Capture.php @@ -97,8 +97,10 @@ public function execute(array $commandSubject) $this->getValidator()::ACTION_TYPE_CAPTURE ); + $hasTracking = !empty($requestData['tracking']); + // if tracking info is invalid, stop capture - if (!$this->isTrackingInfoValid($requestData['tracking'] ?? null)) { + if ($hasTracking && !$this->isTrackingInfoValid($requestData['tracking'])) { return null; } @@ -122,7 +124,7 @@ public function execute(array $commandSubject) $requestData = $requestData->toArray(); } - if ($this->isProcessingShipment($requestData, $response)) { + if ($hasTracking && $this->isProcessingShipment($requestData, $response)) { $this->addShippingInfoToCapture( $response->getCaptureId(), $klarnaOrder->getReservationId(), @@ -166,13 +168,11 @@ private function addShippingInfoToCapture($captureId, $klarnaOrderId, $trackingD * @param DataObject $response * @return bool */ - private function isProcessingShipment(array $requestData, DataObject - $response): bool + private function isProcessingShipment(array $requestData, DataObject $response): bool { if (isset($requestData['invoice']['do_shipment']) && $requestData['invoice']['do_shipment'] === "1" - && $response->getCaptureId() - && $this->isTrackingInfoValid($requestData['tracking'] ?? null)) { + && $response->getCaptureId()) { return true; } return false; @@ -181,15 +181,11 @@ private function isProcessingShipment(array $requestData, DataObject /** * Validate tracking info * - * @param array|null $trackingInformation + * @param array $trackingInformation * @return bool */ - private function isTrackingInfoValid(?array $trackingInformation): bool + private function isTrackingInfoValid(array $trackingInformation): bool { - if ($trackingInformation === null) { - return true; - } - foreach ((array) $trackingInformation as $info) { if (empty($info['carrier_code']) || empty($info['title']) diff --git a/Model/Api/OrderManagement.php b/Model/Api/OrderManagement.php index 26a37f8..6a99404 100644 --- a/Model/Api/OrderManagement.php +++ b/Model/Api/OrderManagement.php @@ -308,7 +308,9 @@ private function prepareShippingInfo(array $shippingInfo) /** * Get Api Accepted shipping method,For merchant who implement this feature * Create Plugin to overwrite this default method code - * Allowed values matches (PickUpStore|Home|BoxReg|BoxUnreg|PickUpPoint|Own) + * Allowed values matches (PickUpStore|Home|BoxReg|BoxUnreg|PickUpPoint|Own|Postal|DHLPackstation|Digital + * |Undefined|PickUpWarehouse|ClickCollect|PalletDelivery) + * Doc: https://docs.kustom.co/contents/api/order-management/orders/appendordershippinginfo * * @param array $shipping * @return string diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php new file mode 100644 index 0000000..7d4868f --- /dev/null +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -0,0 +1,379 @@ +paymentDataObjectFactory = $objectManager->get(PaymentDataObjectFactory::class); + $this->orderRepository = $objectManager->get(OrderRepositoryInterface::class); + $this->searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); + $this->invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); + + $this->setupMocks($objectManager); + + $this->captureCommand = $objectManager->create( + Capture::class, + [ + 'omFactory' => $this->mockApiFactory, + 'validator' => $this->mockValidator, + 'request' => $this->stubRequest + ] + ); + } + + /** @noinspection ObjectManagerInspection */ + private function setupMocks(ObjectManagerInterface $objectManager): void + { + $this->mockOrderManagement = $this->createMock(OrderManagement::class); + $this->mockValidator = $this->createMock(Validator::class); + $this->stubRequest = $objectManager->create(StubRequest::class); + $this->mockApiFactory = $this->createMock(Factory::class); + + $this->mockApiFactory->method('createOmApi')->willReturn($this->mockOrderManagement); + } + + /** + * Test capture with tracking info present but shipment not processed. + * Scenario: Admin adds tracking to invoice after separate shipment or manual tracking entry. + * Verifies tracking presence alone doesn't trigger shipping API without do_shipment flag. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteCaptureWithValidTrackingInfoButNoShipment(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $this->mockOrderManagement->expects($this->never()) + ->method('addShippingInfo'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + } + + /** + * Test capture with valid tracking info and do_shipment flag set. + * Scenario: Admin creates invoice with shipment and provides full carrier tracking details. + * Verifies capture proceeds and shipping info is sent to Klarna with a success comment on the invoice. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteCaptureWithShipmentProcessing(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $shippingResponse = new DataObject([ + 'is_successful' => true, + ]); + + $this->mockOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->with( + self::TEST_RESERVATION_ID, + self::TEST_CAPTURE_ID, + $this->isType('array') + ) + ->willReturn($shippingResponse); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00 + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $this->assertInvoiceHasComment($reloadedInvoice, 'Shipping info sent to Klarna API'); + } + + /** + * Test capture returns early when tracking info is present but missing required carrier_code. + * Scenario: Incomplete tracking data submitted — tracking array exists but carrier_code is absent. + * Verifies isTrackingInfoValid() rejects the data and capture is never called. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithInvalidTrackingInfoReturnsEarly(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + + $this->stubRequest->setPostData([ + 'tracking' => [ + [ + 'title' => 'UPS', + 'number' => '123', + ] + ] + ]); + + $this->mockOrderManagement->expects($this->never()) + ->method('capture'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + } + + /** + * Test capture with do_shipment flag but empty tracking array skips shipping info call. + * Scenario: do_shipment is set but no tracking entries were provided (e.g., merchant forgot to add them). + * Verifies the $hasTracking guard prevents addShippingInfo from being called when tracking is empty. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithShipmentButEmptyTrackingSkipsShippingInfo(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [], + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $this->mockOrderManagement->expects($this->never()) + ->method('addShippingInfo'); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $comments = $reloadedInvoice->getCommentsCollection(reload: true); + $this->assertCount(0, $comments, 'No comments should be added when tracking is empty'); + } + + /** + * Test capture with shipment processing when the Klarna shipping API returns a failure response. + * Scenario: Valid tracking info and do_shipment flag set, but Klarna API rejects the shipping info. + * Verifies each error message from the API response is recorded as a comment on the invoice. + * + * @magentoDataFixture Klarna_Backend::Test/Integration/_files/invoice_with_klarna_payment.php + * @magentoDbIsolation enabled + * @magentoAppIsolation enabled + */ + public function testExecuteWithShipmentProcessingApiFailure(): void + { + $invoice = $this->getInvoiceByOrderIncrementId(self::TEST_ORDER_INCREMENT_ID); + $order = $invoice->getOrder(); + $payment = $order->getPayment(); + $payment->setData('invoice', $invoice); + + $this->stubRequest->setPostData([ + 'invoice' => ['do_shipment' => '1'], + 'tracking' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ] + ] + ]); + + $this->mockOrderManagement->method('isFullyCaptured')->willReturn(false); + + $captureResponse = new DataObject(['capture_id' => self::TEST_CAPTURE_ID]); + $this->mockOrderManagement->expects($this->once()) + ->method('capture') + ->willReturn($captureResponse); + + $shippingResponse = new DataObject([ + 'is_successful' => false, + 'error_messages' => ['Invalid tracking number', 'Carrier not supported'], + ]); + + $this->mockOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->willReturn($shippingResponse); + + $paymentDataObject = $this->paymentDataObjectFactory->create($payment); + + $this->captureCommand->execute([ + 'payment' => $paymentDataObject, + 'amount' => 100.00, + ]); + + $this->invoiceRepository->save($invoice); + $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); + $this->assertInvoiceHasComment($reloadedInvoice, 'Invalid tracking number'); + $this->assertInvoiceHasComment($reloadedInvoice, 'Carrier not supported'); + } + + /** + * Get invoice by order increment ID + * + * @param string $incrementId + * + * @return \Magento\Sales\Api\Data\InvoiceInterface + */ + private function getInvoiceByOrderIncrementId(string $incrementId): InvoiceInterface + { + /** @var \Magento\Sales\Model\Order $order */ + $searchCriteria = $this->searchCriteriaBuilder->addFilter('increment_id', $incrementId)->create(); + $order = $this->orderRepository->getList($searchCriteria)->getFirstItem(); + + if (!$order->getId()) { + throw new \RuntimeException("Order with increment ID {$incrementId} not found"); + } + + // Force reload of invoice collection + $order = $this->orderRepository->get($order->getId()); + $invoices = $order->getInvoiceCollection(); + + $this->assertGreaterThan( + 0, + $invoices->getSize(), + sprintf( + 'No invoice found for order %s. Order state: %s, can invoice: %s', + $incrementId, + $order->getState(), + $order->canInvoice() ? 'yes' : 'no' + ) + ); + + return $invoices->getFirstItem(); + } + + /** + * Assert that invoice has specific comment + * + * @param \Magento\Sales\Api\Data\InvoiceInterface $invoice + * @param string $commentText + */ + private function assertInvoiceHasComment(InvoiceInterface $invoice, string $commentText): void + { + // Use getCommentsCollection to load from database + $comments = $invoice->getCommentsCollection(true); // true = reload + $found = false; + $existingComments = []; + foreach ($comments as $comment) { + $existingComments[] = $comment->getComment(); + if (\str_contains($comment->getComment(), $commentText)) { + $found = true; + break; + } + } + $this->assertTrue( + $found, + sprintf( + "Invoice comment not found: '%s'. Existing comments: %s", + $commentText, + empty($existingComments) ? 'none' : implode('; ', $existingComments) + ) + ); + } +} diff --git a/Test/Integration/Stub/StubRequest.php b/Test/Integration/Stub/StubRequest.php new file mode 100644 index 0000000..843dcf0 --- /dev/null +++ b/Test/Integration/Stub/StubRequest.php @@ -0,0 +1,65 @@ +create(StubRequest::class); + * $stubRequest->setPostData(['key' => 'value']); + * $value = $stubRequest->getPost('key'); // Returns 'value' + * ``` + */ +class StubRequest extends HttpRequest +{ + /** + * @var array + */ + private array $postData = []; + + /** + * Set POST data for testing + * + * @param array|Parameters $data POST data as array or Parameters object + * @return void + */ + public function setPostData($data): void + { + if ($data instanceof Parameters) { + $this->postData = $data->toArray(); + } else { + $this->postData = $data; + } + } + + /** + * Get POST data + * + * @param string|null $name Parameter name or null to get all data + * @param mixed $default Default value if parameter not found + * @return mixed + */ + public function getPost($name = null, $default = null) + { + if ($name === null) { + return $this->postData; + } + return $this->postData[$name] ?? $default; + } +} diff --git a/Test/Integration/_files/invoice_with_klarna_payment.php b/Test/Integration/_files/invoice_with_klarna_payment.php new file mode 100644 index 0000000..3082df2 --- /dev/null +++ b/Test/Integration/_files/invoice_with_klarna_payment.php @@ -0,0 +1,62 @@ +requireDataFixture('Klarna_Backend::Test/Integration/_files/order_with_klarna_payment.php'); + +$objectManager = Bootstrap::getObjectManager(); + +/** @var Order $order */ +$order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); + +if (!$order->getId()) { + throw new \RuntimeException('Order with increment ID 100000001 not found. Make sure order_with_klarna_payment.php fixture runs successfully.'); +} + +// Check if order can be invoiced +if (!$order->canInvoice()) { + throw new \RuntimeException('Order cannot be invoiced. Order state: ' . $order->getState()); +} + +$orderService = $objectManager->create(InvoiceManagementInterface::class); +$invoice = $orderService->prepareInvoice($order); + +if (!$invoice->getTotalQty()) { + throw new \RuntimeException('Cannot create invoice with zero quantity.'); +} + +$invoice->setRequestedCaptureCase(\Magento\Sales\Model\Order\Invoice::CAPTURE_OFFLINE); +$invoice->register(); + +$order->setIsInProcess(true); + +$transactionSave = $objectManager->create(\Magento\Framework\DB\Transaction::class); +$transactionSave + ->addObject($invoice) + ->addObject($order) + ->save(); diff --git a/Test/Integration/_files/order_with_klarna_payment.php b/Test/Integration/_files/order_with_klarna_payment.php new file mode 100644 index 0000000..9c5bbd2 --- /dev/null +++ b/Test/Integration/_files/order_with_klarna_payment.php @@ -0,0 +1,150 @@ +get(ProductRepositoryInterface::class); + + $product = $objectManager->create(Product::class); + $product->setTypeId(Type::TYPE_SIMPLE) + ->setAttributeSetId(4) + ->setWebsiteIds([1]) + ->setName('Simple Test Product') + ->setSku('simple-test-product-klarna') + ->setPrice(10) + ->setWeight(1) + ->setVisibility(Visibility::VISIBILITY_BOTH) + ->setStatus(Status::STATUS_ENABLED) + ->setStockData([ + 'use_config_manage_stock' => 1, + 'qty' => 100, + 'is_qty_decimal' => 0, + 'is_in_stock' => 1 + ]); + $productRepository->save($product); + + // Create order directly (bypass quote to avoid payment method issues) + /** @var Order $order */ + $order = $objectManager->create(Order::class); + + $storeManager = $objectManager->get(StoreManagerInterface::class); + $store = $storeManager->getStore(); + + // Set order data + $order->setIncrementId('100000001') + ->setState(Order::STATE_PROCESSING) + ->setStatus($order->getConfig()->getStateDefaultStatus(Order::STATE_PROCESSING)) + ->setCustomerIsGuest(true) + ->setCustomerEmail('john.doe.klarna@example.com') + ->setCustomerFirstname('John') + ->setCustomerLastname('Doe') + ->setStoreId($store->getId()) + ->setEmailSent(0) + ->setBaseCurrencyCode('USD') + ->setStoreCurrencyCode('USD') + ->setOrderCurrencyCode('USD'); + + // Set billing address + $billingAddress = $objectManager->create(OrderAddress::class); + $billingAddress->setFirstname('John') + ->setLastname('Doe') + ->setStreet(['123 Test Street']) + ->setCity('Test City') + ->setPostcode('12345') + ->setCountryId('US') + ->setRegionId(1) + ->setTelephone('555-1234') + ->setAddressType('billing'); + $order->setBillingAddress($billingAddress); + + // Set shipping address + $shippingAddress = $objectManager->create(OrderAddress::class); + $shippingAddress->setFirstname('John') + ->setLastname('Doe') + ->setStreet(['123 Test Street']) + ->setCity('Test City') + ->setPostcode('12345') + ->setCountryId('US') + ->setRegionId(1) + ->setTelephone('555-1234') + ->setAddressType('shipping'); + $order->setShippingAddress($shippingAddress); + + // Set payment - use checkmo (always available) to simulate Klarna + $payment = $objectManager->create(Payment::class); + $payment->setMethod('checkmo'); + $order->setPayment($payment); + + // Add order item + $orderItem = $objectManager->create(OrderItem::class); + $orderItem->setProductId($product->getId()) + ->setQtyOrdered(1) + ->setBasePrice($product->getPrice()) + ->setPrice($product->getPrice()) + ->setRowTotal($product->getPrice()) + ->setBaseRowTotal($product->getPrice()) + ->setProductType(Type::TYPE_SIMPLE) + ->setName($product->getName()) + ->setSku($product->getSku()); + $order->addItem($orderItem); + + // Set order totals + $shippingAmount = 5.00; + $order->setSubtotal($product->getPrice()) + ->setBaseSubtotal($product->getPrice()) + ->setGrandTotal($product->getPrice() + $shippingAmount) + ->setBaseGrandTotal($product->getPrice() + $shippingAmount) + ->setShippingAmount($shippingAmount) + ->setBaseShippingAmount($shippingAmount) + ->setShippingDescription('Flat Rate - Fixed'); + $order->save(); + + // Create Klarna order entry + $klarnaOrderFactory = $objectManager->get(KlarnaOrderFactory::class); + $klarnaOrderRepository = $objectManager->get(KlarnaOrderRepositoryInterface::class); + $klarnaOrder = $klarnaOrderFactory->create(); + $klarnaOrder->setOrderId((int)$order->getEntityId()) + ->setReservationId('test-reservation-id-12345') + ->setSessionId('test-session-id-67890') + ->setKlarnaOrderId('test-klarna-order-id-abcde'); + $klarnaOrderRepository->save($klarnaOrder); +} catch (\Exception $e) { + throw new \RuntimeException( + 'Failed to create order fixture: ' . $e->getMessage() . "\n" . $e->getTraceAsString() + ); +} diff --git a/Test/Integration/_files/order_with_klarna_payment_rollback.php b/Test/Integration/_files/order_with_klarna_payment_rollback.php new file mode 100644 index 0000000..c850dcf --- /dev/null +++ b/Test/Integration/_files/order_with_klarna_payment_rollback.php @@ -0,0 +1,69 @@ +get(Registry::class); +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', true); + +// Delete order +try { + /** @var \Magento\Sales\Model\Order $order */ + $order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); + + if ($order->getId()) { + $klarnaOrderFactory = $objectManager->get(KlarnaOrderFactory::class); + $klarnaOrderResource = $objectManager->get(KlarnaOrderResource::class); + $klarnaOrder = $klarnaOrderFactory->create(); + $klarnaOrderResource->load($klarnaOrder, $order->getEntityId(), 'order_id'); + + if ($klarnaOrder->getId()) { + $klarnaOrderResource->delete($klarnaOrder); + } + + $order->delete(); + } +} catch (\Exception $e) { + // Order already deleted +} + +// Delete product +try { + $productRepository = $objectManager->get(ProductRepositoryInterface::class); + $product = $productRepository->get('simple-test-product-klarna'); + $productRepository->delete($product); +} catch (\Exception $e) { + // Product already deleted +} + +$registry->unregister('isSecureArea'); +$registry->register('isSecureArea', false); diff --git a/Test/Unit/Model/Api/OrderManagementTest.php b/Test/Unit/Model/Api/OrderManagementTest.php index 27c4250..24e7bee 100644 --- a/Test/Unit/Model/Api/OrderManagementTest.php +++ b/Test/Unit/Model/Api/OrderManagementTest.php @@ -5,12 +5,18 @@ * For the full copyright and license information, please view the NOTICE * and LICENSE files that were distributed with this source code. */ +declare(strict_types=1); namespace Klarna\Backend\Test\Model\Api; +use Klarna\AdminSettings\Model\Configurations\Api as KlarnaConfigurationsApi; +use Klarna\Backend\Model\Api\Builder as KlarnaApiBuilder; use Klarna\Backend\Model\Api\OrderManagement as ApiOrderManagement; use Klarna\Backend\Model\Api\Rest\Service\Ordermanagement; use Klarna\Orderlines\Model\Container\Parameter; +use Magento\Framework\DataObject; +use PHPUnit\Framework\Attributes\DataProvider; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; /** @@ -18,63 +24,162 @@ */ class OrderManagementTest extends TestCase { - /** - * @var Parameter|\PHPUnit\Framework\MockObject\MockObject - */ - private $mockParameter; - /** @var Ordermanagement */ - private $model; - - /** @var Ordermanagement | PHPUnit_Framework_MockObject_MockObject */ - private $mockOrderManagement; - - /** @var \Klarna\Base\Helper\KlarnaConfig | PHPUnit_Framework_MockObject_MockObject */ - private $mockKlarnaConfig; - - /** @var \Klarna\Base\Helper\DataConverter | PHPUnit_Framework_MockObject_MockObject */ - private $mockDataConverter; - - /** @var \Magento\Framework\DataObjectFactory | PHPUnit_Framework_MockObject_MockObject */ - private $mockDataObjectFactory; - - public function testGetKlarnaShippingMethod() - { - self::assertEquals( - ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, - $this->model->getKlarnaShippingMethod([]) - ); - } + private ApiOrderManagement $model; + private Ordermanagement|MockObject $mockOrderManagement; + private \Magento\Framework\DataObjectFactory|MockObject $mockDataObjectFactory; + private Parameter|MockObject $mockParameter; + private \Klarna\Base\Helper\DataConverter|MockObject $mockDataConverter; + private KlarnaConfigurationsApi|MockObject $mockApi; + private KlarnaApiBuilder|MockObject $mockBuilder; protected function setUp(): void { - $objectManager = new \Magento\Framework\TestFramework\Unit\Helper\ObjectManager($this); + $this->mockOrderManagement = $this->getMockBuilder(Ordermanagement::class) + ->disableOriginalConstructor() + ->getMock(); - $this->mockOrderManagement = $this->getMockBuilder(Ordermanagement::class) + $this->mockDataConverter = $this->getMockBuilder(\Klarna\Base\Helper\DataConverter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockKlarnaConfig = $this->getMockBuilder(\Klarna\Base\Helper\KlarnaConfig::class) + + $this->mockParameter = $this->getMockBuilder(Parameter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataConverter = $this->getMockBuilder(\Klarna\Base\Helper\DataConverter::class) + + $this->mockDataObjectFactory = $this->getMockBuilder(\Magento\Framework\DataObjectFactory::class) ->disableOriginalConstructor() ->getMock(); - $this->mockParameter = $this->getMockBuilder(Parameter::class) + $this->mockDataObjectFactory->method('create')->willReturn(new DataObject()); + + $this->mockApi = $this->getMockBuilder(KlarnaConfigurationsApi::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataObjectFactory = $this->getMockBuilder(\Magento\Framework\DataObjectFactory::class) + + $this->mockBuilder = $this->getMockBuilder(KlarnaApiBuilder::class) ->disableOriginalConstructor() ->getMock(); - $this->model = $objectManager->getObject( - ApiOrderManagement::class, - [ - 'orderManagement' => $this->mockOrderManagement, - 'klarnaConfig' => $this->mockKlarnaConfig, - 'dataConverter' => $this->mockDataConverter, - 'dataObjectFactory' => $this->mockDataObjectFactory, - 'parameter' => $this->mockParameter, - 'builderType' => '' - ] + $this->model = new ApiOrderManagement( + $this->mockOrderManagement, + $this->mockDataConverter, + $this->mockDataObjectFactory, + $this->mockParameter, + $this->mockApi, + $this->mockBuilder, ); } + + /** + * Test that addShippingInfo transforms tracking entries into the correct Klarna API payload format. + * Scenario: Various combinations of tracking input — standard, oversized fields, multiple entries. + * Verifies the shipping_info wrapper key is present and each entry contains correctly mapped + * and truncated tracking_number, shipping_method, and shipping_company fields. + */ + #[DataProvider('addShippingInfoFormatProvider')] + public function testAddShippingInfoFormatsData(array $input, array $expectedPayload): void + { + $this->mockOrderManagement->expects($this->once()) + ->method('addShippingInfo') + ->with( + $this->anything(), + $this->anything(), + $expectedPayload + ); + + $this->model->addShippingInfo( + orderId: 'order-id', + captureId: 'capture-id', + shippingInfo: $input + ); + } + + /** + * Provides input tracking arrays and the corresponding expected Klarna API payloads. + */ + public static function addShippingInfoFormatProvider(): array + { + return [ + 'single entry maps all fields correctly' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => '1Z999AA10123456784', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => '1Z999AA10123456784', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS Ground', + ], + ], + ], + ], + 'tracking number longer than 100 chars is truncated' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS', + 'number' => \str_repeat('A', 101)], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => \str_repeat('A', 100), + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS', + ], + ], + ], + ], + 'shipping company longer than 100 chars is truncated' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => \str_repeat('B', 101), + 'number' => 'TRACK123', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => 'TRACK123', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => \str_repeat('B', 100), + ], + ], + ], + ], + 'multiple entries produce one shipping_info item each' => [ + 'input' => [ + [ + 'carrier_code' => 'ups', + 'title' => 'UPS Ground', + 'number' => 'TRACK001', + ], + [ + 'carrier_code' => 'fedex', + 'title' => 'FedEx', + 'number' => 'TRACK002', + ], + ], + 'expectedPayload' => [ + 'shipping_info' => [ + [ + 'tracking_number' => 'TRACK001', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'UPS Ground', + ], + [ + 'tracking_number' => 'TRACK002', + 'shipping_method' => ApiOrderManagement::KLARNA_API_SHIPPING_METHOD_HOME, + 'shipping_company' => 'FedEx', + ], + ], + ], + ], + ]; + } } From 51efd2e7a8a7ad77a4c94f8fff536726b1c0f1e5 Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Tue, 2 Jun 2026 09:06:33 +0300 Subject: [PATCH 2/8] KUSTOM-78 Minor cs --- CHANGELOG.md | 5 ++++ .../Gateway/Command/CaptureTest.php | 25 ++++++------------- Test/Integration/Stub/StubRequest.php | 21 ++-------------- .../_files/invoice_with_klarna_payment.php | 4 ++- .../_files/order_with_klarna_payment.php | 2 +- .../order_with_klarna_payment_rollback.php | 4 +-- 6 files changed, 21 insertions(+), 40 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a98c395..a0e8dc8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +11.0.19 / 2026-06-02 +================== + + * KUSTOM-78 Skip sending shipping info when not provided + 11.0.18 / 2026-03-05 ================== diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php index 7d4868f..d6c5d94 100644 --- a/Test/Integration/Gateway/Command/CaptureTest.php +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -16,7 +16,6 @@ use Klarna\Backend\Test\Integration\Stub\StubRequest; use Magento\Framework\Api\SearchCriteriaBuilder; use Magento\Framework\DataObject; -use Magento\Framework\ObjectManagerInterface; use Magento\Payment\Gateway\Data\PaymentDataObjectFactory; use Magento\Sales\Api\Data\InvoiceInterface; use Magento\Sales\Api\InvoiceRepositoryInterface; @@ -32,9 +31,7 @@ class CaptureTest extends TestCase private const string TEST_CAPTURE_ID = 'test-capture-123'; private Capture $captureCommand; - private Factory|MockObject $mockApiFactory; private OrderManagement|MockObject $mockOrderManagement; - private Validator|MockObject $mockValidator; private StubRequest $stubRequest; private PaymentDataObjectFactory $paymentDataObjectFactory; private OrderRepositoryInterface $orderRepository; @@ -52,29 +49,23 @@ protected function setUp(): void $this->searchCriteriaBuilder = $objectManager->get(SearchCriteriaBuilder::class); $this->invoiceRepository = $objectManager->get(InvoiceRepositoryInterface::class); - $this->setupMocks($objectManager); + $mockApiFactory = $this->createMock(Factory::class); + $mockValidator = $this->createMock(Validator::class); + $this->stubRequest = $objectManager->create(StubRequest::class); + $this->mockOrderManagement = $this->createMock(OrderManagement::class); + + $mockApiFactory->method('createOmApi')->willReturn($this->mockOrderManagement); $this->captureCommand = $objectManager->create( Capture::class, [ - 'omFactory' => $this->mockApiFactory, - 'validator' => $this->mockValidator, + 'omFactory' => $mockApiFactory, + 'validator' => $mockValidator, 'request' => $this->stubRequest ] ); } - /** @noinspection ObjectManagerInspection */ - private function setupMocks(ObjectManagerInterface $objectManager): void - { - $this->mockOrderManagement = $this->createMock(OrderManagement::class); - $this->mockValidator = $this->createMock(Validator::class); - $this->stubRequest = $objectManager->create(StubRequest::class); - $this->mockApiFactory = $this->createMock(Factory::class); - - $this->mockApiFactory->method('createOmApi')->willReturn($this->mockOrderManagement); - } - /** * Test capture with tracking info present but shipment not processed. * Scenario: Admin adds tracking to invoice after separate shipment or manual tracking entry. diff --git a/Test/Integration/Stub/StubRequest.php b/Test/Integration/Stub/StubRequest.php index 843dcf0..5410e1b 100644 --- a/Test/Integration/Stub/StubRequest.php +++ b/Test/Integration/Stub/StubRequest.php @@ -12,20 +12,6 @@ use Laminas\Stdlib\Parameters; use Magento\Framework\App\Request\Http as HttpRequest; -/** - * Stub Request class for testing. - * - * Provides a testable HTTP request implementation that allows setting POST data - * without requiring an actual HTTP request. Useful for integration tests that need - * to simulate request data. - * - * Usage: - * ```php - * $stubRequest = $objectManager->create(StubRequest::class); - * $stubRequest->setPostData(['key' => 'value']); - * $value = $stubRequest->getPost('key'); // Returns 'value' - * ``` - */ class StubRequest extends HttpRequest { /** @@ -49,17 +35,14 @@ public function setPostData($data): void } /** - * Get POST data - * - * @param string|null $name Parameter name or null to get all data - * @param mixed $default Default value if parameter not found - * @return mixed + * @inheritDoc */ public function getPost($name = null, $default = null) { if ($name === null) { return $this->postData; } + return $this->postData[$name] ?? $default; } } diff --git a/Test/Integration/_files/invoice_with_klarna_payment.php b/Test/Integration/_files/invoice_with_klarna_payment.php index 3082df2..5fd82a8 100644 --- a/Test/Integration/_files/invoice_with_klarna_payment.php +++ b/Test/Integration/_files/invoice_with_klarna_payment.php @@ -35,7 +35,9 @@ $order = $objectManager->get(OrderInterfaceFactory::class)->create()->loadByIncrementId('100000001'); if (!$order->getId()) { - throw new \RuntimeException('Order with increment ID 100000001 not found. Make sure order_with_klarna_payment.php fixture runs successfully.'); + throw new \RuntimeException( + 'Order with increment ID 100000001 not found. Make sure order_with_klarna_payment.php fixture runs successfully.' + ); } // Check if order can be invoiced diff --git a/Test/Integration/_files/order_with_klarna_payment.php b/Test/Integration/_files/order_with_klarna_payment.php index 9c5bbd2..c4266aa 100644 --- a/Test/Integration/_files/order_with_klarna_payment.php +++ b/Test/Integration/_files/order_with_klarna_payment.php @@ -54,7 +54,7 @@ 'use_config_manage_stock' => 1, 'qty' => 100, 'is_qty_decimal' => 0, - 'is_in_stock' => 1 + 'is_in_stock' => 1, ]); $productRepository->save($product); diff --git a/Test/Integration/_files/order_with_klarna_payment_rollback.php b/Test/Integration/_files/order_with_klarna_payment_rollback.php index c850dcf..a68699f 100644 --- a/Test/Integration/_files/order_with_klarna_payment_rollback.php +++ b/Test/Integration/_files/order_with_klarna_payment_rollback.php @@ -52,7 +52,7 @@ $order->delete(); } -} catch (\Exception $e) { +} catch (\Exception) { // Order already deleted } @@ -61,7 +61,7 @@ $productRepository = $objectManager->get(ProductRepositoryInterface::class); $product = $productRepository->get('simple-test-product-klarna'); $productRepository->delete($product); -} catch (\Exception $e) { +} catch (\Exception) { // Product already deleted } From b38c5bc2679d36fb143b4057b13c6fa0f85ccf23 Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Tue, 2 Jun 2026 09:15:56 +0300 Subject: [PATCH 3/8] KUSTOM-78 Unify --- Test/Unit/Model/Api/OrderManagementTest.php | 26 +++++++++++---------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/Test/Unit/Model/Api/OrderManagementTest.php b/Test/Unit/Model/Api/OrderManagementTest.php index 24e7bee..40666ad 100644 --- a/Test/Unit/Model/Api/OrderManagementTest.php +++ b/Test/Unit/Model/Api/OrderManagementTest.php @@ -12,9 +12,11 @@ use Klarna\AdminSettings\Model\Configurations\Api as KlarnaConfigurationsApi; use Klarna\Backend\Model\Api\Builder as KlarnaApiBuilder; use Klarna\Backend\Model\Api\OrderManagement as ApiOrderManagement; -use Klarna\Backend\Model\Api\Rest\Service\Ordermanagement; -use Klarna\Orderlines\Model\Container\Parameter; +use Klarna\Backend\Model\Api\Rest\Service\Ordermanagement as ServiceOrderManagement; +use Klarna\Base\Helper\DataConverter as KlarnaDataConverter; +use Klarna\Orderlines\Model\Container\Parameter as KlarnaParameter; use Magento\Framework\DataObject; +use Magento\Framework\DataObjectFactory; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -25,28 +27,28 @@ class OrderManagementTest extends TestCase { private ApiOrderManagement $model; - private Ordermanagement|MockObject $mockOrderManagement; - private \Magento\Framework\DataObjectFactory|MockObject $mockDataObjectFactory; - private Parameter|MockObject $mockParameter; - private \Klarna\Base\Helper\DataConverter|MockObject $mockDataConverter; + private ServiceOrderManagement|MockObject $mockSrvOrderManagement; + private DataObjectFactory|MockObject $mockDataObjectFactory; + private KlarnaParameter|MockObject $mockParameter; + private KlarnaDataConverter|MockObject $mockDataConverter; private KlarnaConfigurationsApi|MockObject $mockApi; private KlarnaApiBuilder|MockObject $mockBuilder; protected function setUp(): void { - $this->mockOrderManagement = $this->getMockBuilder(Ordermanagement::class) + $this->mockSrvOrderManagement = $this->getMockBuilder(ServiceOrderManagement::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataConverter = $this->getMockBuilder(\Klarna\Base\Helper\DataConverter::class) + $this->mockDataConverter = $this->getMockBuilder(KlarnaDataConverter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockParameter = $this->getMockBuilder(Parameter::class) + $this->mockParameter = $this->getMockBuilder(KlarnaParameter::class) ->disableOriginalConstructor() ->getMock(); - $this->mockDataObjectFactory = $this->getMockBuilder(\Magento\Framework\DataObjectFactory::class) + $this->mockDataObjectFactory = $this->getMockBuilder(DataObjectFactory::class) ->disableOriginalConstructor() ->getMock(); $this->mockDataObjectFactory->method('create')->willReturn(new DataObject()); @@ -60,7 +62,7 @@ protected function setUp(): void ->getMock(); $this->model = new ApiOrderManagement( - $this->mockOrderManagement, + $this->mockSrvOrderManagement, $this->mockDataConverter, $this->mockDataObjectFactory, $this->mockParameter, @@ -78,7 +80,7 @@ protected function setUp(): void #[DataProvider('addShippingInfoFormatProvider')] public function testAddShippingInfoFormatsData(array $input, array $expectedPayload): void { - $this->mockOrderManagement->expects($this->once()) + $this->mockSrvOrderManagement->expects($this->once()) ->method('addShippingInfo') ->with( $this->anything(), From cd4e1a23c209c06941309635c7bc893e72f8266e Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Tue, 2 Jun 2026 09:38:47 +0300 Subject: [PATCH 4/8] KUSTOM-78 Support for previous format --- Test/Unit/Model/Api/OrderManagementTest.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Test/Unit/Model/Api/OrderManagementTest.php b/Test/Unit/Model/Api/OrderManagementTest.php index 40666ad..278d64f 100644 --- a/Test/Unit/Model/Api/OrderManagementTest.php +++ b/Test/Unit/Model/Api/OrderManagementTest.php @@ -72,6 +72,8 @@ protected function setUp(): void } /** + * @dataProvider addShippingInfoFormatProvider + * * Test that addShippingInfo transforms tracking entries into the correct Klarna API payload format. * Scenario: Various combinations of tracking input — standard, oversized fields, multiple entries. * Verifies the shipping_info wrapper key is present and each entry contains correctly mapped From 46d9245c270b6962d4dd03d13358dcd319198f4f Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Tue, 2 Jun 2026 09:57:48 +0300 Subject: [PATCH 5/8] KUSTOM-78 Removed changelog --- CHANGELOG.md | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0e8dc8..a98c395 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,3 @@ -11.0.19 / 2026-06-02 -================== - - * KUSTOM-78 Skip sending shipping info when not provided - 11.0.18 / 2026-03-05 ================== From a2e2d39e48f77aa7a443c24dc10da5c50928798d Mon Sep 17 00:00:00 2001 From: Joona Melartin Date: Mon, 8 Jun 2026 09:42:46 +0300 Subject: [PATCH 6/8] KUSTOM-78: Adjusted one of the new integration tests to not use explicit constant types, as this is supported only from PHP 8.3 and above --- Test/Integration/Gateway/Command/CaptureTest.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php index d6c5d94..d6b5fbd 100644 --- a/Test/Integration/Gateway/Command/CaptureTest.php +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -26,9 +26,9 @@ class CaptureTest extends TestCase { - private const string TEST_ORDER_INCREMENT_ID = '100000001'; - private const string TEST_RESERVATION_ID = 'test-reservation-id-12345'; - private const string TEST_CAPTURE_ID = 'test-capture-123'; + private const TEST_ORDER_INCREMENT_ID = '100000001'; + private const TEST_RESERVATION_ID = 'test-reservation-id-12345'; + private const TEST_CAPTURE_ID = 'test-capture-123'; private Capture $captureCommand; private OrderManagement|MockObject $mockOrderManagement; From 00cce33ce7aed915bc3cfc8ea84148d5273a4448 Mon Sep 17 00:00:00 2001 From: Joona Melartin Date: Mon, 8 Jun 2026 09:45:58 +0300 Subject: [PATCH 7/8] KUSTOM-78: One more test adjustment, set the test properties as nullable and with default null value, as sometimes the properties instantiate before setUp() is called in Magento Testing Framework --- Test/Integration/Gateway/Command/CaptureTest.php | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php index d6b5fbd..d0885d2 100644 --- a/Test/Integration/Gateway/Command/CaptureTest.php +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -30,13 +30,13 @@ class CaptureTest extends TestCase private const TEST_RESERVATION_ID = 'test-reservation-id-12345'; private const TEST_CAPTURE_ID = 'test-capture-123'; - private Capture $captureCommand; - private OrderManagement|MockObject $mockOrderManagement; - private StubRequest $stubRequest; - private PaymentDataObjectFactory $paymentDataObjectFactory; - private OrderRepositoryInterface $orderRepository; - private SearchCriteriaBuilder $searchCriteriaBuilder; - private InvoiceRepositoryInterface $invoiceRepository; + private ?Capture $captureCommand = null; + private OrderManagement|MockObject|null $mockOrderManagement = null; + private ?StubRequest $stubRequest = null; + private ?PaymentDataObjectFactory $paymentDataObjectFactory = null; + private ?OrderRepositoryInterface $orderRepository = null; + private ?SearchCriteriaBuilder $searchCriteriaBuilder = null; + private ?InvoiceRepositoryInterface $invoiceRepository = null; /** @noinspection ObjectManagerInspection */ protected function setUp(): void From 45445c3c82ed3e821d65093253c6904cc9a92cbb Mon Sep 17 00:00:00 2001 From: Henri Junttila Date: Thu, 11 Jun 2026 16:14:06 +0300 Subject: [PATCH 8/8] KUSTOM-78 Combine messages to support MGO 2.4.9 --- Gateway/Command/Capture.php | 15 +++++++++++++-- Test/Integration/Gateway/Command/CaptureTest.php | 8 +++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/Gateway/Command/Capture.php b/Gateway/Command/Capture.php index 0ab98f3..b65b4fe 100644 --- a/Gateway/Command/Capture.php +++ b/Gateway/Command/Capture.php @@ -156,9 +156,20 @@ private function addShippingInfoToCapture($captureId, $klarnaOrderId, $trackingD $invoice->addComment("Shipping info sent to Klarna API", false, false); return; } - foreach ($response->getErrorMessages() as $message) { - $invoice->addComment($message, false, false); + + $errorMessages = $response->getErrorMessages(); + + if (!$errorMessages) { + return; } + + $errorMessages = \implode('. ', $errorMessages); + + $invoice->addComment( + "Received error(s) when sending shipping info to Kustom API: {$errorMessages}.", + notify: false, + visibleOnFront: false + ); } /** diff --git a/Test/Integration/Gateway/Command/CaptureTest.php b/Test/Integration/Gateway/Command/CaptureTest.php index d0885d2..e3bde1a 100644 --- a/Test/Integration/Gateway/Command/CaptureTest.php +++ b/Test/Integration/Gateway/Command/CaptureTest.php @@ -300,8 +300,10 @@ public function testExecuteWithShipmentProcessingApiFailure(): void $this->invoiceRepository->save($invoice); $reloadedInvoice = $this->invoiceRepository->get($invoice->getEntityId()); - $this->assertInvoiceHasComment($reloadedInvoice, 'Invalid tracking number'); - $this->assertInvoiceHasComment($reloadedInvoice, 'Carrier not supported'); + $this->assertInvoiceHasComment( + $reloadedInvoice, + 'Received error(s) when sending shipping info to Kustom API: Invalid tracking number. Carrier not supported.' + ); } /** @@ -348,7 +350,7 @@ private function getInvoiceByOrderIncrementId(string $incrementId): InvoiceInter private function assertInvoiceHasComment(InvoiceInterface $invoice, string $commentText): void { // Use getCommentsCollection to load from database - $comments = $invoice->getCommentsCollection(true); // true = reload + $comments = $invoice->getCommentsCollection(reload: true); $found = false; $existingComments = []; foreach ($comments as $comment) {