From 1171fde132982ad032c7519037dce065e62b1eab Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Fri, 15 May 2026 10:12:46 +0530 Subject: [PATCH 01/10] Payment setup done --- .gitignore | 6 + RestroHub/ProjectDetails.md | 217 -------------- RestroHub/gradle.properties | 2 + RestroHub/settings.gradle | 5 +- .../auth/controller/AuthController.java | 268 +++++++----------- .../payment/controller/PaymentController.java | 5 + .../payment/service/PaymentService.java | 7 + .../payment/service/PaymentServiceImpl.java | 30 ++ .../qrmenu/security/JwtTokenProvider.java | 4 +- .../main/resources/application-dev.properties | 10 +- .../resources/application-prod.properties | 6 +- .../resources/application-test.properties | 4 +- .../src/main/resources/application.properties | 18 +- 13 files changed, 179 insertions(+), 403 deletions(-) delete mode 100644 RestroHub/ProjectDetails.md create mode 100644 RestroHub/gradle.properties create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java diff --git a/.gitignore b/.gitignore index d52c783..ef2620a 100644 --- a/.gitignore +++ b/.gitignore @@ -70,3 +70,9 @@ out/ ### VS Code ### .vscode/ + +### Credentials ### +*.env + +### Can Remove This ### +personalnotes/ \ No newline at end of file diff --git a/RestroHub/ProjectDetails.md b/RestroHub/ProjectDetails.md deleted file mode 100644 index f4d7dc2..0000000 --- a/RestroHub/ProjectDetails.md +++ /dev/null @@ -1,217 +0,0 @@ - -# ๐Ÿ“˜ Project Details โ€” Restroly - -## ๐Ÿงญ Overview - -**Restroly** is a backend application built using **Spring Boot** to support a **QR-based restaurant menu and ordering system**. -It provides RESTful APIs to manage food items, categories, and future order workflows, enabling a modern **contactless dining experience**. - -The project is designed to be **modular, scalable, and production-ready**, serving as a foundation for mobile apps, web dashboards, or POS integrations. - ---- - -## ๐ŸŽฏ Objectives - -- Enable QR-based menu browsing -- Provide clean REST APIs for menu management -- Support scalable restaurant operations -- Serve as a backend for web/mobile clients -- Follow industry best practices (DTOs, validation, mapping) - ---- - -## ๐Ÿงฉ Core Features - -### โœ… Implemented - -- Food & Category management -- CRUD REST APIs -- DTO-based request/response handling -- MapStruct-based object mapping -- PostgreSQL persistence -- Validation & global exception handling -- Swagger / OpenAPI documentation -- Context-path aware API routing (`/restroly`) - -### ๐Ÿ”ฎ Planned / Future Enhancements - -- JWT-based authentication & authorization -- Role-based access (Admin, Staff) -- Order management & tracking -- WebSocket-based live order updates -- Multi-restaurant (multi-tenant) support -- Analytics & reporting dashboards - ---- - -## ๐Ÿ—๏ธ Architecture - -The project follows a **layered architecture**: - -``` -Controller โ†’ Service โ†’ Repository โ†’ Database -โ†“ -DTOs โ†” MapStruct โ†” Entities -``` - -### Key Layers - -- **Controller Layer** - - REST endpoints - - Request validation -- **Service Layer** - - Business logic - - Transaction management -- **Repository Layer** - - JPA repositories - - Database access -- **DTO Layer** - - Request / Response models -- **Entity Layer** - - JPA entities -- **Mapper Layer** - - Conversion class between entity & dto -- **Config Layer** - - Security, Swagger, application configs - ---- - -## ๐Ÿง  Technology Stack - -| Layer | Technology | -|-----|-----------| -| Language | Java 21 | -| Framework | Spring Boot | -| Database | PostgreSQL | -| ORM | Spring Data JPA | -| Mapper | MapStruct | -| Build Tool | Gradle | -| API Docs | SpringDoc OpenAPI | -| Security | Spring Security | -| Validation | Jakarta Validation | - ---- - -## ๐Ÿ—„๏ธ Database Design - -- PostgreSQL as primary database -- JPA/Hibernate for ORM -- Relationships: - - Many-to-Many between **Food** and **Category** -- Schema auto-managed using Hibernate (`ddl-auto=update`) - ---- - -## ๐ŸŒ API Structure - -- Context Path: `/restroly` -- API Versioning: `/api/v1` - -Example: -```/restroly/api/v1/foods``` - - -This allows: -- Clean versioning -- Future backward compatibility - ---- - -## ๐Ÿ“˜ Swagger & API Docs - -Swagger UI is enabled for easy API testing and documentation. -```/restroly/swagger-ui.html``` - -Swagger is explicitly configured to respect the context path. ---- -## AUTH APIS -```bash -# 1. Login and get tokens -curl -X POST http://localhost:8080/api/v1/auth/login \ - -H "Content-Type: application/json" \ - -d '{ - "username": "admin", - "password": "admin123" - }' - -# Response: -# { -# "success": true, -# "message": "Login successful", -# "data": { -# "accessToken": "eyJhbGciOiJIUzI1NiJ9...", -# "refreshToken": "eyJhbGciOiJIUzI1NiJ9...", -# "tokenType": "Bearer", -# "expiresIn": 86400, -# "username": "admin", -# "roles": ["ROLE_ADMIN", "ROLE_USER"] -# } -# } - -# 2. Use the token to access protected endpoints -curl -X POST http://localhost:8080/api/v1/foods \ - -H "Content-Type: application/json" \ - -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." \ - -d '{ - "name": "Margherita Pizza", - "price": 12.99, - "category": "Pizza", - "restaurantId": 1 - }' - -# 3. Refresh the token -curl -X POST http://localhost:8080/api/v1/auth/refresh \ - -H "Content-Type: application/json" \ - -d '{ - "refreshToken": "eyJhbGciOiJIUzI1NiJ9..." - }' - -# 4. Validate token -curl -X GET http://localhost:8080/api/v1/auth/validate \ - -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." - -# 5. Logout -curl -X POST http://localhost:8080/api/v1/auth/logout \ - -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." -``` ---- - -## โš™๏ธ Build & Run - -```bash -./gradlew clean build -./gradlew bootRun -``` -Ensure PostgreSQL is running and configured correctly. - ---- - -## ๐Ÿงช Testing Strategy - -* Unit tests with JUnit -* Service-level testing -* Future scope: Integration tests with Testcontainers - ---- - -## ๐Ÿ“Œ Use Cases - -* Restaurants wanting QR-based menus -* Hotels & cafes managing digital menus -* Backend service for food-ordering apps -* Learning reference for Spring Boot best practices - ---- - -## ๐Ÿ“„ License - -This project is licensed under the **MIT License**. - ---- - -## ๐Ÿงก Conclusion - -**Restroly** is a solid, extensible backend foundation for modern restaurant systems. -It emphasizes clean architecture, maintainability, and real-world scalability. - ---- diff --git a/RestroHub/gradle.properties b/RestroHub/gradle.properties new file mode 100644 index 0000000..3c92298 --- /dev/null +++ b/RestroHub/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.java.installations.auto-download=true +org.gradle.java.installations.paths=C:/Users/svkcc/.vscode/extensions/redhat.java-1.45.0-win32-x64/jre/21.0.8-win32-x86_64 diff --git a/RestroHub/settings.gradle b/RestroHub/settings.gradle index d86e7bb..16511db 100644 --- a/RestroHub/settings.gradle +++ b/RestroHub/settings.gradle @@ -1 +1,4 @@ -rootProject.name = 'restroly' +plugins { + id 'org.gradle.toolchains.foojay-resolver-convention' version '0.8.0' +} +rootProject.name = 'restroly' \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java b/RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java index fe7bacd..d267c09 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/auth/controller/AuthController.java @@ -32,175 +32,109 @@ @Tag(name = "Authentication", description = "APIs for user authentication and token management") public class AuthController { - @Autowired - private AuthService authService; - - @PostMapping("/login") - @Operation( - summary = "Authenticate user and generate tokens", - description = "Authenticates user credentials and returns JWT access token and refresh token" - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "Authentication successful", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = AuthResponse.class), - examples = @ExampleObject(value = """ - { - "success": true, - "message": "Login successful", - "data": { - "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", - "tokenType": "Bearer", - "expiresIn": 86400, - "username": "admin", - "roles": ["ROLE_ADMIN"] - }, - "timestamp": "2024-01-15T10:30:00" - } - """) - ) - ), - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "400", - description = "Invalid request - validation failed", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class) - ) - ), - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "401", - description = "Authentication failed - invalid credentials", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class), - examples = @ExampleObject(value = """ - { - "status": 401, - "error": "UNAUTHORIZED", - "message": "Invalid username or password", - "path": "/api/v1/auth/login", - "timestamp": "2024-01-15T10:30:00", - "traceId": "abc123" - } - """) - ) - ) - }) - @io.swagger.v3.oas.annotations.parameters.RequestBody( - description = "Login credentials", - required = true, - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = LoginRequest.class), - examples = @ExampleObject(value = """ - { - "username": "admin", - "password": "admin123" - } - """) - ) - ) - public ResponseEntity> login( - @Valid @RequestBody LoginRequest loginRequest) { - - log.info("Login request received for user: {}", loginRequest.getUsername()); - - AuthResponse authResponse = authService.login(loginRequest); - - return ResponseEntity.ok(ApiResponse.success(authResponse, "Login successful")); - } - - @PostMapping("/refresh") - @Operation( - summary = "Refresh access token", - description = "Generates new access and refresh tokens using a valid refresh token" - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "Token refreshed successfully", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = AuthResponse.class) - ) - ), - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "400", - description = "Invalid or expired refresh token", - content = @Content( - mediaType = "application/json", - schema = @Schema(implementation = ErrorResponse.class) - ) - ) - }) - public ResponseEntity> refreshToken( - @Valid @RequestBody RefreshTokenRequest refreshTokenRequest) { - - log.debug("Refresh token request received"); - - AuthResponse authResponse = authService.refreshToken(refreshTokenRequest); - - return ResponseEntity.ok(ApiResponse.success(authResponse, "Token refreshed successfully")); - } - - @PostMapping("/logout") - @Operation( - summary = "Logout user", - description = "Invalidates the current user session" - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "Logout successful" - ) - }) - public ResponseEntity> logout(HttpServletRequest request) { - String token = extractToken(request); - authService.logout(token); - - log.info("User logged out successfully"); - - return ResponseEntity.ok(ApiResponse.success("Logout successful")); - } - - @GetMapping("/validate") - @Operation( - summary = "Validate token", - description = "Validates if the provided JWT token is valid and not expired" - ) - @ApiResponses(value = { - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "200", - description = "Token is valid" - ), - @io.swagger.v3.oas.annotations.responses.ApiResponse( - responseCode = "401", - description = "Token is invalid or expired" - ) - }) - public ResponseEntity> validateToken(HttpServletRequest request) { - String token = extractToken(request); - - if (token == null) { - return ResponseEntity.status(HttpStatus.UNAUTHORIZED) - .body(ApiResponse.error("No token provided")); + @Autowired + private AuthService authService; + + @PostMapping("/login") + @Operation(summary = "Authenticate user and generate tokens", description = "Authenticates user credentials and returns JWT access token and refresh token") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Authentication successful", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class), examples = @ExampleObject(value = """ + { + "success": true, + "message": "Login successful", + "data": { + "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "tokenType": "Bearer", + "expiresIn": 86400, + "username": "admin", + "roles": ["ROLE_ADMIN"] + }, + "timestamp": "2024-01-15T10:30:00" + } + """))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid request - validation failed", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Authentication failed - invalid credentials", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class), examples = @ExampleObject(value = """ + { + "status": 401, + "error": "UNAUTHORIZED", + "message": "Invalid username or password", + "path": "/api/v1/auth/login", + "timestamp": "2024-01-15T10:30:00", + "traceId": "abc123" + } + """))) + }) + @io.swagger.v3.oas.annotations.parameters.RequestBody(description = "Login credentials", required = true, content = @Content(mediaType = "application/json", schema = @Schema(implementation = LoginRequest.class), examples = @ExampleObject(value = """ + { + "username": "admin", + "password": "admin123" + } + """))) + public ResponseEntity> login( + @Valid @RequestBody LoginRequest loginRequest) { + + log.info("Login request received for user: {}", loginRequest.getUsername()); + + AuthResponse authResponse = authService.login(loginRequest); + + return ResponseEntity.ok(ApiResponse.success(authResponse, "Login successful")); } - // Token validation happens in the security filter - // If we reach here, the token is valid - return ResponseEntity.ok(ApiResponse.success("Token is valid")); - } + @PostMapping("/refresh") + @Operation(summary = "Refresh access token", description = "Generates new access and refresh tokens using a valid refresh token") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Token refreshed successfully", content = @Content(mediaType = "application/json", schema = @Schema(implementation = AuthResponse.class))), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "400", description = "Invalid or expired refresh token", content = @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))) + }) + public ResponseEntity> refreshToken( + @Valid @RequestBody RefreshTokenRequest refreshTokenRequest) { - private String extractToken(HttpServletRequest request) { - String bearerToken = request.getHeader("Authorization"); - if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { - return bearerToken.substring(7); + log.debug("Refresh token request received"); + + AuthResponse authResponse = authService.refreshToken(refreshTokenRequest); + + return ResponseEntity.ok(ApiResponse.success(authResponse, "Token refreshed successfully")); + } + + @PostMapping("/logout") + @Operation(summary = "Logout user", description = "Invalidates the current user session") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Logout successful") + }) + public ResponseEntity> logout(HttpServletRequest request) { + String token = extractToken(request); + authService.logout(token); + + log.info("User logged out successfully"); + + return ResponseEntity.ok(ApiResponse.success("Logout successful")); + } + + @GetMapping("/validate") + @Operation(summary = "Validate token", description = "Validates if the provided JWT token is valid and not expired") + @ApiResponses(value = { + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "200", description = "Token is valid"), + @io.swagger.v3.oas.annotations.responses.ApiResponse(responseCode = "401", description = "Token is invalid or expired") + }) + public ResponseEntity> validateToken(HttpServletRequest request) { + String token = extractToken(request); + + if (token == null) { + return ResponseEntity.status(HttpStatus.UNAUTHORIZED) + .body(ApiResponse.error("No token provided")); + } + + // Token validation happens in the security filter + // If we reach here, the token is valid + return ResponseEntity.ok(ApiResponse.success("Token is valid")); + } + + private String extractToken(HttpServletRequest request) { + String bearerToken = request.getHeader("Authorization"); + if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) { + return bearerToken.substring(7); + } + return null; } - return null; - } } \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java new file mode 100644 index 0000000..f2f5a00 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java @@ -0,0 +1,5 @@ +package com.restroly.qrmenu.payment.controller; + +public class PaymentController { + +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java new file mode 100644 index 0000000..ab243b2 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java @@ -0,0 +1,7 @@ +package com.restroly.qrmenu.payment.service; + +public interface PaymentService { + String generatePaymentLink(double amount, Long orderId, String upiId); + void generateUPIQR(double amount, String upiId, String description); + boolean verifyPayment(String paymentId); +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java new file mode 100644 index 0000000..9100e70 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java @@ -0,0 +1,30 @@ +package com.restroly.qrmenu.payment.service; + +import org.springframework.stereotype.Service; +import lombok.extern.slf4j.Slf4j; + +@Service +@Slf4j +public class PaymentServiceImpl implements PaymentService { + + @Override + public String generatePaymentLink(double amount, Long orderId, String upiId) { + log.info("Generating payment link for orderId: {}, amount: {}, upiId: {}", orderId, amount, upiId); + // Implementation for payment link generation + return null; + } + + @Override + public void generateUPIQR(double amount, String upiId, String description) { + log.info("Generating UPI QR for amount: {}, upiId: {}, description: {}", amount, upiId, description); + // Implementation for UPI QR code generation + } + + @Override + public boolean verifyPayment(String paymentId) { + log.info("Verifying payment with paymentId: {}", paymentId); + // Implementation for payment verification + return false; + } + +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/security/JwtTokenProvider.java b/RestroHub/src/main/java/com/restroly/qrmenu/security/JwtTokenProvider.java index a29252f..c3cd4c5 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/security/JwtTokenProvider.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/security/JwtTokenProvider.java @@ -25,10 +25,10 @@ public class JwtTokenProvider { @Value("${security.jwt.secret}") private String jwtSecret; - @Value("${security.jwt.expiration}") + @Value("${security.jwt.expiration}") // 1 hour default private long jwtExpiration; - @Value("${security.jwt.refresh-expiration:604800000}") + @Value("${security.jwt.refresh-expiration}") // 7 days default private long refreshExpiration; private SecretKey key; diff --git a/RestroHub/src/main/resources/application-dev.properties b/RestroHub/src/main/resources/application-dev.properties index aaa0243..a0ae181 100644 --- a/RestroHub/src/main/resources/application-dev.properties +++ b/RestroHub/src/main/resources/application-dev.properties @@ -2,8 +2,8 @@ # Datasource (PostgreSQL) # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=${DB_USERNAME:postgres} -spring.datasource.password=${DB_PASSWORD:MySQL03@} +spring.datasource.username=postgres +spring.datasource.password=123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== # HikariCP @@ -49,6 +49,6 @@ spring.servlet.multipart.max-request-size=10MB # =============================== # Security / JWT # =============================== -security.jwt.secret=${JWT_SECRET:your-256-bit-secret-key-here-change-in-production} -security.jwt.expiration=${JWT_EXPIRATION:86400000} -security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:604800000} \ No newline at end of file +security.jwt.secret=dmVyeS1zZWNyZXQtcmFuZG9tLWtlYS1mb3Itand0LWF1dGhlbnRpY2F0aW9uLTEyMzQ1Njc4OQ== +security.jwt.expiration=86400000 +security.jwt.refresh-expiration=604800000 \ No newline at end of file diff --git a/RestroHub/src/main/resources/application-prod.properties b/RestroHub/src/main/resources/application-prod.properties index 8c9b079..6d93899 100644 --- a/RestroHub/src/main/resources/application-prod.properties +++ b/RestroHub/src/main/resources/application-prod.properties @@ -1,9 +1,9 @@ # =============================== # Datasource (PostgreSQL) # =============================== -spring.datasource.url=${DATABASE_URL} -spring.datasource.username=${DATABASE_USERNAME} -spring.datasource.password=${DATABASE_PASSWORD} +spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB +spring.datasource.username=postgres +spring.datasource.password=123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== diff --git a/RestroHub/src/main/resources/application-test.properties b/RestroHub/src/main/resources/application-test.properties index 707f46a..1ea34d6 100644 --- a/RestroHub/src/main/resources/application-test.properties +++ b/RestroHub/src/main/resources/application-test.properties @@ -21,8 +21,8 @@ spring.h2.console.enabled=true # =============================== # Security / JWT # =============================== -security.jwt.secret=test-secret-key-for-testing-purposes-only-256-bits -security.jwt.expiration=3600000 +security.jwt.secret=${JWT_SECRET} +security.jwt.expiration=${JWT_EXPIRATION} # =============================== # Logging diff --git a/RestroHub/src/main/resources/application.properties b/RestroHub/src/main/resources/application.properties index 2e0bb29..1128e26 100644 --- a/RestroHub/src/main/resources/application.properties +++ b/RestroHub/src/main/resources/application.properties @@ -9,9 +9,9 @@ build.version=1.0.0 # =============================== # = DATABASE CONFIGURATION # =============================== -spring.datasource.url=jdbc:postgresql://localhost:5432/RestrHub_DB +spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB spring.datasource.username=postgres -spring.datasource.password=MySQL03@ +spring.datasource.password=123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== @@ -58,7 +58,7 @@ spring.jackson.default-property-inclusion=non_null # =============================== # Server Configuration # =============================== -server.port=${SERVER_PORT:8181} +server.port=8181 server.servlet.context-path=/restroly server.error.include-message=always server.error.include-binding-errors=always @@ -85,9 +85,15 @@ api.license.url=https://www.apache.org/licenses/LICENSE-2.0 # =============================== # Security / JWT # =============================== -security.jwt.secret=${JWT_SECRET:your-256-bit-secret-key-here-change-in-production} -security.jwt.expiration=${JWT_EXPIRATION:86400000} -security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:604800000} +security.jwt.secret==dmVyeS1zZWNyZXQtcmFuZG9tLWtlYS1mb3Itand0LWF1dGhlbnRpY2F0aW9uLTEyMzQ1Njc4OQ== +security.jwt.expiration=86400000 +security.jwt.refresh-expiration=604800000 + +# =============================== +# Payment Gateway +# =============================== +razorpay.key-id=rzp_test_Sp7QUlzgYcmjkw +razorpay.key-secret=i8X7w7Br6mvxGJB8oMdKQvcq5 # =============================== # Security / CORS From 8d41a758127f7795fe10b05dfba17a1937e079e2 Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sat, 16 May 2026 10:54:18 +0530 Subject: [PATCH 02/10] feat(payment): UPI link and QR Code generation -- Payment Module added --- RestroHub/build.gradle | 8 +- .../logs/restroly-qrmenu-2026-05-14.0.log.gz | Bin 0 -> 8670 bytes .../logs/restroly-qrmenu-2026-05-15.0.log.gz | Bin 0 -> 14582 bytes .../payment/controller/PaymentController.java | 50 +++++- .../qrmenu/payment/entity/PaymentStatus.java | 7 + .../payment/entity/PaymentVerification.java | 39 +++++ .../PaymentVerificationRepository.java | 12 ++ .../payment/service/PaymentService.java | 12 +- .../payment/service/PaymentServiceImpl.java | 124 ++++++++++++-- .../main/resources/application-dev.properties | 10 +- .../resources/application-prod.properties | 4 +- .../src/main/resources/application.properties | 12 +- .../service/PaymentServiceImplTest.java | 162 ++++++++++++++++++ 13 files changed, 411 insertions(+), 29 deletions(-) create mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz create mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-15.0.log.gz create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java create mode 100644 RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java create mode 100644 RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java diff --git a/RestroHub/build.gradle b/RestroHub/build.gradle index 49e56a0..c70225d 100644 --- a/RestroHub/build.gradle +++ b/RestroHub/build.gradle @@ -66,7 +66,7 @@ dependencies { // Testing testCompileOnly "org.projectlombok:lombok:${lombokVersion}" testAnnotationProcessor "org.projectlombok:lombok:${lombokVersion}" - // testImplementation 'org.springframework.boot:spring-boot-starter-test'; + testImplementation 'org.springframework.boot:spring-boot-starter-test'; testImplementation 'org.springframework.security:spring-security-test'; //OpenAPI Documentation @@ -83,11 +83,13 @@ dependencies { implementation 'ch.qos.logback.contrib:logback-jackson:0.1.5' } +// tasks.named('test') { +// enabled = false +// } tasks.named('test') { - enabled = false + useJUnitPlatform() } - //tasks.withType { // useJUnitPlatform() //} \ No newline at end of file diff --git a/RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz b/RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz new file mode 100644 index 0000000000000000000000000000000000000000..a338e0f0896cf1462af1447cced16775e49ff875 GIT binary patch literal 8670 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 Cu64lx literal 0 HcmV?d00001 diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java index f2f5a00..1031c6d 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java @@ -1,5 +1,53 @@ + + +//Preferred : only use for testing purpose, as this module is a plug-and-play type and should not have any external dependencies. --- IGNORE --- +// Written By Contributor who formally made this module --- IGNORE --- + + + + package com.restroly.qrmenu.payment.controller; +import com.restroly.qrmenu.payment.service.PaymentService; +import lombok.RequiredArgsConstructor; +import org.springframework.core.io.Resource; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import static com.restroly.qrmenu.common.util.ApiConstants.*; +@RestController +@RequestMapping(PUBLIC_API_VERSION+"/payments") +@RequiredArgsConstructor public class PaymentController { - + + private final PaymentService paymentService; + + @GetMapping("/link") + public ResponseEntity generatePaymentLink( + @RequestParam double amount, + @RequestParam(required = false) Long orderId, + @RequestParam String upiId) { + String link = paymentService.generatePaymentLink(amount, orderId, upiId); + return ResponseEntity.ok(link); + } + + @GetMapping("/qr") + public ResponseEntity generateUPIQR( + @RequestParam double amount, + @RequestParam String upiId, + @RequestParam(required = false, defaultValue = "RestroHub Payment") String description) { + Resource qrResource = paymentService.generateUPIQR(amount, upiId, description); + return ResponseEntity.ok() + .contentType(MediaType.IMAGE_PNG) + .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"upi-qr.png\"") + .body(qrResource); + } + + @GetMapping("/verify/{paymentId}") + public ResponseEntity verifyPayment(@PathVariable String paymentId) { + boolean verified = paymentService.verifyPayment(paymentId); + return ResponseEntity.ok(verified); + } } diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java new file mode 100644 index 0000000..1c7c923 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java @@ -0,0 +1,7 @@ +package com.restroly.qrmenu.payment.entity; + +public enum PaymentStatus { + PENDING, + SUCCESS, + CANCELLED +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java new file mode 100644 index 0000000..5323bdf --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java @@ -0,0 +1,39 @@ +package com.restroly.qrmenu.payment.entity; + +import jakarta.persistence.*; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Entity +@Table(name = "payment_verification") +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class PaymentVerification { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "payment_id", nullable = false, unique = true) + private String paymentId; + + @Column(name = "order_id",unique = true) + private Long orderId; + + @Column(name = "amount") + private double amount; + + @Enumerated(EnumType.STRING) + @Column(name = "verified", nullable = false) + private PaymentStatus status; + + @Column(name = "transaction_id", unique = true) + private String transactionId; + +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java new file mode 100644 index 0000000..88f00e0 --- /dev/null +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java @@ -0,0 +1,12 @@ +package com.restroly.qrmenu.payment.repository; + +import com.restroly.qrmenu.payment.entity.PaymentVerification; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PaymentVerificationRepository extends JpaRepository { + Optional findByPaymentId(String paymentId); +} diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java index ab243b2..9ec7963 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java @@ -1,7 +1,17 @@ package com.restroly.qrmenu.payment.service; +import org.springframework.core.io.Resource; + +import jakarta.transaction.Transactional; + public interface PaymentService { + @Transactional + String newPayment(Long orderId, double amount); String generatePaymentLink(double amount, Long orderId, String upiId); - void generateUPIQR(double amount, String upiId, String description); + Resource generateUPIQR(double amount, String upiId, String description); + @Transactional + void markPaymentAsVerified(String paymentId, String transactionId); + @Transactional + void markPaymentAsCancelled(String paymentId); boolean verifyPayment(String paymentId); } diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java index 9100e70..c7c19f2 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java @@ -1,30 +1,130 @@ package com.restroly.qrmenu.payment.service; +import com.google.zxing.BarcodeFormat; +import com.google.zxing.WriterException; +import com.google.zxing.client.j2se.MatrixToImageWriter; +import com.google.zxing.common.BitMatrix; +import com.google.zxing.qrcode.QRCodeWriter; +import com.restroly.qrmenu.payment.entity.PaymentStatus; +import com.restroly.qrmenu.payment.entity.PaymentVerification; +import com.restroly.qrmenu.payment.repository.PaymentVerificationRepository; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.Locale; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.Resource; import org.springframework.stereotype.Service; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @Service +@RequiredArgsConstructor @Slf4j public class PaymentServiceImpl implements PaymentService { + private static final int QR_CODE_SIZE = 250; + + @Value("${payment.payee.name:RestroHub}") + private String upiPayerName; + + private final PaymentVerificationRepository verificationRepository; + + public String newPayment(Long orderId, double amount) { + log.info("Creating new payment record with manual verification flag set to false"); + PaymentVerification entity = PaymentVerification.builder() + .paymentId("PAY" + orderId) + .orderId(orderId) + .amount(amount) + .status(PaymentStatus.PENDING) + .build(); + verificationRepository.save(entity); + return entity.getPaymentId(); + } + @Override - public String generatePaymentLink(double amount, Long orderId, String upiId) { - log.info("Generating payment link for orderId: {}, amount: {}, upiId: {}", orderId, amount, upiId); - // Implementation for payment link generation - return null; + public String generatePaymentLink(double amount, Long orderId, String upiId) { + log.info("Generating raw UPI payment link for orderId: {}, amount: {}, upiId: {}", orderId, amount, upiId); + + String description = (orderId != null && orderId > 0) + ? "Payment for Order " + orderId + : "RestroHub payment"; + + // MUST use raw upi://pay to comply with library specs and avoid 404s + return buildUri("upi://pay", amount, orderId, upiId, description); } @Override - public void generateUPIQR(double amount, String upiId, String description) { - log.info("Generating UPI QR for amount: {}, upiId: {}, description: {}", amount, upiId, description); - // Implementation for UPI QR code generation + public Resource generateUPIQR(double amount, String upiId, String description) { + log.info("Generating raw UPI QR for amount: {}, upiId: {}, description: {}", amount, upiId, description); + + String desc = (description != null && !description.isBlank()) ? description : "RestroHub payment"; + + // MUST use raw upi:// for QR codes so mobile scanners open payment apps + // directly + String rawUpiLink = buildUri("upi://pay", amount, null, upiId, desc); + + try { + QRCodeWriter qrCodeWriter = new QRCodeWriter(); + BitMatrix bitMatrix = qrCodeWriter.encode(rawUpiLink, BarcodeFormat.QR_CODE, QR_CODE_SIZE, QR_CODE_SIZE); + + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + MatrixToImageWriter.writeToStream(bitMatrix, "PNG", outputStream); + return new ByteArrayResource(outputStream.toByteArray()); + } + } catch (WriterException | IOException ex) { + log.error("Failed to generate UPI QR code", ex); + throw new IllegalStateException("Unable to generate UPI QR code", ex); + } + } + + private String buildUri( + String baseUrl, + double amount, + Long orderId, + String upiId, + String description) { + + String formattedAmount = String.format(Locale.US, "%.2f", amount); + + String transactionRef = (orderId != null && orderId > 0) + ? ("ORD" + orderId) + : ("PAY" + System.currentTimeMillis()); + + return baseUrl + + "?pa=" + URLEncoder.encode(upiId.trim(), StandardCharsets.UTF_8) + + "&pn=" + URLEncoder.encode(upiPayerName.trim(), StandardCharsets.UTF_8) + + "&am=" + formattedAmount.trim() + + "&cu=INR" + + "&tr=" + URLEncoder.encode(transactionRef.trim(), StandardCharsets.UTF_8) + + "&tn=" + URLEncoder.encode(description.trim(), StandardCharsets.UTF_8); + } + + public void markPaymentAsVerified(String paymentId, String transactionId) { + log.info("Marking paymentId: {} as verified with transactionId: {}", paymentId, transactionId); + verificationRepository.findByPaymentId(paymentId).ifPresent(entity -> { + entity.setStatus(PaymentStatus.SUCCESS); + entity.setTransactionId(transactionId); + verificationRepository.save(entity); + }); + } + + public void markPaymentAsCancelled(String paymentId) { + log.info("Marking paymentId: {} as cancelled", paymentId); + verificationRepository.findByPaymentId(paymentId).ifPresent(entity -> { + entity.setStatus(PaymentStatus.CANCELLED); + verificationRepository.save(entity); + }); } @Override public boolean verifyPayment(String paymentId) { - log.info("Verifying payment with paymentId: {}", paymentId); - // Implementation for payment verification - return false; + log.info("Verifying payment status for paymentId: {} using manual admin verification flag", paymentId); + return verificationRepository.findByPaymentId(paymentId) + .map(entity -> entity.getStatus() == PaymentStatus.SUCCESS) + .orElse(false); } - -} +} \ No newline at end of file diff --git a/RestroHub/src/main/resources/application-dev.properties b/RestroHub/src/main/resources/application-dev.properties index a0ae181..cf27b92 100644 --- a/RestroHub/src/main/resources/application-dev.properties +++ b/RestroHub/src/main/resources/application-dev.properties @@ -2,8 +2,8 @@ # Datasource (PostgreSQL) # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=postgres -spring.datasource.password=123 +spring.datasource.username=${JDBC_USERNAME:postgres} # Use environment variable JDBC_USERNAME or default to postgres +spring.datasource.password=${DB_PASSWORD:123} # Use environment variable DB_PASSWORD or default to 123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== # HikariCP @@ -49,6 +49,6 @@ spring.servlet.multipart.max-request-size=10MB # =============================== # Security / JWT # =============================== -security.jwt.secret=dmVyeS1zZWNyZXQtcmFuZG9tLWtlYS1mb3Itand0LWF1dGhlbnRpY2F0aW9uLTEyMzQ1Njc4OQ== -security.jwt.expiration=86400000 -security.jwt.refresh-expiration=604800000 \ No newline at end of file +security.jwt.secret= ${JWT_SECRET:mysecretkey} +security.jwt.expiration=${JWT_EXPIRATION:3600000} # 1 hour in milliseconds +security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:86400000} # 24 hours in milliseconds \ No newline at end of file diff --git a/RestroHub/src/main/resources/application-prod.properties b/RestroHub/src/main/resources/application-prod.properties index 6d93899..20a475f 100644 --- a/RestroHub/src/main/resources/application-prod.properties +++ b/RestroHub/src/main/resources/application-prod.properties @@ -2,8 +2,8 @@ # Datasource (PostgreSQL) # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=postgres -spring.datasource.password=123 +spring.datasource.username=${JDBC_USERNAME:postgres} # Use environment variable JDBC_USERNAME or default to postgres +spring.datasource.password=${DB_PASSWORD:123} # Use environment variable DB_PASSWORD or default to 123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== diff --git a/RestroHub/src/main/resources/application.properties b/RestroHub/src/main/resources/application.properties index 1128e26..37d634d 100644 --- a/RestroHub/src/main/resources/application.properties +++ b/RestroHub/src/main/resources/application.properties @@ -85,15 +85,17 @@ api.license.url=https://www.apache.org/licenses/LICENSE-2.0 # =============================== # Security / JWT # =============================== -security.jwt.secret==dmVyeS1zZWNyZXQtcmFuZG9tLWtlYS1mb3Itand0LWF1dGhlbnRpY2F0aW9uLTEyMzQ1Njc4OQ== -security.jwt.expiration=86400000 -security.jwt.refresh-expiration=604800000 +security.jwt.secret= ${JWT_SECRET:mysecretkey} +security.jwt.expiration=${JWT_EXPIRATION:3600000} # 1 hour in milliseconds +security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:86400000} # 24 hours in milliseconds # =============================== # Payment Gateway # =============================== -razorpay.key-id=rzp_test_Sp7QUlzgYcmjkw -razorpay.key-secret=i8X7w7Br6mvxGJB8oMdKQvcq5 +# razorpay.key-id= +# razorpay.key-secret= +#--- UPI Link generation used as instructed --- +payment.payee.name=RestroHub # =============================== # Security / CORS diff --git a/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java b/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java new file mode 100644 index 0000000..a70c700 --- /dev/null +++ b/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java @@ -0,0 +1,162 @@ +package com.restroly.qrmenu.payment.service; + +import com.restroly.qrmenu.payment.entity.PaymentStatus; +import com.restroly.qrmenu.payment.entity.PaymentVerification; +import com.restroly.qrmenu.payment.repository.PaymentVerificationRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.core.io.Resource; +import org.springframework.test.util.ReflectionTestUtils; + +import java.io.IOException; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class PaymentServiceImplTest { + + @Mock + private PaymentVerificationRepository verificationRepository; + + @InjectMocks + private PaymentServiceImpl paymentService; + + @BeforeEach + void setUp() { + // Because @Value fields aren't injected in pure unit tests, we set it manually + ReflectionTestUtils.setField(paymentService, "upiPayerName", "RestroHub"); + } + + @Test + void newPayment_ShouldSaveAndReturnPaymentId() { + // Given + Long orderId = 123L; + double amount = 500.00; + + // When + String resultId = paymentService.newPayment(orderId, amount); + + // Then + assertEquals("PAY123", resultId); + // Verify that the repository's save method was called exactly once + verify(verificationRepository, times(1)).save(any(PaymentVerification.class)); + } + + @Test + void generatePaymentLink_ShouldReturnCorrectlyFormattedUpiString() { + // Given + double amount = 150.50; + Long orderId = 456L; + String upiId = "admin@bank"; + + // When + String link = paymentService.generatePaymentLink(amount, orderId, upiId); + + // Then + assertTrue(link.startsWith("upi://pay")); + assertTrue(link.contains("pa=admin%40bank")); // Checks URL encoding + assertTrue(link.contains("pn=RestroHub")); + assertTrue(link.contains("am=150.50")); + assertTrue(link.contains("tr=ORD456")); + } + + @Test + void verifyPayment_WhenStatusIsSuccess_ShouldReturnTrue() { + // Given + String paymentId = "PAY789"; + PaymentVerification mockPayment = PaymentVerification.builder() + .paymentId(paymentId) + .status(PaymentStatus.SUCCESS) + .build(); + + // Tell Mockito: "When the repository is asked for this ID, return our mock payment" + when(verificationRepository.findByPaymentId(paymentId)) + .thenReturn(Optional.of(mockPayment)); + + // When + boolean isVerified = paymentService.verifyPayment(paymentId); + + // Then + assertTrue(isVerified); + } + + @Test + void verifyPayment_WhenStatusIsPending_ShouldReturnFalse() { + // Given + String paymentId = "PAY999"; + PaymentVerification mockPayment = PaymentVerification.builder() + .paymentId(paymentId) + .status(PaymentStatus.PENDING) // Not successful yet! + .build(); + + when(verificationRepository.findByPaymentId(paymentId)) + .thenReturn(Optional.of(mockPayment)); + + // When + boolean isVerified = paymentService.verifyPayment(paymentId); + + // Then + assertFalse(isVerified); + } + @Test + void markPaymentAsVerified_ShouldUpdateStatusAndTransactionId() { + // Given + String paymentId = "PAY123"; + String transactionId = "UTR987654321"; + PaymentVerification mockPayment = PaymentVerification.builder() + .paymentId(paymentId) + .status(PaymentStatus.PENDING) + .build(); + + when(verificationRepository.findByPaymentId(paymentId)).thenReturn(Optional.of(mockPayment)); + + // When + paymentService.markPaymentAsVerified(paymentId, transactionId); + + // Then + assertEquals(PaymentStatus.SUCCESS, mockPayment.getStatus()); + assertEquals(transactionId, mockPayment.getTransactionId()); + verify(verificationRepository, times(1)).save(mockPayment); + } + + @Test + void markPaymentAsCancelled_ShouldUpdateStatusToCancelled() { + // Given + String paymentId = "PAY456"; + PaymentVerification mockPayment = PaymentVerification.builder() + .paymentId(paymentId) + .status(PaymentStatus.PENDING) + .build(); + + when(verificationRepository.findByPaymentId(paymentId)).thenReturn(Optional.of(mockPayment)); + + // When + paymentService.markPaymentAsCancelled(paymentId); + + // Then + assertEquals(PaymentStatus.CANCELLED, mockPayment.getStatus()); + verify(verificationRepository, times(1)).save(mockPayment); + } + + @Test + void generateUPIQR_ShouldReturnResource() throws IOException { + // Given + double amount = 100.0; + String upiId = "test@bank"; + String description = "Test QR"; + + // When + Resource qrResource = paymentService.generateUPIQR(amount, upiId, description); + + // Then + assertNotNull(qrResource); + assertTrue(qrResource.contentLength() > 0); + } +} From 467992a0e307dbc879ad332007b749ddc30e17a1 Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sat, 16 May 2026 10:58:11 +0530 Subject: [PATCH 03/10] feat(payment): UPI link and QR Code generation -- Payment Module added --- RestroHub/COMTRIBUTOR Docs/PaymentModule.md | 89 +++++++++++++++++++ .../COMTRIBUTOR Docs/What It is about.md | 41 +++++++++ 2 files changed, 130 insertions(+) create mode 100644 RestroHub/COMTRIBUTOR Docs/PaymentModule.md create mode 100644 RestroHub/COMTRIBUTOR Docs/What It is about.md diff --git a/RestroHub/COMTRIBUTOR Docs/PaymentModule.md b/RestroHub/COMTRIBUTOR Docs/PaymentModule.md new file mode 100644 index 0000000..25d2d57 --- /dev/null +++ b/RestroHub/COMTRIBUTOR Docs/PaymentModule.md @@ -0,0 +1,89 @@ +Payment Module โ€” Review, Usage, and Recommendations +================================================= + +Overview +-------- +- Purpose: Lightweight, plug-and-play UPI payment support for orders in RestroHub. +- Provides creation of payment records, generation of UPI links and QR codes, and manual verification APIs. + +Reviewed Files +-------------- +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java` โ€” implementation +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java` โ€” interface +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java` โ€” commented-out controller (demo/testing) +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java` โ€” JPA repository +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java` โ€” JPA entity +- `RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java` โ€” enum +- `RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java` โ€” unit tests + +Intended Use +------------ +- Create a payment record for an order using `newPayment(orderId, amount)` which returns a `paymentId`. +- Generate a UPI payment link with `generatePaymentLink(amount, orderId, upiId)`. +- Generate a PNG QR image for the UPI link using `generateUPIQR(amount, upiId, description)`. +- Manually mark payments verified or cancelled via `markPaymentAsVerified(paymentId, transactionId)` and `markPaymentAsCancelled(paymentId)`. +- Check verification status via `verifyPayment(paymentId)` which returns `true` only when status is `SUCCESS`. + +How the Code Works +------------------ +- Architecture: Spring Boot service + Lombok + Spring Data JPA. +- Persistence: `PaymentVerification` entity stores `paymentId`, `orderId`, `amount`, `status` (enum `PENDING|SUCCESS|CANCELLED`), and `transactionId`. +- UPI link building: `PaymentServiceImpl.buildUri()` produces `upi://pay` links with URL-encoded fields and a `tr` reference. +- QR generation: Uses ZXing (`QRCodeWriter`, `MatrixToImageWriter`) to produce a PNG `ByteArrayResource`. +- Verification model: Manual/admin-driven. No automatic gateway/webhook integration present. +- Tests: Unit tests cover link formatting, creation, verification logic, status updates, and QR generation using Mockito. + +What's Implemented +------------------- +- Core service methods implemented and covered by unit tests. +- QR generation for UPI links implemented and tested. +- Repository and entity mapping present. +- Basic logging and error handling for QR generation (throws `IllegalStateException` on failure). + +Gaps, Risks, and Limitations +---------------------------- +- Controller: the example REST controller is commented out โ€” no production endpoints exposed by default. +- Verification workflow: manual-only. No webhook or payment gateway integration to auto-verify transactions. +- Monetary type: `amount` uses `double` which risks precision errors for money โ€” `BigDecimal` recommended. +- DB schema: `order_id` column is `unique=true`, preventing multiple payments for the same order (may be undesirable). +- Input validation: no explicit checks for `upiId` format, `amount > 0`, or nulls. +- Concurrency: basic `@Transactional` usage present, but no advanced concurrency controls for races or reconciliations. +- Migrations: no Flyway/Liquibase migrations shown โ€” ensure schema creation is managed in real deployments. +- Security: no authentication or authorization on the (commented) controller; be careful if endpoints are enabled. +- Auditing & cleanup: no history/audit trail or expiry/cleanup policy for pending payments. + +Concrete Recommendations +------------------------ +- Replace `double` with `BigDecimal` for `amount` in entity, service, and tests. +- Re-evaluate `unique=true` on `order_id` (remove unless business logic requires only one payment per order). +- Add validation for `upiId` (non-blank, well-formed), `amount > 0`, and `orderId` when required. +- Expose secured REST endpoints by adapting and enabling `PaymentController` and protecting routes (roles/JWT). +- Implement webhook/notification endpoints to accept payment provider callbacks and call `markPaymentAsVerified` automatically. +- Add DB migration scripts (Flyway/Liquibase) for `payment_verification` table. +- Add integration tests (MockMVC + testcontainers or an in-memory DB) and reconciliation jobs to cleanup stale `PENDING` records. +- Use a UUID or robust transaction reference scheme to avoid collisions; keep `tr` deterministic when helpful. +- Add metrics/logging (success/failure counters) and monitoring for QR generation failures. + +Quick Usage Example +------------------- +1. Configure payer name (optional) in application properties: + + payment.payee.name=MyRestaurant + +2. On order placement: + - Call `newPayment(orderId, amount)` to create a payment record and receive `paymentId`. + - Use `generatePaymentLink(amount, orderId, upiId)` or `generateUPIQR(amount, upiId, description)` to present payment options to the customer. + +3. After payment confirmation (admin or webhook): + - Call `markPaymentAsVerified(paymentId, transactionId)` to set status to `SUCCESS`. + - Use `verifyPayment(paymentId)` wherever order status needs to check payment completion. + +Next Steps I Can Implement +------------------------- +- Migrate `amount` to `BigDecimal` (code + tests). +- Un-comment and wire `PaymentController` endpoints and add basic security. +- Add Flyway migration SQL for the entity table. +- Add a webhook handler example for auto-verification and an integration test. + +If you want me to implement one of the concrete improvements above, tell me which one and I'll proceed. + diff --git a/RestroHub/COMTRIBUTOR Docs/What It is about.md b/RestroHub/COMTRIBUTOR Docs/What It is about.md new file mode 100644 index 0000000..c2745af --- /dev/null +++ b/RestroHub/COMTRIBUTOR Docs/What It is about.md @@ -0,0 +1,41 @@ +What This Contributor Docs Folder Is About +========================================= + +Purpose +------- +This folder collects targeted documentation to help contributors understand, build, test, and extend RestroHub. It is a lightweight, practical reference that complements the project's main README and developer guides. + +Contents & Scope +---------------- +- Module guides: focused explanations for self-contained modules (for example, `PaymentModule.md`) describing intent, usage, and implementation details. +- How-tos: short instructions for running tasks, local setup, and testing specific parts of the codebase. +- Contribution workflow: guidelines for branch naming, PR format, testing expectations, and review checklist. +- Design notes and constraints: architectural decisions, important trade-offs, and known limitations contributors should be aware of. + +Who Should Read This +--------------------- +- New contributors onboarding to the repository. +- Maintainers and reviewers who want a quick reference for module responsibilities. +- Contributors proposing changes to domain-specific modules (payments, frontend modules, etc.). + +How To Use It +------------- +1. Start with the main `README.md` for overall project context. +2. Open the relevant module doc (e.g., `PaymentModule.md`) for implementation and usage details. +3. Follow the contribution checklist before opening a PR: run tests, include meaningful commit messages, and add/update docs when behavior changes. + +Guidelines for Adding Docs +------------------------- +- Keep entries short and focused; link to code files and tests where helpful. +- Include example commands and configuration snippets when showing how to run or test functionality. +- Note any database schema changes or migration steps required by the module. +- If you add a new module doc, update this file with a one-line summary and link. + +Next Steps for Contributors +--------------------------- +- If you work on a module, add or update its doc here describing: purpose, public API, usage examples, tests, and known gaps. +- Consider adding migration SQL (Flyway) and test instructions to make onboarding smoother. + +Contact +------- +For questions about docs or contribution workflow, open an issue or reach out in the project's communication channels. From 7d8d0d34c8bbb6322c535b4ffbfa71ffebc386c2 Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sat, 16 May 2026 21:20:32 +0530 Subject: [PATCH 04/10] refactor(payment): remove logs and verify ZXing dependencies and set default values --- RestroHub/COMTRIBUTOR Docs/PaymentModule.md | 89 ------------------ .../COMTRIBUTOR Docs/What It is about.md | 41 -------- .../logs/restroly-qrmenu-2026-05-14.0.log.gz | Bin 8670 -> 0 bytes .../logs/restroly-qrmenu-2026-05-15.0.log.gz | Bin 14582 -> 0 bytes .../main/resources/application-dev.properties | 6 +- .../resources/application-test.properties | 4 +- .../src/main/resources/application.properties | 6 +- 7 files changed, 8 insertions(+), 138 deletions(-) delete mode 100644 RestroHub/COMTRIBUTOR Docs/PaymentModule.md delete mode 100644 RestroHub/COMTRIBUTOR Docs/What It is about.md delete mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz delete mode 100644 RestroHub/logs/restroly-qrmenu-2026-05-15.0.log.gz diff --git a/RestroHub/COMTRIBUTOR Docs/PaymentModule.md b/RestroHub/COMTRIBUTOR Docs/PaymentModule.md deleted file mode 100644 index 25d2d57..0000000 --- a/RestroHub/COMTRIBUTOR Docs/PaymentModule.md +++ /dev/null @@ -1,89 +0,0 @@ -Payment Module โ€” Review, Usage, and Recommendations -================================================= - -Overview --------- -- Purpose: Lightweight, plug-and-play UPI payment support for orders in RestroHub. -- Provides creation of payment records, generation of UPI links and QR codes, and manual verification APIs. - -Reviewed Files --------------- -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java` โ€” implementation -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java` โ€” interface -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java` โ€” commented-out controller (demo/testing) -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/repository/PaymentVerificationRepository.java` โ€” JPA repository -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java` โ€” JPA entity -- `RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentStatus.java` โ€” enum -- `RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java` โ€” unit tests - -Intended Use ------------- -- Create a payment record for an order using `newPayment(orderId, amount)` which returns a `paymentId`. -- Generate a UPI payment link with `generatePaymentLink(amount, orderId, upiId)`. -- Generate a PNG QR image for the UPI link using `generateUPIQR(amount, upiId, description)`. -- Manually mark payments verified or cancelled via `markPaymentAsVerified(paymentId, transactionId)` and `markPaymentAsCancelled(paymentId)`. -- Check verification status via `verifyPayment(paymentId)` which returns `true` only when status is `SUCCESS`. - -How the Code Works ------------------- -- Architecture: Spring Boot service + Lombok + Spring Data JPA. -- Persistence: `PaymentVerification` entity stores `paymentId`, `orderId`, `amount`, `status` (enum `PENDING|SUCCESS|CANCELLED`), and `transactionId`. -- UPI link building: `PaymentServiceImpl.buildUri()` produces `upi://pay` links with URL-encoded fields and a `tr` reference. -- QR generation: Uses ZXing (`QRCodeWriter`, `MatrixToImageWriter`) to produce a PNG `ByteArrayResource`. -- Verification model: Manual/admin-driven. No automatic gateway/webhook integration present. -- Tests: Unit tests cover link formatting, creation, verification logic, status updates, and QR generation using Mockito. - -What's Implemented -------------------- -- Core service methods implemented and covered by unit tests. -- QR generation for UPI links implemented and tested. -- Repository and entity mapping present. -- Basic logging and error handling for QR generation (throws `IllegalStateException` on failure). - -Gaps, Risks, and Limitations ----------------------------- -- Controller: the example REST controller is commented out โ€” no production endpoints exposed by default. -- Verification workflow: manual-only. No webhook or payment gateway integration to auto-verify transactions. -- Monetary type: `amount` uses `double` which risks precision errors for money โ€” `BigDecimal` recommended. -- DB schema: `order_id` column is `unique=true`, preventing multiple payments for the same order (may be undesirable). -- Input validation: no explicit checks for `upiId` format, `amount > 0`, or nulls. -- Concurrency: basic `@Transactional` usage present, but no advanced concurrency controls for races or reconciliations. -- Migrations: no Flyway/Liquibase migrations shown โ€” ensure schema creation is managed in real deployments. -- Security: no authentication or authorization on the (commented) controller; be careful if endpoints are enabled. -- Auditing & cleanup: no history/audit trail or expiry/cleanup policy for pending payments. - -Concrete Recommendations ------------------------- -- Replace `double` with `BigDecimal` for `amount` in entity, service, and tests. -- Re-evaluate `unique=true` on `order_id` (remove unless business logic requires only one payment per order). -- Add validation for `upiId` (non-blank, well-formed), `amount > 0`, and `orderId` when required. -- Expose secured REST endpoints by adapting and enabling `PaymentController` and protecting routes (roles/JWT). -- Implement webhook/notification endpoints to accept payment provider callbacks and call `markPaymentAsVerified` automatically. -- Add DB migration scripts (Flyway/Liquibase) for `payment_verification` table. -- Add integration tests (MockMVC + testcontainers or an in-memory DB) and reconciliation jobs to cleanup stale `PENDING` records. -- Use a UUID or robust transaction reference scheme to avoid collisions; keep `tr` deterministic when helpful. -- Add metrics/logging (success/failure counters) and monitoring for QR generation failures. - -Quick Usage Example -------------------- -1. Configure payer name (optional) in application properties: - - payment.payee.name=MyRestaurant - -2. On order placement: - - Call `newPayment(orderId, amount)` to create a payment record and receive `paymentId`. - - Use `generatePaymentLink(amount, orderId, upiId)` or `generateUPIQR(amount, upiId, description)` to present payment options to the customer. - -3. After payment confirmation (admin or webhook): - - Call `markPaymentAsVerified(paymentId, transactionId)` to set status to `SUCCESS`. - - Use `verifyPayment(paymentId)` wherever order status needs to check payment completion. - -Next Steps I Can Implement -------------------------- -- Migrate `amount` to `BigDecimal` (code + tests). -- Un-comment and wire `PaymentController` endpoints and add basic security. -- Add Flyway migration SQL for the entity table. -- Add a webhook handler example for auto-verification and an integration test. - -If you want me to implement one of the concrete improvements above, tell me which one and I'll proceed. - diff --git a/RestroHub/COMTRIBUTOR Docs/What It is about.md b/RestroHub/COMTRIBUTOR Docs/What It is about.md deleted file mode 100644 index c2745af..0000000 --- a/RestroHub/COMTRIBUTOR Docs/What It is about.md +++ /dev/null @@ -1,41 +0,0 @@ -What This Contributor Docs Folder Is About -========================================= - -Purpose -------- -This folder collects targeted documentation to help contributors understand, build, test, and extend RestroHub. It is a lightweight, practical reference that complements the project's main README and developer guides. - -Contents & Scope ----------------- -- Module guides: focused explanations for self-contained modules (for example, `PaymentModule.md`) describing intent, usage, and implementation details. -- How-tos: short instructions for running tasks, local setup, and testing specific parts of the codebase. -- Contribution workflow: guidelines for branch naming, PR format, testing expectations, and review checklist. -- Design notes and constraints: architectural decisions, important trade-offs, and known limitations contributors should be aware of. - -Who Should Read This ---------------------- -- New contributors onboarding to the repository. -- Maintainers and reviewers who want a quick reference for module responsibilities. -- Contributors proposing changes to domain-specific modules (payments, frontend modules, etc.). - -How To Use It -------------- -1. Start with the main `README.md` for overall project context. -2. Open the relevant module doc (e.g., `PaymentModule.md`) for implementation and usage details. -3. Follow the contribution checklist before opening a PR: run tests, include meaningful commit messages, and add/update docs when behavior changes. - -Guidelines for Adding Docs -------------------------- -- Keep entries short and focused; link to code files and tests where helpful. -- Include example commands and configuration snippets when showing how to run or test functionality. -- Note any database schema changes or migration steps required by the module. -- If you add a new module doc, update this file with a one-line summary and link. - -Next Steps for Contributors ---------------------------- -- If you work on a module, add or update its doc here describing: purpose, public API, usage examples, tests, and known gaps. -- Consider adding migration SQL (Flyway) and test instructions to make onboarding smoother. - -Contact -------- -For questions about docs or contribution workflow, open an issue or reach out in the project's communication channels. diff --git a/RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz b/RestroHub/logs/restroly-qrmenu-2026-05-14.0.log.gz deleted file mode 100644 index a338e0f0896cf1462af1447cced16775e49ff875..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8670 zcmaiZbx>T}vu=O{f(N${5}aTI1P{*O?k>T@5HvVU2n3hl?g0Y92X|+H!GpWI4Q|8b zcg}ga=ic|K-u|Ok)wgu1PRdU>0K>}Wl%BoKywP#s&AMWd`WJXYHya`gn-m4+2*Rl7D9lPdpnH{MHy`B zybC@HGrXWd0FKL+s9T@tRQ0$U`;zVK7AE-W;B5#)t_e78#DUNfY@7Ko`N2u?(>){B z9gC6-U%xdcoQAWnBlu1>_FcI2Y|F*2a{7IgV^%W;`!ogF1OnDNhAPQ`>Os~K)Qb2p z*Te0h`n0^B2vF0HM1j~N>$ zHm;M;4*ue=NmE!LYy7+U=OXpw~rvC~~^=$#A<`j4SZ=Z2Fpef^Cg46bz6Jbpnd)C7Wv?eKX7oD7`O2(vC{{L_dG09Zu-v~W zAiLxFVS|zDZ0vu@dGSi3-S{{1b^g=Sqgk@Ss^+*_O!W1=F5GE@mCS&53*(hKbB$-Y zad0rI>8`P)3k|nB`UBNSK=J2$nOiaTssJfrif^jkpSx$>Nu$O9Ss@8y;luG*4JOLM zba>5qcz#D0{Zb|qD{G*#QXG^IIC{G;+W1I2UXb?MJl|_uP<`KkRc7)IkktDwM;nwr zz2%P^*d+3b%fRW3@;14w8R77pD0)eP0#fMW4Tm3(r#c_d<1iLVTk88yy!Pj2Rw&CR z`jv$t3TO>#Ns|9EP)>P3Pd7qR9{4d(5F4SiX1kS#>!TGk`-N@{GS}0EOG=>YjWQmb zcKUD!WFNA2lAn3~qSYF-gJXJdPtvxj?){P>nFoLxIN3N<>9|mYJfvzZ{)%U2Gs2!!OAatiZhODxhaCQC-BI z>Z32qGWx}X39n?^$ltsi+co;$pD7&XY7y@r2r+w3twYBMcFOV*et9dA z^g;XvbF#Ioib6%Fq&o0Hh&r=xi}BUXD7V5xYu^?L`Vv!4W2+T~F^;yUvzY&r9we;T z{u|6(p-*O_1XhqY6fhU8c{U}v{WEOoZNa3p- zGrifq-l!uHHh`+T$cc?p3~jkyzDH}HE*Ig?U{|=aOfS}mA2EZrWbt)k2v`E#)yL~3 zcCRXDLJr|q6pm1Pl4%t9jxWEw70u;c6SLgwS$&@ohl1FiF20Uaa%eXSQC( zVDlgXo$u1gpdX2*PpvPfrJm-7@S-nKiIiz*UL)Hf=%^%(yAtl{@;YVlE!~Wbru?a! zl3H=A8kL@zJp78%$c{{>DWx!DDu7Z(r!A%y*_3VCr1cESfQKX|{)^vF0va%2+2|V6jMV*2dCM4l zeV&-iB!nB+_Rb1ph7W#Jx5@<@n6Op_EEOYpieJX|$u#7m(QU|NiW z0qPm!^$z8ou(I~ru^MC_ymor&NK;!1OM4qvN;L5>;g_KVR83x(6lO=`+jESFXl9ht zyh<=o_~j9t8fe--OhHk~chOIz`1_UC2QVixkay*P@0OyhJXc~y8bP}u6^+_lB(S&K z`C*qap;Ek=NZ%odDIGlO5KO%>Q^N|1ew%42j==Id8nAF)Sy@!X*7Eu{k?RCKbU*)YUH{iS-A1I2j;IZsBIxPr zA_Ed6zwE5rmG-$4<dn@Ih1R5ziEy*d)Hauz!8Ox(uw@W3aZ}n6jxWRcNVU00zi~C1^7RL)38|l7g`%y8 zAC(|2YP3!VLMXk47C4zXrZ{_m4|BHA}&Xb%1>>ATT|8|25t3nbf$^fxh^U z*`Qy$J-j0nrv$RIdh#Jc-Fr@IbN#0mIixY&csjcTU@O5YLPNIFF|0*U_{CZG+ylb$ zQfy_w@rLqZV654TydrplF;RVnJZe>z(7HU)x)QI-aUFN(^UDgA<>o1H`or){-)L{_ zsnBMUX*NK^>*5YPu=H@G>n!LHPlv0BP)WEAl{zLMyOot2=DZlpEM7J`{WVsWpJK(@ zf#1)pD%B#yPM*8A$@tU)HPtmaEgt?oGEalKj>Hx%Ok?fpYZ?MRTgAeaS| z2MCb{sV2qfUm9$)M9NtuM|7tE4I)FEKD_hzq#%0;mnHE!vdo@Nw>+R0TC!6pLRljw z@k;VN0|(R9Q+#dtNM&$-Q?~D5@G#YgU>8n@`}etDkYb|69Xi|M9q8rt3?%SJOJQ=X zl7IFbUTFDh$HXvI$~28h&X+;L-i&2Km?jx#&xud_py;Y<6)O!MX>Qi?=UKPR0lscn zh*}~F{>Hz{I_S+uepA1Gjb_;KqSiKP4DFh!=R8G;W1QnO(95L0s~UFT6itUUHIGj+ z1Go*mb$~)Gob95*)6B|i#HEPGZW~Tb`k=Oj?SXyJ;>U^UoM_xt-rU>|<{B{=8;!PE zX<&{zOH-4Vq-MKLEx%J!$u%_$95Ss^&C%`#wj?&iJ4VxCl$D(DwgC^HT1Xg}Lqr^G z>!8Rvwxm#C&vQg~9n@(%X)&-4s+KV!&E{m+`LmSDv(3OZV%Q*?A#xPkdP zH4O<_JroL*V&lv(|Nb{*HhrXya|`^p)BP#lxX9xPe@81+ntz{-z_*SQ{Xd@T3_U;z zHn{(nkpDy$(POJDig&!1{fBcJr%bVypc0+bNn~vKRhXAKo3NQ*`%; zBWmg@HKs?Yzrp%!;7%HBZuWFEXj&y?4x z*G%S<+k^kj=YN|1A3kYFu0tG0zlYtuHlhAIe$P48KcqWFyA1=X`ABBo;VMLt_9UEA z%ahhnN9^d;L-R*3U#S?aH`Uk3O0jW({KHxDaP?x{4q3=NSh=SGWV*lGZF5;a`GvDY zd&u?ActWA~?T@;7Vz<+!4)L4n0Va%1$d>-Cn+m5tx*@}uj)n0^ouSr2)3ZRqVIV9J zBruHSHF-^#fqD3{ad;R@(9nIEz%~?^05{PDvN;)cViCT5K-r`onDxNmivmgrs7FfwNvc8m@)m;;pF^3_9gTu~_hvr?Jw^*J)`O$fm%5E&P2@*+n6g3H&tkqqgJV9RdAKT1?Um zYS|4v{=S-$Io#kFgQ`hlratDKiae%1g9eN0&Et7PIk>>bavQlyw6{-3ONi*+7}3!s z({O=&PUFmn4O3#Uvaml?;f9>6#C&tnY&nfkiJ=RR$dgWh*C@ymGrr$&UwA=Rj~N%C z8betd83TJG5cqWndim;!`#V|FeDANS4p<)zNb@#c@Rz_|$jkfClej!pu{4pFOA4#R zZ(e-T?xNUTqtqceB|fQ*L}Z5 z?b@#I0Rh+W)jJSlpgD0OHd6Piww#VS$gQ{YWT3ZDv>q^B*gi$lP4DnyNS`)mLQ_*i z&BD}T_iXAk?ocPi4avFvw+;YVr*|`Xx3{h-1;vyn_cTPB2`e;}e6yl$T-(5*O~>~E z4o3Vycj@63LGNQmP}THd2qjmeKJ1_{irq?CG=IxgN|y|CkEG^0Y3kx2RFES(MV6nf z{ox5=x1407X6)Q2bbBV=Ixu>_vLD4ubU8B6dD_W~HcLv56B*tP6QGfJ4QEOKl#QI& zKb-FDudg+B)-q@&ssM48;zaF&fS+k`O0K_r1x7qQIolok1y-D{YySA#(F%`jDt#J7 zb0vS#INxDJLVAPXB32DbDDgoHIv>Q9H=M{4HRVQsR zJkd90Fn+ymLA5WNQ$HY^*FV{i+)BXiup<1pSSiG*H_qmu6|%yai5*6)V4`_DeB*M_L5!h%DL*SC1>ums^UF&DA^fo+S+TT(`c+ZKJYYoX?L_UVxD#D`L26qe<3@6%1u?Dbvw);9rx`Q6 z&1+=CJ68L3Yqp{%z5{Z`Q(I{pTkq0*MrV#>i|Iq!>g$2RbHUGNGWo@Cx^`nJlT?O2dL z+Hq~lxPQ}wHzWs_Iro1BU&t3gz3*A#)G42lbE?GBl{ey z#L8MoVvkI3c*HYslI#D5Akt<+6hGl~iiMX7+!))WdbA7<&7SA|YiT0m{3F7_ZCwzN z#h0D^D4G*rlk(?dcyjjTE_E8S^aS!dzc1Y?fh(2JA=^q$rx$XnevGEddvs^?Eku?j zUt{B96SE|$0OP&^s$F6E`xNc%1v!p%J~z?b08MLJG1< zi{I3w&?#!Hf9;(97+%uz#~8_j&K+N*C$gintE4rBNtY{c9nkyciB;dv3KLxIQt~t+ z+wY>LhHAA3x?wnP<#!aDxxFR0BJ_+xWe~a6Eq;H!u97cUUEpiH|KWV{>@DismQB>d z`k18OB1xD;RT@2}M}w_z8gd%b#_}Xvm6KEx9H(O_Jgiv33`&b5%wWa~&TGS!?!7DB zDYhj4crWI+8^hPWPrp`8ot0YO-mKKe9g`g?G&gx(iXXVgE`I#_LBUn_yi@8vRGp}s@@Q54Q=SDTNu&J=BPt!|%ah6{g1_XA zAQBwLvn}|kJ6X+SjiN~{r#toIiVdc7=T>T^Kbzp=d$oup(FLWumCvU(XRlZpj$MJj z95OXGJyH_%e!ONLU$;nmPR1ZoAN@AnQ$ESd->BBU+n~%Xx3-P1A&}DX&B`ifVKXar zr9iZ8MlinS&|IL?7NyJJaGv5v%}8kXo!?vkcQ+`bRKW<*GqfC$7x#YKxRT)4e?x6N z;MdMa()`5`I?d^CgbE1YK)i^?e;v^17nYn`QJ`l7SZDS0zLO##o1bRSJenaIsmmqtJ@{;t zBTZ-qX-X(Oz-O#e^ zRs?*H{lk3T_2&fcw=u3mM^%*42+F8F(#?&JRg6i-?kVL@`EJV}tla>Jcdbq*>{NYT zNXwOyzO2UWy^%4mf7O3`B8d-VBtVGs@qVmu5 z-j1k^S{5eBbzEUDo+Qf)YrDt*cM2Kb{KPy6_E%0WCZ+V=UQTs8K6$z6AIT4+8Z#*F z6aX`c)0N)K7(7v{$B?{Fc&lLVcPsVrRo{!*o8TfM!h@M=x+A$O3vlgMwrJt%=8nR1++tm|6BJIX;5GQd-4R+&BTXidNh z6DL6__h?OaBQE2p>x|xex}BW)lmL1wHd9AtR?FlyAWx9O>Hl-m$BPl)K-OJ2A1Kg zBTYe@Lhp{UJ#YuT8bOPlgmX!>&tspO#=KU*50L!|kR4gWz7-vM;)Hsv2KX^cJwaSp zBU245VpX%_^=3+XcPePPmuHgnfkGXW`~QF4KYjs(RQHnin2N2`ckj()DhA&k;7@@< zl)+D!YAh7@2c*B*g3-OIn$lN-quB1peF( z2sH1uFg6jlQ&?>Ll?yq@=+Vp@OMok^spgL*R6}l(m5t0`Sl_Jsi^$PtUFVXi?bDsU6Ihuw~pO`H!+PLP?;<@yBrnH05hV%KmtYA2Ry?o)7-l$dK{#9L7yJ31K=%L_b!|GIqplmP;i!1kxf< z27b};(Wq8LC6>}O)8PJN&T|{MO|4c|21DGPSBnDH{W@mS^p?yUQx=o*KxPsXb2sYNZFluARX2lb`Rb{Ys;oxiu znLSs0ECzz-Y?=E)iGuvzXc;6 z6Z&>$IGo%ZNe&XQs(B`eKl&Lc4tw+^hN4~&J6X;>I-!n~17?a?%4}iDtUm-pz1(?X zxb!2~hhW2lv`i4UwzYg00!j_P<8T@{wby%Q;)gy*8>Z%#yK9$auD&gQ_`zS~{ z94`_lOH7vhh?T=e0u%j#h3AZn*+^B(=`|#T(j5>}{7A?cN=z$GI+gK|`H1k{3WLV) z`j-5XgK^oz&=ltTSIs69VATw8f&b<1+#$isx=O%C?VV;yI3gfdwO_$=hWllFX{j!G z#2(+aPC>tNyp&mH7gv!GpTB9&#uV_!u|8@B;@)eNu~)cG@^i!` zpMs?m!!Ve@tKU(}X}emSbG}+sFO+k?&UWYh4=fSW{$n`p8F0^B=UF^-msGS4=M?=g zaBl&ddE(<@pn=y(7^zkrX2WEPJGc&Wl`Xh&*=+Gycad^`=PrlhH3v4%UWkslNd>kJ z?dyYy7COlyJKmyC-YCr+BGK!}+H(6$M#Hkwauof4|8pN&eZEWlnMokCOw5k0v)x27+wl$hIPh7GuG|Gg%-COM)~tci;~U5x*%hr_HE66veukK#93wBj=LM#U03YTu&DK|q)v+_$$TwucR2R< vGMtTZqwz5Dbe()ky+}$i>6spsD$O#F_VV)&IJ`zaCl5TaK(4D108-Q5QX8r)riySoK<|K@q`k2|Z@RIhzb zpQ=+;tGjCNCIiAl{O|X9o@3*(Ce>Ucd|9#j2}#>NvIi9F%yT!T0&@TI5tfoj*g~4$0>9(?PFP=Ar$i{w1g>R|=v|T|}9}ja*=O zn^<%yc-re_?~gF=Ul3jNR_A6_P1@**chcXq+jED%psnnb*P%`XyxXtqHQz6MS{1p+ z9d`^Ij*uS7PH3`fQ~y@n?vshU!A<4R-pX{CX7PQ}2_HPMI2OE~XZv4ue}-h5c~&LY z$u`)vtdx$T?f7IcMr_2q)=p}+b-3pN4GkRn4jxQ84JP&+8G-&3LhOcJ?wMPTIgwkw zc$*=ptjH~0w|-r|djh{+mIL9h>d64PpD%NK`h2WEma=%>?;ThTsz@mwKu>Lxr=xDJ zncdC|jZ3T_g!)r>qqWzR6ZM?0_spLkhP5r$WxsnOh4@w`+j-odY<{I&nd`ZE{oNaP z@cUTHUATYqZpG$GTp1*qc&2na%)lC2dsg(m@JT4^rz7?&gH1f9`e2{Q3LNREx(=zj+ZBiO=*b zeQ4#rwNjJ)+TfT_@*FoRfK(*8`ZKU_{PEAW-UqD~2VN5qOZKDHzU{0eo%fE({Yz8% zo@dj!8Diq<0bv;G^}`jT+wN#?QTb6!w&zxZF<0Q#is4PrJ6B z*8_%(tU9&f*=Ca{WMcS)(wCWd&}#G9GYxIYrT}#88`p1l?DP09h)B&Rcc~6vE{?Uk z^Xd6D?e7AE?8Z-XQplglPGwwEn^(H)IGPB%d;F*e+PiH~`v7wm2gD0;l4IY>_oAfN zzd9pXRjQmW(1#vwk&hknwpv;~*sZL$-F~t~cqfN6IKCoXyYNebM_rFw05_q&CD7#J zbpcuqoI~@PTao%s$?F&c(uU`~Qz@&lC85zLpyY8BRONY+Qia!W zpwIGL{#?vcVAPuPry zU;5MG0@R==q{<}p;og1&+{`qPv`W14>uDd2eLcwW&GK8mpq6~-iyo&(uJPX9fEv)C zDoq)hojQwUI12G}YdYK-Ubv}~Xyp{j%%nEywz=4WBGb3xb=1a`=80n z7S?RHi?|jsib$Z%T!A)X#G?1*U%-lx$-oD8r0Psn=<5fW{VxCI^pcn+h^H>rrybRy zn!MYv(=U-_u{D-fIrobCE7blv{m)3g?cYp7k(vI`3?f_&EKD9C)?GXiXohy`XtvA$f{X`Y^k>T$6 zwi%k$`ug=#UZLJOhsm1C|AfF0CZ~_Y|Ew*hq5P6^8Z^N5Y4V3<&pr^Z75ZTEx5D1K za%5LO?ftpU$Ni^ni$fBfdtRrXM(2)A2o$W&I*(k4P|EG?7Qs5jVZ`cK+AhTf&D*$!q_yO7hhAFRuUB0n&{qowW0($WJv~wFq6dVhT z3>@Y+GmzmJZxXEkS(|1SPP{~%HUG#^?vmpfTyj2NF+sR3@0;?U_31Efx%&J_>i-0C zS6msbb&R+@B{+;Wf9{KC{!y4|LE9%9^mj2Rx?Ng-zm-au^y<{kp6@oad%D=kyU_<+ z|8*F1IP7jm;J(gsyoFGGF`r8t>66^M{>Ta&FMV`8a>cp9xoy8Ix;4u6q4%Ft;~))w zomWU#_O3TM z7XQ-tb9YTERXLoPyaS5HFwMc+Tc`FO(475KvaNL=*nd7u)@T02w}Cw#>M$_!>`K8% zb5Q!{(5BfVtLTP%9_6p(8SR zYxk?mL_2(q`*X>Q&qb`2VjIPIS73*^36*>ORzoP4m7U0fok=M-sD|1h^;7wu!jJ2A zS3O%%f4aYTN;wugKdQtyjuuv4|NR5i`eD6X0N?xC4#Z>bVV*Bk4^okswVD!v)R!k- z!mkt?T{_glSDGviAm=!r)3=lE{X>dpM{(p*%yK9Ow1W!_N-A8ZAp9?gSbyU0x zV@V5|nT+_#V*jFiXVVwZolv%}A_Yk`CH+u)hUaa(esX2T5-eSxTbC?-LNn7@f$e@913d^DgX9;-@0{7+_kb zrTN5aJQanNpBebpaL*?AXUms^ETj`_F_tFj$(LEKnz~xi3U9Ac%7GXZg{xJ*p+5v+fLiV|K zeW1H{>)XGRQJ16L{8(`NppsYZTsT9PJxxkNW!xJsIdZa;kQ|U^QBk9%tOjBn$r*`Njdoe3LoPAE_GYi`N1&GQ7UiLh__KFg0iTu)>RZG#iCD%LqaWYpIq!GG z`;Vit;7*x`(xgvlN4tI<+T~5rBT4AQrtJpm>+|G(!=B&YHEG|NC^}=l z^mwCo!n~jK8C`oD2voXW*s4m5yXn6fwC&XWY4vUmL)0)s`NO$i6({_918bVXPKvGtYWZfcVzrb!ZzkUA3SVW@mI-kS_^& z76mv)UM>&9dq7x3$DIF0)rP@n-T|<>Q>u!}MgbxeXJ^aft+4aL`3++Q($KQv3UA>h zsFf*oq`UZE&E0>;>=p1CGGLht<-x#B5Lr^y(f@;nWY~!=9l`HFXV@LsGZ}}coK3We zyAUVy@uX+-PFTNxQs0X{X?_K|r^%TVE?()^vRK#3ZUSmPNPqh&86FRsaQF`L@EP>@ z)miP=riX1(D}}vEh-aqt{3~QL+J83Y2JHu^>x>S_ecMOYnR_ernxI_s^`W0fV}2*_ zP!_B;FFSAmYThtz zUzMDOc@P|>;i2`Um6-@$o3zXJgKi9`o*00iOpC#KLU~LBXxAnwPA>hHt*d}g;Gpyo zBwesoHUU|S&m-ez+BHgRL%QMQpXcF0c0NPI;$6Cb<6BDE{}v}+D$DUd4&wY?u7}5^ zSwo|DhsTF^$>Q?5%KbI{LK_on+xy~bC6*!meLqI=J*g>!vTZ*u=e_7iE5;!lEndU2 z#)!W9$JE-_N*2qfcQP7O1ml75p1Es<^hMI&PNTX1daNLIdC1q?%6cDxjQh33H)^{! zK=3?AK)fIjT;JZ;C~S6tK^Lp`NJ`S7V^2)Uu~W5wu+_9t^Mpe(*a;=-g;7vB?u8LJ zNU%!S7~e(g#ii%R;Ug*42yUfhumj`&Wzkb=*pLWmk7CoyNyCu|5U0|sjj0UL5lmq7 zmJ66>P>OwV59D*OO;^BRriV6Q?n>+bI=hPV+LjYem>)X%Ws7QF|;3d2>lt9g%Z0#`C2q#p$ZsrzwwP$ zTbyxUup2sJ6fks{iv7zKFpN3f5Y9)I`GOQMtRlnt66=R!+0xjdYdJi8Lf~BrCij#w z6v+Dk)(B59-@HRw-RL(k4i~8{CDPLf`S8?!P)#s{reoRuREab8b79`74H5O4{Qq zBu+5IW{V_$No0b$2M>unnoDuWq)QOFE(zt`MDK$~A-+*>Tsn zoVJasPe2S|EURB6g!e9JIqyVr{!Ai$4jz_Hac^kOdCOdaU*MNF#$hOechZw3#-W(` zHqXQ5S5I3mmo7N#udbnLsKmwwm8bdF#G)as#`6E9(w9t_rP9$#W9B` zFV-TwiX_uP=cn-B_?qEetg0k!)+i^=Fk?CT*3&-!@4DJP)uAS+SX&?K;* zH;#a8OIqT2!oz9)v8=)ZUBtQS$KEZvN51te0*b=LP76&BG4{U%@Zv&SnX}j*F8rKd zOWNf-N&D(IIxBzuX<4g(0hPxJqp4k|b4!t;*AbzN`rku8>fUmB5SP>v$_z;|JxJfA zQ^(25z~Npo8@ki5Ze2}!Y4zr=1g(|k-}H0e-ubpl(0y-b)%x6K{TSKOCxxo|Zb%Ec z6(4R%S=n&byy zhwk!jJ-#~P7`s>eW}6jMC{fe0m6#ysbAt;MJ@U>7ZD>SHHJp5kb!J2b-mHXAajXb< z#K^Xwtetc2sF9wB^!j0vF+QCrQqfLCcx1X6f&k(2dr=DTteu;LPLV_5eutI1Jq&o(gh+0DyLxb#_R31h$EVG6F@UVr9 z`KY>tRnQtL0mp(#lY(TM&QSMhb+c0O zbY#r^9Vt#3ro_aeo+~q-Pu!TAn=N~_X!(3ptPCT^@?oA3I@iqUQ-rni(z${d(}K-| ztR+hHlk6lf^E7csn4Efz``r;k=Ppz^gVI*NO==hye@5VJJW_HNf`g&5d+=1>{Trvw zpw7XV8EHZ+lK8W)q;9?Ip7AM#)}?z3pYhUy;=h{|HPhn1pH+)Kdv&?~Y1OG{H~oxO zPO7?dR+%bO8aDKyFB@eMHyLC0sh3mizLZtUAAdKU^&ge9mwX)RT@8HQ9u^#)!^)QH zoPrHE{5*=<(GxX6>7CR-IwXph1%IkNsP*;TDnIJn>!Kqo z_N^=CswXvyRj(fZ_Grn~+q9aqX5Gq_ritiU5zX4J!0Qs;{AAQsZlQb$OKDR!qu~fX zCj;9-tsI`4{c@*}osD&u5?-+($*+mydv+9M*lNUEys|S*Hyk%(v2$Xj98-Tu=velx zH@#ixbFB+C=l=x;zdtxAWp{=Pb5p8#RdvRchYL$Q4PcO~z!yA0=R}}OUSsh%LDhOk zj(MndKV@}GZYxC^zLAV9A<%az`jCI3!n=lb|JHCJC+2yxa*Rbc45gnsec05jy`{6= zf&Od1t>Yvr>)xEsy7kTl-bS(hK|ASuv(76U6&LfxMEAb*7CidIRKVo+Yk22?1bOIi zb{`1uuPl?Ws-%@{E-7d6bX;a@-U+Qd$u*JgS~6%n0f_CkNOi94#H%NK{P_UX>EJul+i&il~AjvFrR^ecm&H@%;K z?_R7U)so)eLO$-Eg)95}_&KzDJQ^|1$*)UbASBqlg+Q*zGr^N;!$w=Q^nN=vIGT|qm zezgl=(b#C@MO>)R2y1(i`BC$^iFNu_k_zwDR3v9?7D44OYdMT>fjzACqn$;Wz|fZ9 zRHMR3KgEIMR73uR{2mp?Ey$zxlZE=Fa>fqtR6{mD%5?xTU#ThfuLA6uN|~Dc@IorI zXihQV1g&-ocN4T*<=Btm3cUcn6l&Au1eR~;^A)qfIOD`cDrNN&rG4h8P;Q0f50vo1 z-+iNS+0oo8qwYI}QRig|6>rSW_rmyO<>?H0UH5}xklYx|qt+}+Vcil}@$+&y)#jPS zbBb}NLIX=c$oByNT+7{p)~WOZKx>a>J_5dda+;rgW=VdGJ^dtDZq*C*hQlPjzH)!T76471%jzWv&SZjtw z&=!g2MC+Mgoi;Kq63$zt6V^>ZXHr{+LI0W*M4heZuZ(p}FM{I3Db;OkA~-)Bw zjeJUmd!&J=6;_zh!~1OC(`Ko})S~FJdt+o!+j!HJXQc!#aI@Jr29o7xSmuQTSDa;j zeYP{%);M8x0`Hbei)P2O*;NR~g}e)2K$zE3#k>>{Dgk|#l=b|MN^}uMA(2akKXSZW z?|91e`R(?y9o5-v4ZI;HAR6UsXN!WJsh`%J9xXd_A!$nVUTY{m?Hp zQgX8r@5w%$)nG(=H!j!F&1aaz96}GZoqP^%$hOvDfcvhQs$OldVEh34 z^lt*Q;p*q#vs>&UH2l`H68EM`9;uJwH@w13PXdm;04w%Mdyf|ANP%Bh9@e9k;sIOG zexQ1d*6eSgTMY93B3w?ei`ROcZumEWsaJZl#H<$B&3Uo&hxmOC-#qcUHD#|gg7sA! z;85MEo~p;-sjFNZsAxjADw#BIuI}K>j;^GS1}?QR&N~OsB)3eG+46`Xwkl(0)@vUg zDa0H8FeH_a#Z7lk>t$Va-rhEpA-<*BCVbpg%QYpEugs%k1v@N+m9Hxqid#S6){ftD zAgrzqT5putHw89FeN%UCPF)kG>dA`#%sA4z?$&|(1!A93SL`@+PaEMc3zSb5G`;NS zMe&Cg1@=dNUmM$*F#Qk~OjBRRDz}a!NgGd~Q7xtnuMY&E!n6yOSr*j-ajh|GBq+I? z2KPIZtRlwF?zkkz&(gM8_Rm{e|EjfLFQ86?=DMBj1gH3IH~aRlc3+ETo4XRIY8l0g}#s>?d@ zsaudsv984Ldx)Qs5Z%ngXP9ujhENd zfQhzKoT77OefiM#Fzfs*66rv}YYZOq!@Q~*nm=kVGW z!;CZ&MwG@E-QL{gXi5RoEle%`)($9!_KmuZ~@m zA=vg!iU$Vr7I`KHA#worovp=53_`XLS9UVrB!o(apx;*rx)XvUN>^wO&psOKaF8eR zA%qByRB=uQcna$O9!9=fItj&ZYl^8ZW?cRS^`ifKsR8~R4>HQ{7=%)o-3(N`jx4)q zOLidJ8Km5ghL}!lH8dF2Ib+^Od5O3SVZ~-AW2l8LJR|JpI*pS$)I^XZ_+q=L1}+zO zWhgYHM!hc(Tr(8vo)g2ZwKPh&(GQv}50xy(j=Q3n5-o)ibFAS{7NT<)s)kAXLJ(_rV5+B=4VOj;a15F5+q0>`F7EN`DT-3m%j0f9GEJG z=rd;0Ze2EB$b5bm^Ms89Y^HWkfJg7l(EZ+9$c*h(`8Hkg+cI>q=S$oAdVhndr&YWX zP!d#Rt%7SuFLRJHY%+6d$Tsw~8sthx1pbh= zag@+4>vLvmWXBnwAqF-xDPm39m2+xt*7YKL>ts6oB%EbL3GBUb^|I0)-R6c|x`>e{A#8wXI~+|FyrBNe}twzxSx6x7g(xqy)clQL4Y8a8dzzZ7Z*^(%*p8c_K`_D zSj4QTsn{v_7WmTvo^wZQF`!WmP8G@ujj|mDsrcYf<+zVrg*XBHJcXL|7P1v$-BF8o z4pIegW&1bK8F5v&rn}@j%GqE#6{!YIbUuVTO26@-~HJ>Lr9ojjpssX{{T%Rt2?6 znGh<>e&sw2ga1#s5$f?Fmi)0|JS35tdNT#C9zke`Rt7KU+LzV>gEd3Do2rQHOZYC2q+;e<#<)Z8PO!%0Zlc9LpmRr&>h3##-Z-tP?FuA zr=TKODwE^M8KJ9Se44|Igj!$^f18CAZNh35IQ)h2(~7L^$8lMmuuVnzVI2DLpb_pL z>^ZyABX0?o)TcnWMtH;bNPwpl1f^?Sr8-tCdZY#YgKO6x z;K-m1{-nw*bYTg(^B@OVVFqfG64FR0L{CqDLutSBMOwT^BY6G5C^X{kQbD%LrF>Y5 z)ToILhw^_91gJqx)D_J^I%`(OQ$#O-4Xc=0PxRsK)(OshHd~$!4c?UlO0DF%%zF!%FwIFVVrNDB^9>t#yn2?82(g6i|v?m2tS*~lL0QtgWPR?@3;orJavFhvbn_-jwc{ESx>pGZLKe4mEd9whWc389nd#uenD;cqXJB9GtT zaMA&FVTf#L)R;JYzUdGyP&yOjc5@a*QrM7(_1cJ^;D-7vw+1WCf~gSL(o#Kp2cRcH zCwe5SIF4LHAY5ua)!wXH6f3ZDi+BF{U5!D z`agOP+%>k%n5jjs<1guYxCKN;_Bv8=5VvZhPuxOiHIj@yJ`jqTkcTChdl&lnxz*(Ue?&2cj8?h{?94I;`p|Pb&c`{(nI)Vox zdTTJAK(Hr!np~EG9M{1IRg4Ux)Md0rDS)>)z+}6>zs4twA}b-4{Ql6$>G%}`Y0lu= z3C!zPIcPJQ9^x6!T+znMn5pzIimRWxX(VSb>FP2+2f`CdAZ|yLjWaDb%GPOnvcIS% zQx;>5Zr~VgQZ0xcjakSA!P~guRKwp=Ru`Q#0?1WeQKd@>jad`}HmY(|sU}L~oDjKw zgtTgG&jgd*U62{GP`j@433`TjRg9@%-$Y;>mQa6Z_29xaHUKu^8w*LqETbv?EC?5b zA%cFMNat1x@hZ~~9|kDX_I0o?7B<;o9J(P4gSpRBt@Aiqa1B4qFr66*@3KqTrLQHO zu8!x~3ufG91jtdXEiXCkeJ^My7OSV)8KgSyS2B?|^zy0^j@#~6bbCwfeILXyy=93N zCES;)r@F|#HZ0dHR~Wq5u9lRu_`)jg06Sc#zL+tj328+o;}t3G%^s;S!i@0`K3KLN z65&V93TWzOv`lK>dYcQ1{@QZ-d5UN%m?3VMT3t3aFqrWQfLL$0Dq}-*8y?(m%50(R z{EXR8`EKZ@xy@NKwK&HYVyFFn%OWo>;Nmve;G z49ar*ALM4d~v|a?V0-LBW@sBmhprJAo})nCJAft|ccoM=MIGpGx)% zw;3L&PoKuzSeB^~TtwwH1-V0+2{$HboVQL~?#tS3yNKwJd`k=Lm7H)bRX|2RYY}&wyG*_(k60B1J|0!cwfC6la5Ut|2EHVA)W%ghPqn8(2nG6PJFJB`VZ723SF4PAXu68E4nbp zEE3W%r98iAZ-WGIMudH?feWmA92Vt2kh+ybEJcLfR?W|mlkcc0|8$yBQloLkNGDyD zwZ_t8iD~p22Pc69n>x+=Vp#@R>{^gxjgXrEjnIj=jT)br^Weo(h6eSD^s+C?(anSQEDWi&p9J#m>a>I2%%+}ZtykGxCuOFu+{C) zOhq{iT#jaq>da<<#yLkmzGSAG4-#TB*ep{w~aND3*!7qw+92|-B9v#$dRF8)i^9^ z!({jSHQ+i*Lfs1uw!Z{CDV80qe~Sd|#e#=1W7{T+4Qf@kvY16`j7k=0StcjSMz~s$ zEYfzg4+&0=VPW?Se_CmXxh~Ru6~_kUoKqoYxeTfxz0mBCYY1)su;RxlmK49$^8KqQ zzvhr`V>7|I+3&pQAou$9DLC|4l#mvN8KsdgA+Gs3@n?{WcOe*GYRJl+e@lt*-(&p* zFA9)K$!}e0ex0NOpnE+{J{Z9xPG?@mUPZtVmNSybG=~#OiQ*PmMd)2YhEvVb)};Kn zF3NE~Z;Wv_ncM*u-t{P?@qm{MqD@5 zuBZ6KitEeQO1UEO{G9*MAL>sydxmI?jg-JdaJqA>d4MIf+ zf230hemq(|SOJ<)7v!ESEdjH{`zC1gO415*E%{S#heenyp%5uV**q~Ib|@?NIp6Tl zW6qk@Sqe)4WgQ!uSvfs=o)rLG;AUGSQulcwSdglE*4-=tIKXi#PNQ?W8ufDMJqDDU zajKOUFm1X)@q!MZNTDnQ$t5^0rd&-Ei<2hm?OZLp)(NH}box{Fum{w1k!VQ*rXh zZ^Wyg86R}io{qLJrWK}$chVJuT2u;fMZ$7ikec_VdUw*Jj#|4D%rj(2-ac$NFGcgg znqIkKCvo)>Q9%hOar1E|>0$YjWIv)YiSDFzn4pj2B>b_Z)bYfrqp#?W<7P3#a5}xg zaVe56@q+1R$wI#ilX>pVk(HzdCdSGz*Cn&jywB9bD!vq%!!{Ge^~0svcBx~=z`5)QL|WB_eY_mKNDB9Ecj0$_(Qwgc*v%h z^}P<;q(s+ynk;;FM%Eu&45~|CichQx+$3SpyFJ%jiLe|M=Y~u+1gs-J(u?UM!4o15i_q&`xo{_~ z;FLKLgiC4(Zs*4I!q06wTlY#DG6|W@3K^!L=ZnTQkLl@^v=x+uuFI3+2 z>UNx3V0c?idjq^b)7R-(FnO>OSHiGhaKN)2S3)ff>0dRd8zPLVO7+kh7(Kv)Ks99$ z90%?Hukq{=#=k1M$zmY zQXc+WT^!r5t6|;2@+)b1_Pd?9R2bKOGG-iG@40E*xNAC zCe7YRv~vrhyVbsg7J)IfWM!D}u_sd$>>f3ffcDwRYV$Wu%%B?HW&0t+>Y`iTKljnX z6@xAXWq0Jz=5Hz1=;{@9N!MDb8%`BPq0!y-W{X_m$jR4QC&u4z)-p*iQmJ9(d`8d- z{7FC2Ajz6e)HmMD1@>wgQfGyghVnT19O+eIwMj*X46P!`T&paHC!fa65n=qMcl@MA zo|0C{x3Db-SoQusCF!c<67V_?Z9%BDv44QojErk78+?wvsW!yPXyG$HPWF!~o9U6K zh8j^?FbLlX2H6`qKCG$tEZ)HHajHEBbVXeP|HTwbvp1B&0;;{*#eBq3Vurn1jo<*- z*Pfq4d2SS$1pfUe$0JXSq3DjSsexAt71OW=0Hc6;9J8KN0^q<_yU)ekOIO-Y3QM#HbZ z3T+Mj;3|nyw8myPA7U4ztlp1ke8HNlYny$KZc5PBDB-P|yeYsF|k?PMZ z>Q%6k2!)!?6Xuc!RdmAL%XH2P1Vk&ed6#^V(9HaEY1!Tzgyxi4wOd7K&%Pq=kZ(19 zI^|u?%?HT&eRfLY{v5K3uXHU8;@WbnI@&S^9Rv#m>+1rN7o86=R2GbSU;)ca9T~ST z0)xlUQWxG8dev=xG~38k=e}JMLuDO@++%SPb(4z_m+z5V;{2!8vnl zkoKE2s`lmwnj7pxK-;NP4=Gb1X-5p-sMRTP&|Yr%Hn}B+|LNcT#2;Ua zrRJ0MB=SLmevIsIPd`TtACBtJL|OrHAcqSDTxLSFwofH963ZYH(dcWi-yFmdrvo-l z>oh4cz_r<^`x~-z@`IY`RdSl4^scJfjID!G$+MQ=s|yRd{$^E3cD%@RREW|@O;<9j zVm7|$s!=t1+U4d&I^8U}d6wO7>9Lae{eMC-OhU^Xjy~64{Zp%kSxK z_~@}YD_mI6zhlmF`qn28Ca7eDJ0b5SFRZ1oPijG}F830!t0SlnX+ zhytQ?*)S92$BgttQ1m2N;Ge=yJOcodcJx8Y=g_STC`Ayho+lj$nmoTNpj&OV**N2Y zYXJaE1@w^=MpFiV6O<9j_s&3og3*9cexO)A{j7KJGr`4YuJ2DGy*PAyvM7Tq}t(dsM+Lr0utMXqH)sd+&i2H~tK9A!)x^xc=?YR^(|&Q2LVd9`9Tad7)4ymJaiV#co`EHpjlJ#%f= zE9~*(&Dbd9X?6&FA~j;c)O6`A@#8g!zT|28B;H`8zg&c+oscsS;4Wzi-TH6r7<#`R zR7@6cqs{!EImziw-5=~r?JdIcO4t?%2!*YFEdUQ-G2s?ZVHGzx7P>Du!+u28YP@(x zyr8x?moP9vY5n_e^&u9l^MMrltXHKFJlI541IqdykMydF?%5#JQk5&hl3w3~WI_^@ ziW|>3z_xw15F7wl&I*Kpo(-=_DegMzZ=90GXHEZ-fO@U~Jrdf_nc*edL7RXyLyl}* zK3F(Q;rxv?c5V@oH+dwK{&3McaMv57)hNnDq5^?3Ee##fqPPU>Ucz~X?WSn{^4i?` zE1tqrJ`#m3AM;UcqNNVdbPz>d?8G_>}$(Kp>ebTWh|&;ObpS0vjI8oT>9Y`p<+>&GP9E z>~+GV@z`im1r~lN&UAoRFM7Ht0fB|{HM6T225{93gee_v;hf<3J-CYg`(XYuf_N(f zznyriw#ySGD~YK2;j~d}NcaJ|oO(PW?3Y(Ey|_#j4pi>=>@qAiApMl}SC*W`+m zY@Gw5NMYLJL`*kdFpNf!F6`3KB;k(#5+&21en~Ms0(8_W05*xTIa_K+6;@3@9g>Mm@^w>wMXz+M{6izs51o>u7&D7IL!-aS%Ck_>6z z2pUyuaKtn{I3vD~$&1T`PT@n!>y)dV3)2KEGc{HoIwiF%<`U0?75{x@*bBNQ2HFSX zjOtMGg_@u21F9(1umnKDl5<5^&Jj8mvr3-|St%!0KzZ1A2a}vLI<{UyU6evab1ON^ zLeZCOrb(^IMcC`Sdd>rkTG4oG142!2e=M`kuHULCIn#xDFb+a&S$wFf>EE~ z4r82M%H@3w&wAqsHtFsN?EMf2N>r%78~jfXSMN}@f|>D2Zo0h6g91mXB>sSnJMSb$ zE7`wW6KD=E=W8ze*JJw2D(V6&qKK!tR)O$iIhR}7*A^||qt3TLXTd*iZ+SC_0yg$s zQhlAT`AAWdbPa3d%Nf}k9!+NJxkUd`gy$6>=Q)SY9fyu9TK(}S96pMgUq~Mm#Qy;Y Cu64lx diff --git a/RestroHub/src/main/resources/application-dev.properties b/RestroHub/src/main/resources/application-dev.properties index cf27b92..213cd48 100644 --- a/RestroHub/src/main/resources/application-dev.properties +++ b/RestroHub/src/main/resources/application-dev.properties @@ -2,8 +2,8 @@ # Datasource (PostgreSQL) # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=${JDBC_USERNAME:postgres} # Use environment variable JDBC_USERNAME or default to postgres -spring.datasource.password=${DB_PASSWORD:123} # Use environment variable DB_PASSWORD or default to 123 +spring.datasource.username=${DB_USERNAME:postgres} # Use environment variable DB_USERNAME or default to postgres +spring.datasource.password=${DB_PASSWORD:postgres} # Use environment variable DB_PASSWORD or default to 123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== # HikariCP @@ -49,6 +49,6 @@ spring.servlet.multipart.max-request-size=10MB # =============================== # Security / JWT # =============================== -security.jwt.secret= ${JWT_SECRET:mysecretkey} +security.jwt.secret= ${JWT_SECRET:test-secret-key-for-testing-purposes-only-256-bits} security.jwt.expiration=${JWT_EXPIRATION:3600000} # 1 hour in milliseconds security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:86400000} # 24 hours in milliseconds \ No newline at end of file diff --git a/RestroHub/src/main/resources/application-test.properties b/RestroHub/src/main/resources/application-test.properties index 1ea34d6..707f46a 100644 --- a/RestroHub/src/main/resources/application-test.properties +++ b/RestroHub/src/main/resources/application-test.properties @@ -21,8 +21,8 @@ spring.h2.console.enabled=true # =============================== # Security / JWT # =============================== -security.jwt.secret=${JWT_SECRET} -security.jwt.expiration=${JWT_EXPIRATION} +security.jwt.secret=test-secret-key-for-testing-purposes-only-256-bits +security.jwt.expiration=3600000 # =============================== # Logging diff --git a/RestroHub/src/main/resources/application.properties b/RestroHub/src/main/resources/application.properties index 37d634d..b13066f 100644 --- a/RestroHub/src/main/resources/application.properties +++ b/RestroHub/src/main/resources/application.properties @@ -10,8 +10,8 @@ build.version=1.0.0 # = DATABASE CONFIGURATION # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=postgres -spring.datasource.password=123 +spring.datasource.username=${DB_USERNAME:postgres} # Use environment variable DB_USERNAME or default to postgres +spring.datasource.password=${DB_PASSWORD:postgres} # Use environment variable DB_PASSWORD or default to 123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== @@ -85,7 +85,7 @@ api.license.url=https://www.apache.org/licenses/LICENSE-2.0 # =============================== # Security / JWT # =============================== -security.jwt.secret= ${JWT_SECRET:mysecretkey} +security.jwt.secret= ${JWT_SECRET:test-secret-key-for-testing-purposes-only-256-bits} security.jwt.expiration=${JWT_EXPIRATION:3600000} # 1 hour in milliseconds security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:86400000} # 24 hours in milliseconds From 3ed80acbf2d4fc249ddd4234007d851d1bff7d47 Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sat, 16 May 2026 21:22:57 +0530 Subject: [PATCH 05/10] refactor(payment): remove logs and verify ZXing dependencies and set default values --- RestroHub/src/main/resources/application-dev.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/RestroHub/src/main/resources/application-dev.properties b/RestroHub/src/main/resources/application-dev.properties index 213cd48..cabfd54 100644 --- a/RestroHub/src/main/resources/application-dev.properties +++ b/RestroHub/src/main/resources/application-dev.properties @@ -2,7 +2,7 @@ # Datasource (PostgreSQL) # =============================== spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=${DB_USERNAME:postgres} # Use environment variable DB_USERNAME or default to postgres +spring.datasource.username=${DB_USERNAME:postgres} # Use environment variable JDBC_USERNAME or default to postgres spring.datasource.password=${DB_PASSWORD:postgres} # Use environment variable DB_PASSWORD or default to 123 spring.datasource.driver-class-name=org.postgresql.Driver # =============================== From bd55a8bcd3ac87b421bd7e346a5c94ee98c4cccf Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sat, 16 May 2026 21:24:13 +0530 Subject: [PATCH 06/10] refactor(payment): remove logs and verify ZXing dependencies and set default values --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index ef2620a..6a3cab4 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ out/ *.env ### Can Remove This ### -personalnotes/ \ No newline at end of file +personalnotes/ +logs/ \ No newline at end of file From fe0c1bd7a9f258f186255af685f4ec801ffdff04 Mon Sep 17 00:00:00 2001 From: Souvik-D10 Date: Sun, 17 May 2026 09:49:36 +0530 Subject: [PATCH 07/10] refactor(order): linked the payment module --- .../com/restroly/qrmenu/branch/entity/Branch.java | 3 +++ .../qrmenu/order/controller/OrderController.java | 10 +++++++++- .../com/restroly/qrmenu/order/dto/OrderResponse.java | 7 +++++++ .../java/com/restroly/qrmenu/order/entity/Order.java | 4 ++++ .../com/restroly/qrmenu/order/mapper/OrderMapper.java | 1 + .../qrmenu/order/service/impl/OrderServiceImpl.java | 2 ++ .../qrmenu/payment/controller/PaymentController.java | 11 +++++++---- .../qrmenu/payment/entity/PaymentVerification.java | 4 +++- .../qrmenu/payment/service/PaymentService.java | 8 +++++--- .../qrmenu/payment/service/PaymentServiceImpl.java | 9 +++++---- .../payment/service/PaymentServiceImplTest.java | 7 ++++--- 11 files changed, 50 insertions(+), 16 deletions(-) diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/branch/entity/Branch.java b/RestroHub/src/main/java/com/restroly/qrmenu/branch/entity/Branch.java index eeee800..b5016af 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/branch/entity/Branch.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/branch/entity/Branch.java @@ -75,6 +75,9 @@ public class Branch { @Builder.Default private List tables = new ArrayList<>(); + @Column(name = "branch_upi_id") + private String branchUpiId; + @PrePersist protected void onCreate() { createdDate = LocalDateTime.now(); diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/order/controller/OrderController.java b/RestroHub/src/main/java/com/restroly/qrmenu/order/controller/OrderController.java index 5792a30..4da767f 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/order/controller/OrderController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/order/controller/OrderController.java @@ -19,6 +19,7 @@ import com.restroly.qrmenu.order.dto.OrderResponse; import com.restroly.qrmenu.order.dto.UpdateOrderStatusRequest; import com.restroly.qrmenu.order.service.OrderService; +import com.restroly.qrmenu.payment.service.PaymentService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -29,14 +30,21 @@ @RequestMapping(SECURE_API_VERSION+"/orders") @CrossOrigin(origins = "*") public class OrderController { - + @Autowired + private final PaymentService paymentService = null; @Autowired private final OrderService orderService = null; + //private final OrderService orderService; @PostMapping public ResponseEntity createOrder(@Valid @RequestBody CreateOrderRequest request) { OrderResponse response = orderService.createOrder(request); + //Order tacking improvement needed + //Response is used to carry the UPI Id out of instead of calling it from database again hence reducing the number of calls to database and improving the performance of the application + String paymentUrl = paymentService.generatePaymentLink(response.getTotalAmount(), response.getOrderId(), response.getPaymentLink()); + //Genarated payment link is stored in response object to send it to client and use it for payment + response.setPaymentLink(paymentUrl); return ResponseEntity.status(HttpStatus.CREATED).body(response); } diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/order/dto/OrderResponse.java b/RestroHub/src/main/java/com/restroly/qrmenu/order/dto/OrderResponse.java index de82718..2719a33 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/order/dto/OrderResponse.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/order/dto/OrderResponse.java @@ -26,6 +26,7 @@ public class OrderResponse { private String customerPhone; private String specialInstructions; private BigDecimal totalAmount; + private String paymentLink; private OrderStatus status; private LocalDateTime createdAt; private List items; @@ -101,5 +102,11 @@ public List getItems() { public void setItems(List items) { this.items = items; } + public String getPaymentLink() { + return paymentLink; + } + public void setPaymentLink(String paymentLink) { + this.paymentLink = paymentLink; + } } \ No newline at end of file diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/order/entity/Order.java b/RestroHub/src/main/java/com/restroly/qrmenu/order/entity/Order.java index 607b75e..d3b13e9 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/order/entity/Order.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/order/entity/Order.java @@ -57,6 +57,10 @@ public class Order { @Builder.Default private List orderItems = new ArrayList<>(); + //Not storing in database only for better payment utility + @Transient + private String paymentId; + @PrePersist protected void onCreate() { createdAt = LocalDateTime.now(); diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/order/mapper/OrderMapper.java b/RestroHub/src/main/java/com/restroly/qrmenu/order/mapper/OrderMapper.java index d1c3b4e..cd7ca4b 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/order/mapper/OrderMapper.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/order/mapper/OrderMapper.java @@ -30,6 +30,7 @@ public OrderResponse toResponse(Order order) { .status(order.getStatus()) .createdAt(order.getCreatedAt()) .items(toItemResponseList(order.getOrderItems())) + .paymentLink(order.getPaymentId()) .build(); } diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/order/service/impl/OrderServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/order/service/impl/OrderServiceImpl.java index a8560cb..ea5f943 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/order/service/impl/OrderServiceImpl.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/order/service/impl/OrderServiceImpl.java @@ -78,6 +78,8 @@ public OrderResponse createOrder(CreateOrderRequest request) { // Send notification to admin notificationService.notifyNewOrder(savedOrder); + //Storing paymentId in order for better utility + savedOrder.setPaymentId(branch.getBranchUpiId()); return orderMapper.toResponse(savedOrder); } diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java index 1031c6d..59a75e1 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/controller/PaymentController.java @@ -3,9 +3,10 @@ //Preferred : only use for testing purpose, as this module is a plug-and-play type and should not have any external dependencies. --- IGNORE --- // Written By Contributor who formally made this module --- IGNORE --- +// REMOVE ME IN PRODUCTION: This controller is only for testing the payment module and should not be used in production. The payment module is designed to be plug-and-play without any external dependencies, so this controller is not intended for production use. - - +//-------------- THE CONTROLLER IS NOT TO BE USED IN PRODUCTION, +// IT IS ONLY FOR TESTING PURPOSES. THE MODULE IS DESIGNED TO BE PLUG-AND-PLAY WITHOUT ANY EXTERNAL DEPENDENCIES. ------------------ package com.restroly.qrmenu.payment.controller; import com.restroly.qrmenu.payment.service.PaymentService; @@ -17,6 +18,8 @@ import org.springframework.web.bind.annotation.*; import static com.restroly.qrmenu.common.util.ApiConstants.*; + +import java.math.BigDecimal; @RestController @RequestMapping(PUBLIC_API_VERSION+"/payments") @RequiredArgsConstructor @@ -26,7 +29,7 @@ public class PaymentController { @GetMapping("/link") public ResponseEntity generatePaymentLink( - @RequestParam double amount, + @RequestParam BigDecimal amount, @RequestParam(required = false) Long orderId, @RequestParam String upiId) { String link = paymentService.generatePaymentLink(amount, orderId, upiId); @@ -35,7 +38,7 @@ public ResponseEntity generatePaymentLink( @GetMapping("/qr") public ResponseEntity generateUPIQR( - @RequestParam double amount, + @RequestParam BigDecimal amount, @RequestParam String upiId, @RequestParam(required = false, defaultValue = "RestroHub Payment") String description) { Resource qrResource = paymentService.generateUPIQR(amount, upiId, description); diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java index 5323bdf..e37a9f8 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/entity/PaymentVerification.java @@ -1,5 +1,7 @@ package com.restroly.qrmenu.payment.entity; +import java.math.BigDecimal; + import jakarta.persistence.*; import lombok.AllArgsConstructor; import lombok.Builder; @@ -27,7 +29,7 @@ public class PaymentVerification { private Long orderId; @Column(name = "amount") - private double amount; + private BigDecimal amount; @Enumerated(EnumType.STRING) @Column(name = "verified", nullable = false) diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java index 9ec7963..c7a1754 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentService.java @@ -1,14 +1,16 @@ package com.restroly.qrmenu.payment.service; +import java.math.BigDecimal; + import org.springframework.core.io.Resource; import jakarta.transaction.Transactional; public interface PaymentService { @Transactional - String newPayment(Long orderId, double amount); - String generatePaymentLink(double amount, Long orderId, String upiId); - Resource generateUPIQR(double amount, String upiId, String description); + String newPayment(Long orderId, BigDecimal amount); + String generatePaymentLink(BigDecimal amount, Long orderId, String upiId); + Resource generateUPIQR(BigDecimal amount, String upiId, String description); @Transactional void markPaymentAsVerified(String paymentId, String transactionId); @Transactional diff --git a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java index c7c19f2..ff74797 100644 --- a/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java +++ b/RestroHub/src/main/java/com/restroly/qrmenu/payment/service/PaymentServiceImpl.java @@ -10,6 +10,7 @@ import com.restroly.qrmenu.payment.repository.PaymentVerificationRepository; import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.math.BigDecimal; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Locale; @@ -33,7 +34,7 @@ public class PaymentServiceImpl implements PaymentService { private final PaymentVerificationRepository verificationRepository; - public String newPayment(Long orderId, double amount) { + public String newPayment(Long orderId, BigDecimal amount) { log.info("Creating new payment record with manual verification flag set to false"); PaymentVerification entity = PaymentVerification.builder() .paymentId("PAY" + orderId) @@ -46,7 +47,7 @@ public String newPayment(Long orderId, double amount) { } @Override - public String generatePaymentLink(double amount, Long orderId, String upiId) { + public String generatePaymentLink(BigDecimal amount, Long orderId, String upiId) { log.info("Generating raw UPI payment link for orderId: {}, amount: {}, upiId: {}", orderId, amount, upiId); String description = (orderId != null && orderId > 0) @@ -58,7 +59,7 @@ public String generatePaymentLink(double amount, Long orderId, String upiId) { } @Override - public Resource generateUPIQR(double amount, String upiId, String description) { + public Resource generateUPIQR(BigDecimal amount, String upiId, String description) { log.info("Generating raw UPI QR for amount: {}, upiId: {}, description: {}", amount, upiId, description); String desc = (description != null && !description.isBlank()) ? description : "RestroHub payment"; @@ -83,7 +84,7 @@ public Resource generateUPIQR(double amount, String upiId, String description) { private String buildUri( String baseUrl, - double amount, + BigDecimal amount, Long orderId, String upiId, String description) { diff --git a/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java b/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java index a70c700..2067590 100644 --- a/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java +++ b/RestroHub/src/test/java/com/restroly/qrmenu/payment/service/PaymentServiceImplTest.java @@ -13,6 +13,7 @@ import org.springframework.test.util.ReflectionTestUtils; import java.io.IOException; +import java.math.BigDecimal; import java.util.Optional; import static org.junit.jupiter.api.Assertions.*; @@ -38,7 +39,7 @@ void setUp() { void newPayment_ShouldSaveAndReturnPaymentId() { // Given Long orderId = 123L; - double amount = 500.00; + BigDecimal amount = new BigDecimal("500.00"); // When String resultId = paymentService.newPayment(orderId, amount); @@ -52,7 +53,7 @@ void newPayment_ShouldSaveAndReturnPaymentId() { @Test void generatePaymentLink_ShouldReturnCorrectlyFormattedUpiString() { // Given - double amount = 150.50; + BigDecimal amount = new BigDecimal("150.50"); Long orderId = 456L; String upiId = "admin@bank"; @@ -148,7 +149,7 @@ void markPaymentAsCancelled_ShouldUpdateStatusToCancelled() { @Test void generateUPIQR_ShouldReturnResource() throws IOException { // Given - double amount = 100.0; + BigDecimal amount = new BigDecimal("100.00"); String upiId = "test@bank"; String description = "Test QR"; From b766d51abd45cb760cac6976833529552bc82c49 Mon Sep 17 00:00:00 2001 From: SouvikDX Date: Mon, 18 May 2026 23:19:04 +0530 Subject: [PATCH 08/10] Remove specific Java installation path from gradle.properties --- RestroHub/gradle.properties | 1 - 1 file changed, 1 deletion(-) diff --git a/RestroHub/gradle.properties b/RestroHub/gradle.properties index 3c92298..b93d106 100644 --- a/RestroHub/gradle.properties +++ b/RestroHub/gradle.properties @@ -1,2 +1 @@ org.gradle.java.installations.auto-download=true -org.gradle.java.installations.paths=C:/Users/svkcc/.vscode/extensions/redhat.java-1.45.0-win32-x64/jre/21.0.8-win32-x86_64 From 8c1eec356a127fbb280fc2169734351edea2219c Mon Sep 17 00:00:00 2001 From: SouvikDX Date: Mon, 18 May 2026 23:24:54 +0530 Subject: [PATCH 09/10] Change datasource properties for production environment Updated datasource configuration to use environment variables for production. --- RestroHub/src/main/resources/application-prod.properties | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/RestroHub/src/main/resources/application-prod.properties b/RestroHub/src/main/resources/application-prod.properties index 20a475f..17d3353 100644 --- a/RestroHub/src/main/resources/application-prod.properties +++ b/RestroHub/src/main/resources/application-prod.properties @@ -1,9 +1,9 @@ # =============================== # Datasource (PostgreSQL) # =============================== -spring.datasource.url=jdbc:postgresql://localhost:5432/RestroHub_DB -spring.datasource.username=${JDBC_USERNAME:postgres} # Use environment variable JDBC_USERNAME or default to postgres -spring.datasource.password=${DB_PASSWORD:123} # Use environment variable DB_PASSWORD or default to 123 +spring.datasource.url=${DATABASE_URL} +spring.datasource.username=${DATABASE_USERNAME} # Use environment variable JDBC_USERNAME +spring.datasource.password=${DATABASE_PASSWORD} # Use environment variable DB_PASSWORD spring.datasource.driver-class-name=org.postgresql.Driver # =============================== From 5440e1b6246f904fe7d4828110805131d2c437b7 Mon Sep 17 00:00:00 2001 From: SouvikDX Date: Mon, 18 May 2026 23:26:34 +0530 Subject: [PATCH 10/10] Update payee name from RestroHub to Restroly --- RestroHub/src/main/resources/application.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RestroHub/src/main/resources/application.properties b/RestroHub/src/main/resources/application.properties index ab502fc..94bb8c2 100644 --- a/RestroHub/src/main/resources/application.properties +++ b/RestroHub/src/main/resources/application.properties @@ -95,7 +95,7 @@ security.jwt.refresh-expiration=${JWT_REFRESH_EXPIRATION:86400000} # 24 hours in # razorpay.key-id= # razorpay.key-secret= #--- UPI Link generation used as instructed --- -payment.payee.name=RestroHub +payment.payee.name=Restroly # =============================== # Security / CORS @@ -116,4 +116,4 @@ management.info.env.enabled=true # Maximum size per file spring.servlet.multipart.max-file-size=10MB # Maximum total request size (all files + form data) -spring.servlet.multipart.max-request-size=10MB \ No newline at end of file +spring.servlet.multipart.max-request-size=10MB