Skip to content

MOSU-166 refactor : 알림톡 연동#169

Merged
chominju02 merged 6 commits intodevelopfrom
refactor/mosu-166
Jul 29, 2025
Merged

MOSU-166 refactor : 알림톡 연동#169
chominju02 merged 6 commits intodevelopfrom
refactor/mosu-166

Conversation

@chominju02
Copy link
Contributor

@chominju02 chominju02 commented Jul 29, 2025

✨ 구현한 기능

  • 결제 완료 알림톡 연동
  • 환불 완료 알림톡 연동
  • 회원 가입 알림톡 연동
  • 문의 답변 완료 알림톡 연동

📢 논의하고 싶은 내용

🎸 기타

Summary by CodeRabbit

  • New Features

    • Added notification functionality for successful inquiry answers, profile registrations, payments, and refunds.
    • Two additional users are now created in the initial database setup.
  • Improvements

    • Enhanced refund and payment notification details to include refund amounts and user identification.
    • Improved error handling and messaging in notification-related workflows.
    • Strengthened user authentication and authorization for refund processing.
  • Bug Fixes

    • Corrected inquiry answer existence checks to improve reliability.
  • Localization

    • Updated Korean notification messages for improved clarity and formatting.

@chominju02 chominju02 self-assigned this Jul 29, 2025
@coderabbitai
Copy link

coderabbitai bot commented Jul 29, 2025

Walkthrough

This set of changes introduces and extends notification functionality across several service layers, particularly for inquiry answers, payments, refunds, and profile registration. It adds new notification event publishing logic, updates repository query methods, modifies DTOs and projections to include additional fields, and adjusts method signatures to propagate user and context information. Some resource strings and database initialization logic are also updated.

Changes

Cohort / File(s) Change Summary
Inquiry Answer Notification Integration
src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java, src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerJpaRepository.java
Added notification publishing after answer creation, injected NotifyEventPublisher, added sendNotification method, corrected repository method usage, and added existsByInquiryId method.
Notification Variable & Refund Notification DTOs
src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java, src/main/java/life/mosu/mosuserver/application/notify/dto/RefundNotifyRequest.java, src/main/java/life/mosu/mosuserver/domain/refund/RefundNotifyProjection.java, src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java
Replaced stub code with actual repository queries and exception handling for notification variables, updated refund notification DTO to carry refundAmount, and updated projection and repository query accordingly.
Payment Event & Context Propagation
src/main/java/life/mosu/mosuserver/application/payment/PaymentEventTxService.java, src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java, src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentContext.java, src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentTxEventListener.java, src/main/java/life/mosu/mosuserver/presentation/payment/PaymentWidgetController.java
Updated methods to include and propagate userId in payment events and context, added notification sending after successful payment, and adjusted method signatures and event listeners accordingly.
Refund Event & Context Propagation
src/main/java/life/mosu/mosuserver/application/refund/RefundEventTxService.java, src/main/java/life/mosu/mosuserver/application/refund/RefundService.java, src/main/java/life/mosu/mosuserver/application/refund/tx/RefundContext.java, src/main/java/life/mosu/mosuserver/application/refund/tx/RefundTxEventListener.java, src/main/java/life/mosu/mosuserver/presentation/refund/RefundController.java
Added userId and examApplicationId to refund context and event publishing, updated service and controller method signatures, and introduced notification sending after successful refund.
Profile Registration Notification
src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java
Added notification publishing after successful profile registration.
Exam Application Repository
src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java
Annotated repository method parameter with @Param.
Notification Event Factory Overloads
src/main/java/life/mosu/mosuserver/infra/notify/dto/NotificationEvent.java
Split static factory method into two overloads, adding validation for the status and separating creation logic.
Database Initialization
src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java
Changed initial user phone numbers and added creation of two additional users with profiles.
Resource Messages
src/main/resources/messages_ko.properties
Minor formatting and content tweaks to notification message templates.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Controller
    participant Service
    participant EventTxService
    participant EventListener
    participant NotifyEventPublisher

    User->>Controller: Trigger (e.g., payment, refund, inquiry answer)
    Controller->>Service: Call service method (with userId)
    Service->>EventTxService: publishSuccessEvent/publishFailureEvent (userId, ...)
    EventTxService->>EventListener: Publish event (after commit)
    EventListener->>NotifyEventPublisher: sendNotification (NotificationEvent)
    NotifyEventPublisher-->>User: Notification sent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

