VoucherRedemptionService
in package
Validates voucher codes and snapshots their redemption onto an order.
Checks per voucher, in order (the first failure returns a failure Result); they apply only
to newly redeemed codes, while a code held by a non-canceled order is carried over without
validation (see applyVouchers()):
- the code exists and
available = 1, - validity
start <= $at <= end(one shared snapshot instant), - usage limit:
maxNumber > 0 && currentNumber >= maxNumbermeans exhausted (0 is unlimited); the atomic VoucherManager::incrementUsage() is the final guard under concurrency, this check is only a pre-check, - a price row for the currency exists (without it the voucher is not valid in that currency),
- the cart value (items with VAT after discounts) lies within
[minPrice, maxPrice]of the currency row, 4/voucher.limitPerOrder: maximum vouchers per order (gates only new redemptions).
Snapshot: code and percents come from the voucher, price is the fixed discount base
without VAT; vat and priceWithVat are filled by OrderCalculator::recalculate()
because they depend on the final set of items.
Vouchers are read through the Doctrine repository (outside a save hook, called before
manager->save); counters are written only by atomic SQL in the manager.
Table of Contents
Properties
- $calculator : OrderCalculator
- $em : EntityManagerInterface
Methods
- __construct() : mixed
- applyVouchers() : Result
- Validates the codes and replaces the `$order->vouchers` collection with snapshots.
-
reason()
: array{errorCode: string, params: array
} - Builds the machine-readable rejection reason for the caller (the front checkout translates it into the customer's language). The `message` stays for admin and log only; without the code every language would show the same text.
Properties
$calculator read-only
private
OrderCalculator
$calculator
$em read-only
private
EntityManagerInterface
$em
Methods
__construct()
public
__construct(EntityManagerInterface $em, OrderCalculator $calculator) : mixed
Parameters
- $em : EntityManagerInterface
- $calculator : OrderCalculator
applyVouchers()
Validates the codes and replaces the `$order->vouchers` collection with snapshots.
public
applyVouchers(Order $order, array<int, string> $codes, int $voucherLimitPerOrder[, DateTimeInterface|null $at = null ]) : Result
Does not increment currentNumber; that is an effect of saving the order
(VoucherManager::incrementUsage() is called inside
OrderManager::beforeSave(), within the save
transaction before the commit).
IMPORTANT: validation belongs only to the creation of a redemption. A new code (order insert
or a code added during an edit) is fully validated (availability, window, capacity, currency,
cart bounds). A code already held by a non-canceled order is not validated at all and its
snapshot is carried over unchanged (same entity values from the time of redemption; later
voucher configuration changes do not touch an existing order). Without the carry-over the
capacity pre-check would block itself (current_number already counts this order) and an
order with an exhausted or expired held voucher could be neither edited nor recalculated.
A canceled order holds no redemption (cancellation decremented it); restoring is guarded by
OrderTransitionManager::checkVoucherCapacity(). Removing a code during an edit drops its
snapshot from the collection (orphan removal) and the usage counter diff decrements it.
Parameters
- $order : Order
- $codes : array<int, string>
- $voucherLimitPerOrder : int
-
4/voucher.limitPerOrder(0 means no limit); read by the calling manager throughSettingContext - $at : DateTimeInterface|null = null
Return values
Resultreason()
Builds the machine-readable rejection reason for the caller (the front checkout translates it into the customer's language). The `message` stays for admin and log only; without the code every language would show the same text.
private
static reason(string $errorCode[, array<string, int|string> $params = [] ]) : array{errorCode: string, params: array}
Parameters
- $errorCode : string
- $params : array<string, int|string> = []