63 lines
2.9 KiB
PHP
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;
|
|
});
|
|
}
|
|
}
|