Skip to content

Commit

Permalink
Add fast checkout hydrateOrder endpoint (#261)
Browse files Browse the repository at this point in the history
* Add fast checkout hydrateOrder endpoint

* Add missing fields & return response

* Add fee and discount calculations

* Account for 0 discount total
  • Loading branch information
nicolenorman authored May 29, 2024
1 parent 26e93b3 commit 1798d82
Show file tree
Hide file tree
Showing 6 changed files with 241 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
.DS_Store
21 changes: 21 additions & 0 deletions Api/Order/HydrateOrderFromQuoteInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Bold\Checkout\Api\Order;

use Bold\Checkout\Api\Data\Http\Client\ResultInterface;
use Magento\Quote\Api\Data\CartInterface;

/**
* Hydrate Bold order from Magento quote.
*/
interface HydrateOrderFromQuoteInterface
{
/**
* Hydrate Bold simple order with Magento quote data
*
* @param CartInterface $quote
* @param string $publicOrderId
* @return ResultInterface
*/
public function hydrate(CartInterface $quote, string $publicOrderId): ResultInterface;
}
15 changes: 13 additions & 2 deletions Model/Http/BoldClient.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Bold\Checkout\Model\Http\Client\Command\GetCommand;
use Bold\Checkout\Model\Http\Client\Command\PatchCommand;
use Bold\Checkout\Model\Http\Client\Command\PostCommand;
use Bold\Checkout\Model\Http\Client\Command\PutCommand;
use Bold\Checkout\Model\Http\Client\UserAgent;
use Magento\Framework\Exception\LocalizedException;

Expand Down Expand Up @@ -51,28 +52,36 @@ class BoldClient implements ClientInterface
*/
private $deleteCommand;

/**
* @var PutCommand
*/
private $putCommand;

/**
* @param ConfigInterface $config
* @param UserAgent $userAgent
* @param GetCommand $getCommand
* @param PostCommand $postCommand
* @param PatchCommand $patchCommand
* @param DeleteCommand $deleteCommand
* @param PutCommand $putCommand
*/
public function __construct(
ConfigInterface $config,
UserAgent $userAgent,
GetCommand $getCommand,
PostCommand $postCommand,
PatchCommand $patchCommand,
DeleteCommand $deleteCommand
DeleteCommand $deleteCommand,
PutCommand $putCommand
) {
$this->config = $config;
$this->userAgent = $userAgent;
$this->getCommand = $getCommand;
$this->postCommand = $postCommand;
$this->patchCommand = $patchCommand;
$this->deleteCommand = $deleteCommand;
$this->putCommand = $putCommand;
}

/**
Expand Down Expand Up @@ -100,7 +109,9 @@ public function post(int $websiteId, string $url, array $data): ResultInterface
*/
public function put(int $websiteId, string $url, array $data): ResultInterface
{
throw new LocalizedException(__('Put method is not implemented.'));
$url = $this->getUrl($websiteId, $url);
$headers = $this->getHeaders($websiteId);
return $this->putCommand->execute($websiteId, $url, $headers, $data);
}

/**
Expand Down
198 changes: 198 additions & 0 deletions Model/Order/HydrateOrderFromQuote.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
<?php
declare(strict_types=1);

namespace Bold\Checkout\Model\Order;

use Bold\Checkout\Api\Data\Http\Client\ResultInterface;
use Bold\Checkout\Api\Http\ClientInterface;
use Bold\Checkout\Api\Order\HydrateOrderFromQuoteInterface;
use Bold\Checkout\Model\Order\Address\Converter;
use Bold\Checkout\Model\Quote\GetCartLineItems;
use Magento\Quote\Api\Data\CartInterface;
use Magento\Quote\Model\Quote\Address\ToOrderAddress;

/**
* Hydrate Bold order from Magento quote.
*/
class HydrateOrderFromQuote implements HydrateOrderFromQuoteInterface
{
private const HYDRATE_ORDER_URL = 'checkout_sidekick/{{shopId}}/order/%s';
private const EXPECTED_SEGMENTS = [
'subtotal',
'shipping',
'discount',
'tax',
'grand_total',
];

/**
* @var ClientInterface
*/
private $client;

/**
* @var GetCartLineItems
*/
private $getCartLineItems;

/**
* @var Converter
*/
private $addressConverter;

/**
* @var ToOrderAddress
*/
private $quoteToOrderAddressConverter;

/**
* @param ClientInterface $client
* @param GetCartLineItems $getCartLineItems
* @param Converter $addressConverter
* @param ToOrderAddress $quoteToOrderAddressConverter
*/
public function __construct(
ClientInterface $client,
GetCartLineItems $getCartLineItems,
Converter $addressConverter,
ToOrderAddress $quoteToOrderAddressConverter,
) {
$this->client = $client;
$this->getCartLineItems = $getCartLineItems;
$this->addressConverter = $addressConverter;
$this->quoteToOrderAddressConverter = $quoteToOrderAddressConverter;
}

/**
* @param CartInterface $quote
* @param string $publicOrderId
* @return ResultInterface
*/
public function hydrate(CartInterface $quote, string $publicOrderId): ResultInterface
{
$websiteId = (int)$quote->getStore()->getWebsiteId();
$billingAddress = $this->quoteToOrderAddressConverter->convert($quote->getBillingAddress());

if ($quote->getIsVirtual()) {
$totals = $quote->getBillingAddress()->getTotals();
} else {
$totals = $quote->getShippingAddress()->getTotals();
$shippingDescription = $quote->getShippingAddress()->getShippingDescription();
}

list($fees, $discounts) = $this->getFeesAndDiscounts($totals);
$discountTotal = array_reduce($discounts, function($sum, $discountLine) {
return $sum + $discountLine['value'];
});

$body = [
'billing_address' => $this->addressConverter->convert($billingAddress),
'cart_items' => $this->getCartLineItems->getItems($quote),
'taxes' => $this->getTaxLines($totals['tax']['full_info']),
'discounts' => $discounts,
'fees' => $fees,
'shipping_line' => [
'rate_name' => $shippingDescription ?? '',
'cost' => $this->convertToCents($totals['shipping']['value'])
],
'totals' => [
'sub_total' => $this->convertToCents($totals['subtotal']['value']),
'tax_total' => $this->convertToCents($totals['tax']['value']),
'discount_total' => $discountTotal ?? 0,
'shipping_total' => $this->convertToCents($totals['shipping']['value']),
'order_total' => $this->convertToCents($totals['grand_total']['value'])
],
];

if ($quote->getCustomer()->getId()) {
$body['customer'] = [
'platform_id' => (string)$quote->getCustomerId(),
'first_name' => $quote->getCustomerFirstname(),
'last_name' => $quote->getCustomerLastname(),
'email_address' => $quote->getCustomerEmail(),
];
} else {
$body['customer'] = [
'platform_id' => null,
'first_name' => $billingAddress->getFirstname(),
'last_name' => $billingAddress->getLastname(),
'email_address' => $billingAddress->getEmail(),
];
}

$url = sprintf(self::HYDRATE_ORDER_URL, $publicOrderId);
return $this->client->put($websiteId, $url, $body);
}

/**
* Converts a dollar amount to cents
*
* @param float|string $dollars
* @return integer
*/
private function convertToCents(float|string $dollars): int
{
return (int)round(floatval($dollars) * 100);
}

/**
* Get formatted tax lines
*
* @param array $taxes
* @return array
*/
private function getTaxLines(array $taxes): array
{
$taxLines = [];

foreach ($taxes as $tax){
$taxLines[] = [
'name' => $tax['id'],
'value' => $this->convertToCents($tax['base_amount'])
];
}

return $taxLines;
}

/**
* Looks at total segments and makes unrecognized segments into fees and discounts
*
* @param array $totals
* @return array
*/
private function getFeesAndDiscounts(array $totals): array
{
$fees = [];
$discounts = [];

if (isset($totals['discount'])) {
$discounts[] = [
'line_text' => $totals['discount']['code'],
'value' => abs($this->convertToCents($totals['discount']['value']))
];
}

foreach ($totals as $segment) {
if (in_array($segment['code'], self::EXPECTED_SEGMENTS) || !$segment['value']) {
continue;
}

$description = $totalSegment['title'] ?? ucfirst(str_replace('_', ' ', $segment['code']));

if ($segment['value'] > 0) {
$fees[] = [
'description' => $description,
'value' => $this->convertToCents($segment['value'])
];
} else {
$discounts[] = [
'line_text' => $description,
'value' => abs($this->convertToCents($segment['value']))
];
}
}

return [$fees, $discounts];
}
}
2 changes: 2 additions & 0 deletions Model/Quote/GetCartLineItems.php
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ private function getLineItem(CartItemInterface $item): array
{
return [
'id' => (int)$item->getProduct()->getId(),
'sku' => $item->getSku(),
'quantity' => $this->extractLineItemQuantity($item),
'title' => $this->getLineItemName($item),
'product_title' => $this->getLineItemName($item),
Expand All @@ -107,6 +108,7 @@ private function getLineItem(CartItemInterface $item): array
'requires_shipping' => !$item->getProduct()->getIsVirtual(),
'line_item_key' => (string)$item->getId(),
'price' => $this->getLineItemPrice($item),
'vendor' => '',
];
}

Expand Down
5 changes: 5 additions & 0 deletions etc/di.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@
<argument name="client" xsi:type="object">Bold\Checkout\Model\Http\BoldClient</argument>
</arguments>
</type>
<type name="Bold\Checkout\Model\Order\HydrateOrderFromQuote">
<arguments>
<argument name="client" xsi:type="object">Bold\Checkout\Model\Http\BoldClient</argument>
</arguments>
</type>
<type name="Bold\Checkout\Model\Payment\Gateway\Service">
<arguments>
<argument name="httpClient" xsi:type="object">Bold\Checkout\Model\Http\BoldClient</argument>
Expand Down

0 comments on commit 1798d82

Please sign in to comment.