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
34 changes: 8 additions & 26 deletions src/Storefront/Controller/RefundController.php
Original file line number Diff line number Diff line change
Expand Up @@ -200,12 +200,8 @@ public function refund(Request $request, Context $context): JsonResponse
}

$rawAmount = (string)$request->request->get('amount');
$orderAmountTotal = $order->getAmountTotal();

['amountInUnits' => $amountInUnits] = $this->normalizeRefundAmount(
$rawAmount,
$orderAmountTotal
);
['amountInUnits' => $amountInUnits] = $this->normalizeRefundAmount($rawAmount);

try {
$orderTransactionId = $this->getLatestMultiSafepayTransactionId($order);
Expand Down Expand Up @@ -433,26 +429,21 @@ private function getLatestMultiSafepayTransactionId(OrderEntity $order): ?string
/**
* Normalize the refund amount coming from Admin UI.
*
* The admin UI may send the amount either:
* - As full units (e.g. "10", "10.00", "10,00")
* - Or as cents (e.g. "1000")
*
* Heuristic used for integer-like inputs (no dot/comma):
* - If the integer is less than or equal to the order total (rounded to an int), treat it as units.
* - Otherwise treat it as cents.
* The admin UI service (MultiSafepayApi.service.js) always sends the amount as an
* integer number of cents, so integer-like input (no dot/comma) is interpreted as cents.
* Decimal input (dot or comma) is interpreted as full units.
*
* Examples:
* - raw "9.99" => units 9.99, cents 999
* - raw "9,99" => units 9.99, cents 999
* - raw "10" (order total 100.00) => units 10.0, cents 1000
* - raw "1000" (order total 10.00) => units 10.0, cents 1000
* - raw "20" => units 0.20, cents 20
* - raw "1000" => units 10.0, cents 1000
*
* @param string $rawAmount Amount as received from the request
* @param float $orderAmountTotal Order total in full units (e.g., 100.00)
*
* @return array{amountInUnits: float, amountInCents: int} Normalized amount in units and cents
*/
private function normalizeRefundAmount(string $rawAmount, float $orderAmountTotal): array
private function normalizeRefundAmount(string $rawAmount): array
{
if (str_contains($rawAmount, ',') || str_contains($rawAmount, '.')) {
$amountInUnits = (float)str_replace(',', '.', $rawAmount);
Expand All @@ -461,16 +452,7 @@ private function normalizeRefundAmount(string $rawAmount, float $orderAmountTota
return ['amountInUnits' => $amountInUnits, 'amountInCents' => $amountInCents];
}

$amountAsInt = (int)$rawAmount;

if ($amountAsInt <= (int)round($orderAmountTotal)) {
$amountInUnits = (float)$amountAsInt;
$amountInCents = (int)round($amountInUnits * 100);

return ['amountInUnits' => $amountInUnits, 'amountInCents' => $amountInCents];
}

$amountInCents = $amountAsInt;
$amountInCents = (int)$rawAmount;
$amountInUnits = $amountInCents / 100;

return ['amountInUnits' => $amountInUnits, 'amountInCents' => $amountInCents];
Expand Down
21 changes: 10 additions & 11 deletions tests/Unit/Storefront/Controller/RefundControllerCoverageTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -113,30 +113,29 @@ protected function setUp(): void
/**
* Verifies amount normalization for the admin refund endpoint.
*
* The controller accepts user input either as full units ("9.99", "9,99", "10") or as cents ("1000").
* This test ensures both the parsed unit value and the derived cents value are consistent.
* The admin UI service always sends integer cents, so integer-like input is cents;
* decimal input ("9.99", "9,99") is full units.
*
* @throws ReflectionException
*/
public function testNormalizeRefundAmountParsesDecimalsAndCentsCorrectly(): void
{
$method = new ReflectionMethod(RefundController::class, 'normalizeRefundAmount');

$result = $method->invoke($this->controller, '9.99', 100.00);
$result = $method->invoke($this->controller, '9.99');
$this->assertEqualsWithDelta(9.99, $result['amountInUnits'], 0.00001);
$this->assertSame(999, $result['amountInCents']);

$result = $method->invoke($this->controller, '9,99', 100.00);
$result = $method->invoke($this->controller, '9,99');
$this->assertEqualsWithDelta(9.99, $result['amountInUnits'], 0.00001);
$this->assertSame(999, $result['amountInCents']);

// Integer-like input <= order total => treat as units
$result = $method->invoke($this->controller, '10', 100.00);
$this->assertEqualsWithDelta(10.0, $result['amountInUnits'], 0.00001);
$this->assertSame(1000, $result['amountInCents']);
// Integer-like input => always cents, even when the value fits within the order total
$result = $method->invoke($this->controller, '20');
$this->assertEqualsWithDelta(0.20, $result['amountInUnits'], 0.00001);
$this->assertSame(20, $result['amountInCents']);

// Integer-like input > order total => treat as cents
$result = $method->invoke($this->controller, '1000', 10.00);
$result = $method->invoke($this->controller, '1000');
$this->assertEqualsWithDelta(10.0, $result['amountInUnits'], 0.00001);
$this->assertSame(1000, $result['amountInCents']);
}
Expand Down Expand Up @@ -542,7 +541,7 @@ public function testRefundRequiresShoppingCartUsesCentsForRefundItemUnitPrice():

$request = new Request([], [
'orderId' => $orderId,
'amount' => 15.00,
'amount' => 1500,
]);

$response = $this->controller->refund($request, $this->context);
Expand Down