Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
RequestSignatureOtpAction
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
3 / 3
7
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
1 / 1
4
 resolveContact
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace ImovelZapAi\Signatures\Application\Actions;
6
7use ImovelZapAi\Conversations\Application\Actions\SendOutboundMessageAction;
8use ImovelZapAi\Conversations\Infrastructure\Persistence\Eloquent\ContactModel;
9use ImovelZapAi\Signatures\Application\Services\SignatureAccessTokenService;
10use ImovelZapAi\Signatures\Application\Services\SignatureAuditWriter;
11use ImovelZapAi\Signatures\Application\Services\SignatureOtpService;
12use ImovelZapAi\Signatures\Domain\Enums\SignatureAuditEvent;
13use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignatureRequestModel;
14use ImovelZapAi\Signatures\Infrastructure\Persistence\Eloquent\SignerModel;
15
16final class RequestSignatureOtpAction
17{
18    public function __construct(
19        private readonly SignatureAccessTokenService $tokens,
20        private readonly SignatureOtpService $otp,
21        private readonly SignatureAuditWriter $audit,
22        private readonly SendOutboundMessageAction $sendText,
23    ) {}
24
25    /** @return array{sent: bool, otp?: string} */
26    public function execute(string $plainToken): array
27    {
28        $resolved = $this->tokens->resolve($plainToken);
29        $signer = $resolved['signer'];
30        $request = $resolved['request'];
31
32        $plainOtp = $this->otp->issue($signer);
33
34        $contact = $this->resolveContact($signer, $request);
35        if ($contact !== null) {
36            try {
37                $request->loadMissing('deal.conversation');
38                $body = "Seu código para assinar \"{$request->title}\": {$plainOtp}\n\nVálido por "
39                    .config('signatures.otp_ttl_minutes', 15).' minutos.';
40                $this->sendText->execute(
41                    $request->tenant_id,
42                    $contact,
43                    $body,
44                    $request->deal?->conversation,
45                );
46            } catch (\Throwable) {
47                // OTP gerado; falha de envio não invalida o código.
48            }
49        }
50
51        $this->audit->write(
52            $request->tenant_id,
53            $request->id,
54            SignatureAuditEvent::OtpSent,
55            actorType: 'signer',
56            signerId: $signer->id,
57            message: 'Código de verificação enviado.',
58        );
59
60        $result = ['sent' => true];
61        if (app()->environment('testing')) {
62            $result['otp'] = $plainOtp;
63        }
64
65        return $result;
66    }
67
68    private function resolveContact(SignerModel $signer, SignatureRequestModel $request): ?ContactModel {
69        if ($signer->contact_id !== null) {
70            $signer->loadMissing('contact');
71
72            return $signer->contact;
73        }
74
75        $request->loadMissing('document.contact');
76
77        return $request->document?->contact;
78    }
79}