Files
Investidor-V2/app/Services/InterestSubmission.php
T
nala 250ac35fd1
tests / ci (push) Canceled after 0s
first commit
2026-09-28 10:01:09 +02:00

63 lines
2.9 KiB
PHP

<?php
namespace App\Services;
use App\Models\InterestRequest;
use Closure;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use SensitiveParameter;
final class InterestSubmission
{
/**
* @param array<string, mixed> $payload Validated business fields only.
* @param Closure(): InterestRequest $create
*/
public function receive(#[SensitiveParameter] array $payload, #[SensitiveParameter] ?string $key, Closure $create): InterestRequest
{
$canonical = array_replace(['phone' => null, 'opportunityReference' => null, 'investmentRange' => null], $payload);
$canonical['email'] = strtolower(trim($canonical['email']));
$canonical['fullName'] = trim($canonical['fullName']);
$canonical['message'] = trim($canonical['message']);
$canonical['privacyConsent'] = true; // FormRequest requires acceptance.
ksort($canonical);
$payloadHash = hash_hmac('sha256', json_encode($canonical, JSON_THROW_ON_ERROR), config()->string('app.key'));
$keyHash = $key === null ? null : hash('sha256', strtolower($key));
return DB::transaction(function () use ($keyHash, $payloadHash, $create): InterestRequest {
if ($keyHash !== null) {
// Serialise concurrent submissions of this key across all web workers.
DB::select('select pg_advisory_xact_lock(250925, hashtext(?))', [$keyHash]);
$receipt = DB::table('interest_submission_receipts')->where('key_hash', $keyHash)->first();
if ($receipt) {
if (! hash_equals($receipt->payload_hash, $payloadHash)) {
throw new IdempotencyConflict;
}
return InterestRequest::withTrashed()->where('id', $receipt->interest_request_id)->firstOrFail();
}
}
$interest = $create();
$integrationId = (string) Str::uuid();
$interest->forceFill(['integration_id' => $integrationId])->save();
DB::table('outbox_events')->insert([
'source_system' => 'portal-intake',
'external_id' => 'ExpressionOfInterestSubmitted:'.$integrationId,
'aggregate_type' => 'interest_request', 'aggregate_id' => $integrationId,
'event_type' => 'ExpressionOfInterestSubmitted', 'data_classification' => 'confidential',
'correlation_id' => $integrationId, 'status' => 'pending',
'payload' => json_encode(['schema_version' => 1, 'interest_id' => $integrationId], JSON_THROW_ON_ERROR),
]);
if ($keyHash !== null) {
DB::table('interest_submission_receipts')->insert([
'key_hash' => $keyHash, 'payload_hash' => $payloadHash,
'interest_request_id' => $interest->id, 'created_at' => now(),
]);
}
return $interest;
});
}
}