feat: voucher purchase flow
This commit is contained in:
1 parent
a628e96d06
commit
f3dd122678
70 files changed
+3672
-122
No files matched your search
@@ -6,6 +6,7 @@ namespace App\Tests\BusProNet;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\DataProcessor\BookingDataProcessor;
|
||||
use App\BusProNet\DataProcessor\VoucherSalePayloadBuilder;
|
||||
use App\BusProNet\Exception\ApiClientException;
|
||||
use App\BusProNet\Exception\ImmediateConnectionCloseException;
|
||||
use App\BusProNet\XmlParser\ApiResponseParser;
|
||||
@@ -34,6 +35,7 @@ class ApiClientReceiveTest extends TestCase
|
||||
$this->createStub(FilesystemOperator::class),
|
||||
$this->logger,
|
||||
$this->createStub(BookingDataProcessor::class),
|
||||
new VoucherSalePayloadBuilder(),
|
||||
new RequestStack(),
|
||||
new RequestIdGenerator(),
|
||||
[
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\DataProcessor;
|
||||
|
||||
use App\BusProNet\Constants;
|
||||
use App\BusProNet\DataProcessor\VoucherSalePayloadBuilder;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherTemplate;
|
||||
use App\Form\Model\VoucherPurchaseDto;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use Symfony\Component\Serializer\Encoder\XmlEncoder;
|
||||
|
||||
class VoucherSalePayloadBuilderTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* The shape the test system accepted with <buchung>möglich</buchung> on 2026-10-01.
|
||||
*/
|
||||
public function testTheSalePayloadMatchesTheProbedRequest(): void
|
||||
{
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildSalePayload($this->createDto(), Constants::BOOKING_TYPE_INQUIRY);
|
||||
|
||||
$xml = (new XmlEncoder())->encode(
|
||||
['satz' => ['@typ' => 'GUTSCHEINBUCHUNG']] + $payload,
|
||||
'xml',
|
||||
[XmlEncoder::ROOT_NODE_NAME => 'anfrage', XmlEncoder::ENCODING => 'UTF-8'],
|
||||
);
|
||||
|
||||
self::assertStringContainsString(
|
||||
'<satz typ="GUTSCHEINBUCHUNG"/><buchungsart>Anfrage</buchungsart><gutscheinart>K</gutscheinart>'
|
||||
.'<idgutscheinvorlage>3</idgutscheinvorlage><kaeufer>Probe Kaeufer</kaeufer><empfaenger>Probe Empfaenger</empfaenger>'
|
||||
.'<kaufpreis>25</kaufpreis><text></text>'
|
||||
.'<rechnungsempfänger><name>Probe</name><vorname>Myep</vorname><anrede>Herr</anrede><titel/><geschlecht></geschlecht><geburtsdatum></geburtsdatum>'
|
||||
.'<anschrift><strasse>Teststr. 1</strasse><plz>12345</plz><ort>Testort</ort><ortsteil/><land>D</land></anschrift>'
|
||||
.'<kommunikation><telefonprivat/><email>[email protected]</email><telefonmobil>0170123456</telefonmobil></kommunikation>'
|
||||
.'</rechnungsempfänger>'
|
||||
.'<zahlung idzahlungsart="2" bezeichnung="Überweisung" art="UEBERW"/>',
|
||||
$xml,
|
||||
);
|
||||
}
|
||||
|
||||
public function testThePurchaseDiffersOnlyInItsSaleType(): void
|
||||
{
|
||||
$builder = new VoucherSalePayloadBuilder();
|
||||
$dto = $this->createDto();
|
||||
|
||||
$inquiry = $builder->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
$purchase = $builder->buildSalePayload($dto, Constants::BOOKING_TYPE_BOOKING);
|
||||
|
||||
self::assertSame('Buchung', $purchase['buchungsart']);
|
||||
self::assertSame(array_diff_key($inquiry, ['buchungsart' => true]), array_diff_key($purchase, ['buchungsart' => true]));
|
||||
}
|
||||
|
||||
public function testAnEmptyMobileIsLeftOut(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->mobile = ' ';
|
||||
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
|
||||
self::assertArrayNotHasKey('telefonmobil', $payload['rechnungsempfänger']['kommunikation']);
|
||||
}
|
||||
|
||||
public function testThePreviewPayloadCarriesTheVoucherOnly(): void
|
||||
{
|
||||
$payload = (new VoucherSalePayloadBuilder())->buildPreviewPayload($this->createDto());
|
||||
|
||||
self::assertSame([
|
||||
'idgutscheinvorlage' => 3,
|
||||
'kaeufer' => 'Probe Kaeufer',
|
||||
'empfaenger' => 'Probe Empfaenger',
|
||||
'kaufpreis' => 25,
|
||||
'text' => '',
|
||||
], $payload);
|
||||
}
|
||||
|
||||
public function testASaleWithoutATemplateIsALogicError(): void
|
||||
{
|
||||
$dto = $this->createDto();
|
||||
$dto->template = null;
|
||||
|
||||
$this->expectException(\LogicException::class);
|
||||
|
||||
(new VoucherSalePayloadBuilder())->buildSalePayload($dto, Constants::BOOKING_TYPE_INQUIRY);
|
||||
}
|
||||
|
||||
private function createDto(): VoucherPurchaseDto
|
||||
{
|
||||
$dto = new VoucherPurchaseDto();
|
||||
$dto->template = new VoucherTemplate(3, 'K', 'Allgemein');
|
||||
$dto->paymentMethod = new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW');
|
||||
$dto->from = 'Probe Kaeufer';
|
||||
$dto->to = 'Probe Empfaenger';
|
||||
$dto->amount = 25;
|
||||
$dto->salutation = VoucherPurchaseDto::SALUTATION_MR;
|
||||
$dto->lastName = 'Probe';
|
||||
$dto->firstName = 'Myep';
|
||||
$dto->address->street = 'Teststr. 1';
|
||||
$dto->address->postCode = '12345';
|
||||
$dto->address->city = 'Testort';
|
||||
$dto->email = '[email protected]';
|
||||
$dto->mobile = '0170123456';
|
||||
|
||||
return $dto;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\BusProNet\XmlParser;
|
||||
|
||||
use App\BusProNet\ApiClient;
|
||||
use App\BusProNet\Exception\ResponseParserException;
|
||||
use App\BusProNet\Model\Notification;
|
||||
use App\BusProNet\Model\VoucherPaymentMethod;
|
||||
use App\BusProNet\Model\VoucherPreview;
|
||||
use App\BusProNet\Model\VoucherSaleResponse;
|
||||
use App\BusProNet\Model\VoucherTemplates;
|
||||
use App\BusProNet\XmlParser\ApiResponseParser;
|
||||
use App\BusProNet\XmlParser\TravelParser;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
/**
|
||||
* The voucher purchase responses. Fixtures are the probes against the test system of 2026-10-01,
|
||||
* except the completed purchase, which follows the vendor documentation.
|
||||
*/
|
||||
class VoucherResponseParserTest extends TestCase
|
||||
{
|
||||
public function testTemplatesWithTheUndocumentedAmountBounds(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORLAGEN"></satz><gutscheinminbetrag>10</gutscheinminbetrag><gutscheinmaxbetrag>5000</gutscheinmaxbetrag><gutscheine><gutschein idgutscheinvorlage="3"><gutscheinart>K</gutscheinart><bezeichnung>Allgemein</bezeichnung><hinweis></hinweis></gutschein><gutschein idgutscheinvorlage="4"><gutscheinart>K</gutscheinart><bezeichnung>Ski</bezeichnung><hinweis></hinweis></gutschein><gutschein idgutscheinvorlage="5"><gutscheinart>K</gutscheinart><bezeichnung>Weihnachten</bezeichnung><hinweis></hinweis></gutschein></gutscheine></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_TEMPLATES, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherTemplates::class, $result);
|
||||
self::assertSame(10, $result->minAmount);
|
||||
self::assertSame(5000, $result->maxAmount);
|
||||
self::assertSame([3, 4, 5], array_map(static fn ($template) => $template->id, $result->templates));
|
||||
self::assertSame(['Allgemein', 'Ski', 'Weihnachten'], array_map(static fn ($template) => $template->label, $result->templates));
|
||||
self::assertSame('K', $result->templates[1]->type);
|
||||
self::assertNull($result->templates[1]->note);
|
||||
self::assertSame('Ski', $result->find(4)?->label);
|
||||
self::assertNull($result->find(999));
|
||||
}
|
||||
|
||||
public function testTemplatesWithoutAmountBoundsAreRejected(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORLAGEN"></satz><gutscheine><gutschein idgutscheinvorlage="3"><gutscheinart>K</gutscheinart><bezeichnung>Allgemein</bezeichnung></gutschein></gutscheine></ergebnis>';
|
||||
|
||||
$this->expectException(ResponseParserException::class);
|
||||
$this->expectExceptionMessage('amount bounds');
|
||||
|
||||
$this->parse(ApiClient::TYPE_VOUCHER_TEMPLATES, $xml);
|
||||
}
|
||||
|
||||
public function testPaymentMethods(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINZAHLUNGSARTEN"></satz><zahlungsarten><zahlungsart id="2" bezeichnung="Überweisung" art="UEBERW"></zahlungsart></zahlungsarten></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_PAYMENT_METHODS, $xml);
|
||||
|
||||
self::assertEquals([new VoucherPaymentMethod(2, 'Überweisung', 'UEBERW')], $result);
|
||||
self::assertTrue($result[0]->isTransfer());
|
||||
}
|
||||
|
||||
public function testPreviewIsDecodedFromItsDataUri(): void
|
||||
{
|
||||
$pdf = "%PDF-1.4\nprobe";
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORSCHAU"/><gutscheinpdf>data:application/pdf;base64,'.base64_encode($pdf).'</gutscheinpdf></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_PREVIEW, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherPreview::class, $result);
|
||||
self::assertSame($pdf, $result->pdf);
|
||||
}
|
||||
|
||||
public function testPreviewWithoutAPdfDataUriIsRejected(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINVORSCHAU"/><gutscheinpdf></gutscheinpdf></ergebnis>';
|
||||
|
||||
$this->expectException(ResponseParserException::class);
|
||||
|
||||
$this->parse(ApiClient::TYPE_VOUCHER_PREVIEW, $xml);
|
||||
}
|
||||
|
||||
public function testTheDryRunAnswersPossibleAndIssuesNothing(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINBUCHUNG"></satz><buchung>möglich</buchung></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherSaleResponse::class, $result);
|
||||
self::assertTrue($result->isInquiryValid());
|
||||
self::assertFalse($result->isSaleSuccessful());
|
||||
self::assertNull($result->voucherNumber);
|
||||
self::assertNull($result->redemptionCode);
|
||||
}
|
||||
|
||||
public function testACompletedPurchase(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="GUTSCHEINBUCHUNG"/><buchung>erfolgt</buchung><idgutschein>4711</idgutschein><gutscheinnummer>K-001234</gutscheinnummer><einloesecode>ABCD1234</einloesecode><preis>111,00</preis><rechnungsnummer>R-998877</rechnungsnummer><email>[email protected]</email></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(VoucherSaleResponse::class, $result);
|
||||
self::assertTrue($result->isSaleSuccessful());
|
||||
self::assertSame(4711, $result->voucherId);
|
||||
self::assertSame('K-001234', $result->voucherNumber);
|
||||
self::assertSame('ABCD1234', $result->redemptionCode);
|
||||
self::assertSame(111.0, $result->price);
|
||||
self::assertSame('R-998877', $result->invoiceNumber);
|
||||
self::assertSame('[email protected]', $result->email);
|
||||
}
|
||||
|
||||
/**
|
||||
* Missing address data comes as 650, the code Notification::isError() treats as success.
|
||||
* The caller must take any HINWEIS as a rejection on this path.
|
||||
*/
|
||||
public function testAMissingAddressFieldIsAHinweis650(): void
|
||||
{
|
||||
$xml = '<?xml version="1.0" encoding="utf-8"?><ergebnis><satz typ="HINWEIS"><nr>650</nr><text>Fehlende Adress Daten. Bitte folgende Eingabefelder prüfen: Strasse</text></satz></ergebnis>';
|
||||
|
||||
$result = $this->parse(ApiClient::TYPE_VOUCHER_SALE, $xml);
|
||||
|
||||
self::assertInstanceOf(Notification::class, $result);
|
||||
self::assertSame(650, $result->code);
|
||||
}
|
||||
|
||||
private function parse(string $type, string $xml): mixed
|
||||
{
|
||||
return (new ApiResponseParser($this->createStub(TravelParser::class)))->parseXmlString($type, $xml);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user