Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
DeclineLoanAction
0.00% covered (danger)
0.00%
0 / 13
0.00% covered (danger)
0.00%
0 / 2
20
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 __invoke
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
12
1<?php
2
3declare(strict_types=1);
4
5namespace App\Action\Loan;
6
7use App\Domain\Exception\BadRequestException;
8use App\Domain\Loan\Service\LoanService;
9use App\Renderer\JsonRenderer;
10use App\Support\Row;
11use Psr\Http\Message\ResponseInterface;
12use Psr\Http\Message\ServerRequestInterface;
13
14/**
15 * POST /api/loans/{id}/decline
16 *
17 * FSC-94: investor declines the terms of a `pending_acceptance` loan.
18 * FSC-130: the loan returns to the admin queue as `under_review` with the
19 * investor-supplied reason, so the admin can revise the terms and re-approve
20 * rather than the request ending at `denied`. No disbursement is enqueued.
21 */
22final readonly class DeclineLoanAction
23{
24    public function __construct(
25        private LoanService $loanService,
26        private JsonRenderer $renderer,
27    ) {}
28
29    /**
30     * @param array<string, string> $args
31     * @param ServerRequestInterface $request
32     * @param ResponseInterface $response
33     */
34    public function __invoke(
35        ServerRequestInterface $request,
36        ResponseInterface $response,
37        array $args,
38    ): ResponseInterface {
39        $investorId = Row::int($request->getAttributes(), 'investorId');
40        $loanId = Row::int($args, 'id');
41        $body = (array)$request->getParsedBody();
42
43        $reason = Row::nullableString($body, 'reason');
44        if ($reason === null || trim($reason) === '') {
45            throw new BadRequestException('A reason is required to decline a loan');
46        }
47
48        $loan = $this->loanService->declineLoan($loanId, $investorId, $reason);
49
50        return $this->renderer->json($response, [
51            'success' => true,
52            'message' => 'Terms declined — your request has been returned to our team for review.',
53            'data' => ['loan' => $loan->toArray()],
54        ]);
55    }
56}