diff --git a/authentication-service/pom.xml b/authentication-service/pom.xml
index c055845..681dc69 100644
--- a/authentication-service/pom.xml
+++ b/authentication-service/pom.xml
@@ -32,6 +32,11 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.kafka
+ spring-kafka
+
+
org.springframework.boot
@@ -79,6 +84,12 @@
org.springframework.boot
spring-boot-starter-data-jpa
+
+ org.springframework.cloud
+ spring-cloud-commons
+ 4.2.0-RC1
+ compile
+
diff --git a/authentication-service/src/main/java/com/finpay/authentication/clients/UserServiceClient.java b/authentication-service/src/main/java/com/finpay/authentication/clients/UserServiceClient.java
new file mode 100644
index 0000000..c031f8c
--- /dev/null
+++ b/authentication-service/src/main/java/com/finpay/authentication/clients/UserServiceClient.java
@@ -0,0 +1,38 @@
+package com.finpay.authentication.clients;
+import com.finpay.authentication.dtos.UserDto;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+import java.util.Optional;
+
+@Component
+public class UserServiceClient {
+
+ private final RestTemplate restTemplate;
+ private final String apiGatewayBaseUrl = "http://api-gateway"; // API Gateway base URL
+ private final String userServicePath = "/user-service/api/users"; // Path to User Service in the gateway
+
+ public UserServiceClient(RestTemplate restTemplate) {
+ this.restTemplate = restTemplate;
+ }
+
+ /**
+ * Fetch a user by email from the User Service via the API Gateway.
+ *
+ * @param email the email of the user
+ * @return an Optional containing UserDto if the user exists, or empty if not found
+ */
+ public Optional getUserByEmail(String email) {
+ String url = UriComponentsBuilder.fromHttpUrl(apiGatewayBaseUrl + userServicePath)
+ .queryParam("email", email)
+ .toUriString();
+ try {
+ UserDto userDto = restTemplate.getForObject(url, UserDto.class);
+ return Optional.ofNullable(userDto);
+ } catch (Exception e) {
+ // Log error or handle exception as needed
+ return Optional.empty();
+ }
+ }
+}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/config/AppConfig.java b/authentication-service/src/main/java/com/finpay/authentication/config/AppConfig.java
new file mode 100644
index 0000000..0d41040
--- /dev/null
+++ b/authentication-service/src/main/java/com/finpay/authentication/config/AppConfig.java
@@ -0,0 +1,16 @@
+package com.finpay.authentication.config;
+
+import org.springframework.cloud.client.loadbalancer.LoadBalanced;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class AppConfig {
+
+ @Bean
+ @LoadBalanced
+ public RestTemplate restTemplate() {
+ return new RestTemplate();
+ }
+}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/controllers/AuthController.java b/authentication-service/src/main/java/com/finpay/authentication/controllers/AuthController.java
index e93d57f..a0ec406 100644
--- a/authentication-service/src/main/java/com/finpay/authentication/controllers/AuthController.java
+++ b/authentication-service/src/main/java/com/finpay/authentication/controllers/AuthController.java
@@ -1,11 +1,7 @@
package com.finpay.authentication.controllers;
-import com.finpay.authentication.dtos.JwtResponse;
-import com.finpay.authentication.dtos.LoginRequest;
-import com.finpay.authentication.dtos.MessageResponse;
-import com.finpay.authentication.dtos.SignupRequest;
-import com.finpay.authentication.models.User;
-import com.finpay.authentication.repository.UserRepository;
+import com.finpay.authentication.clients.UserServiceClient;
+import com.finpay.authentication.dtos.*;
import com.finpay.authentication.utils.JwtUtil;
import org.springframework.http.*;
import org.springframework.security.authentication.*;
@@ -14,52 +10,30 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
-import java.util.UUID;
-
@RestController
@RequestMapping("/api/auth")
public class AuthController {
private final AuthenticationManager authenticationManager;
- private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
private final JwtUtil jwtUtil;
+ private final UserServiceClient userServiceClient; // Add UserServiceClient
// Constructor-based injection
public AuthController(AuthenticationManager authenticationManager,
- UserRepository userRepository,
PasswordEncoder passwordEncoder,
- JwtUtil jwtUtil) {
+ JwtUtil jwtUtil,
+ UserServiceClient userServiceClient) {
this.authenticationManager = authenticationManager;
- this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
this.jwtUtil = jwtUtil;
- }
-
- @PostMapping("/register")
- public ResponseEntity> registerUser(@RequestBody SignupRequest signupRequest) {
- if (userRepository.existsByEmail(signupRequest.getEmail())) {
- return ResponseEntity
- .badRequest()
- .body(new MessageResponse("Error: Email is already in use!"));
- }
-
- // Create new user
- User user = User.builder()
- .id(UUID.randomUUID())
- .name(signupRequest.getName())
- .email(signupRequest.getEmail())
- .password(passwordEncoder.encode(signupRequest.getPassword()))
- .build();
-
- userRepository.save(user);
-
- return ResponseEntity.ok(new MessageResponse("User registered successfully!"));
+ this.userServiceClient = userServiceClient; // Initialize UserServiceClient
}
@PostMapping("/login")
- public ResponseEntity> authenticateUser(@RequestBody LoginRequest loginRequest) {
+ public ResponseEntity> authenticateUser(@RequestBody LoginRequest loginRequest) {
try {
+ // Authenticate user
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(
loginRequest.getEmail(),
@@ -67,14 +41,15 @@ public ResponseEntity> authenticateUser(@RequestBody LoginRequest loginRequest
)
);
- // If authentication is successful
- String jwt = jwtUtil.generateJwtToken(loginRequest.getEmail());
-
- User user = userRepository.findByEmail(loginRequest.getEmail())
+ // Fetch user details from User Service
+ UserDto userDto = userServiceClient.getUserByEmail(loginRequest.getEmail())
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
- JwtResponse jwtResponse = new JwtResponse(jwt, user.getId(), user.getName(), user.getEmail());
+ // Generate JWT with email and roles
+ String jwt = jwtUtil.generateJwtToken(loginRequest.getEmail(), userDto.getRoles());
+ // Build JWT response
+ JwtResponse jwtResponse = new JwtResponse(jwt, userDto.getId(), userDto.getName(), userDto.getEmail());
return ResponseEntity.ok(jwtResponse);
} catch (BadCredentialsException e) {
@@ -83,4 +58,6 @@ public ResponseEntity> authenticateUser(@RequestBody LoginRequest loginRequest
.body(new MessageResponse("Error: Invalid email or password"));
}
}
+
+
}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/dtos/UserDto.java b/authentication-service/src/main/java/com/finpay/authentication/dtos/UserDto.java
new file mode 100644
index 0000000..9053dbe
--- /dev/null
+++ b/authentication-service/src/main/java/com/finpay/authentication/dtos/UserDto.java
@@ -0,0 +1,15 @@
+package com.finpay.authentication.dtos;
+
+import lombok.Data;
+
+import java.util.Set;
+import java.util.UUID;
+
+@Data
+public class UserDto {
+ private UUID id;
+ private String email;
+ private String password;
+ private String name;
+ private Set roles;
+}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/dtos/UserEvent.java b/authentication-service/src/main/java/com/finpay/authentication/dtos/UserEvent.java
new file mode 100644
index 0000000..2942650
--- /dev/null
+++ b/authentication-service/src/main/java/com/finpay/authentication/dtos/UserEvent.java
@@ -0,0 +1,11 @@
+//package com.finpay.authentication.dtos;
+//
+//import lombok.Data;
+//import java.util.UUID;
+//
+//@Data
+//public class UserEvent {
+// private UUID id;
+// private String email;
+// private String roles;
+//}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/filter/JwtAuthenticationFilter.java b/authentication-service/src/main/java/com/finpay/authentication/filter/JwtAuthenticationFilter.java
index 46a9c8f..fa1cc96 100644
--- a/authentication-service/src/main/java/com/finpay/authentication/filter/JwtAuthenticationFilter.java
+++ b/authentication-service/src/main/java/com/finpay/authentication/filter/JwtAuthenticationFilter.java
@@ -3,6 +3,7 @@
import com.finpay.authentication.services.UserDetailsServiceImpl;
import com.finpay.authentication.utils.JwtUtil;
import org.springframework.security.authentication.*;
+import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.filter.OncePerRequestFilter;
@@ -11,6 +12,9 @@
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
+import java.util.List;
+import java.util.Set;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
@@ -38,21 +42,18 @@ protected void doFilterInternal(HttpServletRequest request,
}
if (email != null && SecurityContextHolder.getContext().getAuthentication() == null) {
- UserDetails userDetails = userDetailsService.loadUserByUsername(email);
- if (jwtUtil.validateJwtToken(jwt)) {
- UsernamePasswordAuthenticationToken authToken =
- new UsernamePasswordAuthenticationToken(
- userDetails,
- null,
- userDetails.getAuthorities()
- );
- authToken.setDetails(
- new WebAuthenticationDetailsSource().buildDetails(request)
- );
- SecurityContextHolder.getContext().setAuthentication(authToken);
- }
+ Set roles = jwtUtil.getRolesFromJwtToken(jwt); // Add method to extract roles
+ List authorities = roles.stream()
+ .map(SimpleGrantedAuthority::new)
+ .toList();
+
+ UsernamePasswordAuthenticationToken authToken =
+ new UsernamePasswordAuthenticationToken(email, null, authorities);
+
+ SecurityContextHolder.getContext().setAuthentication(authToken);
}
filterChain.doFilter(request, response);
}
+
}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/models/User.java b/authentication-service/src/main/java/com/finpay/authentication/models/User.java
deleted file mode 100644
index caa3dc5..0000000
--- a/authentication-service/src/main/java/com/finpay/authentication/models/User.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package com.finpay.authentication.models;
-import jakarta.persistence.*;
-import lombok.*;
-
-import java.util.UUID;
-
-@Entity
-@Table(name = "users")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-@Builder
-public class User {
-
- @Id
- @GeneratedValue
- private UUID id;
-
- @Column(nullable = false, unique = true)
- private String email;
-
- @Column(nullable = false)
- private String password;
-
- @Column(nullable = false)
- private String name;
-
-
-}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/repository/UserRepository.java b/authentication-service/src/main/java/com/finpay/authentication/repository/UserRepository.java
deleted file mode 100644
index 826e577..0000000
--- a/authentication-service/src/main/java/com/finpay/authentication/repository/UserRepository.java
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.finpay.authentication.repository;
-import com.finpay.authentication.models.User;
-import org.springframework.data.jpa.repository.JpaRepository;
-import java.util.Optional;
-import java.util.UUID;
-
-public interface UserRepository extends JpaRepository {
- Optional findByEmail(String email);
- boolean existsByEmail(String email);
-}
\ No newline at end of file
diff --git a/authentication-service/src/main/java/com/finpay/authentication/services/UserDetailsServiceImpl.java b/authentication-service/src/main/java/com/finpay/authentication/services/UserDetailsServiceImpl.java
index ec3d514..f51ddd5 100644
--- a/authentication-service/src/main/java/com/finpay/authentication/services/UserDetailsServiceImpl.java
+++ b/authentication-service/src/main/java/com/finpay/authentication/services/UserDetailsServiceImpl.java
@@ -1,8 +1,6 @@
package com.finpay.authentication.services;
-
-import com.finpay.authentication.models.User;
-import com.finpay.authentication.repository.UserRepository;
-import org.springframework.security.core.*;
+import com.finpay.authentication.clients.UserServiceClient;
+import com.finpay.authentication.dtos.UserDto;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.*;
import org.springframework.stereotype.Service;
@@ -10,29 +8,27 @@
import java.util.*;
@Service
+
public class UserDetailsServiceImpl implements UserDetailsService {
- private final UserRepository userRepository;
+ private final UserServiceClient userServiceClient; // REST or gRPC client to fetch user data
- // Constructor-based injection
- public UserDetailsServiceImpl(UserRepository userRepository) {
- this.userRepository = userRepository;
+ public UserDetailsServiceImpl(UserServiceClient userServiceClient) {
+ this.userServiceClient = userServiceClient;
}
@Override
- public UserDetails loadUserByUsername(String email)
- throws UsernameNotFoundException {
- User user = userRepository.findByEmail(email)
- .orElseThrow(() ->
- new UsernameNotFoundException("User Not Found with email: " + email));
+ public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
+ UserDto userDto = userServiceClient.getUserByEmail(email)
+ .orElseThrow(() -> new UsernameNotFoundException("User Not Found with email: " + email));
- // For simplicity, all users have ROLE_USER
- List authorities =
- Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
+ List authorities = userDto.getRoles().stream()
+ .map(SimpleGrantedAuthority::new)
+ .toList();
return new org.springframework.security.core.userdetails.User(
- user.getEmail(),
- user.getPassword(),
+ userDto.getEmail(),
+ userDto.getPassword(),
authorities
);
}
diff --git a/authentication-service/src/main/java/com/finpay/authentication/utils/JwtUtil.java b/authentication-service/src/main/java/com/finpay/authentication/utils/JwtUtil.java
index 353a94b..ee203d6 100644
--- a/authentication-service/src/main/java/com/finpay/authentication/utils/JwtUtil.java
+++ b/authentication-service/src/main/java/com/finpay/authentication/utils/JwtUtil.java
@@ -8,9 +8,12 @@
import javax.crypto.SecretKey;
import java.util.Date;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
@Component
-@Slf4j // Lombok annotation to add SLF4J logging
+@Slf4j
public class JwtUtil {
@Value("${jwt.secret}")
@@ -20,23 +23,23 @@ public class JwtUtil {
private Long jwtExpirationMs;
private SecretKey getSigningKey() {
- // Use the proper secret key from jwtSecret
return Keys.hmacShaKeyFor(jwtSecret.getBytes());
}
// Generate JWT Token
- public String generateJwtToken(String email) {
+ public String generateJwtToken(String email, Set roles) {
return Jwts.builder()
.setSubject(email)
+ .claim("roles", roles)
.setIssuedAt(new Date())
.setExpiration(new Date((new Date()).getTime() + jwtExpirationMs))
- .signWith(getSigningKey(), SignatureAlgorithm.HS512) // Updated to use SecretKey
+ .signWith(getSigningKey(), SignatureAlgorithm.HS512)
.compact();
}
// Get Email from JWT Token
public String getEmailFromJwtToken(String token) {
- return Jwts.parserBuilder() // Updated to use parserBuilder
+ return Jwts.parserBuilder()
.setSigningKey(getSigningKey())
.build()
.parseClaimsJws(token)
@@ -44,6 +47,20 @@ public String getEmailFromJwtToken(String token) {
.getSubject();
}
+ // Get Roles from JWT Token
+ public Set getRolesFromJwtToken(String token) {
+ Claims claims = Jwts.parserBuilder()
+ .setSigningKey(getSigningKey())
+ .build()
+ .parseClaimsJws(token)
+ .getBody();
+
+ @SuppressWarnings("unchecked")
+ Set roles = new HashSet<>(((List) claims.get("roles")));
+
+ return roles;
+ }
+
// Validate JWT Token
public boolean validateJwtToken(String authToken) {
try {
diff --git a/payment-service/pom.xml b/payment-service/pom.xml
index 413b88d..9e8f1a4 100644
--- a/payment-service/pom.xml
+++ b/payment-service/pom.xml
@@ -38,13 +38,43 @@
org.springframework.boot
spring-boot-starter-web
+
+ com.stripe
+ stripe-java
+ 28.2.0
+
+
+ jakarta.el
+ jakarta.el-api
+ 6.0.1
+
+
+ jakarta.validation
+ jakarta.validation-api
+ 3.1.0
+
+
+ org.hibernate.validator
+ hibernate-validator
+ 8.0.1.Final
+
+
+
+
+
+
org.springframework.boot
spring-boot-devtools
runtime
true
+
+ org.postgresql
+ postgresql
+ runtime
+
org.projectlombok
lombok
@@ -56,9 +86,27 @@
test
- org.springframework.boot
- spring-boot-starter-data-jpa
+ com.github.docker-java
+ docker-java-api
+ 3.4.0
+ compile
+
+ org.springframework.security
+ spring-security-oauth2-resource-server
+
+
+ org.springframework.security
+ spring-security-config
+
+
+
+ org.jetbrains
+ annotations
+ 17.0.0
+ compile
+
+
diff --git a/payment-service/src/main/java/com/finpay/payment_service/config/MpesaConfig.java b/payment-service/src/main/java/com/finpay/payment_service/config/MpesaConfig.java
new file mode 100644
index 0000000..07d1292
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/config/MpesaConfig.java
@@ -0,0 +1,21 @@
+package com.finpay.payment_service.config;
+
+import lombok.Data;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+@ConfigurationProperties(prefix = "mpesa")
+@Data
+public class MpesaConfig {
+
+ private String consumerKey;
+ private String consumerSecret;
+ private String passkey;
+ private String businessShortCode;
+ private String initiatorName;
+ private String initiatorPassword;
+ private String callbackUrl;
+ private String registerUrl;
+
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/config/SecurityConfig.java b/payment-service/src/main/java/com/finpay/payment_service/config/SecurityConfig.java
new file mode 100644
index 0000000..a253d66
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/config/SecurityConfig.java
@@ -0,0 +1,44 @@
+package com.finpay.payment_service.config;
+
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
+import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
+import org.springframework.security.web.SecurityFilterChain;
+
+@Configuration
+@EnableMethodSecurity(prePostEnabled = true)
+public class SecurityConfig {
+
+ @Bean
+ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/api/payments/initiate").hasAnyRole("USER", "ADMIN")
+ .requestMatchers("/api/payments/refund").hasAnyRole("USER", "ADMIN")
+ .requestMatchers("/api/payments/**").authenticated()
+ .anyRequest().permitAll()
+ )
+ .oauth2ResourceServer(oauth2 -> oauth2
+ .jwt(jwt -> jwt
+ .jwtAuthenticationConverter(jwtAuthenticationConverter())
+ )
+ );
+
+ return http.build();
+ }
+
+ private JwtAuthenticationConverter jwtAuthenticationConverter() {
+ JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
+ grantedAuthoritiesConverter.setAuthoritiesClaimName("roles"); // Ensure this matches your JWT
+ grantedAuthoritiesConverter.setAuthorityPrefix("ROLE_");
+
+ JwtAuthenticationConverter authenticationConverter = new JwtAuthenticationConverter();
+ authenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
+ return authenticationConverter;
+ }
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/CardPaymentRequest.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/CardPaymentRequest.java
new file mode 100644
index 0000000..6fd7e16
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/CardPaymentRequest.java
@@ -0,0 +1,31 @@
+package com.finpay.payment_service.dtos;
+import lombok.Data;
+//import jakarta.validation.constraints.NotBlank;
+//import jakarta.validation.constraints.Pattern;
+@Data
+public class CardPaymentRequest {
+
+// @NotBlank(message = "Card number is required")
+// @Pattern(regexp = "^[0-9]{13,19}$", message = "Invalid card number")
+
+ private String cardNumber;
+
+// @NotBlank(message = "Expiry month is required")
+// @Pattern(regexp = "^(0[1-9]|1[0-2])$", message = "Invalid expiry month")
+ private String expiryMonth;
+
+// @NotBlank(message = "Expiry year is required")
+// @Pattern(regexp = "^[0-9]{2}$", message = "Invalid expiry year")
+ private String expiryYear;
+
+// @NotBlank(message = "CVV is required")
+// @Pattern(regexp = "^[0-9]{3,4}$", message = "Invalid CVV")
+ private String cvv;
+
+// @NotBlank(message = "Cardholder name is required")
+ private String cardHolderName;
+
+// @NotBlank(message = "Billing address is required")
+ private String billingAddress;
+
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/ErrorResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/ErrorResponse.java
new file mode 100644
index 0000000..424d71e
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/ErrorResponse.java
@@ -0,0 +1,17 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class ErrorResponse {
+ private UUID errorId;
+ private LocalDateTime timestamp;
+ private int status;
+ private String error;
+ private String message;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/InvoiceResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/InvoiceResponse.java
new file mode 100644
index 0000000..2a31be2
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/InvoiceResponse.java
@@ -0,0 +1,16 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Data
+public class InvoiceResponse {
+ private UUID id;
+ private UUID userId;
+ private BigDecimal totalAmount;
+ private String currency;
+ private String status;
+ // Add other relevant fields as necessary
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentGatewayResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentGatewayResponse.java
new file mode 100644
index 0000000..5af1a5c
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentGatewayResponse.java
@@ -0,0 +1,32 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class PaymentGatewayResponse {
+ /**
+ * Status of the payment operation.
+ * Possible values: SUCCESS, FAILURE
+ */
+ private String status;
+
+ /**
+ * Message providing additional information about the payment operation.
+ */
+ private String message;
+
+ /**
+ * Unique identifier for the payment, provided by the payment gateway.
+ * Example: A transaction ID or reference number.
+ */
+ private String paymentId;
+
+ /**
+ * Additional details or metadata about the payment (optional).
+ */
+ private String additionalDetails;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentRequest.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentRequest.java
new file mode 100644
index 0000000..5c03ebe
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentRequest.java
@@ -0,0 +1,21 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class PaymentRequest {
+ private BigDecimal amount;
+ private String currency;
+ private String paymentMethod;
+ private UUID invoiceId;
+ private String paymentGateway;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentResponse.java
new file mode 100644
index 0000000..0a82e94
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/PaymentResponse.java
@@ -0,0 +1,21 @@
+package com.finpay.payment_service.dtos;
+
+import com.finpay.payment_service.models.PaymentStatus;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class PaymentResponse {
+ private UUID id;
+ private String paymentReference;
+ private BigDecimal amount;
+ private String currency;
+ private PaymentStatus status;
+ private UUID invoiceId;
+ private String paymentGateway;
+ private String message;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundGatewayResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundGatewayResponse.java
new file mode 100644
index 0000000..3bb3a7b
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundGatewayResponse.java
@@ -0,0 +1,32 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+@Data
+@NoArgsConstructor
+@AllArgsConstructor
+public class RefundGatewayResponse {
+ /**
+ * Status of the refund operation.
+ * Possible values: SUCCESS, FAILURE
+ */
+ private String status;
+
+ /**
+ * Message providing additional information about the refund operation.
+ */
+ private String message;
+
+ /**
+ * Unique identifier for the refund, provided by the payment gateway.
+ * Example: A refund transaction ID or reference number.
+ */
+ private String refundId;
+
+ /**
+ * Additional details or metadata about the refund (optional).
+ */
+ private String additionalDetails;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundRequest.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundRequest.java
new file mode 100644
index 0000000..607c5d9
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundRequest.java
@@ -0,0 +1,13 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.util.UUID;
+
+@Data
+public class RefundRequest {
+ private UUID paymentId;
+ private BigDecimal amount;
+ private String reason;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundResponse.java
new file mode 100644
index 0000000..dad76b8
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/RefundResponse.java
@@ -0,0 +1,20 @@
+package com.finpay.payment_service.dtos;
+
+import com.finpay.payment_service.models.RefundStatus;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class RefundResponse {
+ private UUID id;
+ private String refundReference;
+ private RefundStatus status;
+ private BigDecimal amount;
+ private String reason;
+ private LocalDateTime createdAt;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionRequest.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionRequest.java
new file mode 100644
index 0000000..813c516
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionRequest.java
@@ -0,0 +1,11 @@
+package com.finpay.payment_service.dtos;
+
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Data
+public class TransactionRequest {
+ private String transactionType;
+ private BigDecimal amount;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionResponse.java b/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionResponse.java
new file mode 100644
index 0000000..b9b0558
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/dtos/TransactionResponse.java
@@ -0,0 +1,21 @@
+package com.finpay.payment_service.dtos;
+
+import com.finpay.payment_service.models.TransactionStatus;
+import com.finpay.payment_service.models.TransactionType;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@Data
+@AllArgsConstructor
+public class TransactionResponse {
+ private UUID id;
+ private String transactionReference;
+ private TransactionType type;
+ private TransactionStatus status;
+ private BigDecimal amount;
+ private LocalDateTime createdAt;
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/exceptions/GlobalExceptionHandler.java b/payment-service/src/main/java/com/finpay/payment_service/exceptions/GlobalExceptionHandler.java
new file mode 100644
index 0000000..b903052
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/exceptions/GlobalExceptionHandler.java
@@ -0,0 +1,48 @@
+package com.finpay.payment_service.exceptions;
+import com.finpay.payment_service.dtos.ErrorResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.UUID;
+
+@ControllerAdvice
+public class GlobalExceptionHandler {
+
+ @ExceptionHandler(IllegalArgumentException.class)
+ public ResponseEntity handleBadRequest(Exception ex) {
+ ErrorResponse error = new ErrorResponse(
+ UUID.randomUUID(),
+ LocalDateTime.now(),
+ HttpStatus.BAD_REQUEST.value(),
+ "Bad Request",
+ ex.getMessage()
+ );
+ return new ResponseEntity<>(error, HttpStatus.BAD_REQUEST);
+ }
+
+ @ExceptionHandler(IllegalStateException.class)
+ public ResponseEntity handleConflict(Exception ex) {
+ ErrorResponse error = new ErrorResponse(
+ UUID.randomUUID(),
+ LocalDateTime.now(),
+ HttpStatus.CONFLICT.value(),
+ "Conflict",
+ ex.getMessage()
+ );
+ return new ResponseEntity<>(error, HttpStatus.CONFLICT);
+ }
+
+ @ExceptionHandler(Exception.class)
+ public ResponseEntity handleInternalServerError(Exception ex) {
+ ErrorResponse error = new ErrorResponse(
+ UUID.randomUUID(),
+ LocalDateTime.now(),
+ HttpStatus.INTERNAL_SERVER_ERROR.value(),
+ "Internal Server Error",
+ "An unexpected error occurred."
+ );
+ return new ResponseEntity<>(error, HttpStatus.INTERNAL_SERVER_ERROR);
+ }
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/gateways/PaymentGateway.java b/payment-service/src/main/java/com/finpay/payment_service/gateways/PaymentGateway.java
new file mode 100644
index 0000000..82fb05c
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/gateways/PaymentGateway.java
@@ -0,0 +1,25 @@
+package com.finpay.payment_service.gateways;
+
+import com.finpay.payment_service.dtos.PaymentRequest;
+import com.finpay.payment_service.dtos.PaymentGatewayResponse;
+import com.finpay.payment_service.dtos.RefundRequest;
+import com.finpay.payment_service.dtos.RefundGatewayResponse;
+
+public interface PaymentGateway {
+
+ /**
+ * Processes a payment through the specific gateway.
+ *
+ * @param paymentRequest The payment details.
+ * @return The response from the payment gateway.
+ */
+ PaymentGatewayResponse processPayment(PaymentRequest paymentRequest);
+
+ /**
+ * Processes a refund through the specific gateway.
+ *
+ * @param refundRequest The refund details.
+ * @return The response from the refund gateway.
+ */
+ RefundGatewayResponse processRefund(RefundRequest refundRequest);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/gateways/mpesa/MpesaPaymentGateway.java b/payment-service/src/main/java/com/finpay/payment_service/gateways/mpesa/MpesaPaymentGateway.java
new file mode 100644
index 0000000..87a033f
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/gateways/mpesa/MpesaPaymentGateway.java
@@ -0,0 +1,165 @@
+package com.finpay.payment_service.gateways.mpesa;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.finpay.payment_service.dtos.PaymentGatewayResponse;
+import com.finpay.payment_service.dtos.RefundGatewayResponse;
+import com.finpay.payment_service.gateways.PaymentGateway;
+import com.finpay.payment_service.config.MpesaConfig;
+import com.finpay.payment_service.model.MpesaTransaction;
+import com.finpay.payment_service.repository.MpesaTransactionRepository;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import org.springframework.web.client.RestTemplate;
+
+import java.time.LocalDateTime;
+import java.util.Base64;
+
+@Component
+@RequiredArgsConstructor
+public class MpesaPaymentGateway implements PaymentGateway {
+
+ private final MpesaConfig mpesaConfig;
+ private final RestTemplate restTemplate;
+ private final ObjectMapper objectMapper;
+ private final MpesaTransactionRepository mpesaTransactionRepository;
+
+ /**
+ * Processes a payment through M-Pesa STK-PUSH.
+ *
+ * @param paymentRequest The payment details.
+ * @return The response from M-Pesa.
+ */
+ @Override
+ public PaymentGatewayResponse processPayment(com.finpay.payment_service.dtos.PaymentRequest paymentRequest) {
+ PaymentGatewayResponse response = new PaymentGatewayResponse();
+
+ try {
+ String accessToken = getAccessToken();
+
+ String url = "https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest";
+
+ String timestamp = LocalDateTime.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
+ String password = Base64.getEncoder().encodeToString(
+ (mpesaConfig.getBusinessShortCode() + mpesaConfig.getPasskey() + timestamp).getBytes()
+ );
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.APPLICATION_JSON);
+ headers.setBearerAuth(accessToken);
+
+ JsonNode payload = objectMapper.createObjectNode()
+ .put("BusinessShortCode", mpesaConfig.getBusinessShortCode())
+ .put("Password", password)
+ .put("Timestamp", timestamp)
+ .put("TransactionType", "CustomerPayBillOnline")
+ .put("Amount", paymentRequest.getAmount())
+ .put("PartyA", paymentRequest.getPhoneNumber())
+ .put("PartyB", mpesaConfig.getBusinessShortCode())
+ .put("PhoneNumber", paymentRequest.getPhoneNumber())
+ .put("CallBackURL", mpesaConfig.getCallbackUrl())
+ .put("AccountReference", paymentRequest.getAccountReference())
+ .put("TransactionDesc", paymentRequest.getTransactionDesc());
+
+ HttpEntity entity = new HttpEntity<>(objectMapper.writeValueAsString(payload), headers);
+
+ ResponseEntity apiResponse = restTemplate.exchange(url, HttpMethod.POST, entity, String.class);
+
+ if (apiResponse.getStatusCode() == HttpStatus.OK) {
+ JsonNode responseBody = objectMapper.readTree(apiResponse.getBody());
+
+ String merchantRequestID = responseBody.get("MerchantRequestID").asText();
+ String checkoutRequestID = responseBody.get("CheckoutRequestID").asText();
+ String responseCode = responseBody.get("ResponseCode").asText();
+ String responseDescription = responseBody.get("ResponseDescription").asText();
+ String customerMessage = responseBody.get("CustomerMessage").asText();
+
+ // Save transaction details
+ MpesaTransaction transaction = MpesaTransaction.builder()
+ .merchantRequestID(merchantRequestID)
+ .checkoutRequestID(checkoutRequestID)
+ .amount(paymentRequest.getAmount())
+ .phoneNumber(paymentRequest.getPhoneNumber())
+ .status("PENDING")
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ mpesaTransactionRepository.save(transaction);
+
+ response.setStatus("SUCCESS");
+ response.setMessage("STK-PUSH initiated successfully.");
+ response.setPaymentReference(checkoutRequestID);
+ } else {
+ response.setStatus("FAILURE");
+ response.setMessage("Failed to initiate STK-PUSH.");
+ }
+ } catch (Exception e) {
+ response.setStatus("FAILURE");
+ response.setMessage("Error processing M-Pesa payment: " + e.getMessage());
+ // Optionally, log the error using a logger
+ }
+
+ return response;
+ }
+
+ /**
+ * Processes a refund through M-Pesa.
+ *
+ * @param refundRequest The refund details.
+ * @return The response from M-Pesa.
+ */
+ @Override
+ public RefundGatewayResponse processRefund(com.finpay.payment_service.dtos.RefundRequest refundRequest) {
+ RefundGatewayResponse response = new RefundGatewayResponse();
+
+ // Implement refund logic if M-Pesa supports it. Currently, M-Pesa's C2B API does not support refunds.
+ // This is a placeholder for future implementation.
+
+ response.setStatus("FAILED");
+ response.setMessage("Refunds are not supported through M-Pesa STK-PUSH.");
+ return response;
+ }
+
+ /**
+ * Retrieves the current access token, fetching a new one if necessary.
+ *
+ * @return The access token.
+ */
+ private String getAccessToken() {
+ // Implement caching logic with Redis or similar if desired
+ String accessToken = fetchAccessToken();
+ return accessToken;
+ }
+
+ /**
+ * Fetches a new access token from M-Pesa.
+ *
+ * @return The new access token.
+ */
+ private String fetchAccessToken() {
+ try {
+ String url = "https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials";
+
+ String credentials = mpesaConfig.getConsumerKey() + ":" + mpesaConfig.getConsumerSecret();
+ String encodedCredentials = Base64.getEncoder().encodeToString(credentials.getBytes());
+
+ HttpHeaders headers = new HttpHeaders();
+ headers.set("Authorization", "Basic " + encodedCredentials);
+ headers.setContentType(MediaType.APPLICATION_JSON);
+
+ HttpEntity entity = new HttpEntity<>(null, headers);
+
+ ResponseEntity response = restTemplate.exchange(url, HttpMethod.GET, entity, String.class);
+
+ if (response.getStatusCode() == HttpStatus.OK) {
+ JsonNode node = objectMapper.readTree(response.getBody());
+ return node.get("access_token").asText();
+ } else {
+ throw new RuntimeException("Failed to fetch access token from M-Pesa");
+ }
+ } catch (Exception e) {
+ throw new RuntimeException("Error fetching access token: " + e.getMessage(), e);
+ }
+ }
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/models/Payment.java b/payment-service/src/main/java/com/finpay/payment_service/models/Payment.java
index 1fe0e72..def3021 100644
--- a/payment-service/src/main/java/com/finpay/payment_service/models/Payment.java
+++ b/payment-service/src/main/java/com/finpay/payment_service/models/Payment.java
@@ -1,7 +1,12 @@
package com.finpay.payment_service.models;
+import jakarta.persistence.*;
import lombok.*;
+import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
+
import java.math.BigDecimal;
+import java.time.LocalDateTime;
import java.util.Set;
import java.util.UUID;
@@ -18,7 +23,7 @@ public class Payment {
private UUID id;
@Column(nullable = false, unique = true)
- private String paymentReference;
+ private String paymentReference; // Unique reference for the payment
@Column(nullable = false)
private BigDecimal amount;
@@ -30,13 +35,11 @@ public class Payment {
@Column(nullable = false)
private PaymentStatus status;
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "payment_method_id")
- private PaymentMethod paymentMethod;
+ @Column(nullable = false)
+ private UUID invoiceId; // Reference to Invoice managed by Invoice Service
- @ManyToOne(fetch = FetchType.LAZY)
- @JoinColumn(name = "payment_gateway_id")
- private PaymentGateway paymentGateway;
+ @Column(nullable = false)
+ private String paymentGateway; // e.g., Stripe, PayPal
@OneToMany(mappedBy = "payment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set transactions;
@@ -44,8 +47,10 @@ public class Payment {
@OneToMany(mappedBy = "payment", cascade = CascadeType.ALL, orphanRemoval = true)
private Set refunds;
- @Column(nullable = false)
+ @CreationTimestamp
+ @Column(nullable = false, updatable = false)
private LocalDateTime createdAt;
+ @UpdateTimestamp
private LocalDateTime updatedAt;
}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/models/PaymentGateway.java b/payment-service/src/main/java/com/finpay/payment_service/models/PaymentGateway.java
index 774a153..5a5f3d5 100644
--- a/payment-service/src/main/java/com/finpay/payment_service/models/PaymentGateway.java
+++ b/payment-service/src/main/java/com/finpay/payment_service/models/PaymentGateway.java
@@ -1,5 +1,6 @@
package com.finpay.payment_service.models;
+import jakarta.persistence.*;
import lombok.*;
import java.util.Set;
import java.util.UUID;
diff --git a/payment-service/src/main/java/com/finpay/payment_service/models/PaymentMethod.java b/payment-service/src/main/java/com/finpay/payment_service/models/PaymentMethod.java
index 3c8324a..71f8894 100644
--- a/payment-service/src/main/java/com/finpay/payment_service/models/PaymentMethod.java
+++ b/payment-service/src/main/java/com/finpay/payment_service/models/PaymentMethod.java
@@ -1,5 +1,6 @@
package com.finpay.payment_service.models;
+import jakarta.persistence.*;
import lombok.*;
import java.util.Set;
import java.util.UUID;
diff --git a/payment-service/src/main/java/com/finpay/payment_service/models/Refund.java b/payment-service/src/main/java/com/finpay/payment_service/models/Refund.java
index de23413..fec4839 100644
--- a/payment-service/src/main/java/com/finpay/payment_service/models/Refund.java
+++ b/payment-service/src/main/java/com/finpay/payment_service/models/Refund.java
@@ -1,5 +1,6 @@
package com.finpay.payment_service.models;
+import jakarta.persistence.*;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
diff --git a/payment-service/src/main/java/com/finpay/payment_service/models/Transaction.java b/payment-service/src/main/java/com/finpay/payment_service/models/Transaction.java
index 31aabfd..4e1509a 100644
--- a/payment-service/src/main/java/com/finpay/payment_service/models/Transaction.java
+++ b/payment-service/src/main/java/com/finpay/payment_service/models/Transaction.java
@@ -4,6 +4,7 @@
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.UUID;
+import jakarta.persistence.*;
@Entity
@Table(name = "transactions")
diff --git a/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentGatewayRepository.java b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentGatewayRepository.java
new file mode 100644
index 0000000..d190941
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentGatewayRepository.java
@@ -0,0 +1,10 @@
+package com.finpay.payment_service.repository;
+
+import com.finpay.payment_service.models.PaymentGateway;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface PaymentGatewayRepository extends JpaRepository {
+ Optional findByName(String name);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentMethodRepository.java b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentMethodRepository.java
new file mode 100644
index 0000000..ae2b4b9
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentMethodRepository.java
@@ -0,0 +1,10 @@
+package com.finpay.payment_service.repository;
+
+import com.finpay.payment_service.models.PaymentMethod;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface PaymentMethodRepository extends JpaRepository {
+ Optional findByType(String type);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentRepository.java b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentRepository.java
new file mode 100644
index 0000000..1dbf0fb
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/repository/PaymentRepository.java
@@ -0,0 +1,10 @@
+package com.finpay.payment_service.repository;
+
+import com.finpay.payment_service.models.Payment;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface PaymentRepository extends JpaRepository {
+ Optional findByPaymentReference(String paymentReference);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/repository/RefundRepository.java b/payment-service/src/main/java/com/finpay/payment_service/repository/RefundRepository.java
new file mode 100644
index 0000000..35938fa
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/repository/RefundRepository.java
@@ -0,0 +1,10 @@
+package com.finpay.payment_service.repository;
+
+import com.finpay.payment_service.models.Refund;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface RefundRepository extends JpaRepository {
+ Optional findByRefundReference(String refundReference);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/repository/TransactionRepository.java b/payment-service/src/main/java/com/finpay/payment_service/repository/TransactionRepository.java
new file mode 100644
index 0000000..30b864f
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/repository/TransactionRepository.java
@@ -0,0 +1,10 @@
+package com.finpay.payment_service.repository;
+
+import com.finpay.payment_service.models.Transaction;
+import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.Optional;
+import java.util.UUID;
+
+public interface TransactionRepository extends JpaRepository {
+ Optional findByTransactionReference(String transactionReference);
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/services/PaymentService.java b/payment-service/src/main/java/com/finpay/payment_service/services/PaymentService.java
new file mode 100644
index 0000000..4e13a1a
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/services/PaymentService.java
@@ -0,0 +1,14 @@
+package com.finpay.payment_service.services;
+
+import com.finpay.payment_service.dtos.PaymentRequest;
+import com.finpay.payment_service.dtos.PaymentResponse;
+import com.finpay.payment_service.dtos.RefundRequest;
+import com.finpay.payment_service.dtos.RefundResponse;
+
+import java.util.Optional;
+public interface PaymentService {
+ PaymentResponse initiatePayment(PaymentRequest paymentRequest);
+ Optional getPaymentByReference(String paymentReference);
+ RefundResponse processRefund(RefundRequest refundRequest);
+
+}
diff --git a/payment-service/src/main/java/com/finpay/payment_service/services/PaymentServiceImpl.java b/payment-service/src/main/java/com/finpay/payment_service/services/PaymentServiceImpl.java
new file mode 100644
index 0000000..bd23b3f
--- /dev/null
+++ b/payment-service/src/main/java/com/finpay/payment_service/services/PaymentServiceImpl.java
@@ -0,0 +1,162 @@
+package com.finpay.payment_service.services;
+
+import com.finpay.payment_service.dtos.*;
+import com.finpay.payment_service.gateways.PaymentGateway;
+import com.finpay.payment_service.gateways.PaymentGatewayFactory;
+import com.finpay.payment_service.models.Payment;
+import com.finpay.payment_service.models.PaymentStatus;
+import com.finpay.payment_service.models.Refund;
+import com.finpay.payment_service.models.RefundStatus;
+import com.finpay.payment_service.repository.PaymentRepository;
+import com.finpay.payment_service.repository.RefundRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.time.LocalDateTime;
+import java.util.Optional;
+import java.util.UUID;
+
+@Service
+public class PaymentServiceImpl implements PaymentService {
+
+ private final PaymentRepository paymentRepository;
+ private final RefundRepository refundRepository;
+ //private final InvoiceServiceClient invoiceServiceClient;
+ private final PaymentGatewayFactory paymentGatewayFactory;
+
+ public PaymentServiceImpl(PaymentRepository paymentRepository,
+ RefundRepository refundRepository,
+// InvoiceServiceClient invoiceServiceClient,
+ PaymentGatewayFactory paymentGatewayFactory) {
+ this.paymentRepository = paymentRepository;
+ this.refundRepository = refundRepository;
+// this.invoiceServiceClient = invoiceServiceClient;
+ this.paymentGatewayFactory = paymentGatewayFactory;
+ }
+
+ @Override
+ @Transactional
+ public PaymentResponse initiatePayment(PaymentRequest paymentRequest) {
+ // Validate Invoice
+// InvoiceResponse invoice = invoiceServiceClient.getInvoiceById(paymentRequest.getInvoiceId());
+// if (invoice == null || !"PAID".equalsIgnoreCase(invoice.getStatus())) {
+// throw new IllegalArgumentException("Invalid or unpaid invoice.");
+// }
+
+ // Create Payment entity
+ Payment payment = Payment.builder()
+ .paymentReference(UUID.randomUUID().toString())
+ .amount(paymentRequest.getAmount())
+ .currency(paymentRequest.getCurrency())
+ .status(PaymentStatus.PENDING)
+ .invoiceId(paymentRequest.getInvoiceId())
+ .paymentGateway(paymentRequest.getPaymentGateway())
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // Save Payment
+ paymentRepository.save(payment);
+
+ // Get the appropriate PaymentGateway
+ PaymentGateway gateway = paymentGatewayFactory.getPaymentGateway(paymentRequest);
+
+ // Process Payment via Gateway
+ PaymentGatewayResponse gatewayResponse = gateway.processPayment(paymentRequest);
+
+ // Update Payment Status based on Gateway Response
+ if ("SUCCESS".equalsIgnoreCase(gatewayResponse.getStatus())) {
+ payment.setStatus(PaymentStatus.COMPLETED);
+ } else {
+ payment.setStatus(PaymentStatus.FAILED);
+ }
+ payment.setUpdatedAt(LocalDateTime.now());
+ paymentRepository.save(payment);
+
+ // Return Response
+ return new PaymentResponse(
+ payment.getId(),
+ payment.getPaymentReference(),
+ payment.getAmount(),
+ payment.getCurrency(),
+ payment.getStatus(),
+ payment.getInvoiceId(),
+ payment.getPaymentGateway(),
+ gatewayResponse.getMessage()
+ );
+ }
+
+ @Override
+ public Optional getPaymentByReference(String paymentReference) {
+ return paymentRepository.findByPaymentReference(paymentReference)
+ .map(payment -> new PaymentResponse(
+ payment.getId(),
+ payment.getPaymentReference(),
+ payment.getAmount(),
+ payment.getCurrency(),
+ payment.getStatus(),
+ payment.getInvoiceId(),
+ payment.getPaymentGateway(),
+ "Payment retrieved successfully."
+ ));
+ }
+
+ @Override
+ @Transactional
+ public RefundResponse processRefund(RefundRequest refundRequest) {
+ // Validate Payment
+ Optional paymentOpt = paymentRepository.findById(refundRequest.getPaymentId());
+ if (paymentOpt.isEmpty()) {
+ throw new IllegalArgumentException("Payment not found.");
+ }
+
+ Payment payment = paymentOpt.get();
+ if (payment.getStatus() != PaymentStatus.COMPLETED) {
+ throw new IllegalStateException("Only completed payments can be refunded.");
+ }
+
+ // Create Refund entity
+ Refund refund = Refund.builder()
+ .refundReference(UUID.randomUUID().toString())
+ .status(RefundStatus.INITIATED)
+ .amount(refundRequest.getAmount())
+ .payment(payment)
+ .reason(refundRequest.getReason())
+ .createdAt(LocalDateTime.now())
+ .build();
+
+ // Save Refund
+ refundRepository.save(refund);
+
+ // Get the appropriate PaymentGateway
+ //PaymentRequest dummyPaymentRequest = new PaymentRequest(); // Not used here
+ PaymentGateway gateway = paymentGatewayFactory.getPaymentGateway(
+ PaymentRequest.builder()
+ .paymentMethod(payment.getPaymentGateway())
+ .build()
+ );
+
+ // Process Refund via Gateway
+ RefundGatewayResponse gatewayResponse = gateway.processRefund(refundRequest);
+
+ // Update Refund Status based on Gateway Response
+ if ("SUCCESS".equalsIgnoreCase(gatewayResponse.getStatus())) {
+ refund.setStatus(RefundStatus.COMPLETED);
+ payment.setStatus(PaymentStatus.REFUNDED);
+ } else {
+ refund.setStatus(RefundStatus.FAILED);
+ }
+ refund.setUpdatedAt(LocalDateTime.now());
+ refundRepository.save(refund);
+ paymentRepository.save(payment);
+
+ // Return Response
+ return new RefundResponse(
+ refund.getId(),
+ refund.getRefundReference(),
+ refund.getStatus(),
+ refund.getAmount(),
+ refund.getReason(),
+ refund.getCreatedAt()
+ );
+ }
+}
diff --git a/payment-service/src/main/resources/application.properties b/payment-service/src/main/resources/application.properties
deleted file mode 100644
index 85f2700..0000000
--- a/payment-service/src/main/resources/application.properties
+++ /dev/null
@@ -1 +0,0 @@
-spring.application.name=payment-service
diff --git a/payment-service/src/main/resources/application.yml b/payment-service/src/main/resources/application.yml
new file mode 100644
index 0000000..e690158
--- /dev/null
+++ b/payment-service/src/main/resources/application.yml
@@ -0,0 +1,21 @@
+server:
+ port: 8081
+
+spring:
+ datasource:
+ url: jdbc:postgresql://localhost:5432/finpay_payment
+ username: postgres
+ password: 0412@zeP
+ driver-class-name: org.postgresql.Driver
+ jpa:
+ hibernate:
+ ddl-auto: update
+ show-sql: true
+ properties:
+ hibernate:
+ format_sql: true
+
+eureka:
+ client:
+ service-url:
+ defaultZone: http://localhost:8761/eureka/
diff --git a/user-service/pom.xml b/user-service/pom.xml
index 1382c65..518db85 100644
--- a/user-service/pom.xml
+++ b/user-service/pom.xml
@@ -47,6 +47,10 @@
org.springframework.boot
spring-boot-starter-oauth2-resource-server
+
+ org.springframework.kafka
+ spring-kafka
+
org.springframework.boot
diff --git a/user-service/src/main/java/com/finpay/user_service/config/SecurityConfig.java b/user-service/src/main/java/com/finpay/user_service/config/SecurityConfig.java
index 3708ca3..aa75a06 100644
--- a/user-service/src/main/java/com/finpay/user_service/config/SecurityConfig.java
+++ b/user-service/src/main/java/com/finpay/user_service/config/SecurityConfig.java
@@ -21,6 +21,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/users/register").permitAll()
+ .requestMatchers("/api/users/{id}/roles").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
diff --git a/user-service/src/main/java/com/finpay/user_service/controllers/UserController.java b/user-service/src/main/java/com/finpay/user_service/controllers/UserController.java
index 8c443bb..0c291fb 100644
--- a/user-service/src/main/java/com/finpay/user_service/controllers/UserController.java
+++ b/user-service/src/main/java/com/finpay/user_service/controllers/UserController.java
@@ -5,6 +5,7 @@
import com.finpay.user_service.dtos.UserResponse;
import com.finpay.user_service.models.Role;
import com.finpay.user_service.models.User;
+//import com.finpay.user_service.services.KafkaProducerService;
import com.finpay.user_service.services.UserService;
import org.springframework.http.*;
import org.springframework.security.access.prepost.PreAuthorize;
@@ -20,12 +21,14 @@
public class UserController {
private final UserService userService;
+// private final KafkaProducerService kafkaProducerService;
// Constructor-based injection
public UserController(UserService userService) {
this.userService = userService;
}
@PostMapping("/register")
+
public ResponseEntity> registerUser(@RequestBody UserRegistrationRequest registrationRequest) {
if (userService.existsByEmail(registrationRequest.getEmail())) {
return ResponseEntity
@@ -33,7 +36,6 @@ public ResponseEntity> registerUser(@RequestBody UserRegistrationRequest regis
.body("Error: Email is already in use!");
}
- // Assign default role
Set defaultRoles = Set.of(Role.ROLE_USER);
User user = User.builder()
@@ -46,17 +48,35 @@ public ResponseEntity> registerUser(@RequestBody UserRegistrationRequest regis
User savedUser = userService.save(user);
- UserResponse userResponse = new UserResponse(savedUser.getId(), savedUser.getName(), savedUser.getEmail());
+ // Publish event to Kafka
+ String event = String.format("{\"id\": \"%s\", \"email\": \"%s\", \"roles\": \"%s\"}",
+ savedUser.getId(),
+ savedUser.getEmail(),
+ defaultRoles);
+// kafkaProducerService.sendMessage("user-registration", event);
+ UserResponse userResponse = new UserResponse(savedUser.getId(), savedUser.getName(), savedUser.getEmail());
return ResponseEntity.status(HttpStatus.CREATED).body(userResponse);
}
+ @GetMapping("/email/{email}")
+ public ResponseEntity> getUserByEmail(@PathVariable String email) {
+ Optional userOpt = userService.findByEmail(email);
+
+ if (userOpt.isEmpty()) {
+ return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found.");
+ }
+
+ User user = userOpt.get();
+ UserResponse userResponse = new UserResponse(user.getId(), user.getName(), user.getEmail());
+ return ResponseEntity.ok(userResponse);
+ }
@GetMapping("/profile")
public ResponseEntity> getUserProfile(Authentication authentication) {
String email = authentication.getName();
Optional userOpt = userService.findByEmail(email);
- if (!userOpt.isPresent()) {
+ if (userOpt.isEmpty()) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("User not found.");
}