fix: stop caching rejected voucher checks, document response codes

This commit is contained in:
fromme committed 2026-10-06 11:29:14 +02:00
1 parent a14c41de4e
commit 56af0f921b
3 files changed
+102 -3

No files matched your search

+11 -1
View File
@@ -424,7 +424,17 @@ fehlerhaft · 1003 Status im Vorgang nicht korrekt · 1011-1018 Zahlungsabgleich
605 Empfängername · 606 Betrag fehlt · 607/608 Betrag kleiner/größer als #1# Euro) ·
610/611 Buchungsart · 612/613 Gutscheinart · 614/615 Gutscheinstamm · 616 Kapazität nicht
ausreichend · 617 Rechnungsempfänger fehlt · 618 Zahlungsart fehlt · 619/620 Agentur-ID ·
621 Gutscheindaten fehlen · 622 Einlösecode fehlt · 630-632 Aktionsgutschein
621 Gutscheindaten fehlen · 622 Einlösecode fehlt · 630 Aktionsgutschein fehlt ·
631 Aktionsgutschein mit Code #1# nicht gefunden · 632 Aktionsgutschein für geprüfte Reise ungültig ·
**633 Aktionsgutschein vom Buchungszeitraum ungültig** · **634 Aktionsgutschein vom Reisezeitraum
ungültig**
633 and 634 are not in the PDF; both were measured on `AKTIONSGUTSCHEIN` on 2026-10-06. 633 means
the request falls outside the promo's booking period (`GutscheinAktion.BuchungDtVon`/`BuchungDtBis`):
a test promo whose booking period was being edited answered 633 and was accepted once the period
covered the request date. 634 refers to the travel period (`ReiseDtVon`/`ReiseDtBis`). It was
returned on prod for a promo whose stored travel period did contain the travel's `TerminVon`/
`TerminBis`. What it actually compares has not been established.
## Applying this: the teamer placeholder booking
+12 -2
View File
@@ -13,6 +13,10 @@ use Psr\Log\LoggerInterface;
use Symfony\Contracts\Cache\CacheInterface;
use Symfony\Contracts\Cache\ItemInterface;
/**
* Checks voucher codes with BusPro. Only accepted codes are cached: a rejection is asked again on
* the next attempt, so a code fixed in BusPro (or a passing outage) is picked up immediately.
*/
class VoucherValidator
{
private const CACHE_TTL = 900; // 15 minutes
@@ -30,13 +34,15 @@ class VoucherValidator
$cacheKey = sprintf('voucher.purchase.%s', md5($redemptionCode));
try {
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($redemptionCode) {
return $this->cache->get($cacheKey, function (ItemInterface $item, bool &$save) use ($redemptionCode) {
$item->expiresAfter(self::CACHE_TTL);
try {
$result = $this->voucherClient->validatePurchaseVoucher($redemptionCode);
if ($result instanceof Notification) {
$save = false;
return new PurchaseVoucherValidationResult(
isValid: false,
errorMessage: $result->message,
@@ -49,6 +55,7 @@ class VoucherValidator
voucher: $result
);
} catch (ApiClientException $e) {
$save = false;
$this->logger->error('Purchase voucher validation failed', [
'redemptionCode' => $redemptionCode,
'error' => $e->getMessage(),
@@ -85,13 +92,15 @@ class VoucherValidator
);
try {
return $this->cache->get($cacheKey, function (ItemInterface $item) use ($promoCode, $travelId, $participantPrice) {
return $this->cache->get($cacheKey, function (ItemInterface $item, bool &$save) use ($promoCode, $travelId, $participantPrice) {
$item->expiresAfter(self::CACHE_TTL);
try {
$result = $this->voucherClient->validatePromoVoucher($promoCode, $travelId, $participantPrice);
if ($result instanceof Notification) {
$save = false;
return new PromoVoucherValidationResult(
isValid: false,
errorMessage: $result->message,
@@ -104,6 +113,7 @@ class VoucherValidator
voucher: $result
);
} catch (ApiClientException $e) {
$save = false;
$this->logger->error('Promo voucher validation failed', [
'promoCode' => $promoCode,
'travelId' => $travelId,
+79
View File
@@ -0,0 +1,79 @@
<?php
declare(strict_types=1);
namespace App\Tests\Service;
use App\BusProNet\Client\VoucherClient;
use App\BusProNet\Exception\ApiClientException;
use App\BusProNet\Model\Notification;
use App\BusProNet\Model\PromoVoucher;
use App\BusProNet\Model\PurchaseVoucher;
use App\Service\VoucherValidator;
use PHPUnit\Framework\TestCase;
use Psr\Log\NullLogger;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
class VoucherValidatorTest extends TestCase
{
public function testAcceptedPromoCodeIsCached(): void
{
$client = $this->createMock(VoucherClient::class);
$client->expects(self::once())->method('validatePromoVoucher')
->willReturn(new PromoVoucher('R-TEST26', 'Testen', PromoVoucher::APPLICABILITY_PER_PERSON, 9.99, 0.0, 0.0));
$validator = new VoucherValidator($client, new ArrayAdapter(), new NullLogger());
self::assertTrue($validator->validatePromoVoucher('R-TEST26', 12497, 532.8)->isValid);
self::assertTrue($validator->validatePromoVoucher('R-TEST26', 12497, 532.8)->isValid);
}
public function testRejectedPromoCodeIsAskedAgain(): void
{
// The code is fixed in BusPro between the two attempts
$client = $this->createMock(VoucherClient::class);
$client->expects(self::exactly(2))->method('validatePromoVoucher')
->willReturnOnConsecutiveCalls(
new Notification(633, 'Aktionsgutschein vom Buchungszeitraum ungültig'),
new PromoVoucher('R-TEST26', 'Testen', PromoVoucher::APPLICABILITY_PER_PERSON, 9.99, 0.0, 0.0),
);
$validator = new VoucherValidator($client, new ArrayAdapter(), new NullLogger());
$rejected = $validator->validatePromoVoucher('R-TEST26', 12497, 532.8);
self::assertFalse($rejected->isValid);
self::assertSame(633, $rejected->errorCode);
self::assertTrue($validator->validatePromoVoucher('R-TEST26', 12497, 532.8)->isValid);
}
public function testFailedPromoCheckIsAskedAgain(): void
{
$client = $this->createMock(VoucherClient::class);
$client->expects(self::exactly(2))->method('validatePromoVoucher')
->willReturnOnConsecutiveCalls(
self::throwException(new ApiClientException('Connection refused')),
new PromoVoucher('R-TEST26', 'Testen', PromoVoucher::APPLICABILITY_PER_PERSON, 9.99, 0.0, 0.0),
);
$validator = new VoucherValidator($client, new ArrayAdapter(), new NullLogger());
self::assertFalse($validator->validatePromoVoucher('R-TEST26', 12497, 532.8)->isValid);
self::assertTrue($validator->validatePromoVoucher('R-TEST26', 12497, 532.8)->isValid);
}
public function testRejectedPurchaseCodeIsAskedAgain(): void
{
$client = $this->createMock(VoucherClient::class);
$client->expects(self::exactly(2))->method('validatePurchaseVoucher')
->willReturnOnConsecutiveCalls(
new Notification(995, 'Gutschein nicht gefunden'),
new PurchaseVoucher('80190', '1F7V6PADRZ', 1, 100.0, PurchaseVoucher::TYPE_PURCHASE),
);
$validator = new VoucherValidator($client, new ArrayAdapter(), new NullLogger());
self::assertFalse($validator->validatePurchaseVoucher('1F7V6PADRZ')->isValid);
self::assertTrue($validator->validatePurchaseVoucher('1F7V6PADRZ')->isValid);
}
}