From a4a99283a1a2893e7ac3eddf415e3dadf3c051a7 Mon Sep 17 00:00:00 2001 From: Bharat Kunwar Date: Tue, 22 Oct 2024 12:05:38 +0100 Subject: [PATCH] CET-480/feat: Support new fulfilments endpoint in Magento plugin --- .gitignore | 5 + Block/Adminhtml/Order/View.php | 62 ++++------ Model/Two.php | 109 ++++++++++-------- Observer/SalesOrderSaveAfter.php | 44 ++++--- Observer/SalesOrderShipmentAfter.php | 91 +++++++-------- Service/Order/ComposeCapture.php | 22 +--- Service/Order/ComposeShipment.php | 40 +------ composer.json | 11 ++ .../adminhtml/templates/order/view/view.phtml | 20 ++-- 9 files changed, 185 insertions(+), 219 deletions(-) diff --git a/.gitignore b/.gitignore index b1714e56..b5fa2f20 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,8 @@ .direnv/ .envrc +# PHP +.php-cs-fixer.cache +auth.json +vendor/ +composer.lock diff --git a/Block/Adminhtml/Order/View.php b/Block/Adminhtml/Order/View.php index c2d01819..a2c49bc6 100755 --- a/Block/Adminhtml/Order/View.php +++ b/Block/Adminhtml/Order/View.php @@ -9,6 +9,7 @@ use Magento\Sales\Block\Adminhtml\Order\View as OrderView; use Two\Gateway\Api\Config\RepositoryInterface as ConfigRepository; +use Two\Gateway\Service\Api\Adapter as Adapter; /** * Order View Block @@ -20,10 +21,16 @@ class View extends OrderView */ public $configRepository; + /** + * @var Adapter + */ + private $apiAdapter; + /** * View constructor. * * @param ConfigRepository $configRepository + * @param Adapter $adapter * @param \Magento\Backend\Block\Widget\Context $context * @param \Magento\Framework\Registry $registry * @param \Magento\Sales\Model\Config $salesConfig @@ -32,6 +39,7 @@ class View extends OrderView */ public function __construct( ConfigRepository $configRepository, + Adapter $apiAdapter, \Magento\Backend\Block\Widget\Context $context, \Magento\Framework\Registry $registry, \Magento\Sales\Model\Config $salesConfig, @@ -39,53 +47,31 @@ public function __construct( array $data = [] ) { $this->configRepository = $configRepository; + $this->apiAdapter = $apiAdapter; parent::__construct($context, $registry, $salesConfig, $reorderHelper, $data); } /** - * Get payment additional data - * - * @return array - */ - public function getAdditionalData(): array - { - $order = $this->getOrder(); - $payment = $order->getPayment(); - return $payment->getAdditionalInformation(); - } - - /** - * Get Two Credit Note Url - * - * @param array $data - * - * @return string - */ - public function getTwoCreditNoteUrl(array $data): string - { - $order = $this->getOrder(); - $billingAddress = $order->getBillingAddress(); - $langParams = $billingAddress->getCountryId() == 'NO' ? '?lang=nb_NO' : '?lang=en_US'; - - return isset($data['gateway_data']['credit_note_url']) - ? $data['gateway_data']['credit_note_url'] . $langParams - : ''; - } - - /** - * Get Two Credit Invoice Url + * Get Two Fulfillments * * @param array $data * - * @return string + * @return array */ - public function getTwoInvoiceUrl(array $data): string + public function getTwoOrderFulfillments(): array { $order = $this->getOrder(); - $billingAddress = $order->getBillingAddress(); - $langParams = $billingAddress->getCountryId() == 'NO' ? '?lang=nb_NO' : '?lang=en_US'; + $response = $this->apiAdapter->execute( + "/v1/order/" . $order->getTwoOrderId() . "/fulfillments", + [], + 'GET' + ); + $error = $order->getPayment()->getMethodInstance()->getErrorFromResponse($response); + if ($error) { + return []; + } - return (isset($data['gateway_data']['invoice_url'])) ? $data['gateway_data']['invoice_url'] . $langParams : ''; + return $response; } /** @@ -95,9 +81,9 @@ public function getTwoInvoiceUrl(array $data): string * * @return string */ - public function getTwoOrderId(array $data): string + public function getTwoOrderId(): string { - return (isset($data['gateway_data']['external_order_id'])) ? $data['gateway_data']['external_order_id'] : ''; + return $this->getOrder()->getTwoOrderId(); } /** diff --git a/Model/Two.php b/Model/Two.php index 819208f2..ed5c5ad3 100755 --- a/Model/Two.php +++ b/Model/Two.php @@ -227,9 +227,9 @@ public function authorize(InfoInterface $payment, $amount) ); } + $twoOrderId = $response['id']; + $order->setTwoOrderId($twoOrderId); $order->setTwoOrderReference($orderReference); - $order->setTwoOrderId($response['id']); - $payload['gateway_data']['external_order_id'] = $response['id']; $payload['gateway_data']['external_order_status'] = $response['external_order_status']; $payload['gateway_data']['original_order_id'] = $response['original_order_id']; $payload['gateway_data']['state'] = $response['state']; @@ -242,7 +242,7 @@ public function authorize(InfoInterface $payment, $amount) unset($payload['merchant_urls']); $payment->setAdditionalInformation($payload); - $payment->setTransactionId($response['external_order_id']) + $payment->setTransactionId($twoOrderId) ->setIsTransactionClosed(0) ->setIsTransactionPending(true); $this->urlCookie->set($response['payment_url']); @@ -327,10 +327,6 @@ public function getErrorFromResponse(array $response): ?Phrase return $this->_getMessageWithTrace($message, $traceID); } - if (empty($response['id'])) { - return $this->_getMessageWithTrace($generalError, $traceID); - } - return null; } @@ -403,6 +399,28 @@ public function cancel(InfoInterface $payment) return $this; } + private function isWholeOrderInvoiced(OrderInterface $order): bool + { + foreach ($order->getAllVisibleItems() as $orderItem) { + /** @var Order\Item $orderItem */ + if ($orderItem->getQtyInvoiced() < $orderItem->getQtyOrdered()) { + return false; + } + } + + return true; + } + + /** + * @param OrderInterface $order + * @return bool + */ + private function isPartialOrder(OrderInterface $order): bool + { + $additionalInformation = $order->getPayment()->getAdditionalInformation(); + return $additionalInformation['gateway_data']['partial_order'] ?? false; + } + /** * @inheritDoc */ @@ -417,20 +435,11 @@ public function capture(InfoInterface $payment, $amount) __('Could not initiate capture with %1', $this->configRepository::PROVIDER) ); } - $orderItems = $order->getAllVisibleItems(); - $wholeOrderInvoiced = 1; - if ($orderItems) { - foreach ($orderItems as $item) { - if ($item->getQtyInvoiced() < $item->getQtyOrdered()) { - $wholeOrderInvoiced = 0; - break; - } - } - } - if ($wholeOrderInvoiced == 1) { - $response = $this->apiAdapter->execute('/v1/order/' . $twoOrderId . '/fulfilled'); - } else { + $payload = []; + $isWholeOrderInvoiced = $this->isWholeOrderInvoiced($order); + $isPartialOrder = $this->isPartialOrder($order); + if (!$isWholeOrderInvoiced || $isPartialOrder) { $invoices = $order->getInvoiceCollection(); $totalInvoices = count($invoices); $cnt = 1; @@ -441,22 +450,20 @@ public function capture(InfoInterface $payment, $amount) } $cnt++; } - $remainItemInvoice = $payment->getOrder()->prepareInvoice(); - $response = $this->partialMainOrder($order, $createdInvoice, $remainItemInvoice); + $payload = [ + 'partial' => $this->composeCapture->execute($createdInvoice), + ]; } + $response = $this->apiAdapter->execute('/v1/order/' . $twoOrderId . '/fulfillments', $payload); - if (!empty($response) && isset($response['id'])) { - $payment->setTransactionId($response['id'])->setIsTransactionClosed(0); + if (!empty($response) && isset($response['fulfilled_order']['id'])) { + $payment->setTransactionId($response['fulfilled_order']['id'])->setIsTransactionClosed(0); } else { $payment->setIsTransactionClosed(0); } - $this->parseFulfillResponse($response, $order); $payment->save(); - $error = $this->getErrorFromResponse($response); - if ($error) { - throw new LocalizedException($error); - } + $this->parseFulfillResponse($response, $order); } else { throw new LocalizedException(__('The capture action is not available.')); } @@ -480,14 +487,12 @@ public function canCapture() * @return array * @throws LocalizedException */ - public function partialMainOrder(Order $order, ?Order\Invoice $invoice, Order\Invoice $remainItemInvoice): array + public function partialFulfill(Order $order, ?Order\Invoice $invoice): array { $twoOrderId = $order->getTwoOrderId(); - $payload['partially_fulfilled_order'] = $this->composeCapture->execute($invoice); - $payload['remained_order'] = $this->composeCapture->execute($remainItemInvoice); /* Partially fulfill order*/ - return $this->apiAdapter->execute('/v1/order/' . $twoOrderId . '/fulfilled', $payload); + return $this->apiAdapter->execute('/v1/order/' . $twoOrderId . '/fulfillments', $payload); } /** @@ -500,29 +505,37 @@ public function partialMainOrder(Order $order, ?Order\Invoice $invoice, Order\In */ private function parseFulfillResponse(array $response, Order $order): void { - $error = $order->getPayment()->getMethodInstance()->getErrorFromResponse($response); + $error = $this->getErrorFromResponse($response); if ($error) { throw new LocalizedException($error); } - if (empty($response['invoice_details'] || - empty($response['invoice_details']['invoice_number']))) { + if (empty($response['fulfilled_order'] || + empty($response['fulfilled_order']['id']))) { return; } - + $partialOrder = !empty($response['remained_order']); $additionalInformation = $order->getPayment()->getAdditionalInformation(); - $additionalInformation['gateway_data']['invoice_number'] = $response['invoice_details']['invoice_number']; - $additionalInformation['gateway_data']['invoice_url'] = $response['invoice_url']; + if ($partialOrder) { + $additionalInformation['gateway_data']['partial_order'] = true; + } $additionalInformation['marked_completed'] = true; $order->getPayment()->setAdditionalInformation($additionalInformation); - $comment = __( - '%1 order marked as completed with invoice number %2', - $this->configRepository::PROVIDER, - $response['invoice_details']['invoice_number'], - ); + if ($partialOrder) { + $comment = __( + '%1 order marked as partially completed.', + $this->configRepository::PROVIDER, + ); + } else { + $comment = __( + '%1 order marked as completed.', + $this->configRepository::PROVIDER, + ); + } + $this->addStatusToOrderHistory($order, $comment->render()); } @@ -565,7 +578,7 @@ public function refund(InfoInterface $payment, $amount) $payload = $this->composeRefund->execute( $payment->getCreditmemo(), - (double)$amount, + (float)$amount, $order ); $response = $this->apiAdapter->execute( @@ -587,15 +600,11 @@ public function refund(InfoInterface $payment, $amount) $reason ); $this->addOrderComment($order, $message); - throw new LocalizedException( + throw new LocalizedException( $message ); } - $additionalInformation = $payment->getAdditionalInformation(); - $additionalInformation['gateway_data']['credit_note_url'] = $response['credit_note_url']; - $payment->setAdditionalInformation($additionalInformation); - $comment = __( 'Successfully refunded order with %1 for order ID: %2. Refund reference: %3', $this->configRepository::PROVIDER, diff --git a/Observer/SalesOrderSaveAfter.php b/Observer/SalesOrderSaveAfter.php index f486d9d8..0e9348fe 100755 --- a/Observer/SalesOrderSaveAfter.php +++ b/Observer/SalesOrderSaveAfter.php @@ -88,23 +88,13 @@ public function execute(Observer $observer) throw new LocalizedException($error); } - $langParams = '?lang=en_US'; - if ($order->getBillingAddress()->getCountryId() == 'NO') { - $langParams = '?lang=nb_NO'; - } - //full fulfilment $response = $this->apiAdapter->execute( - "/v1/order/" . $order->getTwoOrderId() . "/fulfilled" . $langParams + "/v1/order/" . $order->getTwoOrderId() . "/fulfillments", ); - foreach ($order->getInvoiceCollection() as $invoice) { - $invoice->pay(); - $invoice->setTransactionId($order->getPayment()->getLastTransId()); - $invoice->save(); - } - $this->parseResponse($response, $order); + $this->parseFulfillResponse($response, $order); } } } @@ -131,7 +121,7 @@ private function isWholeOrderShipped(OrderInterface $order): bool * @return void * @throws Exception */ - private function parseResponse(array $response, Order $order): void + private function parseFulfillResponse(array $response, Order $order): void { $error = $order->getPayment()->getMethodInstance()->getErrorFromResponse($response); @@ -139,23 +129,31 @@ private function parseResponse(array $response, Order $order): void throw new LocalizedException($error); } - if (empty($response['invoice_details'] || - empty($response['invoice_details']['invoice_number']))) { + if (empty($response['fulfilled_order'] || + empty($response['fulfilled_order']['id']))) { return; } - + $partialOrder = !empty($response['remained_order']); $additionalInformation = $order->getPayment()->getAdditionalInformation(); - $additionalInformation['gateway_data']['invoice_number'] = $response['invoice_details']['invoice_number']; - $additionalInformation['gateway_data']['invoice_url'] = $response['invoice_url']; + if ($partialOrder) { + $additionalInformation['gateway_data']['partial_order'] = true; + } $additionalInformation['marked_completed'] = true; $order->getPayment()->setAdditionalInformation($additionalInformation); - $comment = __( - '%1 order marked as completed with invoice number %2', - $this->configRepository::PROVIDER, - $response['invoice_details']['invoice_number'] - ); + if ($partialOrder) { + $comment = __( + '%1 order marked as partially completed.', + $this->configRepository::PROVIDER, + ); + } else { + $comment = __( + '%1 order marked as completed.', + $this->configRepository::PROVIDER, + ); + } + $this->addStatusToOrderHistory($order, $comment->render()); } diff --git a/Observer/SalesOrderShipmentAfter.php b/Observer/SalesOrderShipmentAfter.php index 826edd00..13398821 100755 --- a/Observer/SalesOrderShipmentAfter.php +++ b/Observer/SalesOrderShipmentAfter.php @@ -90,28 +90,27 @@ public function execute(Observer $observer) && $order->getTwoOrderId() ) { if ($this->configRepository->getFulfillTrigger() == 'shipment') { - if (!$this->isWholeOrderShipped($order)) { - $response = $this->partialFulfill($shipment); - $this->parseResponse($response, $order); - return; + $payload = []; + $isWholeOrderShipped = $this->isWholeOrderShipped($order); + $isPartialOrder = $this->isPartialOrder($order); + if (!$isWholeOrderShipped || $isPartialOrder) { + // partial fulfilment + $payload = [ + 'partial' => $this->composeShipment->execute($shipment, $order), + ]; } - - $langParams = '?lang=en_US'; - if ($order->getBillingAddress()->getCountryId() == 'NO') { - $langParams = '?lang=nb_NO'; - } - - //full fulfilment $response = $this->apiAdapter->execute( - "/v1/order/" . $order->getTwoOrderId() . "/fulfilled" . $langParams + "/v1/order/" . $order->getTwoOrderId() . "/fulfillments", + $payload ); - foreach ($order->getInvoiceCollection() as $invoice) { - $invoice->pay(); - $invoice->setTransactionId($order->getPayment()->getLastTransId()); - $invoice->save(); + $this->parseFulfillResponse($response, $order); + if ($isWholeOrderShipped) { + foreach ($order->getInvoiceCollection() as $invoice) { + $invoice->pay(); + $invoice->setTransactionId($order->getPayment()->getLastTransId()); + $invoice->save(); + } } - - $this->parseResponse($response, $order); } elseif ($this->configRepository->getFulfillTrigger() == 'complete') { foreach ($order->getInvoiceCollection() as $invoice) { $invoice->pay(); @@ -130,6 +129,16 @@ public function execute(Observer $observer) } } + /** + * @param OrderInterface $order + * @return bool + */ + private function isPartialOrder(OrderInterface $order): bool + { + $additionalInformation = $order->getPayment()->getAdditionalInformation(); + return $additionalInformation['gateway_data']['partial_order'] ?? false; + } + /** * @param OrderInterface $order * @return bool @@ -146,29 +155,13 @@ private function isWholeOrderShipped(OrderInterface $order): bool return true; } - /** - * @param ShipmentInterface $shipment - * @return array - * @throws NoSuchEntityException - * @throws LocalizedException - */ - private function partialFulfill(ShipmentInterface $shipment): array - { - $order = $shipment->getOrder(); - $twoOrderId = $order->getTwoOrderId(); - - $payload = $this->composeShipment->execute($shipment, $order); - - return $this->apiAdapter->execute('/v1/order/' . $twoOrderId . '/fulfilled', $payload); - } - /** * @param array $response * @param Order $order * @return void * @throws Exception */ - private function parseResponse(array $response, Order $order): void + private function parseFulfillResponse(array $response, Order $order): void { $error = $order->getPayment()->getMethodInstance()->getErrorFromResponse($response); @@ -176,23 +169,31 @@ private function parseResponse(array $response, Order $order): void throw new LocalizedException($error); } - if (empty($response['invoice_details'] || - empty($response['invoice_details']['invoice_number']))) { + if (empty($response['fulfilled_order'] || + empty($response['fulfilled_order']['id']))) { return; } - + $partialOrder = !empty($response['remained_order']); $additionalInformation = $order->getPayment()->getAdditionalInformation(); - $additionalInformation['gateway_data']['invoice_number'] = $response['invoice_details']['invoice_number']; - $additionalInformation['gateway_data']['invoice_url'] = $response['invoice_url']; + if ($partialOrder) { + $additionalInformation['gateway_data']['partial_order'] = true; + } $additionalInformation['marked_completed'] = true; $order->getPayment()->setAdditionalInformation($additionalInformation); - $comment = __( - '%1 order marked as completed with invoice number %2', - $this->configRepository::PROVIDER, - $response['invoice_details']['invoice_number'] - ); + if ($partialOrder) { + $comment = __( + '%1 order marked as partially completed.', + $this->configRepository::PROVIDER, + ); + } else { + $comment = __( + '%1 order marked as completed.', + $this->configRepository::PROVIDER, + ); + } + $this->addStatusToOrderHistory($order, $comment->render()); } diff --git a/Service/Order/ComposeCapture.php b/Service/Order/ComposeCapture.php index b0b7ac4a..c9c6f53d 100755 --- a/Service/Order/ComposeCapture.php +++ b/Service/Order/ComposeCapture.php @@ -16,7 +16,6 @@ */ class ComposeCapture extends OrderService { - /** * Compose request body for two capture order * @@ -28,30 +27,15 @@ class ComposeCapture extends OrderService public function execute(Order\Invoice $invoice, ?string $twoOriginalOrderId = ''): array { $order = $invoice->getOrder(); + $lineItems = $this->getLineItemsInvoice($invoice, $order); $reqBody = [ - 'billing_address' => $this->getAddress($order, [], 'billing'), - 'shipping_address' => $this->getAddress($order, [], 'shipping'), - 'currency' => $invoice->getOrderCurrencyCode(), 'discount_amount' => $this->roundAmt(abs((float)$invoice->getDiscountAmount())), 'gross_amount' => $this->roundAmt($invoice->getGrandTotal()), + 'line_items' => $lineItems, 'net_amount' => $this->roundAmt($invoice->getGrandTotal() - $invoice->getTaxAmount()), 'tax_amount' => $this->roundAmt($invoice->getTaxAmount()), - 'tax_rate' => $this->roundAmt( - (1.0 * $order->getTaxAmount() / ($order->getGrandTotal() - $order->getTaxAmount())) - ), - 'discount_rate' => '0', - 'invoice_type' => 'FUNDED_INVOICE', - 'line_items' => $this->getLineItemsInvoice($invoice, $order), - 'merchant_order_id' => (string)($order->getIncrementId()), - 'merchant_reference' => '', - 'merchant_additional_info' => '', + 'tax_subtotal' => $this->getTaxSubtotals($lineItems), ]; - if (!$order->getIsVirtual()) { - $reqBody['shipping_details'] = $this->getShippingDetails($order); - } - if ($twoOriginalOrderId) { - $reqBody['original_order_id'] = $twoOriginalOrderId; - } return $reqBody; } diff --git a/Service/Order/ComposeShipment.php b/Service/Order/ComposeShipment.php index bf6ce120..bc13c575 100755 --- a/Service/Order/ComposeShipment.php +++ b/Service/Order/ComposeShipment.php @@ -20,7 +20,6 @@ */ class ComposeShipment extends OrderService { - /** * Compose request body for two ship order * @@ -33,41 +32,14 @@ class ComposeShipment extends OrderService public function execute(Order\Shipment $shipment, Order $order): array { $shipmentItems = $this->getLineItemsShipment($order, $shipment); - $orderItems = $this->getRemainingItems($order); return [ - 'partially_fulfilled_order' => [ - 'billing_address' => $this->getAddress($order, [], 'billing'), - 'shipping_address' => $this->getAddress($order, [], 'shipping'), - 'currency' => $order->getOrderCurrencyCode(), - 'discount_amount' => $this->getSum($shipmentItems, 'discount_amount'), - 'gross_amount' => $this->getSum($shipmentItems, 'gross_amount'), - 'net_amount' => $this->getSum($shipmentItems, 'net_amount'), - 'tax_amount' => $this->getSum($shipmentItems, 'tax_amount'), - 'tax_rate' => reset($shipmentItems)['tax_rate'] ?? 0, - 'discount_rate' => '0', - 'invoice_type' => 'FUNDED_INVOICE', - 'line_items' => array_values($shipmentItems), - 'merchant_additional_info' => $order->getIncrementId(), - 'merchant_order_id' => (string)($order->getIncrementId()), - 'merchant_reference' => $order->getIncrementId(), - ], - 'remained_order' => [ - 'billing_address' => $this->getAddress($order, [], 'billing'), - 'shipping_address' => $this->getAddress($order, [], 'shipping'), - 'currency' => $order->getOrderCurrencyCode(), - 'discount_amount' => $this->getSum($orderItems, 'discount_amount'), - 'gross_amount' => $this->getSum($orderItems, 'gross_amount'), - 'net_amount' => $this->getSum($orderItems, 'net_amount'), - 'tax_amount' => $this->getSum($orderItems, 'tax_amount'), - 'tax_rate' => reset($orderItems)['tax_rate'] ?? 0, - 'discount_rate' => '0', - 'invoice_type' => 'FUNDED_INVOICE', - 'line_items' => array_values($orderItems), - 'merchant_additional_info' => $order->getIncrementId(), - 'merchant_order_id' => (string)($order->getIncrementId()), - 'merchant_reference' => $order->getIncrementId(), - ], + 'discount_amount' => $this->getSum($shipmentItems, 'discount_amount'), + 'gross_amount' => $this->getSum($shipmentItems, 'gross_amount'), + 'line_items' => array_values($shipmentItems), + 'net_amount' => $this->getSum($shipmentItems, 'net_amount'), + 'tax_amount' => $this->getSum($shipmentItems, 'tax_amount'), + 'tax_subtotals' => $this->getTaxSubtotals($shipmentItems), ]; } diff --git a/composer.json b/composer.json index 75398304..0b3e98e7 100755 --- a/composer.json +++ b/composer.json @@ -17,5 +17,16 @@ "psr-4": { "Two\\Gateway\\": "" } + }, + "repositories": [ + { + "type": "composer", + "url": "https://repo.magento.com/" + } + ], + "config": { + "allow-plugins": { + "magento/composer-dependency-version-audit-plugin": true + } } } diff --git a/view/adminhtml/templates/order/view/view.phtml b/view/adminhtml/templates/order/view/view.phtml index 7d5417ca..f063d5c6 100755 --- a/view/adminhtml/templates/order/view/view.phtml +++ b/view/adminhtml/templates/order/view/view.phtml @@ -13,36 +13,36 @@
- getAdditionalData(); ?> - getTwoOrderId($data) != ''): ?> + getTwoOrderId(); ?> + + escapeHtml($twoOrderId); ?> - getTwoInvoiceUrl($data) != ''): ?> + getTwoOrderFulfillments(); ?> + - - getTwoCreditNoteUrl($data) != ''): ?> + + - - +
escapeHtml(__('%1 Order ID', $provider)); ?>: - escapeHtml($block->getTwoOrderId($data)); ?>
escapeHtml(__('%1 Invoice', $provider)); ?>: - escapeHtml(__('Download')); ?>
escapeHtml(__('%1 Credit Note', $provider)); ?>: - escapeHtml(__('Download')); ?>