Skip to main content

Grenke Actions

app/Actions/Grenke/

Jede Action ist eine Klasse mit genau einer execute()-Methode. Sie kapseln EINE API-Operation und sind die kleinste wiederverwendbare Einheit. Controller und RequestFlowService injizieren sie via DI.

Übersicht

ActionEndpunkt (Grenke)Returns
CreateGrenkeRequestActionPOST /requestsRequestResponseData
GetGrenkeRequestActionGET /requests/{financingId}RequestResponseData
PatchGrenkeRequestActionPATCH /requests/{financingId}RequestResponseData
PatchGrenkeLesseeActionPATCH /requests/{financingId}/lesseearray
WaitForGrenkeReadyToSignActionpolling GET /requests/{financingId} bis ReadyToSignRequestResponseData
GetGrenkeESignatureConfigurationActionGET /requests/{financingId}/e-signature/configurationESignatureConfigurationData
StartGrenkeESignatureActionPOST /requests/{financingId}/e-signaturearray
CancelGrenkeESignatureActionPUT /requests/{financingId}/cancel-e-signaturearray
SwitchGrenkeToPaperContractActionPUT /requests/{financingId}/switchToPaperContractarray
GetGrenkeContractDocumentActionGET /requests/{financingId}/contractdocument (Base64-PDF)array
PostGrenkeContractDocumentActionPOST /requests/{financingId}/contractdocument (Download-Link)array

Beispiel — Create

namespace App\Actions\Grenke;

use App\DataTransferObjects\Grenke\RequestPayloadData;
use App\DataTransferObjects\Grenke\RequestResponseData;
use App\Services\Grenke\ApiService;

class CreateGrenkeRequestAction
{
public function __construct(
protected ApiService $grenkeApiService
) {}

public function execute(RequestPayloadData $payload): RequestResponseData
{
$response = $this->grenkeApiService->postJson('requests', $payload->toArray());

return RequestResponseData::fromArray($response);
}
}

Aufruf im Controller:

public function create(CreateGrenkeRequestRequest $request): JsonResponse
{
$dto = RequestPayloadData::fromArray($request->validated());
$result = $this->createGrenkeRequestAction->execute($dto);

return response()->json([
'success' => true,
'data' => $result->toArray(),
'meta' => $this->grenkeRequestStateService->buildStateMeta($result),
]);
}

Beispiel — WaitForReadyToSign (Polling + State-Branching)

WaitForGrenkeReadyToSignAction ist die einzige Action mit mehreren API-Calls. Sie pollt GET /requests/{id}, bricht je nach State ab oder triggert den Mailflow.

public function execute(
string $financingId,
int $maxAttempts = 10,
int $sleepMilliseconds = 1000,
array $mailContext = []
): RequestResponseData {
for ($attempt = 1; $attempt <= $maxAttempts; $attempt++) {
$lastResponse = $this->getGrenkeRequestAction->execute($financingId);

match ($lastResponse->state) {
RequestState::ReadyToSign => return $lastResponse,
RequestState::MissingInfo => $this->grenkeMissingInfoMailFlowService->handle($lastResponse, $mailContext),
RequestState::Cancelled,
RequestState::Declined,
RequestState::RunningContract,
RequestState::ContractPrinted => throw new RuntimeException(...),
default => null,
};

if ($attempt < $maxAttempts) {
usleep($sleepMilliseconds * 1000);
}
}

throw new RuntimeException("Status [ReadyToSign] nicht erreicht.");
}

Eigene Action schreiben

  1. Datei anlegen: app/Actions/Grenke/<VerbDomainSubject>Action.php
  2. Constructor mit protected ApiService $grenkeApiService (oder weitere Actions)
  3. execute(...) mit klarem Return-Type — Idealerweise eine DTO statt array
  4. Im Controller injizieren
namespace App\Actions\Grenke;

use App\Services\Grenke\ApiService;

class GetGrenkeContractsAction
{
public function __construct(
protected ApiService $grenkeApiService
) {}

public function execute(int $page = 1, int $pageSize = 100): array
{
return $this->grenkeApiService->getJson('contracts', [
'contractListParameter.page' => $page,
'contractListParameter.pageSize' => $pageSize,
]);
}
}

Konvention: Action-Klassennamen lesen sich wie ein Verb-Phrasen-Imperativ: Create…, Get…, Patch…, Wait…, Cancel…, Switch…. Das macht Code im Controller selbsterklärend.