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
1 change: 0 additions & 1 deletion core/components/minishop3/config/ms3.services.example.php
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@
/*
* Configuration:
* -------------
* 'ms3_config_manager' - Configuration manager
* 'ms3_field_config_manager' - Field configuration manager
* 'ms3_config_service' - Facade over config managers
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,7 @@ private function isNested(array $params): bool

private function scopeService(): CategoryProductScopeService
{
$service = isset($this->modx->services)
? $this->modx->services->get('ms3_category_product_scope')
: null;
$service = $this->modx->services->get('ms3_category_product_scope');

return $service instanceof CategoryProductScopeService
? $service
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,14 @@ public function send(msOrder $order): array;
*/
public function receive(msOrder $order): array;

/**
* Get payment link for order without redirecting the customer
*
* @param msOrder $order Order for payment
* @return string|null Payment link or null if unavailable
*/
public function getPaymentLink(msOrder $order): ?string;

/**
* Calculate cost including payment system fee
*
Expand Down
4 changes: 0 additions & 4 deletions core/components/minishop3/src/ServiceRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,6 @@ class ServiceRegistry
* @var array
*/
protected array $defaultServices = [
'ms3_config_manager' => [
'class' => \MiniShop3\Services\ConfigManager::class,
'interface' => null,
],
'ms3_field_config_manager' => [
'class' => \MiniShop3\Services\FieldConfigManager::class,
'interface' => null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,7 @@ public function findInCategory(int $categoryId, int $productId, bool $nested = f

private function treeService(): CategoryTreeService
{
$service = isset($this->modx->services)
? $this->modx->services->get('ms3_category_tree')
: null;
$service = $this->modx->services->get('ms3_category_tree');

return $service instanceof CategoryTreeService
? $service
Expand Down
29 changes: 17 additions & 12 deletions core/components/minishop3/src/Services/Order/OrderStatusService.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

namespace MiniShop3\Services\Order;

use MiniShop3\Controllers\Payment\Payment;
use MiniShop3\MiniShop3;
use MiniShop3\Model\msOrder;
use MiniShop3\Model\msPayment;
use MiniShop3\Services\Payment\PaymentService;
use MiniShop3\Model\msOrderAddress;
use MiniShop3\Model\msOrderStatus as msOrderStatusModel;
use MiniShop3\Model\msCustomer;
Expand Down Expand Up @@ -402,18 +403,22 @@ protected function getLang(msOrder $msOrder): string
*/
protected function getPaymentLink(mixed $msPayment, msOrder $msOrder): string
{
$class = $msPayment->get('class');
if (!empty($class)) {
$this->ms3->loadCustomClasses('payment');
if (class_exists($class)) {
/** @var Payment $controller */
$controller = new $class($msOrder);
if (method_exists($controller, 'getPaymentLink')) {
return $controller->getPaymentLink($msOrder);
}
}
if (!$msPayment instanceof msPayment) {
return '';
}

$class = (string) $msPayment->get('class');
if ($class === '') {
return '';
}

/** @var PaymentService $paymentService */
$paymentService = $this->modx->services->get('ms3_payment_service');
$controller = $paymentService->loadPaymentHandler($msPayment);
if ($controller === null) {
return '';
}

return '';
return $controller->getPaymentLink($msOrder) ?? '';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
<?php

/**
* Regression: OrderStatusService::getPaymentLink must resolve payment handlers via PaymentService (#484).
*
* Run: php tests/OrderStatusServiceGetPaymentLinkTest.php
*/

declare(strict_types=1);

require __DIR__ . '/support/xpdo_stub.php';
require __DIR__ . '/support/xpdo_om_stub.php';
require __DIR__ . '/../vendor/autoload.php';
require __DIR__ . '/stubs/ModxStub.php';
require __DIR__ . '/stubs/StubMsOrder.php';
require __DIR__ . '/stubs/StubMsPayment.php';
require __DIR__ . '/stubs/StubPaymentLinkProvider.php';

use MiniShop3\MiniShop3;
use MiniShop3\Services\Order\OrderLogService;
use MiniShop3\Services\Order\OrderStatusService;
use MiniShop3\Services\Payment\PaymentService;
use MiniShop3\Tests\Stubs\StubMsOrder;
use MiniShop3\Tests\Stubs\StubMsPayment;
use MiniShop3\Tests\Stubs\StubPaymentLinkProvider;
use MODX\Revolution\modX;

$fail = static function (string $message): never {
fwrite(STDERR, "FAIL: {$message}\n");
exit(1);
};

$assertSame = static function (mixed $expected, mixed $actual, string $label) use ($fail): void {
if ($actual !== $expected) {
$fail(sprintf(
"%s:\nexpected: %s\nactual: %s",
$label,
var_export($expected, true),
var_export($actual, true)
));
}
};

$invokeGetPaymentLink = static function (
OrderStatusService $service,
mixed $msPayment,
StubMsOrder $msOrder
): string {
$method = new ReflectionMethod(OrderStatusService::class, 'getPaymentLink');
$method->setAccessible(true);

return $method->invoke($service, $msPayment, $msOrder);
};

$modx = new class extends modX {
public object $services;
public object $lexicon;

public function __construct()
{
parent::__construct();
$this->lexicon = new class {
public function load(string ...$topics): void
{
}
};
$this->services = new class {
/** @var array<string, object> */
private array $services = [];

public function register(string $key, object $service): void
{
$this->services[$key] = $service;
}

public function has(string $key): bool
{
return isset($this->services[$key]);
}

public function get(string $key): object
{
return $this->services[$key];
}
};
}
};

$ms3 = new class($modx) extends MiniShop3 {
public function __construct(modX $modx)
{
$this->modx = $modx;
}
};

$paymentService = new PaymentService($modx);
$ms3Property = new ReflectionProperty(PaymentService::class, 'ms3');
$ms3Property->setAccessible(true);
$ms3Property->setValue($paymentService, $ms3);

$modx->services->register('ms3', $ms3);
$modx->services->register('ms3_payment_service', $paymentService);

$orderLog = new OrderLogService($modx, $ms3);
$service = new OrderStatusService($modx, $ms3, $orderLog);

$msOrder = new StubMsOrder(['id' => 42]);

$link = $invokeGetPaymentLink(
$service,
new StubMsPayment(['class' => StubPaymentLinkProvider::class, 'id' => 1]),
$msOrder
);
$assertSame(StubPaymentLinkProvider::PAYMENT_LINK, $link, 'payment link via PaymentService');

$emptyForScalar = $invokeGetPaymentLink($service, new stdClass(), $msOrder);
$assertSame('', $emptyForScalar, 'non-msPayment returns empty string');

$emptyForMissingClass = $invokeGetPaymentLink(
$service,
new StubMsPayment(['class' => 'MiniShop3\\Tests\\Stubs\\MissingPaymentProvider', 'id' => 2]),
$msOrder
);
$assertSame('', $emptyForMissingClass, 'missing handler returns empty string');

$emptyForBlankClass = $invokeGetPaymentLink(
$service,
new StubMsPayment(['class' => '', 'id' => 3]),
$msOrder
);
$assertSame('', $emptyForBlankClass, 'empty payment class returns empty string');

fwrite(STDOUT, "OK OrderStatusServiceGetPaymentLinkTest\n");
exit(0);
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
*/
class CategoryProductScopeModxStub extends modX
{
/** @var object */
public object $services;

/** @var list<array{id: int, parent: int, published?: int, deleted?: int}> */
public array $products = [];

Expand All @@ -22,6 +25,22 @@ class CategoryProductScopeModxStub extends modX
/** @var list<array{class: class-string, criteria: mixed}> */
public array $getObjectCalls = [];

public function __construct()
{
parent::__construct();
$this->services = new class {
public function get(string $key): null
{
return null;
}

public function has(string $key): bool
{
return false;
}
};
}

public function getObject($className = '', $criteria = null, $cacheFlag = true)
{
$this->getObjectCalls[] = ['class' => $className, 'criteria' => $criteria];
Expand Down
4 changes: 4 additions & 0 deletions core/components/minishop3/tests/stubs/ModxStub.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
*/
class modX
{
public const LOG_LEVEL_ERROR = 1;
public const LOG_LEVEL_WARN = 2;
public const LOG_LEVEL_INFO = 3;

/** @var object|null */
public $user;

Expand Down
26 changes: 26 additions & 0 deletions core/components/minishop3/tests/stubs/StubMsOrder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Tests\Stubs;

use MiniShop3\Model\msOrder;

final class StubMsOrder extends msOrder
{
/** @var array<string, mixed> */
private array $fields;

/**
* @param array<string, mixed> $fields
*/
public function __construct(array $fields = [])
{
$this->fields = $fields;
}

public function get($key)
{
return $this->fields[$key] ?? null;
}
}
27 changes: 27 additions & 0 deletions core/components/minishop3/tests/stubs/StubMsPayment.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Tests\Stubs;

use MiniShop3\Model\msOrder;
use MiniShop3\Model\msPayment;

final class StubMsPayment extends msPayment
{
/** @var array<string, mixed> */
private array $fields;

/**
* @param array<string, mixed> $fields
*/
public function __construct(array $fields = [])
{
$this->fields = $fields;
}

public function get($key)
{
return $this->fields[$key] ?? null;
}
}
31 changes: 31 additions & 0 deletions core/components/minishop3/tests/stubs/StubPaymentLinkProvider.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace MiniShop3\Tests\Stubs;

use MiniShop3\Controllers\Payment\Payment;
use MiniShop3\Model\msOrder;

final class StubPaymentLinkProvider extends Payment
{
public const PAYMENT_LINK = 'https://pay.example/test-order';

public function send(msOrder $order): array
{
return [
'success' => true,
'message' => '',
'data' => ['payment_link' => self::PAYMENT_LINK],
];
}

public function receive(msOrder $order): array
{
return [
'success' => true,
'message' => '',
'data' => [],
];
}
}
31 changes: 31 additions & 0 deletions core/components/minishop3/tests/support/xpdo_om_stub.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace xPDO\Om;

use xPDO\xPDO;

class xPDOObject
{
}

class xPDOSimpleObject extends xPDOObject
{
/** @var xPDO|null */
protected $xpdo;

public function __construct(?xPDO $xpdo = null)
{
$this->xpdo = $xpdo;
}

/**
* @param string $key
* @return mixed
*/
public function get($key)
{
return null;
}
}
Loading