Skip to content
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
7 changes: 7 additions & 0 deletions Api/ConfigInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ interface ConfigInterface
public const CONFIG_WEBHOOK_URL = 'agentic_commerce/agentic_checkout/webhook_url';
public const CONFIG_WEBHOOK_SECRET = 'agentic_commerce/agentic_checkout/webhook_secret';
public const CONFIG_WEBHOOKS_ENABLED = 'agentic_commerce/agentic_checkout/enable_webhooks';
public const CONFIG_API_TOKEN = 'agentic_commerce/agentic_checkout/api_token';
public const CONFIG_ORDER_STATUS_MAP = 'agentic_commerce/agentic_checkout/order_status_map';

public const CONFIG_GTIN_SOURCE = 'agentic_commerce/product_feed/gtin_source';
Expand Down Expand Up @@ -124,6 +125,12 @@ public function getIdempotencyTtl(?int $storeId = null): int;
*/
public function getIsWebhooksEnabled(?int $storeId = null): bool;

/**
* @param int|null $storeId
* @return string|null
*/
public function getApiToken(?int $storeId = null): ?string;

/**
* Get order status mapping configuration
*
Expand Down
8 changes: 7 additions & 1 deletion Controller/ApiController.php
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,14 @@ protected function createRequestObjectAndValidate(callable $factory): mixed
public function makeErrorResponse(ErrorResponseInterface $errorResponse, int $statusCode = 400): ResultJson
{
/** @var ErrorResponse $errorResponse */
if ($errorResponse->getData('_statusCode')) {
// @phpstan-ignore cast.int
$statusCode = (int) $errorResponse->getData('_statusCode');
$errorResponse->unsetData('_statusCode');
}

/** @var array<mixed> $data */
$data = $errorResponse->getData();
$data = $errorResponse->toArray();

return $this->makeJsonResponse($data, $statusCode);
}
Expand Down
16 changes: 16 additions & 0 deletions Model/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,22 @@ public function getIsWebhooksEnabled(?int $storeId = null): bool
return $this->scopeConfig->isSetFlag(ConfigInterface::CONFIG_WEBHOOKS_ENABLED, ScopeInterface::SCOPE_STORE, $storeId);
}

/**
* @param int|null $storeId
* @return string|null
*/
public function getApiToken(?int $storeId = null): ?string
{
/** @var string|null $token */
$token = $this->scopeConfig->getValue(
ConfigInterface::CONFIG_API_TOKEN,
ScopeInterface::SCOPE_STORE,
$storeId
);

return $token ?: null;
}

/**
* Get order status mapping configuration
*
Expand Down
29 changes: 28 additions & 1 deletion Service/ComplianceService.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use Magebit\AgenticCommerce\Api\Data\Response\ErrorResponseInterfaceFactory;
use Magebit\AgenticCommerce\Model\Idempotency\Management as IdempotencyManagement;
use Magento\Framework\Encryption\EncryptorInterface;
use Magebit\AgenticCommerce\Api\ConfigInterface;

class ComplianceService
{
Expand All @@ -29,12 +30,14 @@ class ComplianceService
* @param IdempotencyManagement $idempotencyManagement
* @param JsonFactory $resultJsonFactory
* @param EncryptorInterface $encryptor
* @param ConfigInterface $config
*/
public function __construct(
protected readonly ErrorResponseInterfaceFactory $errorResponseFactory,
protected readonly IdempotencyManagement $idempotencyManagement,
protected readonly JsonFactory $resultJsonFactory,
protected readonly EncryptorInterface $encryptor
protected readonly EncryptorInterface $encryptor,
protected readonly ConfigInterface $config
) {
}

Expand All @@ -47,6 +50,21 @@ public function validateApiVersion(Http $request): bool
return $request->getHeader('API-Version') === self::API_VERSION;
}

/**
* @param Http $request
* @return bool
*/
public function validateApiToken(Http $request): bool
{
$apiToken = $this->config->getApiToken();

if (!$apiToken) {
return true;
}

return $request->getHeader('Authorization') === 'Bearer ' . $apiToken;
}

/**
* @param Http $request
* @return null|ErrorResponseInterface
Expand All @@ -61,6 +79,15 @@ public function validateRequest(Http $request): ?ErrorResponseInterface
]]);
}

if (!$this->validateApiToken($request)) {
return $this->errorResponseFactory->create(['data' => [
'type' => ErrorResponseInterface::TYPE_INVALID_REQUEST,
'code' => 'invalid_api_token',
'message' => 'Invalid API token',
'_statusCode' => 401,
]]);
}

if ($idempotencyError = $this->validateIdempotency($request)) {
return $idempotencyError;
}
Expand Down
18 changes: 12 additions & 6 deletions etc/adminhtml/system.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,36 +37,42 @@
<comment>Base path to use for the router. Default is `checkout_sessions`</comment>
<validate>required-entry</validate>
</field>
<field id="order_success_page" type="text" sortOrder="20" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<field id="api_token" type="obscure" sortOrder="20" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>API Token</label>
<comment>Leave empty to skip Bearer token validation</comment>
<backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
</field>
<field id="order_success_page" type="text" sortOrder="30" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>Order Success Page</label>
<validate>required-entry</validate>
</field>
<field id="session_links" type="text" sortOrder="30" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<field id="session_links" type="text" sortOrder="40" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>Checkout Session Links</label>
<comment>List of links (e.g. ToS/privacy policy/etc.) to be displayed to the customer. List of links (e.g. ToS/privacy policy/etc.) to be displayed to the customer</comment>
<frontend_model>Magebit\AgenticCommerce\Block\Adminhtml\Form\Field\SessionLinks</frontend_model>
<backend_model>Magento\Config\Model\Config\Backend\Serialized\ArraySerialized</backend_model>
</field>
<field id="enable_webhooks" type="select" sortOrder="40" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<field id="enable_webhooks" type="select" sortOrder="50" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>Enable Webhooks</label>
<source_model>Magento\Config\Model\Config\Source\Yesno</source_model>
</field>
<field id="webhook_url" type="text" sortOrder="50" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<field id="webhook_url" type="text" sortOrder="60" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>Webhook URL</label>
<comment>URL to send webhooks to</comment>
<validate>required-entry</validate>
<depends>
<field id="enable_webhooks">1</field>
</depends>
</field>
<field id="webhook_secret" type="text" sortOrder="60" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<field id="webhook_secret" type="obscure" sortOrder="70" showInWebsite="1" showInStore="1" showInDefault="1" translate="label">
<label>Webhook Secret</label>
<comment>Secret to use for the webhook</comment>
<backend_model>Magento\Config\Model\Config\Backend\Encrypted</backend_model>
<depends>
<field id="enable_webhooks">1</field>
</depends>
</field>
<field id="order_status_map" translate="label" sortOrder="70" showInDefault="1" showInWebsite="1" showInStore="1">
<field id="order_status_map" translate="label" sortOrder="80" showInDefault="1" showInWebsite="1" showInStore="1">
<label>Order Status Mapping</label>
<comment>Map Magento order statuses to Agentic Commerce order statuses for webhooks</comment>
<frontend_model>Magebit\AgenticCommerce\Block\Adminhtml\Form\Field\OrderStatusMap</frontend_model>
Expand Down
2 changes: 2 additions & 0 deletions etc/config.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
<router_base_path>checkout_sessions</router_base_path>
<order_success_page>checkout/onepage/success</order_success_page>
<enable_webhooks>0</enable_webhooks>
<api_token backend_model="Magento\Config\Model\Config\Backend\Encrypted" />
<webhook_secret backend_model="Magento\Config\Model\Config\Backend\Encrypted" />
<order_status_map>
<item1>
<magento_order_status>pending</magento_order_status>
Expand Down