post; } return $this->post[$key] ?? null; } public function getFile(string $name) { return null; } } class TestableRefundController extends RefundController { public function __construct() { } public function setRequestObject($request): self { $this->request = $request; return $this; } public function setResponseObject($response): self { $this->response = $response; return $this; } } class RefundEligibilityForControllerHarness extends RefundEligibilityService { public function __construct(private int $completedPayoutCents) { } public function getCompletedPayoutTotalCentsForRefund(int $refundId): int { return $this->completedPayoutCents; } } class RefundControllerRegressionTest extends CIUnitTestCase { public function testRequestRefundReadsRequestTypeFromPostBody(): void { $controller = $this->controllerWithPost([ 'parent_id' => '123', 'amount' => '25.00', 'request_type' => 'unsupported', ]); $response = $controller->requestRefund(); $this->assertSame(['error' => 'Invalid refund request type.'], $this->jsonResponse($response)); } public function testPayRefundRouteArgumentIsAcceptedBeforePostRefundId(): void { $controller = $this->controllerWithPost([ 'paid_amount' => '10.00', 'payment_method' => 'Wire', ]); $response = $controller->payRefund(77); $this->assertSame(['error' => 'Invalid refund payment method.'], $this->jsonResponse($response)); } public function testCheckRefundRequiresCheckNumberBeforeDatabaseWrite(): void { $controller = $this->controllerWithPost([ 'refund_id' => '77', 'paid_amount' => '10.00', 'payment_method' => 'Check', ]); $response = $controller->updatePayment(); $this->assertSame(['error' => 'Check number is required for check refunds.'], $this->jsonResponse($response)); } public function testUpdateStatusAcceptsRouteRefundId(): void { $controller = $this->controllerWithPost([ 'status' => 'Voided', 'reason' => 'duplicate request', ]); $response = $controller->updateStatus(77); $this->assertSame(['error' => 'Invalid status update request.'], $this->jsonResponse($response)); } public function testReversePayoutRequiresReasonBeforeDatabaseWrite(): void { $controller = $this->controllerWithPost([ 'idempotency_key' => 'reverse-77', ]); $response = $controller->reversePayout(77); $this->assertSame(['error' => 'Reversal reason is required.'], $this->jsonResponse($response)); } public function testReversePayoutRequiresIdempotencyKeyBeforeDatabaseWrite(): void { $controller = $this->controllerWithPost([ 'reason' => 'wrong payout amount', ]); $response = $controller->reversePayout(77); $this->assertSame(['error' => 'Missing reversal idempotency key.'], $this->jsonResponse($response)); } public function testReversePayoutRejectsNonPositiveAmountBeforeDatabaseWrite(): void { $controller = $this->controllerWithPost([ 'reason' => 'wrong payout amount', 'idempotency_key' => 'reverse-77', 'amount' => '0.00', ]); $response = $controller->reversePayout(77); $this->assertSame(['error' => 'Reversal amount must be greater than zero.'], $this->jsonResponse($response)); } public function testApprovedRefundRecalculationCannotDropBelowCompletedPayouts(): void { $controller = $this->controllerWithPost([]); $property = (new \ReflectionClass(RefundController::class))->getProperty('refundEligibilityService'); $property->setAccessible(true); $property->setValue($controller, new RefundEligibilityForControllerHarness(2500)); $method = (new \ReflectionClass(RefundController::class))->getMethod('buildRefundRecalculationUpdate'); $method->setAccessible(true); $update = $method->invoke($controller, [ 'id' => 55, 'status' => 'Approved', ], 10.00); $this->assertEqualsWithDelta(25.0, (float)$update['refund_amount'], 0.0001); $this->assertSame(2500, $update['approved_amount_cents']); $this->assertSame('requires_review', $update['reconciliation_status']); $this->assertStringContainsString('Completed payouts', $update['reconciliation_reason']); } private function controllerWithPost(array $post): TestableRefundController { return (new TestableRefundController()) ->setRequestObject(new RefundRequestStub($post)) ->setResponseObject(service('response')); } private function jsonResponse($response): array { return json_decode($response->getBody(), true, 512, JSON_THROW_ON_ERROR); } }