|
1 | 1 | using Microsoft.Extensions.Logging; |
| 2 | +using SEBT.Portal.Core.Models.Auth; |
| 3 | +using SEBT.Portal.Core.Models.Household; |
| 4 | +using SEBT.Portal.Core.Repositories; |
2 | 5 | using SEBT.Portal.Core.Services; |
3 | 6 | using SEBT.Portal.Kernel; |
4 | 7 | using SEBT.Portal.Kernel.Results; |
| 8 | +using SEBT.Portal.StatesPlugins.Interfaces; |
| 9 | +using PluginAddress = SEBT.Portal.StatesPlugins.Interfaces.Models.Household.Address; |
| 10 | +using PluginAddressUpdateRequest = SEBT.Portal.StatesPlugins.Interfaces.Models.Household.AddressUpdateRequest; |
5 | 11 |
|
6 | 12 | namespace SEBT.Portal.UseCases.Household; |
7 | 13 |
|
8 | 14 | /// <summary> |
9 | 15 | /// Handles mailing address updates for an authenticated user's household. |
10 | | -/// Validates input, resolves household identity, and returns success. |
11 | | -/// State connector call is stubbed — actual address persistence is a future integration. |
| 16 | +/// Validates input, checks address validity, enforces benefit-type policy, and persists via state connector. |
12 | 17 | /// </summary> |
13 | 18 | public class UpdateAddressCommandHandler( |
14 | 19 | IValidator<UpdateAddressCommand> validator, |
15 | 20 | IHouseholdIdentifierResolver resolver, |
| 21 | + IAddressValidationService addressValidationService, |
| 22 | + IHouseholdRepository householdRepository, |
| 23 | + IIdProofingRequirementsService idProofingRequirementsService, |
| 24 | + IAddressUpdateService addressUpdateService, |
16 | 25 | ILogger<UpdateAddressCommandHandler> logger) |
17 | 26 | : ICommandHandler<UpdateAddressCommand> |
18 | 27 | { |
@@ -40,15 +49,92 @@ public async Task<Result> Handle(UpdateAddressCommand command, CancellationToken |
40 | 49 | "Address update received for household identifier kind {Kind}", |
41 | 50 | identifierKind); |
42 | 51 |
|
43 | | - // TODO: Call state connector to persist address update. |
44 | | - // This is stubbed — the handler returns success without writing to the state system. |
45 | | - // When DC-160 / state connector work lands, wire up IAddressValidationService and |
46 | | - // the state connector write method here. |
| 52 | + // Policy enforcement: SNAP and TANF households must update via case worker, not the portal. |
| 53 | + var userIalLevel = UserIalLevelExtensions.FromClaimsPrincipal(command.User); |
| 54 | + var piiVisibility = idProofingRequirementsService.GetPiiVisibility(userIalLevel); |
| 55 | + var household = await householdRepository.GetHouseholdByIdentifierAsync( |
| 56 | + identifier, piiVisibility, userIalLevel, cancellationToken); |
47 | 57 |
|
48 | | - logger.LogInformation( |
49 | | - "Address update completed for household identifier kind {Kind}", |
50 | | - identifierKind); |
| 58 | + if (household is { BenefitIssuanceType: BenefitIssuanceType.SnapEbtCard or BenefitIssuanceType.TanfEbtCard }) |
| 59 | + { |
| 60 | + logger.LogWarning( |
| 61 | + "Address update rejected for household identifier kind {Kind}: benefit type {BenefitType} is not eligible for portal self-service", |
| 62 | + identifierKind, |
| 63 | + household.BenefitIssuanceType); |
| 64 | + return Result.PreconditionFailed( |
| 65 | + PreconditionFailedReason.Conflict, |
| 66 | + "Address updates are not available for this benefit type. Please contact your case worker."); |
| 67 | + } |
| 68 | + |
| 69 | + // Validate address via external service (e.g., Smarty). Currently uses AlwaysValidAddressValidator stub. |
| 70 | + var pluginAddress = new PluginAddress |
| 71 | + { |
| 72 | + StreetAddress1 = command.StreetAddress1, |
| 73 | + StreetAddress2 = command.StreetAddress2, |
| 74 | + City = command.City, |
| 75 | + State = command.State, |
| 76 | + PostalCode = command.PostalCode |
| 77 | + }; |
| 78 | + |
| 79 | + var addressValidation = await addressValidationService.ValidateAsync( |
| 80 | + new Address |
| 81 | + { |
| 82 | + StreetAddress1 = pluginAddress.StreetAddress1, |
| 83 | + StreetAddress2 = pluginAddress.StreetAddress2, |
| 84 | + City = pluginAddress.City, |
| 85 | + State = pluginAddress.State, |
| 86 | + PostalCode = pluginAddress.PostalCode |
| 87 | + }, cancellationToken); |
| 88 | + |
| 89 | + if (!addressValidation.IsValid) |
| 90 | + { |
| 91 | + if (addressValidation.SuggestedAddress != null) |
| 92 | + { |
| 93 | + logger.LogInformation("Address validation returned a suggested address for household identifier kind {Kind}", identifierKind); |
| 94 | + return Result.ValidationFailed("Address", "The address could not be verified. A suggested address is available."); |
| 95 | + } |
| 96 | + |
| 97 | + logger.LogInformation("Address validation failed for household identifier kind {Kind}", identifierKind); |
| 98 | + return Result.ValidationFailed("Address", addressValidation.ErrorMessage ?? "The address could not be verified."); |
| 99 | + } |
| 100 | + |
| 101 | + var updateRequest = new PluginAddressUpdateRequest |
| 102 | + { |
| 103 | + HouseholdIdentifierValue = identifier.Value, |
| 104 | + Address = pluginAddress |
| 105 | + }; |
| 106 | + |
| 107 | + try |
| 108 | + { |
| 109 | + var updateResult = await addressUpdateService.UpdateAddressAsync(updateRequest, cancellationToken); |
51 | 110 |
|
52 | | - return Result.Success(); |
| 111 | + if (updateResult.IsSuccess) |
| 112 | + { |
| 113 | + logger.LogInformation("Address update completed for household identifier kind {Kind}", identifierKind); |
| 114 | + return Result.Success(); |
| 115 | + } |
| 116 | + |
| 117 | + if (updateResult.IsPolicyRejection) |
| 118 | + { |
| 119 | + logger.LogWarning( |
| 120 | + "Address update policy rejection for household identifier kind {Kind}: {ErrorCode}", |
| 121 | + identifierKind, |
| 122 | + updateResult.ErrorCode); |
| 123 | + return Result.PreconditionFailed(PreconditionFailedReason.Conflict, updateResult.ErrorMessage); |
| 124 | + } |
| 125 | + |
| 126 | + logger.LogError( |
| 127 | + "Address update backend error for household identifier kind {Kind}: {ErrorCode}", |
| 128 | + identifierKind, |
| 129 | + updateResult.ErrorCode); |
| 130 | + return Result.DependencyFailed(DependencyFailedReason.ConnectionFailed, updateResult.ErrorMessage); |
| 131 | + } |
| 132 | + catch (Exception ex) |
| 133 | + { |
| 134 | + logger.LogError(ex, "Address update plugin failed for household identifier kind {Kind}", identifierKind); |
| 135 | + return Result.DependencyFailed( |
| 136 | + DependencyFailedReason.ConnectionFailed, |
| 137 | + "Address update service is temporarily unavailable."); |
| 138 | + } |
53 | 139 | } |
54 | 140 | } |
0 commit comments