우선순위 중

Suggested reviewers

  • wlgns12370
  • jbh010204

Poem

In the garden of code where events bloom and grow,
Notifications now flutter wherever users go.
Refunds and payments, inquiries too,
All send a message—delightful and true!
With rabbits at keyboards, and carrots in sight,
These updates bring joy—code hops with delight! 🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/mosu-166

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @chominju02, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request integrates Alimtalk notifications across various user lifecycle events, including payment completion, refund completion, user sign-up, and inquiry answer updates. This enhances user communication by providing timely and relevant notifications directly through the Alimtalk service. The changes involve propagating user and application context through transactional events and refining data retrieval for notification content.

Highlights

  • Alimtalk Integration for Key Events: Implemented automated Alimtalk notifications for payment completion, refund completion, new user sign-ups, and inquiry answer updates, enhancing real-time user communication.
  • Enhanced Notification Data Handling: Updated data structures and retrieval logic to include comprehensive details, such as refund amounts and specific exam application information, ensuring richer notification content.
  • Improved Transactional Event Propagation: Modified payment and refund transaction services to correctly propagate user and application IDs, enabling precise targeting of notifications post-transaction.
  • Refinement of Inquiry Answer Logic: Corrected the logic for checking existing inquiry answers to use inquiryId instead of the primary key, preventing potential data integrity issues.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments or fill out our survey to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the application to integrate Alimtalk notifications for key events like payment, refund, sign-up, and inquiry answers. The changes are mostly centered around publishing and handling notification events. There are a few issues related to userId being passed around and a factory method that is error prone. Please address these points to ensure the stability and maintainability of the new notification feature.

Comment on lines +75 to +78
private void sendNotification() {
NotificationEvent event = NotificationEvent.create(NotificationStatus.SIGN_UP_SUCCESS);
notifier.notify(event);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The sendNotification() method creates a NotificationEvent with a null userId because it uses NotificationEvent.create(NotificationStatus.SIGN_UP_SUCCESS). This will cause a NullPointerException in NotifyService, which needs the userId to fetch the user's phone number.

To fix this, the sendNotification method should accept the userId and pass it when creating the event.

Suggested change
private void sendNotification() {
NotificationEvent event = NotificationEvent.create(NotificationStatus.SIGN_UP_SUCCESS);
notifier.notify(event);
}
private void sendNotification(Long userId) {
NotificationEvent event = NotificationEvent.create(NotificationStatus.SIGN_UP_SUCCESS, userId, null);
notifier.notify(event);
}

user.grantUserRole();
syncUserInfoFromProfile(user, request);

sendNotification();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The sendNotification() method should accept the userId and pass it when creating the event. You will also need to update the call to this method on line 42 to pass the userId:

sendNotification(userId);

Suggested change
sendNotification();
sendNotification(userId);

Comment on lines +9 to 14
public static NotificationEvent create(NotificationStatus status) {
if (!NotificationStatus.SIGN_UP_SUCCESS.equals(status)) {
throw new IllegalArgumentException("Unknown notification status: " + status);
}
return new NotificationEvent(status, null, null);
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The new factory method create(NotificationStatus status) is error-prone because it creates an event with a null userId. This is an implicit contract that is not enforced by the type system and can easily lead to NullPointerExceptions in services that consume these events, as seen with ProfileService and NotifyService.

It's safer to remove this method and exclusively use the create(NotificationStatus, Long, Long) factory method, always providing a userId explicitly. For cases like sign-up where there's no specific target entity, null can be passed for targetId.

Comment on lines +66 to +68
targetId)
.orElseThrow(
() -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The error code ErrorCode.APPLICATION_SCHOOL_NOT_FOUND seems incorrect here. The method is trying to find a refund projection by examApplicationId. A more appropriate error code would be ErrorCode.EXAM_APPLICATION_NOT_FOUND (which is used in createApplicationVariable in the same file) or a new specific error code for "refund data not found". Using a misleading error code can make debugging more difficult.

                        () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND));

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🔭 Outside diff range comments (1)
src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java (1)

3-3: Critical: Wrong @param import used.

The import is using Lettuce Core's @Param annotation instead of Spring Data JPA's annotation. This will cause runtime failures when executing the query.

Apply this diff to fix the import:

