feat: voucher purchase flow

This commit is contained in:
fromme committed 2026-10-01 15:03:27 +02:00
1 parent a628e96d06
commit f3dd122678
70 files changed
+3672 -122

No files matched your search

@@ -0,0 +1,27 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* A payment method for voucher purchases (GUTSCHEINZAHLUNGSARTEN).
*
* Echoed unchanged as the <zahlung> attributes of GUTSCHEINBUCHUNG.
*/
final readonly class VoucherPaymentMethod
{
public const string TYPE_TRANSFER = 'UEBERW';
public function __construct(
public int $id,
public string $label,
public string $type,
) {
}
public function isTransfer(): bool
{
return self::TYPE_TRANSFER === $this->type;
}
}
+16
View File
@@ -0,0 +1,16 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* The GUTSCHEINVORSCHAU response, decoded from its data URI to the raw PDF bytes.
*/
final readonly class VoucherPreview
{
public function __construct(
public string $pdf,
) {
}
}
@@ -0,0 +1,41 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* A GUTSCHEINBUCHUNG response that is not a HINWEIS.
*
* The dry run (buchungsart Anfrage) answers <buchung>möglich</buchung> and nothing else, so every
* other field is only set by the actual purchase (buchungsart Buchung, <buchung>erfolgt</buchung>).
*
* The redemption code is deliberately not exposed to the customer before payment: BusPro mails the
* voucher itself once the transfer has arrived.
*/
final readonly class VoucherSaleResponse
{
public const string SALE_POSSIBLE = 'möglich';
public const string SALE_PROCESSED = 'erfolgt';
public function __construct(
public string $status,
public ?int $voucherId = null,
public ?string $voucherNumber = null,
public ?string $redemptionCode = null,
public ?float $price = null,
public ?string $invoiceNumber = null,
public ?string $email = null,
) {
}
public function isInquiryValid(): bool
{
return self::SALE_POSSIBLE === $this->status;
}
public function isSaleSuccessful(): bool
{
return self::SALE_PROCESSED === $this->status;
}
}
+21
View File
@@ -0,0 +1,21 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* A voucher design offered for online sale (GUTSCHEINVORLAGEN).
*
* The id doubles as the key for the local thumbnail, see VoucherThumbnailResolver.
*/
final readonly class VoucherTemplate
{
public function __construct(
public int $id,
public string $type,
public string $label,
public ?string $note = null,
) {
}
}
+35
View File
@@ -0,0 +1,35 @@
<?php
declare(strict_types=1);
namespace App\BusProNet\Model;
/**
* The GUTSCHEINVORLAGEN response: the designs on sale plus BusPro's amount bounds.
*
* The bounds (gutscheinminbetrag/gutscheinmaxbetrag) are not in the vendor documentation,
* they were measured on the test system (10 and 5,000 €, both inclusive).
*/
final readonly class VoucherTemplates
{
/**
* @param list<VoucherTemplate> $templates
*/
public function __construct(
public int $minAmount,
public int $maxAmount,
public array $templates,
) {
}
public function find(int $id): ?VoucherTemplate
{
foreach ($this->templates as $template) {
if ($id === $template->id) {
return $template;
}
}
return null;
}
}