Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add fast checkout hydrateOrder endpoint #261

Merged
merged 4 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.idea
.DS_Store
nicolenorman marked this conversation as resolved.
Show resolved Hide resolved
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();
nicolenorman marked this conversation as resolved.
Show resolved Hide resolved
} 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()) {
nicolenorman marked this conversation as resolved.
Show resolved Hide resolved
$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
nicolenorman marked this conversation as resolved.
Show resolved Hide resolved
{
$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' => '',
nicolenorman marked this conversation as resolved.
Show resolved Hide resolved
];
}

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