Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.91% covered (success)
90.91%
10 / 11
50.00% covered (danger)
50.00%
1 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
IdentityVerificationService
90.91% covered (success)
90.91%
10 / 11
50.00% covered (danger)
50.00%
1 / 2
3.01
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 markIdentityVerifiedIfApplicable
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
2.00
1<?php
2
3declare(strict_types=1);
4
5namespace App\Domain\Document\Service;
6
7use PDO;
8use RuntimeException;
9
10/**
11 * FSC-138: bridges a completed ID-verified document to the KYC workflow.
12 *
13 * PandaDoc ID Check gates signing behind a government-ID proof, so a document
14 * reaching `document.completed` means the recipient passed identity
15 * verification. When that document's template required ID verification, we
16 * advance the investor pending → in_review so they surface in the admin KYC
17 * queue. The verified/rejected decision remains a manual admin step — ID Check
18 * is identity proofing only, not AML or accreditation.
19 *
20 * The advance is idempotent: it only moves investors still in `pending`, so
21 * repeated reads (this runs as a side effect of fetching document status) never
22 * clobber a decision an admin has already made.
23 */
24final readonly class IdentityVerificationService
25{
26    public function __construct(
27        private PDO $pdo,
28    ) {}
29
30    /**
31     * Advance the investor to `in_review` when a completed document belongs to
32     * an ID-verification template. No-op otherwise.
33     *
34     * @param int $investorId
35     * @param string $pandadocId The document that just reached completion
36     * @throws RuntimeException on statement failure
37     */
38    public function markIdentityVerifiedIfApplicable(int $investorId, string $pandadocId): void
39    {
40        $stmt = $this->pdo->prepare(
41            "UPDATE public.investors
42                SET kyc_status = 'in_review', updated_at = CURRENT_TIMESTAMP
43                WHERE investor_id = :investorId
44                  AND kyc_status = 'pending'
45                  AND EXISTS (
46                      SELECT 1
47                      FROM public.investor_documents d
48                      JOIN public.document_templates t ON t.template_id = d.template_id
49                      WHERE d.pandadoc_id = :pandadocId
50                        AND t.requires_id_verification = true
51                  )",
52        );
53
54        if ($stmt === false) {
55            throw new RuntimeException('Failed to prepare identity verification statement');
56        }
57
58        $stmt->execute([
59            'investorId' => $investorId,
60            'pandadocId' => $pandadocId,
61        ]);
62    }
63}