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
| Action | Endpunkt (Grenke) | Returns |
|---|---|---|
CreateGrenkeRequestAction | POST /requests | RequestResponseData |
GetGrenkeRequestAction | GET /requests/{financingId} | RequestResponseData |
PatchGrenkeRequestAction | PATCH /requests/{financingId} | RequestResponseData |
PatchGrenkeLesseeAction | PATCH /requests/{financingId}/lessee | array |
WaitForGrenkeReadyToSignAction | polling GET /requests/{financingId} bis ReadyToSign | RequestResponseData |
GetGrenkeESignatureConfigurationAction | GET /requests/{financingId}/e-signature/configuration | ESignatureConfigurationData |
StartGrenkeESignatureAction | POST /requests/{financingId}/e-signature | array |
CancelGrenkeESignatureAction | PUT /requests/{financingId}/cancel-e-signature | array |
SwitchGrenkeToPaperContractAction | PUT /requests/{financingId}/switchToPaperContract | array |
GetGrenkeContractDocumentAction | GET /requests/{financingId}/contractdocument (Base64-PDF) | array |
PostGrenkeContractDocumentAction | POST /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
- Datei anlegen:
app/Actions/Grenke/<VerbDomainSubject>Action.php - Constructor mit
protected ApiService $grenkeApiService(oder weitere Actions) execute(...)mit klarem Return-Type — Idealerweise eine DTO stattarray- 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.