diff --git a/backend/customer-registration-service/src/docs/customer_flow.md b/backend/customer-registration-service/src/docs/customer_flow.md index d4eba4a9..4d00ef66 100644 --- a/backend/customer-registration-service/src/docs/customer_flow.md +++ b/backend/customer-registration-service/src/docs/customer_flow.md @@ -2,7 +2,7 @@ ## 1. Overview -The Customer Profile Management service is a feature within the Customer Registration microservice that allows for the modification and retrieval of existing customer data. It provides secure endpoints for updating a customer's basic profile information and fetching their addresses from the Fineract core banking platform. This functionality is essential for maintaining accurate and up-to-date customer records. +The Customer Profile Management service is a feature within the Customer Registration microservice that allows for the modification and retrieval of existing customer data. It provides secure endpoints for updating a customer's basic profile information and managing their addresses on the Fineract core banking platform. This functionality is essential for maintaining accurate and up-to-date customer records. --- @@ -17,7 +17,7 @@ This endpoint is used to initiate an update to the authenticated customer's prof ### 2.2. Security Model - **Authentication:** Requires a valid JWT `Bearer` token for an authenticated user. -- **Authorization:** The service determines the Fineract client ID from a `fineract_client_id` or `fineract_external_id` claim in the JWT, ensuring that a user can only update their own profile. +- **Authorization:** Access is restricted by method-level security. The caller must have a valid JWT containing the `ROLE_KYC_MANAGER` authority. The service determines the Fineract client ID from a `fineract_client_id` or `fineract_external_id` claim in the JWT. ### 2.3. Request Payload @@ -44,6 +44,7 @@ The endpoint expects a `Content-Type: application/json` body. Any combination of - **Success (`200 OK`):** A JSON object detailing the changes is returned on a successful update. - **Error (`400 Bad Request`):** Returned for validation errors. - **Error (`401 Unauthorized`):** Returned if the request lacks a valid JWT. +- **Error (`403 Forbidden`):** Returned if the user does not have the `ROLE_KYC_MANAGER` authority. #### Sample Success Response @@ -64,14 +65,14 @@ The endpoint expects a `Content-Type: application/json` body. Any combination of ### 3.1. API Endpoint -#### `GET /api/profile/clients/{clientId}/addresses` +#### `GET /api/profile/addresses` -This endpoint retrieves a list of all addresses associated with a specific Fineract client. +This endpoint retrieves a list of all addresses associated with the authenticated Fineract client. ### 3.2. Security Model - **Authentication:** Requires a valid JWT `Bearer` token. -- **Authorization:** Access is restricted by method-level security. The caller must have a valid JWT containing the `ROLE_KYC_MANAGER` authority. Requests from authenticated users without this role will be rejected with a `403 Forbidden` error. +- **Authorization:** Access is restricted by method-level security. The caller must have a valid JWT containing the `ROLE_KYC_MANAGER` authority. The service determines the Fineract client ID from a `fineract_client_id` or `fineract_external_id` claim in the JWT. Requests from authenticated users without this role will be rejected with a `403 Forbidden` error. ### 3.3. Success Response (`200 OK`) @@ -83,6 +84,7 @@ A successful request returns a JSON object containing a list of addresses. { "addresses": [ { + "addressId": 1, "addressType": "Residential", "addressLine1": "Rue Drouot", "addressLine2": "4.1585", @@ -102,14 +104,14 @@ A successful request returns a JSON object containing a list of addresses. ### 4.1. API Endpoint -#### `POST /api/profile/clients/{clientId}/addresses` +#### `POST /api/profile/addresses` -This endpoint creates a new address for a specific Fineract client. +This endpoint creates a new address for the authenticated Fineract client. ### 4.2. Security Model - **Authentication:** Requires a valid JWT `Bearer` token. -- **Authorization:** No specific role is required. Any authenticated user can create an address for a client. +- **Authorization:** Access is restricted by method-level security. The caller must have a valid JWT containing the `ROLE_KYC_MANAGER` authority. The service determines the Fineract client ID from a `fineract_client_id` or `fineract_external_id` claim in the JWT. ### 4.3. Request Payload @@ -117,37 +119,56 @@ The endpoint expects a `Content-Type: application/json` body. | Field | Type | Required | Description | |---|---|---|---| -| `street` | `String` | Yes | The street name. | -| `addressLine1` | `String` | No | Additional address line 1. | +| `addressLine1` | `String` | Yes | The street name. | | `addressLine2` | `String` | No | Additional address line 2. | | `addressLine3` | `String` | No | Additional address line 3. | | `city` | `String` | Yes | The city. | -| `stateProvince` | `String` | Yes | The name of the state or province. | -| `country` | `String` | Yes | The name of the country. | +| `stateProvince` | `String` | Yes | The name of the state or province. See "Address Field Values" for allowed values. | +| `country` | `String` | Yes | The name of the country. See "Address Field Values" for allowed values. | | `postalCode` | `String` | No | The postal code. | -| `addressType` | `String` | Yes | The type of address (e.g., "Home", "Work"). | +| `addressType` | `String` | Yes | The type of address. See "Address Field Values" for allowed values. | + +#### Address Field Values + +The `addressType`, `stateProvince`, and `country` fields are not free-form strings. They are mapped to pre-configured values in Fineract. Providing a value that is not configured in Fineract will result in an error. + +* **`addressType`**: + * `Residential` + * `Business` + * `Other` +* **`country`**: + * `Cameroon` +* **`stateProvince`**: + * `Adamaoua` + * `Centre` + * `East` + * `Far North` + * `Littoral` + * `North` + * `North-West` + * `South` + * `South-West` + * `West` #### Sample Request ```json { - "street": "Ipca", - "addressLine1": "Kandivali", - "addressLine2": "plot47", - "addressLine3": "charkop", - "city": "Mumbai", - "stateProvince": "Maharashtra", - "country": "India", - "postalCode": "400064", - "addressType": "Home" + "addressLine1": "Rue Deido", + "city": "Douala", + "stateProvince": "Littoral", + "country": "Cameroon", + "postalCode": "00237", + "addressType": "Residential" } ``` ### 4.4. API Responses - **Success (`200 OK`):** A JSON object with the `resourceId` of the newly created address is returned. -- **Error (`400 Bad Request`):** Returned for validation errors. +- **Error (`400 Bad Request`):** Returned for validation errors or if an invalid value is provided for `addressType`, `stateProvince`, or `country`. - **Error (`401 Unauthorized`):** Returned if the request lacks a valid JWT. +- **Error (`403 Forbidden`):** Returned if the user does not have the `ROLE_KYC_MANAGER` authority. #### Sample Success Response @@ -163,14 +184,14 @@ The endpoint expects a `Content-Type: application/json` body. ### 5.1. API Endpoint -#### `PUT /api/profile/clients/{clientId}/addresses` +#### `PUT /api/profile/addresses` -This endpoint updates an existing address for a specific Fineract client. +This endpoint updates an existing address for the authenticated Fineract client. It supports partial updates, so you only need to provide the fields you want to change. ### 5.2. Security Model - **Authentication:** Requires a valid JWT `Bearer` token. -- **Authorization:** No specific role is required. Any authenticated user can update an address for a client. +- **Authorization:** Access is restricted by method-level security. The caller must have a valid JWT containing the `ROLE_KYC_MANAGER` authority. The service determines the Fineract client ID from a `fineract_client_id` or `fineract_external_id` claim in the JWT. ### 5.3. Request Payload @@ -178,38 +199,40 @@ The endpoint expects a `Content-Type: application/json` body. | Field | Type | Required | Description | |---|---|---|---| -| `addressId` | `Long` | Yes | The ID of the address to update. | -| `street` | `String` | No | The new street name. | -| `addressLine1` | `String` | No | Additional address line 1. | +| `addressId` | `Long` | Yes | The ID of the address in Fineract to update. | +| `addressLine1` | `String` | No | The new street name. | | `addressLine2` | `String` | No | Additional address line 2. | | `addressLine3` | `String` | No | Additional address line 3. | | `city` | `String` | No | The new city. | -| `stateProvince` | `String` | No | The new name of the state or province. | -| `country` | `String` | No | The new name of the country. | +| `stateProvince` | `String` | No | The new name of the state or province. See "Address Field Values" for allowed values. | +| `country` | `String` | No | The new name of the country. See "Address Field Values" for allowed values. | | `postalCode` | `String` | No | The new postal code. | -| `addressType` | `String` | Yes | The type of address (e.g., "Home", "Work"). | +| `addressType` | `String` | Yes | The type of address. This field is mandatory for updates. See "Address Field Values" for allowed values. | + +**Note on `addressType`, `stateProvince`, and `country`:** As with creating an address, these fields are sent as strings (e.g., "Business", "Littoral", "Cameroon") and are converted to their corresponding IDs on the backend. #### Sample Request ```json { - "addressId": 67, - "street": "goldensource", - "addressType": "Work" + "addressId": 1, + "addressLine1": "Avenue Deido", + "addressType": "Business" } ``` ### 5.4. API Responses - **Success (`200 OK`):** A JSON object with the `resourceId` of the updated address is returned. -- **Error (`400 Bad Request`):** Returned for validation errors. +- **Error (`400 Bad Request`):** Returned for validation errors or if an invalid value is provided for `addressType`, `stateProvince`, or `country`. - **Error (`401 Unauthorized`):** Returned if the request lacks a valid JWT. +- **Error (`403 Forbidden`):** Returned if the user does not have the `ROLE_KYC_MANAGER` authority. #### Sample Success Response ```json { - "resourceId": 67 + "resourceId": 15 } ``` @@ -258,18 +281,44 @@ curl --location --request PATCH 'http://localhost:8081/api/profile' \ }' ``` -### 6.4. Test Case: Get Client Addresses (SUCCESS) -**Objective:** Verify that a KYC Manager can retrieve the addresses for a specific client. +### 6.4. Test Case: Update Only Email (SUCCESS) +**Objective:** Verify that a customer can update just their email address. +**Expected Result:** `200 OK` + +```bash +curl --location --request PATCH 'http://localhost:8081/api/profile' \ +--header 'Content-Type: application/json' \ +--header "Authorization: Bearer $TOKEN" \ +--data-raw '{ + "emailAddress": "new.email.only@example.com" +}' +``` + +### 6.5. Test Case: Update First and Last Name (SUCCESS) +**Objective:** Verify that a customer can update their first and last name. +**Note:** The first name and last name must always be sent together. **Expected Result:** `200 OK` -*Note: Replace `123` with an actual Fineract Client ID.* +```bash +curl --location --request PATCH 'http://localhost:8081/api/profile' \ +--header 'Content-Type: application/json' \ +--header "Authorization: Bearer $TOKEN" \ +--data-raw '{ + "firstName": "NewFirstName", + "lastName": "NewLastName" +}' +``` + +### 6.6. Test Case: Get Client Addresses (SUCCESS) +**Objective:** Verify that a KYC Manager can retrieve the addresses for the authenticated client. +**Expected Result:** `200 OK` ```bash -curl --location --request GET 'http://localhost:8081/api/profile/clients/123/addresses' \ +curl --location --request GET 'http://localhost:8081/api/profile/addresses' \ --header "Authorization: Bearer $TOKEN" ``` -### 6.5. Test Case: Get Client Addresses (FAILURE) +### 6.7. Test Case: Get Client Addresses (FAILURE) **Objective:** Verify that a user without the `ROLE_KYC_MANAGER` authority cannot retrieve addresses. **Expected Result:** `403 Forbidden` @@ -277,45 +326,44 @@ curl --location --request GET 'http://localhost:8081/api/profile/clients/123/add ```bash # Assuming $USER_TOKEN is a token for a non-manager user -curl --location --request GET 'http://localhost:8081/api/profile/clients/123/addresses' \ +curl --location --request GET 'http://localhost:8081/api/profile/addresses' \ --header "Authorization: Bearer $USER_TOKEN" ``` -### 6.6. Test Case: Create Client Address (SUCCESS) -**Objective:** Verify that a new address can be created for a client. +### 6.8. Test Case: Create Client Address (SUCCESS) +**Objective:** Verify that a new address can be created for the authenticated client. **Expected Result:** `200 OK` -*Note: Replace `123` with an actual Fineract Client ID.* - ```bash -curl --location --request POST 'http://localhost:8081/api/profile/clients/123/addresses' \ +curl --location --request POST 'http://localhost:8081/api/profile/addresses' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $TOKEN" \ --data-raw '{ - "street":"Ipca", - "addressLine1":"Kandivali", - "addressLine2":"plot47", - "addressLine3":"charkop", - "city":"Mumbai", - "stateProvince":"Maharashtra", - "country":"India", - "postalCode":"400064", - "addressType":"Home" + "addressLine1": "Rue Deido", + "city": "Douala", + "stateProvince": "Littoral", + "country": "Cameroon", + "postalCode": "00237", + "addressType": "Residential" }' ``` -### 6.7. Test Case: Update Client Address (SUCCESS) -**Objective:** Verify that an existing address can be updated for a client. +### 6.9. Test Case: Update Client Address (SUCCESS) +**Objective:** Verify that an existing address can be updated for the authenticated client. **Expected Result:** `200 OK` -*Note: Replace `123` with an actual Fineract Client ID and `67` with a valid address ID.* ```bash -curl --location --request PUT 'http://localhost:8081/api/profile/clients/123/addresses' \ +curl --location --request PUT 'http://localhost:8081/api/profile/addresses' \ --header 'Content-Type: application/json' \ --header "Authorization: Bearer $TOKEN" \ --data-raw '{ - "addressId":67, - "street":"goldensource", - "addressType":"Work" + "addressId": 4, // Replace with a valid addressId + "addressType": "Business", + "addressLine2": "New Address Line 2", + "addressLine3": "New Address Line 3", + "city": "New City", + "stateProvince": "Centre", + "country": "Cameroon", + "postalCode": "12345" }' ``` diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/controller/profile/CustomerProfileController.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/controller/profile/CustomerProfileController.java index 8b33f9f4..65bb3f29 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/controller/profile/CustomerProfileController.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/controller/profile/CustomerProfileController.java @@ -1,8 +1,8 @@ package com.adorsys.fineract.registration.controller.profile; -import com.adorsys.fineract.registration.dto.profile.AddressDTO; +import com.adorsys.fineract.registration.dto.profile.AddressRequest; import com.adorsys.fineract.registration.dto.profile.AddressListResponse; -import com.adorsys.fineract.registration.dto.profile.AddressResponseDTO; +import com.adorsys.fineract.registration.dto.profile.AddressResponse; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateRequest; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateResponse; import com.adorsys.fineract.registration.service.profile.CustomerProfileService; @@ -11,7 +11,6 @@ import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.security.SecurityRequirement; import jakarta.validation.Valid; -import jakarta.validation.constraints.NotNull; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.security.access.prepost.PreAuthorize; @@ -20,7 +19,6 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -42,6 +40,7 @@ public class CustomerProfileController { @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "401", description = "Unauthorized") }) + @PreAuthorize("hasAuthority('ROLE_KYC_MANAGER')") public ResponseEntity updateProfile( @Valid @RequestBody ProfileUpdateRequest request, @AuthenticationPrincipal Jwt jwt) { @@ -49,8 +48,8 @@ public ResponseEntity updateProfile( return ResponseEntity.ok(response); } - @GetMapping("/clients/{clientId}/addresses") - @Operation(summary = "Get client addresses", description = "Retrieves a list of addresses for a given client.") + @GetMapping("/addresses") + @Operation(summary = "Get client addresses", description = "Retrieves a list of addresses for the authenticated client.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Addresses retrieved successfully"), @ApiResponse(responseCode = "401", description = "Unauthorized"), @@ -59,36 +58,38 @@ public ResponseEntity updateProfile( }) @PreAuthorize("hasAuthority('ROLE_KYC_MANAGER')") public ResponseEntity getClientAddresses( - @PathVariable Long clientId) { - AddressListResponse addresses = customerProfileService.getAddressesByClientId(clientId); + @AuthenticationPrincipal Jwt jwt) { + AddressListResponse addresses = customerProfileService.getAddressesByClientId(jwt); return ResponseEntity.ok(addresses); } - @PostMapping("/clients/{clientId}/addresses") - @Operation(summary = "Create client address", description = "Creates a new address for a given client.") + @PostMapping("/addresses") + @Operation(summary = "Create client address", description = "Creates a new address for the authenticated client.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Address created successfully"), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "401", description = "Unauthorized") }) - public ResponseEntity createClientAddress( - @PathVariable @NotNull Long clientId, - @Valid @RequestBody AddressDTO addressDTO) { - AddressResponseDTO response = customerProfileService.createClientAddress(clientId, addressDTO); + @PreAuthorize("hasAuthority('ROLE_KYC_MANAGER')") + public ResponseEntity createClientAddress( + @AuthenticationPrincipal Jwt jwt, + @Valid @RequestBody AddressRequest addressRequest) { + AddressResponse response = customerProfileService.createClientAddress(jwt, addressRequest); return ResponseEntity.ok(response); } - @PutMapping("/clients/{clientId}/addresses") - @Operation(summary = "Update client address", description = "Updates an existing address for a given client.") + @PutMapping("/addresses") + @Operation(summary = "Update client address", description = "Updates an existing address for the authenticated client.") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Address updated successfully"), @ApiResponse(responseCode = "400", description = "Invalid input"), @ApiResponse(responseCode = "401", description = "Unauthorized") }) - public ResponseEntity updateClientAddress( - @PathVariable @NotNull Long clientId, - @Valid @RequestBody AddressDTO addressDTO) { - AddressResponseDTO response = customerProfileService.updateClientAddress(clientId, addressDTO); + @PreAuthorize("hasAuthority('ROLE_KYC_MANAGER')") + public ResponseEntity updateClientAddress( + @AuthenticationPrincipal Jwt jwt, + @Valid @RequestBody AddressRequest addressRequest) { + AddressResponse response = customerProfileService.updateClientAddress(jwt, addressRequest); return ResponseEntity.ok(response); } } diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDetailsResponse.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDetailsResponse.java new file mode 100644 index 00000000..73a8e6da --- /dev/null +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDetailsResponse.java @@ -0,0 +1,27 @@ +package com.adorsys.fineract.registration.dto.profile; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Data +@Schema(description = "Represents a single address of a customer") +public class AddressDetailsResponse { + @Schema(description = "The unique identifier for the address", example = "1") + private Long addressId; + @Schema(description = "The type of address (e.g., Residential, Business)", example = "Residential") + private String addressType; + @Schema(description = "The primary address line", example = "Rue Drouot") + private String addressLine1; + @Schema(description = "The second address line, can be used for latitude", example = "4.1585") + private String addressLine2; + @Schema(description = "The third address line, can be used for longitude", example = "9.2435") + private String addressLine3; + @Schema(description = "The city of the address", example = "Douala") + private String city; + @Schema(description = "The state or province of the address", example = "Littoral") + private String stateProvince; + @Schema(description = "The country of the address", example = "Cameroon") + private String country; + @Schema(description = "The postal code of the address", example = "00237") + private String postalCode; +} diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressListResponse.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressListResponse.java index 49c573c3..2a2affd8 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressListResponse.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressListResponse.java @@ -9,5 +9,5 @@ @Schema(description = "Represents a list of customer addresses") public class AddressListResponse { @Schema(description = "A list of addresses") - private List addresses; + private List addresses; } diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDTO.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressRequest.java similarity index 87% rename from backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDTO.java rename to backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressRequest.java index 081a8c72..30fc20af 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressDTO.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressRequest.java @@ -3,9 +3,8 @@ import lombok.Data; @Data -public class AddressDTO { +public class AddressRequest { private Long addressId; - private String street; private String addressLine1; private String addressLine2; private String addressLine3; diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponse.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponse.java index c60c73a4..c69f4006 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponse.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponse.java @@ -1,25 +1,12 @@ package com.adorsys.fineract.registration.dto.profile; -import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; @Data -@Schema(description = "Represents a single address of a customer") +@NoArgsConstructor +@AllArgsConstructor public class AddressResponse { - @Schema(description = "The type of address (e.g., Residential, Business)", example = "Residential") - private String addressType; - @Schema(description = "The primary address line", example = "Rue Drouot") - private String addressLine1; - @Schema(description = "The second address line, can be used for latitude", example = "4.1585") - private String addressLine2; - @Schema(description = "The third address line, can be used for longitude", example = "9.2435") - private String addressLine3; - @Schema(description = "The city of the address", example = "Douala") - private String city; - @Schema(description = "The state or province of the address", example = "Littoral") - private String stateProvince; - @Schema(description = "The country of the address", example = "Cameroon") - private String country; - @Schema(description = "The postal code of the address", example = "00237") - private String postalCode; + private Integer resourceId; } diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponseDTO.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponseDTO.java deleted file mode 100644 index c2dc16a8..00000000 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/dto/profile/AddressResponseDTO.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.adorsys.fineract.registration.dto.profile; - -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -@Data -@NoArgsConstructor -@AllArgsConstructor -public class AddressResponseDTO { - private Integer resourceId; -} diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/FineractService.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/FineractService.java index 1cb2858b..d2830a84 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/FineractService.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/FineractService.java @@ -1,7 +1,7 @@ package com.adorsys.fineract.registration.service; -import com.adorsys.fineract.registration.dto.profile.AddressDTO; -import com.adorsys.fineract.registration.dto.profile.AddressResponseDTO; +import com.adorsys.fineract.registration.dto.profile.AddressRequest; +import com.adorsys.fineract.registration.dto.profile.AddressResponse; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateRequest; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateResponse; import com.adorsys.fineract.registration.dto.registration.RegistrationRequest; @@ -69,11 +69,11 @@ public Long createSavingsAccount(Long clientId) { return fineractAccountService.createSavingsAccount(clientId); } - public AddressResponseDTO createClientAddress(Long clientId, AddressDTO addressDTO) { - return fineractAddressService.createClientAddress(clientId, addressDTO); + public AddressResponse createClientAddress(Long clientId, AddressRequest addressRequest) { + return fineractAddressService.createClientAddress(clientId, addressRequest); } - public AddressResponseDTO updateClientAddress(Long clientId, AddressDTO addressDTO) { - return fineractAddressService.updateClientAddress(clientId, addressDTO); + public AddressResponse updateClientAddress(Long clientId, AddressRequest addressRequest) { + return fineractAddressService.updateClientAddress(clientId, addressRequest); } } diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractAddressService.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractAddressService.java index c837624a..7c78aefd 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractAddressService.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractAddressService.java @@ -1,8 +1,8 @@ package com.adorsys.fineract.registration.service.fineract; import com.adorsys.fineract.registration.config.FineractProperties; -import com.adorsys.fineract.registration.dto.profile.AddressDTO; -import com.adorsys.fineract.registration.dto.profile.AddressResponseDTO; +import com.adorsys.fineract.registration.dto.profile.AddressRequest; +import com.adorsys.fineract.registration.dto.profile.AddressResponse; import com.adorsys.fineract.registration.dto.registration.RegistrationRequest; import com.adorsys.fineract.registration.exception.FineractApiException; import lombok.extern.slf4j.Slf4j; @@ -20,6 +20,14 @@ public class FineractAddressService { private static final String ADDRESS_TYPE = "ADDRESS_TYPE"; private static final String STATE = "STATE"; private static final String COUNTRY = "COUNTRY"; + private static final String ADDRESS_TYPE_ID = "addressTypeId"; + private static final String STATE_PROVINCE_ID = "stateProvinceId"; + private static final String COUNTRY_ID = "countryId"; + private static final String ADDRESS_LINE_1 = "addressLine1"; + private static final String ADDRESS_LINE_2 = "addressLine2"; + private static final String ADDRESS_LINE_3 = "addressLine3"; + private static final String CITY = "city"; + private static final String POSTAL_CODE = "postalCode"; /** * This service centralizes all address-related operations for Fineract clients. @@ -43,7 +51,7 @@ public List> getClientAddresses(Long clientId) { try { return fineractRestClient.get() - .uri("/fineract-provider/api/v1/clients/{clientId}/addresses", clientId) + .uri("/fineract-provider/api/v1/client/{clientId}/addresses", clientId) .retrieve() .body(List.class); } catch (Exception e) { @@ -56,17 +64,17 @@ public Map buildAddressPayload(RegistrationRequest request) { Map address = new HashMap<>(); address.put("isActive", true); - putDynamicIdIfPresent(address, "addressTypeId", ADDRESS_TYPE, request.getAddressType()); - putDynamicIdIfPresent(address, "stateProvinceId", STATE, request.getStateProvince()); - putDynamicIdIfPresent(address, "countryId", COUNTRY, request.getCountry()); + putDynamicIdIfPresent(address, ADDRESS_TYPE_ID, ADDRESS_TYPE, request.getAddressType()); + putDynamicIdIfPresent(address, STATE_PROVINCE_ID, STATE, request.getStateProvince()); + putDynamicIdIfPresent(address, COUNTRY_ID, COUNTRY, request.getCountry()); - putIfPresent(address, "addressLine1", request.getAddressLine1()); - putIfPresent(address, "addressLine2", request.getAddressLine2()); - putIfPresent(address, "addressLine3", request.getAddressLine3()); - putIfPresent(address, "city", request.getCity()); + putIfPresent(address, ADDRESS_LINE_1, request.getAddressLine1()); + putIfPresent(address, ADDRESS_LINE_2, request.getAddressLine2()); + putIfPresent(address, ADDRESS_LINE_3, request.getAddressLine3()); + putIfPresent(address, CITY, request.getCity()); String postalCode = request.getPostalCode(); - address.put("postalCode", (postalCode == null || postalCode.isBlank()) ? fineractProperties.getDefaults().getPostalCode() : postalCode); + address.put(POSTAL_CODE, (postalCode == null || postalCode.isBlank()) ? fineractProperties.getDefaults().getPostalCode() : postalCode); return address; } @@ -85,48 +93,54 @@ private void putDynamicIdIfPresent(Map map, String mapKey, Strin } } } - public AddressResponseDTO createClientAddress(Long clientId, AddressDTO addressDTO) { + public AddressResponse createClientAddress(Long clientId, AddressRequest addressRequest) { log.info("Creating address for client: {}", clientId); Map body = new HashMap<>(); - body.put("street", addressDTO.getStreet()); - body.put("addressLine1", addressDTO.getAddressLine1()); - body.put("addressLine2", addressDTO.getAddressLine2()); - body.put("addressLine3", addressDTO.getAddressLine3()); - body.put("city", addressDTO.getCity()); - body.put("postalCode", addressDTO.getPostalCode()); + body.put(ADDRESS_LINE_1, addressRequest.getAddressLine1()); + body.put(ADDRESS_LINE_2, addressRequest.getAddressLine2()); + body.put(ADDRESS_LINE_3, addressRequest.getAddressLine3()); + body.put(CITY, addressRequest.getCity()); + body.put(POSTAL_CODE, addressRequest.getPostalCode()); - putDynamicIdIfPresent(body, "stateProvinceId", STATE, addressDTO.getStateProvince()); - putDynamicIdIfPresent(body, "countryId", COUNTRY, addressDTO.getCountry()); + putDynamicIdIfPresent(body, STATE_PROVINCE_ID, STATE, addressRequest.getStateProvince()); + putDynamicIdIfPresent(body, COUNTRY_ID, COUNTRY, addressRequest.getCountry()); - Long addressTypeId = fineractCodeValueService.getDynamicId(ADDRESS_TYPE, addressDTO.getAddressType()); + Long addressTypeId = fineractCodeValueService.getDynamicId(ADDRESS_TYPE, addressRequest.getAddressType()); try { return fineractRestClient.post() - .uri("/fineract-provider/api/v1/clients/{clientId}/addresses?type={addressTypeId}", clientId, addressTypeId) + .uri("/fineract-provider/api/v1/client/{clientId}/addresses?type={addressTypeId}", clientId, addressTypeId) .body(body) .retrieve() - .body(AddressResponseDTO.class); + .body(AddressResponse.class); } catch (Exception e) { log.error("Failed to create address for client {}: {}", clientId, e.getMessage()); throw new FineractApiException("Failed to create address", e); } } - public AddressResponseDTO updateClientAddress(Long clientId, AddressDTO addressDTO) { + public AddressResponse updateClientAddress(Long clientId, AddressRequest addressRequest) { log.info("Updating address for client: {}", clientId); Map body = new HashMap<>(); - body.put("addressId", addressDTO.getAddressId()); - body.put("street", addressDTO.getStreet()); + body.put("addressId", addressRequest.getAddressId()); + putIfPresent(body, ADDRESS_LINE_1, addressRequest.getAddressLine1()); + putIfPresent(body, ADDRESS_LINE_2, addressRequest.getAddressLine2()); + putIfPresent(body, ADDRESS_LINE_3, addressRequest.getAddressLine3()); + putIfPresent(body, CITY, addressRequest.getCity()); + putIfPresent(body, POSTAL_CODE, addressRequest.getPostalCode()); - Long addressTypeId = fineractCodeValueService.getDynamicId(ADDRESS_TYPE, addressDTO.getAddressType()); + putDynamicIdIfPresent(body, STATE_PROVINCE_ID, STATE, addressRequest.getStateProvince()); + putDynamicIdIfPresent(body, COUNTRY_ID, COUNTRY, addressRequest.getCountry()); + + Long addressTypeId = fineractCodeValueService.getDynamicId(ADDRESS_TYPE, addressRequest.getAddressType()); try { return fineractRestClient.put() - .uri("/fineract-provider/api/v1/clients/{clientId}/addresses?type={addressTypeId}", clientId, addressTypeId) + .uri("/fineract-provider/api/v1/client/{clientId}/addresses?type={addressTypeId}", clientId, addressTypeId) .body(body) .retrieve() - .body(AddressResponseDTO.class); + .body(AddressResponse.class); } catch (Exception e) { log.error("Failed to update address for client {}: {}", clientId, e.getMessage()); throw new FineractApiException("Failed to update address", e); diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractCodeValueService.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractCodeValueService.java index 1628c1d1..8424339d 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractCodeValueService.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/fineract/FineractCodeValueService.java @@ -57,6 +57,9 @@ public void refreshCodeValueCache(String codeName) { } public Long getDynamicId(String codeName, String label) { + if (label == null) { + return null; + } String key = codeName + ":" + label.toLowerCase(); if (!codeValueCache.containsKey(key)) { refreshCodeValueCache(codeName); diff --git a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/profile/CustomerProfileService.java b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/profile/CustomerProfileService.java index b8f3bb19..fb772342 100644 --- a/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/profile/CustomerProfileService.java +++ b/backend/customer-registration-service/src/main/java/com/adorsys/fineract/registration/service/profile/CustomerProfileService.java @@ -1,9 +1,9 @@ package com.adorsys.fineract.registration.service.profile; -import com.adorsys.fineract.registration.dto.profile.AddressDTO; +import com.adorsys.fineract.registration.dto.profile.AddressRequest; import com.adorsys.fineract.registration.dto.profile.AddressListResponse; +import com.adorsys.fineract.registration.dto.profile.AddressDetailsResponse; import com.adorsys.fineract.registration.dto.profile.AddressResponse; -import com.adorsys.fineract.registration.dto.profile.AddressResponseDTO; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateRequest; import com.adorsys.fineract.registration.dto.profile.ProfileUpdateResponse; import com.adorsys.fineract.registration.service.FineractService; @@ -20,29 +20,15 @@ public class CustomerProfileService { private final FineractService fineractService; public ProfileUpdateResponse updateProfile(ProfileUpdateRequest request, Jwt jwt) { - // Extract fineract_client_id from JWT - String fineractClientId = jwt.getClaimAsString("fineract_client_id"); - if (fineractClientId == null) { - String fineractExternalId = jwt.getClaimAsString("fineract_external_id"); - if (fineractExternalId == null) { - throw new IllegalArgumentException("fineract_client_id and fineract_external_id claims are missing from JWT"); - } - Map client = fineractService.getClientByExternalId(fineractExternalId); - if (client == null || client.isEmpty()) { - throw new IllegalArgumentException("Client not found with external id " + fineractExternalId); - } - fineractClientId = String.valueOf(client.get("id")); - } - - - // Call Fineract to update the client - return fineractService.updateClient(Long.valueOf(fineractClientId), request); + Long clientId = getClientIdFromJwt(jwt); + return fineractService.updateClient(clientId, request); } - public AddressListResponse getAddressesByClientId(Long clientId) { + public AddressListResponse getAddressesByClientId(Jwt jwt) { + Long clientId = getClientIdFromJwt(jwt); List> fineractAddresses = fineractService.getClientAddresses(clientId); - List addresses = fineractAddresses.stream() - .map(this::mapToAddressResponse) + List addresses = fineractAddresses.stream() + .map(this::mapToAddressDetailsResponse) .toList(); AddressListResponse response = new AddressListResponse(); @@ -50,11 +36,11 @@ public AddressListResponse getAddressesByClientId(Long clientId) { return response; } - private AddressResponse mapToAddressResponse(Map fineractAddress) { - AddressResponse address = new AddressResponse(); + private AddressDetailsResponse mapToAddressDetailsResponse(Map fineractAddress) { + AddressDetailsResponse address = new AddressDetailsResponse(); + address.setAddressId(((Number) fineractAddress.get("addressId")).longValue()); address.setAddressType((String) fineractAddress.get("addressType")); - // Fineract uses 'street' for addressLine1 in its GET address response - address.setAddressLine1((String) fineractAddress.get("street")); + address.setAddressLine1((String) fineractAddress.get("addressLine1")); address.setAddressLine2((String) fineractAddress.get("addressLine2")); address.setAddressLine3((String) fineractAddress.get("addressLine3")); address.setCity((String) fineractAddress.get("city")); @@ -64,11 +50,29 @@ private AddressResponse mapToAddressResponse(Map fineractAddress return address; } - public AddressResponseDTO createClientAddress(Long clientId, AddressDTO addressDTO) { - return fineractService.createClientAddress(clientId, addressDTO); + public AddressResponse createClientAddress(Jwt jwt, AddressRequest addressRequest) { + Long clientId = getClientIdFromJwt(jwt); + return fineractService.createClientAddress(clientId, addressRequest); } - public AddressResponseDTO updateClientAddress(Long clientId, AddressDTO addressDTO) { - return fineractService.updateClientAddress(clientId, addressDTO); + public AddressResponse updateClientAddress(Jwt jwt, AddressRequest addressRequest) { + Long clientId = getClientIdFromJwt(jwt); + return fineractService.updateClientAddress(clientId, addressRequest); + } + + private Long getClientIdFromJwt(Jwt jwt) { + String fineractClientId = jwt.getClaimAsString("fineract_client_id"); + if (fineractClientId == null) { + String fineractExternalId = jwt.getClaimAsString("fineract_external_id"); + if (fineractExternalId == null) { + throw new IllegalArgumentException("fineract_client_id and fineract_external_id claims are missing from JWT"); + } + Map client = fineractService.getClientByExternalId(fineractExternalId); + if (client == null || client.isEmpty()) { + throw new IllegalArgumentException("Client not found with external id " + fineractExternalId); + } + fineractClientId = String.valueOf(client.get("id")); + } + return Long.valueOf(fineractClientId); } }