CoolCMS API

VoucherRedemptionService
in package

FinalYes

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()):

  1. the code exists and available = 1,
  2. validity start <= $at <= end (one shared snapshot instant),
  3. usage limit: maxNumber > 0 && currentNumber >= maxNumber means exhausted (0 is unlimited); the atomic VoucherManager::incrementUsage() is the final guard under concurrency, this check is only a pre-check,
  4. a price row for the currency exists (without it the voucher is not valid in that currency),
  5. the cart value (items with VAT after discounts) lies within [minPrice, maxPrice] of the currency row,
  6. 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

Methods

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 through SettingContext

$at : DateTimeInterface|null = null
Return values
Result

reason()

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> = []
Return values
array{errorCode: string, params: array}
On this page

Search results