From 5155cf3dffa5a50775e7fc254049cf25c3d455e6 Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Wed, 26 Nov 2025 18:22:04 -0300 Subject: [PATCH 1/6] chore(dependencies): add stripe-java --- pom.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/pom.xml b/pom.xml index d1ecfb8..87f00ce 100644 --- a/pom.xml +++ b/pom.xml @@ -119,6 +119,11 @@ 5.20.0 test + + com.stripe + stripe-java + 31.0.0 + From 8ff970d89f8c453fef9ae20d3b526efd8cbced34 Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Thu, 27 Nov 2025 15:34:43 -0300 Subject: [PATCH 2/6] feat(payment): implement sponsorship checkout flow using Stripe MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit introduces the full Stripe integration to handle user sponsorships: • Adds PaymentController and StripeService. • Defines DTOs for the payment endpoints. • Implements the checkout session creation and session verification endpoints. • Adds the Stripe webhook handler for 'checkout.session.completed' events. • Updates security configuration and environment properties for Stripe integration. --- .env.example | 7 +- .../controller/payment/PaymentController.java | 99 ++++++++++++++++ .../dto/payment/SponsorshipREQ.java | 17 +++ .../dto/payment/SponsorshipRES.java | 13 ++ .../dto/payment/SponsorshipStatusRES.java | 9 ++ .../implementation/user/UserServiceImpl.java | 6 + .../application/payment/StripeService.java | 111 ++++++++++++++++++ .../com/notehub/domain/user/UserService.java | 2 + .../infra/exception/ControllerAdvice.java | 8 ++ .../infra/exception/CustomExceptions.java | 18 +++ .../infra/security/SecurityConfig.java | 4 +- .../infra/security/SecurityFilter.java | 4 + src/main/resources/application-dev.properties | 4 + .../resources/application-prod.properties | 4 + .../resources/application-test.properties | 4 + 15 files changed, 307 insertions(+), 3 deletions(-) create mode 100644 src/main/java/br/com/notehub/application/controller/payment/PaymentController.java create mode 100644 src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java create mode 100644 src/main/java/br/com/notehub/application/dto/payment/SponsorshipRES.java create mode 100644 src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java create mode 100644 src/main/java/br/com/notehub/application/payment/StripeService.java diff --git a/.env.example b/.env.example index a7480ed..b14d614 100644 --- a/.env.example +++ b/.env.example @@ -7,8 +7,11 @@ PGPASSWORD=root SERVER=http://localhost:8080 CLIENT=http://localhost:3000 SECRET=vasco-da-gama -GHCI=ID -GHCS=SECRET +GHCI=GHCI +GHCS=GHCS +SWK=SWK +SPK=SPK +SSK=SSK RABBITMQ_USER=user RABBITMQ_PASSWORD=root RABBITMQ_ADDRESSES=amqp://user:root@rabbitmq:5672 diff --git a/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java new file mode 100644 index 0000000..c0556f4 --- /dev/null +++ b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java @@ -0,0 +1,99 @@ +package br.com.notehub.application.controller.payment; + +import br.com.notehub.application.dto.payment.SponsorshipREQ; +import br.com.notehub.application.dto.payment.SponsorshipRES; +import br.com.notehub.application.dto.payment.SponsorshipStatusRES; +import br.com.notehub.application.payment.StripeService; +import com.auth0.jwt.JWT; +import com.stripe.exception.SignatureVerificationException; +import com.stripe.model.Event; +import com.stripe.net.Webhook; +import io.swagger.v3.oas.annotations.Hidden; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@CrossOrigin(origins = {"https://notehub.com.br"}) +@RequestMapping("/payment") +@Tag(name = "Payment Controller", description = "Endpoints for managing payments") +@RequiredArgsConstructor +public class PaymentController { + + @Value("${payment.stripe.webhook.key}") + private String secret; + + private final StripeService stripeService; + + private String getSubject(String bearerToken) { + if (bearerToken == null) return null; + return JWT.decode(bearerToken.replace("Bearer ", "")).getSubject(); + } + + @Operation( + summary = "Initiates the Stripe sponsorship checkout process", + description = "Creates a Stripe Checkout Session for a sponsorship with the specified currency and amount." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Checkout session created successfully."), + @ApiResponse(responseCode = "400", description = "Invalid input data.", content = @Content(mediaType = "application/json")), + @ApiResponse(responseCode = "403", description = "Invalid token."), + @ApiResponse(responseCode = "500", description = "Internal server error.", content = @Content(examples = {})) + }) + @PostMapping("/stripe/sponsorship") + public ResponseEntity sponsorshipCheckout( + @Parameter(hidden = true) @RequestHeader("Authorization") String accessToken, + @Valid @RequestBody SponsorshipREQ dto + ) { + String idFromToken = getSubject(accessToken); + SponsorshipRES res = stripeService.sponsorshipCheckout(idFromToken, dto.currency(), dto.amount()); + return ResponseEntity.status(HttpStatus.OK).body(res); + } + + @Operation( + summary = "Verifies the status of a Stripe sponsorship checkout session", + description = "Queries Stripe to determine if the payment for the specified session has been successfully completed." + ) + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Session status returned successfully."), + @ApiResponse(responseCode = "403", description = "Invalid token."), + @ApiResponse(responseCode = "500", description = "Internal server error.", content = @Content(examples = {})) + }) + @GetMapping("/stripe/sponsorship/verify/{sessionId}") + public ResponseEntity verifySession( + @PathVariable String sessionId, + @Parameter(hidden = true) @RequestHeader("Authorization") String accessToken + ) { + String uIdFromToken = getSubject(accessToken); + return ResponseEntity.status(HttpStatus.OK).body(stripeService.verifySession(sessionId, uIdFromToken)); + } + + @Hidden + @PostMapping("/stripe/sponsorship/webhook") + public ResponseEntity handleWebhook( + @RequestBody String payload, + @RequestHeader("Stripe-Signature") String sigHeader + ) { + Event event; + try { + event = Webhook.constructEvent(payload, sigHeader, secret); + } catch (SignatureVerificationException e) { + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + if ("checkout.session.completed".equals(event.getType())) { + stripeService.handleCheckoutSessionCompleted(event); + return ResponseEntity.status(HttpStatus.OK).build(); + } + return ResponseEntity.status(HttpStatus.BAD_REQUEST).build(); + } + +} \ No newline at end of file diff --git a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java new file mode 100644 index 0000000..b4e0291 --- /dev/null +++ b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java @@ -0,0 +1,17 @@ +package br.com.notehub.application.dto.payment; + +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; + +public record SponsorshipREQ( + + @NotBlank(message = "Não pode ser vazia.") + String currency, + + @NotNull(message = "Não pode ser nulo.") + @Min(value = 1, message = "Deve ser maior que 0.") + Long amount + +) { +} \ No newline at end of file diff --git a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipRES.java b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipRES.java new file mode 100644 index 0000000..8c19cef --- /dev/null +++ b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipRES.java @@ -0,0 +1,13 @@ +package br.com.notehub.application.dto.payment; + +import lombok.Builder; + +@Builder +public record SponsorshipRES( + String status, + String message, + String sessionId, + String sessionUrl, + String uId +) { +} \ No newline at end of file diff --git a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java new file mode 100644 index 0000000..59ad206 --- /dev/null +++ b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java @@ -0,0 +1,9 @@ +package br.com.notehub.application.dto.payment; + +public record SponsorshipStatusRES( + String sessionId, + String paymentStatus, + String status, + Long amountTotal +) { +} \ No newline at end of file diff --git a/src/main/java/br/com/notehub/application/implementation/user/UserServiceImpl.java b/src/main/java/br/com/notehub/application/implementation/user/UserServiceImpl.java index e9e47a2..9b44b73 100644 --- a/src/main/java/br/com/notehub/application/implementation/user/UserServiceImpl.java +++ b/src/main/java/br/com/notehub/application/implementation/user/UserServiceImpl.java @@ -142,6 +142,12 @@ public void activate(UUID idFromToken) { changeField(idFromToken, "active", User::isActive, user -> user.setActive(true)); } + @Transactional + @Override + public void promote(UUID idFromToken) { + changeField(idFromToken, "sponsor", User::isSponsor, user -> user.setSponsor(true)); + } + @Transactional @Override public void changePassword(String email, String newPassword) { diff --git a/src/main/java/br/com/notehub/application/payment/StripeService.java b/src/main/java/br/com/notehub/application/payment/StripeService.java new file mode 100644 index 0000000..f0a8245 --- /dev/null +++ b/src/main/java/br/com/notehub/application/payment/StripeService.java @@ -0,0 +1,111 @@ +package br.com.notehub.application.payment; + +import br.com.notehub.application.dto.payment.SponsorshipRES; +import br.com.notehub.application.dto.payment.SponsorshipStatusRES; +import br.com.notehub.domain.user.UserService; +import br.com.notehub.infra.exception.CustomExceptions; +import com.stripe.Stripe; +import com.stripe.exception.StripeException; +import com.stripe.model.Event; +import com.stripe.model.checkout.Session; +import com.stripe.param.checkout.SessionCreateParams; +import jakarta.annotation.Nullable; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.security.access.AccessDeniedException; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Objects; +import java.util.UUID; + +@Service +@RequiredArgsConstructor +public class StripeService { + + @Value("${payment.stripe.secret.key}") + private String secret; + + @Value("${api.client.host}") + private String client; + + private final UserService userService; + + private static final String SPONSORSHIP_NAME = "Patrocínio"; + private static final Long SPONSORSHIP_QUANTITY = 1L; + + private void validateAccess(@Nullable String idFromToken, String idFromSession) { + if (Objects.equals(idFromToken, idFromSession)) return; + throw new AccessDeniedException("Usuário sem permissão."); + } + + public SponsorshipRES sponsorshipCheckout(String idFromToken, String currency, Long amount) { + + Stripe.apiKey = secret; + + try { + var productData = SessionCreateParams.LineItem.PriceData.ProductData.builder() + .setName(SPONSORSHIP_NAME) + .build(); + + var priceData = SessionCreateParams.LineItem.PriceData.builder() + .setCurrency(currency) + .setUnitAmount(amount) + .setProductData(productData) + .build(); + + var lineItem = SessionCreateParams.LineItem.builder() + .setQuantity(SPONSORSHIP_QUANTITY) + .setPriceData(priceData) + .build(); + + var params = SessionCreateParams.builder() + .setMode(SessionCreateParams.Mode.PAYMENT) + .setSuccessUrl(String.format("%s/sponsorship/success?session_id={CHECKOUT_SESSION_ID}", client)) + .setCancelUrl(String.format("%s/sponsorship/cancel", client)) + .addLineItem(lineItem) + .putMetadata("purchaseType", "sponsorship") + .putMetadata("uId", idFromToken) + .build(); + + Session session = Session.create(params); + return new SponsorshipRES( + "SUCCESS", + "Payment session created", + session.getId(), + session.getUrl(), + idFromToken + ); + + } catch (StripeException e) { + throw new CustomExceptions.CustomStripeException(e); + } + } + + public SponsorshipStatusRES verifySession(String sessionId, String uIdFromToken) { + try { + Stripe.apiKey = secret; + Session session = Session.retrieve(sessionId); + String uIdFromSession = session.getMetadata().get("uId"); + validateAccess(uIdFromToken, uIdFromSession); + return new SponsorshipStatusRES( + session.getId(), + session.getPaymentStatus(), + session.getStatus(), + session.getAmountTotal() + ); + } catch (StripeException e) { + throw new CustomExceptions.CustomStripeException(e); + } + } + + @Transactional + public void handleCheckoutSessionCompleted(Event event) { + Session session = (Session) event.getDataObjectDeserializer() + .getObject() + .orElseThrow(() -> new RuntimeException("Failed to deserialize session")); + UUID uId = UUID.fromString(session.getMetadata().get("uId")); + if ("paid".equals(session.getPaymentStatus())) userService.promote(uId); + } + +} \ No newline at end of file diff --git a/src/main/java/br/com/notehub/domain/user/UserService.java b/src/main/java/br/com/notehub/domain/user/UserService.java index ccf7cd0..0c3552e 100644 --- a/src/main/java/br/com/notehub/domain/user/UserService.java +++ b/src/main/java/br/com/notehub/domain/user/UserService.java @@ -17,6 +17,8 @@ public interface UserService { void activate(UUID idFromToken); + void promote(UUID idFromToken); + void changePassword(String email, String newPassword); void changeEmail(String oldEmail, String newEmail); diff --git a/src/main/java/br/com/notehub/infra/exception/ControllerAdvice.java b/src/main/java/br/com/notehub/infra/exception/ControllerAdvice.java index ff62291..bcffe29 100644 --- a/src/main/java/br/com/notehub/infra/exception/ControllerAdvice.java +++ b/src/main/java/br/com/notehub/infra/exception/ControllerAdvice.java @@ -3,6 +3,7 @@ import com.auth0.jwt.exceptions.JWTCreationException; import com.auth0.jwt.exceptions.JWTDecodeException; import com.auth0.jwt.exceptions.TokenExpiredException; +import com.stripe.exception.StripeException; import jakarta.persistence.EntityExistsException; import jakarta.persistence.EntityNotFoundException; import org.springframework.dao.DataIntegrityViolationException; @@ -39,6 +40,13 @@ private ResponseEntity> handleMethdArgumentNotValidExceptio return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); } + @ExceptionHandler(CustomExceptions.CustomStripeException.class) + private ResponseEntity> handleStripeException(CustomExceptions.CustomStripeException ex) { + List errors = new ArrayList<>(); + errors.add(new FieldError("Payment", "Payment", ex.getMessage())); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(errors.stream().map(CustomResponse::new).toList()); + } + @ExceptionHandler(CustomExceptions.InvalidSecretException.class) private ResponseEntity> handleInvalidSecretException(CustomExceptions.InvalidSecretException ex) { List errors = new ArrayList<>(); diff --git a/src/main/java/br/com/notehub/infra/exception/CustomExceptions.java b/src/main/java/br/com/notehub/infra/exception/CustomExceptions.java index 4f6b636..ea79282 100644 --- a/src/main/java/br/com/notehub/infra/exception/CustomExceptions.java +++ b/src/main/java/br/com/notehub/infra/exception/CustomExceptions.java @@ -1,5 +1,7 @@ package br.com.notehub.infra.exception; +import com.stripe.exception.StripeException; + import java.util.UUID; public class CustomExceptions { @@ -10,6 +12,22 @@ public BusinessException(String message) { } } + public static class CustomStripeException extends BusinessException { + + private static String customizeStripeExceptionMessage(StripeException e) { + String code = e.getCode(); + String message = e.getMessage(); + if ("amount_too_small".equals(code)) return "Minimum value not reached."; + if (message.contains("currency")) return "Currency not available."; + return "Error while processing payment. Please, try again later or contact support."; + } + + public CustomStripeException(StripeException e) { + super(customizeStripeExceptionMessage(e)); + } + + } + public static class InvalidSecretException extends BusinessException { public InvalidSecretException() { super("Segredo incorreto."); diff --git a/src/main/java/br/com/notehub/infra/security/SecurityConfig.java b/src/main/java/br/com/notehub/infra/security/SecurityConfig.java index e291244..4094460 100644 --- a/src/main/java/br/com/notehub/infra/security/SecurityConfig.java +++ b/src/main/java/br/com/notehub/infra/security/SecurityConfig.java @@ -27,7 +27,8 @@ public class SecurityConfig { }; private static final String[] PUBLIC_POST_ROUTES = { - "/api/v1/users/register", "/api/v1/auth/**" + "/api/v1/users/register", "/api/v1/auth/**", + "/payment/stripe/sponsorship/webhook" }; private static final String[] PUBLIC_DELETE_ROUTES = { @@ -36,6 +37,7 @@ public class SecurityConfig { private static final String[] PUBLIC_GET_ROUTES = { "/", "/docs", + "/payment/stripe/sponsorship/verify/**", "/api/v1/auth/refresh", "/api/v1/users", "/api/v1/users/**", "/api/v1/notes", "/api/v1/notes/**", diff --git a/src/main/java/br/com/notehub/infra/security/SecurityFilter.java b/src/main/java/br/com/notehub/infra/security/SecurityFilter.java index 22ab9d0..2afcdb8 100644 --- a/src/main/java/br/com/notehub/infra/security/SecurityFilter.java +++ b/src/main/java/br/com/notehub/infra/security/SecurityFilter.java @@ -48,6 +48,10 @@ private String getToken(HttpServletRequest request) { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String accessToken = getToken(request); + if (request.getServletPath().contains("/payment/stripe/sponsorship/webhook")) { + filterChain.doFilter(request, response); + return; + } if (accessToken != null) { try { UUID id = UUID.fromString(service.validateToken(accessToken)); diff --git a/src/main/resources/application-dev.properties b/src/main/resources/application-dev.properties index d959776..7e9b7a2 100644 --- a/src/main/resources/application-dev.properties +++ b/src/main/resources/application-dev.properties @@ -25,6 +25,10 @@ api.server.security.token.secret=${SECRET} oauth.github.client.id=${GHCI} oauth.github.client.secret=${GHCS} +payment.stripe.webhook.key=${SWK} +payment.stripe.publishable.key=${SPK} +payment.stripe.secret.key=${SSK} + spring.rabbitmq.addresses=${RABBITMQ_ADDRESSES} broker.queue.activation.name=default.activation broker.queue.secret.name=default.secret diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 6a22878..65b5fb4 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -13,6 +13,10 @@ api.server.security.token.secret=${SECRET} oauth.github.client.id=${GHCI} oauth.github.client.secret=${GHCS} +payment.stripe.webhook.key=${SWK} +payment.stripe.publishable.key=${SPK} +payment.stripe.secret.key=${SSK} + spring.rabbitmq.addresses=${RABBITMQ_ADDRESSES} broker.queue.activation.name=default.activation broker.queue.secret.name=default.secret diff --git a/src/main/resources/application-test.properties b/src/main/resources/application-test.properties index f33d2d0..7f3a3da 100644 --- a/src/main/resources/application-test.properties +++ b/src/main/resources/application-test.properties @@ -25,6 +25,10 @@ api.server.security.token.secret=vasco-da-gama oauth.github.client.id=1898 oauth.github.client.secret=98 +payment.stripe.webhook.key=whsec +payment.stripe.publishable.key=pk +payment.stripe.secret.key=sk + spring.mail.friendly.name=NoteHub spring.mail.host=mailhog spring.mail.port=1025 From 3ead5f87f4835eb0559f372521bb71d27d723cc5 Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Fri, 12 Dec 2025 10:29:18 -0300 Subject: [PATCH 3/6] fix(security): allow Stripe webhook endpoint to bypass authentication --- src/main/java/br/com/notehub/infra/security/SecurityFilter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/br/com/notehub/infra/security/SecurityFilter.java b/src/main/java/br/com/notehub/infra/security/SecurityFilter.java index 2afcdb8..bc77db7 100644 --- a/src/main/java/br/com/notehub/infra/security/SecurityFilter.java +++ b/src/main/java/br/com/notehub/infra/security/SecurityFilter.java @@ -48,7 +48,7 @@ private String getToken(HttpServletRequest request) { @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String accessToken = getToken(request); - if (request.getServletPath().contains("/payment/stripe/sponsorship/webhook")) { + if (request.getServletPath().contains("/api/v1/payment/stripe/sponsorship/webhook")) { filterChain.doFilter(request, response); return; } From 6940b2420f79c8efe614c6e1885529c80c257f39 Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Fri, 12 Dec 2025 10:31:27 -0300 Subject: [PATCH 4/6] refactor(payment): standardize routes under /api/v1/payment base path --- .../application/controller/payment/PaymentController.java | 2 +- .../java/br/com/notehub/infra/security/SecurityConfig.java | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java index c0556f4..20289fc 100644 --- a/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java +++ b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java @@ -24,7 +24,7 @@ @RestController @CrossOrigin(origins = {"https://notehub.com.br"}) -@RequestMapping("/payment") +@RequestMapping("/api/v1/payment") @Tag(name = "Payment Controller", description = "Endpoints for managing payments") @RequiredArgsConstructor public class PaymentController { diff --git a/src/main/java/br/com/notehub/infra/security/SecurityConfig.java b/src/main/java/br/com/notehub/infra/security/SecurityConfig.java index 4094460..f4f4f75 100644 --- a/src/main/java/br/com/notehub/infra/security/SecurityConfig.java +++ b/src/main/java/br/com/notehub/infra/security/SecurityConfig.java @@ -28,7 +28,7 @@ public class SecurityConfig { private static final String[] PUBLIC_POST_ROUTES = { "/api/v1/users/register", "/api/v1/auth/**", - "/payment/stripe/sponsorship/webhook" + "/api/v1/payment/stripe/sponsorship/webhook" }; private static final String[] PUBLIC_DELETE_ROUTES = { @@ -37,13 +37,13 @@ public class SecurityConfig { private static final String[] PUBLIC_GET_ROUTES = { "/", "/docs", - "/payment/stripe/sponsorship/verify/**", "/api/v1/auth/refresh", "/api/v1/users", "/api/v1/users/**", "/api/v1/notes", "/api/v1/notes/**", "/api/v1/flames", "/api/v1/flames/**", "/api/v1/replies", "/api/v1/replies/**", - "/api/v1/mail", "/api/v1/mail/**" + "/api/v1/mail", "/api/v1/mail/**", + "/api/v1/payment", "/api/v1/payment/**", }; private static final String[] PRIVATE_GET_ROUTES = { From 29951e28a3ec24bcfe0fa282a1fb077e01bd9095 Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Fri, 12 Dec 2025 10:32:45 -0300 Subject: [PATCH 5/6] feat(payment): add locale support to sponsorship checkout --- .../controller/payment/PaymentController.java | 2 +- .../notehub/application/dto/payment/SponsorshipREQ.java | 3 +++ .../application/dto/payment/SponsorshipStatusRES.java | 2 ++ .../com/notehub/application/payment/StripeService.java | 9 +++++++-- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java index 20289fc..d1c00ed 100644 --- a/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java +++ b/src/main/java/br/com/notehub/application/controller/payment/PaymentController.java @@ -55,7 +55,7 @@ public ResponseEntity sponsorshipCheckout( @Valid @RequestBody SponsorshipREQ dto ) { String idFromToken = getSubject(accessToken); - SponsorshipRES res = stripeService.sponsorshipCheckout(idFromToken, dto.currency(), dto.amount()); + SponsorshipRES res = stripeService.sponsorshipCheckout(idFromToken, dto.locale(), dto.currency(), dto.amount()); return ResponseEntity.status(HttpStatus.OK).body(res); } diff --git a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java index b4e0291..062ba57 100644 --- a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java +++ b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipREQ.java @@ -6,6 +6,9 @@ public record SponsorshipREQ( + @NotBlank(message = "Não pode ser vazia.") + String locale, + @NotBlank(message = "Não pode ser vazia.") String currency, diff --git a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java index 59ad206..56f334d 100644 --- a/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java +++ b/src/main/java/br/com/notehub/application/dto/payment/SponsorshipStatusRES.java @@ -4,6 +4,8 @@ public record SponsorshipStatusRES( String sessionId, String paymentStatus, String status, + String locale, + String currency, Long amountTotal ) { } \ No newline at end of file diff --git a/src/main/java/br/com/notehub/application/payment/StripeService.java b/src/main/java/br/com/notehub/application/payment/StripeService.java index f0a8245..d580700 100644 --- a/src/main/java/br/com/notehub/application/payment/StripeService.java +++ b/src/main/java/br/com/notehub/application/payment/StripeService.java @@ -39,11 +39,12 @@ private void validateAccess(@Nullable String idFromToken, String idFromSession) throw new AccessDeniedException("Usuário sem permissão."); } - public SponsorshipRES sponsorshipCheckout(String idFromToken, String currency, Long amount) { + public SponsorshipRES sponsorshipCheckout(String idFromToken, String locale, String currency, Long amount) { Stripe.apiKey = secret; try { + var productData = SessionCreateParams.LineItem.PriceData.ProductData.builder() .setName(SPONSORSHIP_NAME) .build(); @@ -61,8 +62,9 @@ public SponsorshipRES sponsorshipCheckout(String idFromToken, String currency, L var params = SessionCreateParams.builder() .setMode(SessionCreateParams.Mode.PAYMENT) + .setLocale(SessionCreateParams.Locale.valueOf(locale)) .setSuccessUrl(String.format("%s/sponsorship/success?session_id={CHECKOUT_SESSION_ID}", client)) - .setCancelUrl(String.format("%s/sponsorship/cancel", client)) + .setCancelUrl(String.format("%s/sponsorship", client)) .addLineItem(lineItem) .putMetadata("purchaseType", "sponsorship") .putMetadata("uId", idFromToken) @@ -80,6 +82,7 @@ public SponsorshipRES sponsorshipCheckout(String idFromToken, String currency, L } catch (StripeException e) { throw new CustomExceptions.CustomStripeException(e); } + } public SponsorshipStatusRES verifySession(String sessionId, String uIdFromToken) { @@ -92,6 +95,8 @@ public SponsorshipStatusRES verifySession(String sessionId, String uIdFromToken) session.getId(), session.getPaymentStatus(), session.getStatus(), + session.getLocale(), + session.getCurrency(), session.getAmountTotal() ); } catch (StripeException e) { From 8c9dfb41b30ff4b04c339d243bb8f2a930789d9f Mon Sep 17 00:00:00 2001 From: Lucas Aguiar Date: Fri, 12 Dec 2025 13:58:39 -0300 Subject: [PATCH 6/6] chore(release): update version to 2.0 --- Dockerfile | 2 +- README.md | 4 ++-- pom.xml | 2 +- .../application/validation/NoForbiddenWordsValidator.java | 1 + 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 2d01e16..3fd9477 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,6 +8,6 @@ RUN ./mvnw clean install FROM eclipse-temurin:21-jdk-alpine -COPY --from=build ./target/NoteHub-1.7.0.jar app.jar +COPY --from=build ./target/NoteHub-2.0.0.jar app.jar ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/README.md b/README.md index cab6c16..0c2dddf 100644 --- a/README.md +++ b/README.md @@ -11,8 +11,8 @@
diff --git a/pom.xml b/pom.xml index 87f00ce..ab8e8e0 100644 --- a/pom.xml +++ b/pom.xml @@ -10,7 +10,7 @@ br.com.notehub NoteHub - 1.7.0 + 2.0.0 NoteHub https://notehub.com.br diff --git a/src/main/java/br/com/notehub/application/validation/NoForbiddenWordsValidator.java b/src/main/java/br/com/notehub/application/validation/NoForbiddenWordsValidator.java index cf86a92..32f2931 100644 --- a/src/main/java/br/com/notehub/application/validation/NoForbiddenWordsValidator.java +++ b/src/main/java/br/com/notehub/application/validation/NoForbiddenWordsValidator.java @@ -17,6 +17,7 @@ public class NoForbiddenWordsValidator implements ConstraintValidator