-import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.repository.query.Param;
🧹 Nitpick comments (2)
src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java (1)

139-169: Consider parameterizing fixed values for better maintainability.

The createAdditionalUsers() method correctly follows the existing pattern but uses several hardcoded values. Consider extracting constants for repeated values like phone number, school info, and agreement flags to improve maintainability.

+    private static final String DEFAULT_PHONE = "010-9161-2960";
+    private static final String DEFAULT_SCHOOL_NAME = "모수고등학교";
+    private static final String DEFAULT_SCHOOL_ADDRESS = "서울시 모수구 123";
+    private static final String DEFAULT_ZIPCODE = "12345";
+
     private void createAdditionalUsers() {
         for (int i = 11; i <= 12; i++) {
             UserJpaEntity user = UserJpaEntity.builder()
                     .loginId("userid" + i)
                     .password(passwordEncoder.encode("Password!" + i))
                     .gender(i % 2 == 0 ? Gender.MALE : Gender.FEMALE)
                     .name("모수학생" + i)
-                    .phoneNumber("010-9161-2960")
+                    .phoneNumber(DEFAULT_PHONE)
                     // ... other fields
src/main/java/life/mosu/mosuserver/presentation/refund/RefundController.java (1)

28-30: Simplify the authorization expression.

The isAuthenticated() check is redundant when checking for a role, as hasRole('USER') already implies authentication.

-    @PreAuthorize("isAuthenticated() and hasRole('USER')")
+    @PreAuthorize("hasRole('USER')")
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57896ab and 61189a1.

📒 Files selected for processing (21)
  • src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/notify/dto/RefundNotifyRequest.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/application/payment/PaymentEventTxService.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentContext.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentTxEventListener.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java (4 hunks)
  • src/main/java/life/mosu/mosuserver/application/refund/RefundEventTxService.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/refund/RefundService.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/application/refund/tx/RefundContext.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/application/refund/tx/RefundTxEventListener.java (3 hunks)
  • src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/domain/refund/RefundNotifyProjection.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java (2 hunks)
  • src/main/java/life/mosu/mosuserver/infra/notify/dto/NotificationEvent.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/payment/PaymentWidgetController.java (1 hunks)
  • src/main/java/life/mosu/mosuserver/presentation/refund/RefundController.java (2 hunks)
  • src/main/resources/messages_ko.properties (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/main/java/life/mosu/mosuserver/presentation/refund/RefundController.java (2)
src/main/java/life/mosu/mosuserver/application/refund/RefundEventTxService.java (1)
  • Slf4j (13-44)
src/main/java/life/mosu/mosuserver/application/refund/RefundService.java (1)
  • Slf4j (20-88)
src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java (2)
src/main/java/life/mosu/mosuserver/application/inquiry/InquiryService.java (1)
  • Slf4j (24-93)
src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java (1)
  • Service (15-77)
🔇 Additional comments (36)
src/main/java/life/mosu/mosuserver/domain/inquiryAnswer/InquiryAnswerJpaRepository.java (1)

10-10: LGTM! Method correctly implements inquiry existence check.

The new existsByInquiryId method properly follows Spring Data JPA naming conventions and fixes the incorrect usage of existsById(postId) mentioned in the AI summary. This ensures accurate validation of whether an answer already exists for a specific inquiry.

src/main/resources/messages_ko.properties (1)

8-8: Good formatting improvements for Korean notification templates.

The formatting changes enhance the AlimTalk message templates:

  • Removing spaces after commas in lines 8 and 17 follows proper Korean typography conventions
  • Adding the bullet point (■) symbol in line 121 provides visual consistency with other notification formats

These changes improve the user experience for Korean notification messages.

Also applies to: 17-17, 121-121

src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java (1)

91-92: LGTM! Parameter annotation correctly added.

The explicit @Param("targetId") annotation ensures proper parameter binding in the JPQL query. This change supports the notification integration by enabling accurate data retrieval for notification variables.

src/main/java/life/mosu/mosuserver/domain/refund/RefundNotifyProjection.java (1)

10-10: LGTM! Refund amount field appropriately added.

The new refundAmount field completes the refund notification data structure. Using Integer type is appropriate for monetary amounts in this context, and the field placement maintains logical ordering in the record.

src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java (2)

10-12: LGTM! Proper dependency injection for notification functionality.

The notification dependencies are correctly injected following the established pattern used in other services within this PR.

Also applies to: 27-27


42-42: Ensure notification failures don’t rollback the main transaction

Notifications are correctly published after profile registration, following the existing event‐publishing pattern. However, since sendNotification() is invoked inside the registration transaction (see src/main/java/life/mosu/mosuserver/application/profile/ProfileService.java:42), any exception thrown there will roll back the entire transaction. The same pattern appears in:

  • RefundTxEventListener (src/main/java/life/mosu/mosuserver/application/refund/tx/RefundTxEventListener.java)
  • PaymentTxEventListener (src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentTxEventListener.java)
  • InquiryAnswerService (src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java)

If notifications are non-critical, consider:

  • Executing them in a separate transaction or asynchronously
  • Wrapping the call in error handling so notification failures don’t bubble up and rollback the core transaction
src/main/java/life/mosu/mosuserver/presentation/payment/PaymentWidgetController.java (1)

48-48: LGTM! Proper integration of userId for payment confirmation.

The addition of userId parameter to the paymentService.confirm() call correctly propagates user context through the payment flow, enabling user-specific notifications as intended by the 알림톡 integration.

src/main/java/life/mosu/mosuserver/application/notify/dto/RefundNotifyRequest.java (1)

15-15: LGTM! Improved type safety and field naming.

The change from String amount to Integer refundAmount provides better type safety and more descriptive naming. The corresponding update in toMap() correctly handles the Integer-to-String conversion for serialization.

Also applies to: 33-33

src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java (3)

48-48: LGTM! Proper method signature update for user context.

The addition of userId parameter enables user-specific event publishing and notifications as part of the 알림톡 integration.


70-75: LGTM! Consistent user context propagation in event publishing.

Both success and failure event publishing methods correctly receive the userId parameter, ensuring consistent user context for notifications regardless of payment outcome.


58-58: Ignore userId parameter suggestion

The verifyAmount method in PaymentAmountCalculator is declared as:

public void verifyAmount(int applicationCount, int lunchAmount, int requestedAmount) { … }

It does not accept a userId, nor is it passed in the call:

amountCalculator.verifyAmount(applications.size(), lunchAmount, request.amount());

No user context is involved or required. You can safely disregard this review comment.

Likely an incorrect or invalid review comment.

src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java (1)

109-109: LGTM! Phone number update for test data.

The phone number change for test users is straightforward and maintains consistency.

src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentTxEventListener.java (3)

4-6: LGTM! Proper dependency injection for notification functionality.

The addition of NotifyEventPublisher dependency and related imports correctly enables the notification functionality for successful payment events.

Also applies to: 22-22


33-38: LGTM! Well-structured after-commit event handler.

The afterCommitHandler method properly implements the 알림톡 notification functionality:

  • Uses correct transaction phase (AFTER_COMMIT)
  • Includes appropriate logging for debugging
  • Efficiently iterates over exam school IDs
  • Maintains clean separation from rollback handling

46-50: LGTM! Clean notification dispatch implementation.

The sendNotification method follows good practices:

  • Clear method documentation
  • Appropriate use of APPLICATION_SUCCESS status
  • Proper encapsulation of notification event creation
  • Clean separation of concerns
src/main/java/life/mosu/mosuserver/application/refund/tx/RefundTxEventListener.java (1)

29-34: LGTM! Proper transaction phase handling for notifications.

The use of AFTER_COMMIT phase ensures notifications are only sent after successful refund transactions, preventing notifications for failed operations.

src/main/java/life/mosu/mosuserver/application/refund/RefundService.java (2)

32-32: LGTM! Proper user context integration.

The addition of userId parameter enables proper user association for refund operations and downstream notifications.


46-47: LGTM! Consistent event publishing with enhanced context.

The event publishing calls now properly include userId and examApplicationId, enabling notifications to be sent to the correct user after successful refund processing.

src/main/java/life/mosu/mosuserver/presentation/refund/RefundController.java (1)

30-34: LGTM! Proper user authentication integration.

The addition of @UserId parameter and passing it to the service layer correctly integrates user context into the refund processing flow.

src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java (2)

36-36: LGTM! Proper user context extraction.

Extracting the user ID from the inquiry entity ensures notifications are sent to the correct user who created the inquiry.


90-90: ExistsByInquiryId method confirmed

  • InquiryAnswerJpaRepository declares boolean existsByInquiryId(Long inquiryId); (domain/inquiryAnswer/InquiryAnswerJpaRepository.java:10)
  • InquiryAnswerService uses it correctly at line 90 (application/inquiry/InquiryAnswerService.java:90)

LGTM!

src/main/java/life/mosu/mosuserver/application/refund/tx/RefundContext.java (3)

8-9: LGTM! Proper context enhancement for notification support.

The addition of userId and examApplicationId fields enables the refund event listeners to access the necessary context for sending notifications to the correct user.


16-19: LGTM! Consistent factory method updates.

The ofSuccess factory method properly accepts and passes the new parameters to maintain consistency with the record structure.

Also applies to: 23-24


31-34: LGTM! Consistent factory method updates.

The ofFailure factory method properly accepts and passes the new parameters, maintaining consistency with the success case.

Also applies to: 38-39

src/main/java/life/mosu/mosuserver/application/payment/tx/PaymentContext.java (3)

6-13: LGTM! Clean addition of userId field to payment context.

The addition of the userId field to the PaymentContext record is well-positioned and enables user-specific notification functionality. The field placement maintains good readability of the record structure.


15-28: Factory method updated consistently for success context.

The ofSuccess factory method correctly accepts and passes the userId parameter to the constructor, maintaining consistency with the record structure.


30-43: Factory method updated consistently for failure context.

The ofFailure factory method correctly accepts and passes the userId parameter to the constructor, maintaining consistency with the record structure and the ofSuccess method.

src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java (1)

15-32: Well-structured query for refund notification data.

The JPQL query correctly joins multiple entities (RefundJpaEntity, PaymentJpaEntity, ExamApplicationJpaEntity, ExamJpaEntity) to gather comprehensive refund notification information. The filtering by examApplicationId and refundStatus = 'DONE' is appropriate for completed refunds.

src/main/java/life/mosu/mosuserver/application/payment/PaymentEventTxService.java (2)

22-27: Consistent addition of userId parameter to success event publishing.

The publishSuccessEvent method correctly accepts and propagates the userId parameter to the PaymentContext.ofSuccess factory method, enabling user-specific notification functionality.


29-35: Consistent addition of userId parameter to failure event publishing.

The publishFailureEvent method correctly accepts and propagates the userId parameter to the PaymentContext.ofFailure factory method, maintaining consistency with the success event method.

src/main/java/life/mosu/mosuserver/application/refund/RefundEventTxService.java (2)

22-31: Well-implemented addition of user and exam application context to success events.

The publishSuccessEvent method correctly accepts and propagates the userId and examApplicationId parameters to enable targeted refund notifications. The parameter order is logical and consistent.


33-43: Consistent handling of user and exam application context in failure events.

The publishFailureEvent method maintains consistency with the success event method by accepting and properly propagating the same additional parameters (userId and examApplicationId).

src/main/java/life/mosu/mosuserver/infra/notify/dto/NotificationEvent.java (2)

16-19: Clean implementation of parameterized factory method.

The three-parameter factory method provides a clean API for creating notification events with specific user and target context, supporting the enhanced notification functionality.


9-14: Restrictive Validation Is Safe for Single-Parameter Factory

The single-parameter create(NotificationStatus) is only ever called with SIGN_UP_SUCCESS (in ProfileService.java). All other statuses use the three-argument overload (create(status, userId, targetId)), so the current restriction will not introduce runtime errors.

src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java (2)

8-8: LGTM! Import statements correctly added.

The new import statements for RefundNotifyRequest, ExamApplicationNotifyProjection, and RefundNotifyProjection are properly added to support the implemented methods.

Also applies to: 13-13, 18-18


75-81: LGTM! Clean implementation with proper error handling.

The implementation correctly:

  • Uses the appropriate repository method with proper parameter
  • Handles the Optional result with meaningful error code
  • Delegates object construction to the DTO's factory method

This follows good practices by keeping the factory method focused and delegating complex object construction to the DTO itself.

Comment on lines +95 to +99
private void sendNotification(Long userId, Long postId) {
NotificationEvent event = NotificationEvent.create(
NotificationStatus.INQUIRY_ANSWER_SUCCESS, userId, postId);
notifier.notify(event);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error handling for notification failures.

Similar to the refund notification, consider adding error handling to prevent inquiry answer creation from failing due to notification issues.

 private void sendNotification(Long userId, Long postId) {
+    try {
         NotificationEvent event = NotificationEvent.create(
                 NotificationStatus.INQUIRY_ANSWER_SUCCESS, userId, postId);
         notifier.notify(event);
+    } catch (Exception e) {
+        log.error("문의 답변 완료 알림톡 발송 실패: userId={}, postId={}", 
+                userId, postId, e);
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void sendNotification(Long userId, Long postId) {
NotificationEvent event = NotificationEvent.create(
NotificationStatus.INQUIRY_ANSWER_SUCCESS, userId, postId);
notifier.notify(event);
}
private void sendNotification(Long userId, Long postId) {
try {
NotificationEvent event = NotificationEvent.create(
NotificationStatus.INQUIRY_ANSWER_SUCCESS, userId, postId);
notifier.notify(event);
} catch (Exception e) {
log.error("문의 답변 완료 알림톡 발송 실패: userId={}, postId={}",
userId, postId, e);
}
}
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerService.java
around lines 95 to 99, the sendNotification method lacks error handling for
notification failures. Wrap the notification sending logic in a try-catch block
to catch any exceptions thrown by notifier.notify(event). Log the error or
handle it appropriately to ensure that notification failures do not cause the
inquiry answer creation process to fail.

Comment on lines 64 to 73
private NotificationVariable createRefundVariable(Long targetId) {
// RefundNotifyProjection projection = refundJpaRepository.findRefundByApplicationSchoolId(
// targetId)
// .orElseThrow(
// () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND));
// return new RefundNotifyRequest(
// projection.paymentKey(), projection.examDate(), projection.schoolName(),
// projection.paymentMethod().getName(), projection.reason()
// );
return null;
RefundNotifyProjection projection = refundJpaRepository.findRefundByExamApplicationId(
targetId)
.orElseThrow(
() -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND));
return new RefundNotifyRequest(
projection.paymentKey(), projection.examDate(), projection.schoolName(),
projection.refundAmount(), projection.paymentMethod().getName(), projection.reason()
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify error code consistency and consider null safety.

The implementation looks correct, but there are a couple of points to verify:

  1. Error Code Mismatch: The method uses ErrorCode.APPLICATION_SCHOOL_NOT_FOUND when no refund data is found, but this seems semantically incorrect - it should probably be REFUND_NOT_FOUND or similar.

  2. Method Chain Safety: The call projection.paymentMethod().getName() assumes paymentMethod() is never null. Consider adding null safety if the payment method could be null.

Please verify the following:

Consider this improvement for null safety:

 return new RefundNotifyRequest(
     projection.paymentKey(), projection.examDate(), projection.schoolName(),
-    projection.refundAmount(), projection.paymentMethod().getName(), projection.reason()
+    projection.refundAmount(), 
+    projection.paymentMethod() != null ? projection.paymentMethod().getName() : "N/A", 
+    projection.reason()
 );

🏁 Script executed:

#!/bin/bash
# Check if REFUND_NOT_FOUND or similar error code exists in ErrorCode enum
ast-grep --pattern 'enum ErrorCode {
  $$$
}'

Length of output: 47


🏁 Script executed:

#!/bin/bash
set -e
# Locate the ErrorCode enum definition
file=$(fd ErrorCode.java | head -n1)
echo "Found ErrorCode enum in: $file"
# Dump its contents to list all defined error codes
sed -n '/enum ErrorCode/,/};/p' "$file"

Length of output: 6687


🏁 Script executed:

#!/bin/bash
set -e
# Locate and print the RefundNotifyProjection interface to check paymentMethod() return type
proj_file=$(fd RefundNotifyProjection.java | head -n1)
echo "=== $proj_file ==="
sed -n '1,200p' "$proj_file"

# Locate and inspect the Refund entity for the paymentMethod field nullability
entity_file=$(fd Refund.java | head -n1)
echo "=== $entity_file ==="
grep -R -n "paymentMethod" "$entity_file" -n
sed -n '1,200p' "$entity_file"

Length of output: 1198


Use a dedicated REFUND_NOT_FOUND error code and guard against null paymentMethod

Your implementation should throw a semantically correct error when no refund is found and only assume paymentMethod is non-null if your DB/schema guarantees it.

• In ErrorCode enum (src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java), add:

REFUND_NOT_FOUND(HttpStatus.NOT_FOUND, "환불 정보를 찾을 수 없습니다."),

• In NotifyVariableFactory#createRefundVariable (src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java), update the exception and (optionally) guard the paymentMethod name:

- .orElseThrow(() -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND));
+ .orElseThrow(() -> new CustomRuntimeException(ErrorCode.REFUND_NOT_FOUND));

return new RefundNotifyRequest(
    projection.paymentKey(),
    projection.examDate(),
    projection.schoolName(),
    projection.refundAmount(),
-   projection.paymentMethod().getName(),
+   projection.paymentMethod() != null
+       ? projection.paymentMethod().getName()
+       : "N/A",
    projection.reason()
);

• If paymentMethod is declared non-nullable in your JPA mapping and DB schema, you can omit the null check; otherwise keep a safe fallback or throw a different exception.

🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java
lines 64-73, replace the current error code APPLICATION_SCHOOL_NOT_FOUND with a
new dedicated REFUND_NOT_FOUND error code that you must add to the ErrorCode
enum in src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java.
Additionally, add a null check for paymentMethod before accessing its name; if
paymentMethod can be null, either provide a safe fallback value or throw an
appropriate exception to avoid NullPointerException.

Comment on lines +36 to +41
private void sendNotification(Long userId, Long examApplicationId) {
NotificationEvent notificationEvent = NotificationEvent.create(
NotificationStatus.REFUND_SUCCESS,
userId, examApplicationId);
notifier.notify(notificationEvent);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider adding error handling for notification failures.

The notification sending could fail and should not cause the refund transaction to fail since it occurs after commit. Consider wrapping the notification logic in a try-catch block to log failures without propagating exceptions.

 private void sendNotification(Long userId, Long examApplicationId) {
+    try {
         NotificationEvent notificationEvent = NotificationEvent.create(
                 NotificationStatus.REFUND_SUCCESS,
                 userId, examApplicationId);
         notifier.notify(notificationEvent);
+    } catch (Exception e) {
+        log.error("환불 성공 알림톡 발송 실패: userId={}, examApplicationId={}", 
+                userId, examApplicationId, e);
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private void sendNotification(Long userId, Long examApplicationId) {
NotificationEvent notificationEvent = NotificationEvent.create(
NotificationStatus.REFUND_SUCCESS,
userId, examApplicationId);
notifier.notify(notificationEvent);
}
private void sendNotification(Long userId, Long examApplicationId) {
try {
NotificationEvent notificationEvent = NotificationEvent.create(
NotificationStatus.REFUND_SUCCESS,
userId, examApplicationId);
notifier.notify(notificationEvent);
} catch (Exception e) {
log.error("환불 성공 알림톡 발송 실패: userId={}, examApplicationId={}",
userId, examApplicationId, e);
}
}
🤖 Prompt for AI Agents
In
src/main/java/life/mosu/mosuserver/application/refund/tx/RefundTxEventListener.java
around lines 36 to 41, the sendNotification method currently does not handle
exceptions that may occur during notification sending. To fix this, wrap the
notification logic inside a try-catch block that catches any exceptions, logs
the failure with relevant details, and prevents the exception from propagating
further, ensuring the refund transaction is not affected by notification
failures.

@@ -1,24 +1,35 @@
package life.mosu.mosuserver.domain.refund;

import io.lettuce.core.dynamic.annotation.Param;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix incorrect import for @param annotation.

The import is using io.lettuce.core.dynamic.annotation.Param instead of the correct Spring Data annotation.

-import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.repository.query.Param;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import io.lettuce.core.dynamic.annotation.Param;
-import io.lettuce.core.dynamic.annotation.Param;
+import org.springframework.data.repository.query.Param;
🤖 Prompt for AI Agents
In src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java at
line 3, the import statement for the @Param annotation is incorrect. Replace the
import of io.lettuce.core.dynamic.annotation.Param with the correct Spring Data
annotation import org.springframework.data.repository.query.Param to ensure
proper functionality.

@chominju02 chominju02 merged commit 707e1fa into develop Jul 29, 2025
2 checks passed
@chominju02 chominju02 deleted the refactor/mosu-166 branch July 29, 2025 17:51
@chominju02 chominju02 changed the title Mosu-166 refactor : 알림톡 연동 MOSU-166 refactor : 알림톡 연동 Jul 29, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant