Skip to content
Open
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
25 changes: 25 additions & 0 deletions core/components/minishop3/config/routes/web.php
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,31 @@
});
});

// Public checkout discovery — active deliveries / payments (no cart token)
$router->group('/delivery', function ($router) use ($modx) {
$router->get('/get/{id}', function ($params) use ($modx) {
$controller = new \MiniShop3\Controllers\Api\Web\DeliveryController($modx);
return $controller->get($params);
});

$router->get('/list', function ($params) use ($modx) {
$controller = new \MiniShop3\Controllers\Api\Web\DeliveryController($modx);
return $controller->getList($params);
});
});

$router->group('/payment', function ($router) use ($modx) {
$router->get('/get/{id}', function ($params) use ($modx) {
$controller = new \MiniShop3\Controllers\Api\Web\PaymentController($modx);
return $controller->get($params);
});

$router->get('/list', function ($params) use ($modx) {
$controller = new \MiniShop3\Controllers\Api\Web\PaymentController($modx);
return $controller->getList($params);
});
});

$router->get('/health', function () use ($modx) {
return Response::success([
'status' => 'ok',
Expand Down
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/en/default.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@
$_lang['ms3_err_product_not_in_category_scope'] = 'Product is not in the scope of this category.';
$_lang['ms3_err_status_nf'] = 'Status with this identifier not found.';
$_lang['ms3_err_delivery_nf'] = 'Delivery method with this identifier not found.';
$_lang['ms3_err_delivery_id_required'] = 'Delivery ID is required';
$_lang['ms3_err_payment_nf'] = 'Payment method with this identifier not found.';
$_lang['ms3_err_payment_id_required'] = 'Payment ID is required';
$_lang['ms3_err_status_final'] = 'Final status is set. It cannot be changed.';
$_lang['ms3_err_status_fixed'] = 'Fixed status is set. You cannot change it to earlier one.';
$_lang['ms3_err_status_wrong'] = 'Invalid order status.';
Expand Down
2 changes: 2 additions & 0 deletions core/components/minishop3/lexicon/ru/default.inc.php
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,9 @@
$_lang['ms3_err_product_not_in_category_scope'] = 'Товар не входит в область этой категории.';
$_lang['ms3_err_status_nf'] = 'Статус с таким идентификатором не найден.';
$_lang['ms3_err_delivery_nf'] = 'Способ доставки с таким идентификатором не найден.';
$_lang['ms3_err_delivery_id_required'] = 'Не указан ID доставки';
$_lang['ms3_err_payment_nf'] = 'Способ оплаты с таким идентификатором не найден.';
$_lang['ms3_err_payment_id_required'] = 'Не указан ID оплаты';
$_lang['ms3_err_status_final'] = 'Установлен финальный статус. Его нельзя менять.';
$_lang['ms3_err_status_fixed'] = 'Установлен фиксирующий статус. Вы не можете сменить его на более ранний.';
$_lang['ms3_err_status_wrong'] = 'Неверный статус заказа.';
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Controllers\Api\Web;

use MiniShop3\Router\HttpStatus;
use MiniShop3\Router\Response;
use MiniShop3\Services\Delivery\DeliveryCatalogService;
use MODX\Revolution\modX;

/**
* Public delivery discovery for Web API (#568).
*/
class DeliveryController
{
protected modX $modx;

public function __construct(modX $modx)
{
$this->modx = $modx;
$this->modx->lexicon->load('minishop3:default');
}

/**
* GET /api/v1/delivery/get/{id}
*
* @param array<string, mixed> $params
*/
public function get(array $params = []): Response
{
$id = (int) ($params['id'] ?? 0);
if ($id <= 0) {
return Response::error(
$this->modx->lexicon('ms3_err_delivery_id_required'),
HttpStatus::BAD_REQUEST
);
}

$item = $this->catalog()->getById($id, $params);
if ($item === null) {
return Response::error(
$this->modx->lexicon('ms3_err_delivery_nf'),
HttpStatus::NOT_FOUND
);
}

return Response::success($item);
}

/**
* GET /api/v1/delivery/list
*
* Query: include_payments (default true), include_required_fields (default false)
*
* @param array<string, mixed> $params
*/
public function getList(array $params = []): Response
{
return Response::success($this->catalog()->getList($params));
}

private function catalog(): DeliveryCatalogService
{
/** @var DeliveryCatalogService $service */
$service = $this->modx->services->get('ms3_delivery_catalog');

return $service;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Controllers\Api\Web;

use MiniShop3\Router\HttpStatus;
use MiniShop3\Router\Response;
use MiniShop3\Services\Payment\PaymentCatalogService;
use MODX\Revolution\modX;

/**
* Public payment discovery for Web API (#569).
*/
class PaymentController
{
protected modX $modx;

public function __construct(modX $modx)
{
$this->modx = $modx;
$this->modx->lexicon->load('minishop3:default');
}

/**
* GET /api/v1/payment/get/{id}
*
* @param array<string, mixed> $params
*/
public function get(array $params = []): Response
{
$id = (int) ($params['id'] ?? 0);
if ($id <= 0) {
return Response::error(
$this->modx->lexicon('ms3_err_payment_id_required'),
HttpStatus::BAD_REQUEST
);
}

$item = $this->catalog()->getById($id, $params);
if ($item === null) {
return Response::error(
$this->modx->lexicon('ms3_err_payment_nf'),
HttpStatus::NOT_FOUND
);
}

return Response::success($item);
}

/**
* GET /api/v1/payment/list
*
* Query: delivery_id (optional), include_delivery_ids (default false)
*
* @param array<string, mixed> $params
*/
public function getList(array $params = []): Response
{
return Response::success($this->catalog()->getList($params));
}

private function catalog(): PaymentCatalogService
{
/** @var PaymentCatalogService $service */
$service = $this->modx->services->get('ms3_payment_catalog');

return $service;
}
}
4 changes: 4 additions & 0 deletions core/components/minishop3/src/Middleware/TokenMiddleware.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class TokenMiddleware implements MiddlewareInterface
'/api/v1/category/get/',
'/api/v1/category/list',
'/api/v1/category/tree',
'/api/v1/delivery/get/',
'/api/v1/delivery/list',
'/api/v1/payment/get/',
'/api/v1/payment/list',
'/api/v1/customer/token/get',
'/api/v1/customer/logout',
'/api/v1/health',
Expand Down
8 changes: 8 additions & 0 deletions core/components/minishop3/src/ServiceRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ class ServiceRegistry
'class' => \MiniShop3\Services\Category\CategoryCatalogService::class,
'interface' => null,
],
'ms3_delivery_catalog' => [
'class' => \MiniShop3\Services\Delivery\DeliveryCatalogService::class,
'interface' => null,
],
'ms3_payment_catalog' => [
'class' => \MiniShop3\Services\Payment\PaymentCatalogService::class,
'interface' => null,
],
'ms3_repeater_field' => [
'class' => \MiniShop3\Services\ExtraFields\RepeaterFieldService::class,
'interface' => null,
Expand Down
2 changes: 2 additions & 0 deletions core/components/minishop3/src/ServiceRegistryFactories.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ public static function map(): array
'ms3_product_link_service' => $modxOnly(),
'ms3_product_catalog' => $modxOnly(),
'ms3_category_catalog' => $modxOnly(),
'ms3_delivery_catalog' => $modxOnly(),
'ms3_payment_catalog' => $modxOnly(),
'ms3_repeater_field' => $modxOnly(),
'ms3_extra_fields' => $modxOnly(),
'ms3_key_value_field' => $modxOnly(),
Expand Down
22 changes: 22 additions & 0 deletions core/components/minishop3/src/Services/Catalog/CatalogLexicon.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Services\Catalog;

use MODX\Revolution\modX;

/**
* Shared lexicon helpers for public Web API catalogs.
*/
final class CatalogLexicon
{
public static function translateMs3Name(modX $modx, string $name): string
{
if ($name === '' || !str_starts_with($name, 'ms3_')) {
return $name;
}

return $modx->lexicon($name);
}
}
12 changes: 12 additions & 0 deletions core/components/minishop3/src/Services/Catalog/CatalogQuery.php
Original file line number Diff line number Diff line change
Expand Up @@ -100,4 +100,16 @@ public static function toBool(mixed $value): bool

return in_array(strtolower((string) $value), ['1', 'true', 'yes', 'on'], true);
}

/**
* @param array<string, mixed> $params
*/
public static function resolveBool(array $params, string $key, bool $default): bool
{
if (!array_key_exists($key, $params)) {
return $default;
}

return self::toBool($params[$key]);
}
}
108 changes: 108 additions & 0 deletions core/components/minishop3/src/Services/Catalog/CheckoutMemberMap.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Services\Catalog;

use MiniShop3\Model\msDelivery;
use MiniShop3\Model\msDeliveryMember;
use MiniShop3\Model\msPayment;
use MODX\Revolution\modX;
use xPDO\Om\xPDOQuery;

/**
* Batched active delivery↔payment member maps for checkout discovery (#568/#569).
*/
final class CheckoutMemberMap
{
private modX $modx;

public function __construct(modX $modx)
{
$this->modx = $modx;
}

public function isActiveDelivery(int $deliveryId): bool
{
if ($deliveryId <= 0) {
return false;
}

return (bool) $this->modx->getCount(msDelivery::class, [
'id' => $deliveryId,
'active' => 1,
]);
}

/**
* @return array<int, list<int>> delivery_id => payment ids (position ASC, id ASC)
*/
public function paymentIdsByDelivery(): array
{
$c = $this->modx->newQuery(msPayment::class);
$c->setClassAlias('msPayment');
$c->select('msPayment.id AS payment_id, Member.delivery_id AS delivery_id');
$c->innerJoin(
msDeliveryMember::class,
'Member',
'Member.payment_id = msPayment.id'
);
$c->innerJoin(
msDelivery::class,
'Delivery',
'Delivery.id = Member.delivery_id AND Delivery.active = 1'
);
$c->where(['msPayment.active' => 1]);
$c->sortby('msPayment.position', 'ASC');
$c->sortby('msPayment.id', 'ASC');

return $this->fetchGroupedIds($c, 'delivery_id', 'payment_id');
}

/**
* @return array<int, list<int>> payment_id => delivery ids (position ASC, id ASC)
*/
public function deliveryIdsByPayment(): array
{
$c = $this->modx->newQuery(msDelivery::class);
$c->setClassAlias('msDelivery');
$c->select('msDelivery.id AS delivery_id, Member.payment_id AS payment_id');
$c->innerJoin(
msDeliveryMember::class,
'Member',
'Member.delivery_id = msDelivery.id'
);
$c->innerJoin(
msPayment::class,
'Payment',
'Payment.id = Member.payment_id AND Payment.active = 1'
);
$c->where(['msDelivery.active' => 1]);
$c->sortby('msDelivery.position', 'ASC');
$c->sortby('msDelivery.id', 'ASC');

return $this->fetchGroupedIds($c, 'payment_id', 'delivery_id');
}

/**
* @return array<int, list<int>>
*/
private function fetchGroupedIds(xPDOQuery $query, string $groupKey, string $idKey): array
{
if (!$query->prepare() || !$query->stmt->execute()) {
return [];
}

$map = [];
while ($row = $query->stmt->fetch(\PDO::FETCH_ASSOC)) {
$groupId = (int) ($row[$groupKey] ?? 0);
$memberId = (int) ($row[$idKey] ?? 0);
if ($groupId <= 0 || $memberId <= 0) {
continue;
}
$map[$groupId][] = $memberId;
}

return $map;
}
}
Loading