$command * @return array{version:int,id?:string,country?:string}|null */ public function replay(User $actor, ?string $key, array $command): ?array { if ($key === null) { return null; } $event = DB::table('audit_events')->where('source_system', self::SOURCE)->where('actor_user_id', $actor->id)->where('correlation_id', strtolower($key))->first(); if (! $event) { return null; } $data = json_decode($event->after_data, true, 512, JSON_THROW_ON_ERROR); abort_unless(is_array($data) && is_string($data['command_encrypted'] ?? null) && hash_equals(Crypt::decryptString($data['command_encrypted']), $this->canonical($command)), 409, 'Este pedido já foi utilizado com outros dados. Consulte o registo guardado antes de preparar outro pedido.'); $result = is_string($data['result_encrypted'] ?? null) ? json_decode(Crypt::decryptString($data['result_encrypted']), true, 512, JSON_THROW_ON_ERROR) : ($data['result'] ?? null); if (! is_array($result) || ! is_int($result['version'] ?? null) || $result['version'] < 1 || (array_key_exists('id', $result) && (! is_string($result['id']) || ! Str::isUuid($result['id']))) || (array_key_exists('country', $result) && (! is_string($result['country']) || preg_match('/^[A-Z]{2}$/D', $result['country']) !== 1))) { throw new \LogicException('Stored workspace command result unavailable.'); } $receipt = isset($result['id']) ? ['id' => $result['id'], 'version' => $result['version']] : ['version' => $result['version']]; if (isset($result['country'])) { $receipt['country'] = $result['country']; } return $receipt; } /** @param array $command * @param array{version:int,id?:string,country?:string} $result */ public function record(User $actor, ?string $key, array $command, array $result): void { if ($key === null) { return; } DB::table('audit_events')->insert(['id' => (string) Str::uuid(), 'source_system' => self::SOURCE, 'actor_user_id' => $actor->id, 'entity_type' => 'investment-profile-command', 'entity_id' => $result['id'] ?? null, 'action' => 'command_applied', 'correlation_id' => strtolower($key), 'data_classification' => 'restricted', 'after_data' => json_encode(['command_encrypted' => Crypt::encryptString($this->canonical($command)), ...(array_key_exists('country', $result) ? ['result_encrypted' => Crypt::encryptString(json_encode($result, JSON_THROW_ON_ERROR))] : ['result' => $result])], JSON_THROW_ON_ERROR)]); } /** @param array $command */ private function canonical(array $command): string { if (is_array($command['payload'] ?? null)) { ksort($command['payload']); } ksort($command); return json_encode($command, JSON_THROW_ON_ERROR); } }