Skip to content

Conversation

@kkh725
Copy link
Collaborator

@kkh725 kkh725 commented Jan 7, 2026

1. ⭐️ 변경된 내용

  • Billing Module의 Domain, Analytics, Common(원래 사용x) 모듈 의존성 제거
  • Billing Module의 자체 DI 추가
  • Billing Module에서 Mapper를 추가해 Data와의 의존성 끊음 (Data Module의 Billing 모듈 의존성 제거)

2. 🖼️ 스크린샷(선택)

image

image

전체비교


image

image

3. 💡 알게된 부분

4. 📌 이 부분은 꼭 봐주세요!

Billing module 에서 Retrofit을 사용해서 자체적으로 api 호출까지 수행하고, 완전한 독립모듈로 만드는게 나을지,
현재 구조처럼 기존의 호출부(network) 는 연결하되, 나머지 부분만 독립적으로 진행할지. 의견 부탁드립니다 ~!

@tgyuuAn @comst19

Summary by CodeRabbit

  • Bug Fixes

    • Removed revenue analytics calls from billing flow.
  • Refactor

    • Reorganized billing/payment models into dedicated billing packages.
    • Consolidated dependency injection for billing into a central module.
    • Simplified internal module dependencies and removed obsolete bindings.
  • Behavior Change

    • Purchase verification now yields a direct numeric reward result (analytics no longer reported).

✏️ Tip: You can customize this high-level summary in your review settings.

@kkh725 kkh725 requested review from comst19 and tgyuuAn January 7, 2026 07:52
@coderabbitai
Copy link

coderabbitai bot commented Jan 7, 2026

📝 Walkthrough

Walkthrough

This PR reorganizes billing-related models into the billing package, adds a Billing DI module, removes analytics calls from BillingHelperImpl, moves payment network sources into core/billing, updates imports across feature modules, and removes some cross-module dependencies.

Changes

Cohort / File(s) Summary
Build & Dependencies
core/billing/build.gradle.kts, core/data/build.gradle.kts
Removed several module dependencies from billing and removed core/billing dependency from core/data.
DI Modules & Bindings
core/billing/src/main/java/com/puzzle/billing/di/BillingModule.kt, core/data/src/main/java/com/puzzle/data/di/DataModule.kt, core/network/src/main/java/com/puzzle/network/di/NetworkModule.kt
Added new BillingModule with @Binds for PaymentDataSource, PaymentRepository, BillingHelper; removed PaymentDataSource/BillingHelper bindings from DataModule/NetworkModule.
Billing Helper & Analytics
core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt
Removed AnalyticsHelper constructor dependency and deleted the revenue-tracking call; local variable renamed (verifyResult → rewardPuzzleCount).
Model Package Relocation
core/billing/src/main/java/com/puzzle/billing/model/billing/*, core/billing/src/main/java/com/puzzle/billing/model/payment/*, core/domain/src/main/java/com/puzzle/domain/model/payment/PurchaseProduct.kt
Moved several models into billing.model.billing and billing.model.payment; deleted domain PurchaseProduct (moved into billing).
New Mapper & PurchaseProduct DTO
core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt, core/billing/src/main/java/com/puzzle/billing/model/payment/PurchaseProduct.kt
Added mappers (response→domain) in billing and new PurchaseProduct with toDto() and Store enum.
Payment Network Source Relocation
core/billing/src/main/java/com/puzzle/billing/network/source/payment/*, core/network/src/main/java/com/puzzle/network/model/payment/*
Moved PaymentDataSource and impl into core/billing; removed toDomain/toDto mappers from core/network DTOs (mappings moved to billing).
Repository & Domain Interface Adjustments
core/billing/src/main/java/com/puzzle/billing/data/PaymentRepositoryImpl.kt, core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt, core/billing/src/main/java/com/puzzle/billing/domain/BillingHelper.kt, core/billing/src/main/java/com/puzzle/billing/testing/FakeBillingHelper.kt
Updated parameter/return types and imports to reference new billing model packages; verifyPurchaseProduct now returns Int in interface.
Network API Visibility
core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt
Changed unwrapData() extension from internal to public.
Feature Modules — Import Fixes
feature/.../MatchingViewModel.kt, feature/store/.../*.kt
Updated imports to new com.puzzle.billing.model.billing / com.puzzle.billing.model.payment locations across feature modules.

Sequence Diagram(s)

(omitted — changes are refactors and package/DI updates; no new multi-component sequential flow introduced)

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • tgyuuAn

Poem

🐰 Hopped through code with nimble paws,
Models found new cozy laws,
Analytics waved a soft goodbye,
DI threads stitched neat and spry,
A tidy puzzle—hop, rejoice! 🥕✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main change: refactoring billing module dependencies, which aligns with the substantial restructuring work shown in the changeset.
Description check ✅ Passed The PR description follows the template structure with all required sections filled (변경된 내용, 스크린샷, 알게된 부분, 꼭 봐주세요) and includes clear explanations, diagrams, and specific reviewer questions.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

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

Comment @coderabbitai help to get the list of available commands and usage tips.

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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt (1)

100-113: Revenue tracking has been removed - consider alternative observability solution.

The removal of AnalyticsHelper.trackRevenueEvent() eliminates revenue tracking for successful purchases. This impacts business metrics, attribution, and monitoring of payment flows.

While removing the dependency aligns with the module independence goal, consider:

  • Implementing an event callback interface that the Billing module can expose
  • Moving analytics tracking to a higher layer that depends on both Billing and Analytics modules
  • Using a publish-subscribe pattern to decouple tracking from the core billing logic

Would you like me to suggest an implementation pattern for decoupled event tracking?

🤖 Fix all issues with AI agents
In
@core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt:
- Around line 35-38: The mapping uses UNKNOWN_INT as a sentinel for missing
reward (VerifyPurchaseProductResponse.toDomain -> rewardPuzzleCount =
rewardPuzzleCount ?: UNKNOWN_INT) but downstream code treats that value
inconsistently; fix this by normalizing the sentinel before returning final
results: update fetchPurchaseReward() to treat UNKNOWN_INT (and any non-positive
value) as "no reward" and either return 0 or null (so callers can handle it
consistently), or alternatively modify handleSinglePurchase() to check the
reward returned and if reward <= 0 (including UNKNOWN_INT) convert it to the
same behavior as batch purchases (e.g., do not return
PurchaseResult.Success(-1); return Success(0) or the appropriate non-reward
path). Ensure you update the code paths that construct PurchaseResult.Success to
never be passed UNKNOWN_INT and reference
VerifyPurchaseProductResponse.toDomain, fetchPurchaseReward(),
handleSinglePurchase(), and PurchaseResult.Success when making the change.

In @core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt:
- Around line 25-27: The unwrapData function performs an unsafe cast (data ?:
Unit as T) that can throw ClassCastException; change its contract to safely
propagate nulls by making the function return a nullable T (fun <T>
ApiResponse<T>.unwrapData(): T? = data) so callers handle absence explicitly, or
alternatively provide an explicit throwing variant (e.g., unwrapDataOrThrow()
that throws a clear IllegalStateException when data is null) and update any
callers of ApiResponse.unwrapData accordingly; locate the function
ApiResponse.unwrapData and replace the unsafe cast with a safe nullable return
or add the explicit throwing helper.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf43d6e and 2a9bcd7.

📒 Files selected for processing (32)
  • core/billing/build.gradle.kts
  • core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt
  • core/billing/src/main/java/com/puzzle/billing/data/PaymentRepositoryImpl.kt
  • core/billing/src/main/java/com/puzzle/billing/di/BillingModule.kt
  • core/billing/src/main/java/com/puzzle/billing/domain/BillingHelper.kt
  • core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt
  • core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt
  • core/billing/src/main/java/com/puzzle/billing/model/billing/PieceProduct.kt
  • core/billing/src/main/java/com/puzzle/billing/model/billing/PurchaseResult.kt
  • core/billing/src/main/java/com/puzzle/billing/model/payment/BasicCashProduct.kt
  • core/billing/src/main/java/com/puzzle/billing/model/payment/PurchaseProduct.kt
  • core/billing/src/main/java/com/puzzle/billing/model/payment/VerifyPurchaseProduct.kt
  • core/billing/src/main/java/com/puzzle/billing/network/source/payment/PaymentDataSource.kt
  • core/billing/src/main/java/com/puzzle/billing/network/source/payment/PaymentDataSourceImpl.kt
  • core/billing/src/main/java/com/puzzle/billing/testing/FakeBillingHelper.kt
  • core/data/build.gradle.kts
  • core/data/src/main/java/com/puzzle/data/di/DataModule.kt
  • core/domain/src/main/java/com/puzzle/domain/model/payment/PurchaseProduct.kt
  • core/network/src/main/java/com/puzzle/network/di/NetworkModule.kt
  • core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt
  • core/network/src/main/java/com/puzzle/network/model/payment/GetAvailableProductResponse.kt
  • core/network/src/main/java/com/puzzle/network/model/payment/VerifyPurchaseProductRequest.kt
  • core/network/src/main/java/com/puzzle/network/model/payment/VerifyPurchaseProductResponse.kt
  • feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt
  • feature/store/src/main/java/com/puzzle/store/StoreViewModel.kt
  • feature/store/src/main/java/com/puzzle/store/contract/StoreIntent.kt
  • feature/store/src/main/java/com/puzzle/store/contract/StoreSideEffect.kt
  • feature/store/src/main/java/com/puzzle/store/contract/StoreState.kt
  • feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt
  • feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt
  • feature/store/src/main/java/com/puzzle/store/ui/page/CompactStoreScreen.kt
  • feature/store/src/main/java/com/puzzle/store/ui/page/ExpandedStoreScreen.kt
💤 Files with no reviewable changes (6)
  • core/data/build.gradle.kts
  • core/network/src/main/java/com/puzzle/network/di/NetworkModule.kt
  • core/network/src/main/java/com/puzzle/network/model/payment/VerifyPurchaseProductRequest.kt
  • core/billing/build.gradle.kts
  • core/data/src/main/java/com/puzzle/data/di/DataModule.kt
  • core/domain/src/main/java/com/puzzle/domain/model/payment/PurchaseProduct.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (27)
core/billing/src/main/java/com/puzzle/billing/model/payment/BasicCashProduct.kt (1)

1-1: LGTM! Clean package relocation.

The package has been correctly updated to align with the billing module restructuring.

core/billing/src/main/java/com/puzzle/billing/network/source/payment/PaymentDataSourceImpl.kt (2)

1-3: Package and import updates look correct.

The package relocation and updated PurchaseProduct import align with the billing module refactoring.


15-16: Code is correct — toDto() exists as a member function on PurchaseProduct.

PurchaseProduct in core/billing/src/main/java/com/puzzle/billing/model/payment/PurchaseProduct.kt defines toDto() as a member function (lines 10-14), which returns VerifyPurchaseProductRequest. The call at line 16 is valid.

core/billing/src/main/java/com/puzzle/billing/model/billing/PurchaseResult.kt (1)

1-1: LGTM! Package reorganization aligns with module structure.

Moving PurchaseResult to the billing subpackage improves organization within the billing module.

core/billing/src/main/java/com/puzzle/billing/network/source/payment/PaymentDataSource.kt (1)

1-3: LGTM! Interface relocated to billing module.

The package and import updates correctly move PaymentDataSource into the billing module's network layer.

feature/store/src/main/java/com/puzzle/store/StoreViewModel.kt (1)

5-7: LGTM! Import paths updated correctly.

The import paths have been correctly updated to reflect the new billing module package structure (com.puzzle.billing.model.billing). No logic changes required.

core/network/src/main/java/com/puzzle/network/model/payment/VerifyPurchaseProductResponse.kt (1)

6-10: LGTM! Domain mapping correctly moved to billing mapper.

The removal of toDomain() from this network DTO aligns with the refactoring goal of separating concerns. The mapping logic has been relocated to the billing module's mapper, which improves modularity and keeps network models focused on serialization.

feature/matching/src/main/java/com/puzzle/matching/graph/main/MatchingViewModel.kt (1)

5-5: LGTM! Import path updated correctly.

The import path has been correctly updated to com.puzzle.billing.model.billing.PurchaseResult, aligning with the billing module package restructuring.

feature/store/src/main/java/com/puzzle/store/ui/list/NormalProductList.kt (1)

11-12: LGTM! Import paths updated correctly.

The import paths for NormalProduct and PieceProduct have been correctly updated to com.puzzle.billing.model.billing, consistent with the billing module restructuring.

core/billing/src/main/java/com/puzzle/billing/model/payment/PurchaseProduct.kt (1)

5-15: Confirm the store name format matches backend API expectations.

The toDto() method uses store.name which produces uppercase strings ("PLAY_STORE", "APP_STORE"). Verify with your backend API documentation or contract that it expects this exact uppercase enum format, as REST APIs typically use lowercase (e.g., "play_store") or other conventions.

feature/store/src/main/java/com/puzzle/store/ui/page/ExpandedStoreScreen.kt (1)

15-15: LGTM!

Import path correctly updated to reflect the new billing model package structure.

feature/store/src/main/java/com/puzzle/store/contract/StoreState.kt (1)

3-3: LGTM!

Import paths correctly updated for NormalProduct and PromotionProduct to reflect the new billing model package structure.

Also applies to: 5-5

feature/store/src/main/java/com/puzzle/store/ui/page/CompactStoreScreen.kt (1)

11-11: LGTM!

Import path correctly updated to reflect the new billing model package structure.

feature/store/src/main/java/com/puzzle/store/ui/list/PromotionProductList.kt (1)

12-13: LGTM!

Import paths correctly updated for PieceProduct and PromotionProduct to reflect the new billing model package structure.

feature/store/src/main/java/com/puzzle/store/contract/StoreIntent.kt (1)

3-3: Import paths successfully updated across the codebase.

All billing model imports have been correctly migrated to the new com.puzzle.billing.model.billing package. Verification confirms no old import paths remain in the codebase and all 21 references across feature/store, feature/matching, and core/billing modules use the correct new paths.

core/billing/src/main/java/com/puzzle/billing/model/billing/PieceProduct.kt (1)

1-1: LGTM: Clean package relocation.

The move to com.puzzle.billing.model.billing creates better organization for billing-specific models and aligns with the PR's goal of modularizing the billing component.

core/billing/src/main/java/com/puzzle/billing/domain/BillingHelper.kt (1)

4-6: LGTM: Import updates reflect new package structure.

The updated import paths correctly reference the relocated model classes while maintaining the existing interface contract.

core/billing/src/main/java/com/puzzle/billing/testing/FakeBillingHelper.kt (1)

5-7: LGTM: Test fake updated consistently.

The import updates maintain compatibility with the BillingHelper interface while reflecting the new package structure.

feature/store/src/main/java/com/puzzle/store/contract/StoreSideEffect.kt (1)

3-3: LGTM: Cross-module import updated correctly.

The feature/store module correctly references the relocated PieceProduct from the new billing.model.billing package.

core/billing/src/main/java/com/puzzle/billing/model/payment/VerifyPurchaseProduct.kt (1)

1-1: LGTM: Model correctly relocated to billing module with all imports updated.

Moving VerifyPurchaseProduct from the domain module to com.puzzle.billing.model.payment aligns with the PR's decoupling objective. All imports have been properly updated across the codebase:

  • ProductMapper.kt imports and uses it for mapping VerifyPurchaseProductResponse
  • PaymentRepository.kt uses it in the verifyPurchaseProduct method signature

No stale imports from the old domain package exist.

core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt (1)

3-5: LGTM! Import path migration aligns with module decoupling.

The imports have been correctly updated to reference the billing module's own model package, supporting the goal of removing domain module dependencies.

core/network/src/main/java/com/puzzle/network/model/payment/GetAvailableProductResponse.kt (1)

9-26: Excellent separation of concerns!

Removing toDomain() mappings from network DTOs is a solid architectural improvement. The network layer now focuses purely on serialization, while domain mapping responsibility has been properly relocated to the billing module's mapper layer.

core/billing/src/main/java/com/puzzle/billing/data/PaymentRepositoryImpl.kt (1)

4-17: Clean repository implementation with proper mapper integration.

The implementation correctly uses the new mapper layer and updated import paths. The repository pattern is well-structured, delegating to the data source and applying domain mapping via toDomain() extensions.

core/billing/src/main/java/com/puzzle/billing/di/BillingModule.kt (1)

15-35: DI module structure is correct with all implementations properly configured.

All three implementation classes have @Inject constructors as required:

  • PaymentDataSourceImpl injects PieceApi
  • PaymentRepositoryImpl injects PaymentDataSource
  • BillingHelperImpl injects PaymentRepository and @ApplicationContext Context

The bindings follow standard patterns with appropriate singleton scoping.

core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt (3)

42-45: LGTM! Constructor simplified correctly.

The removal of the AnalyticsHelper dependency aligns with the PR objective to make the Billing module independent. The constructor signature is cleaner and correctly maintains the required dependencies.


42-277: Overall refactoring structure is well-executed.

The billing implementation maintains correct concurrency handling, proper error management, and clean integration with Google Play Billing. The refactoring successfully decouples the module from external dependencies while preserving the core functionality.


18-24: The new package structure is correctly in place and all imports are valid.

All seven imported classes (NormalProduct, PieceProduct, PromotionProduct, PurchaseResult, CashProduct, Product, PurchaseProduct) are properly defined in their respective packages:

  • com.puzzle.billing.model.billing: Contains PieceProduct, NormalProduct, PromotionProduct, and PurchaseResult
  • com.puzzle.billing.model.payment: Contains CashProduct, Product, and PurchaseProduct

Comment on lines 35 to 38
internal fun VerifyPurchaseProductResponse.toDomain(): VerifyPurchaseProduct =
VerifyPurchaseProduct(
rewardPuzzleCount = rewardPuzzleCount ?: UNKNOWN_INT
) No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check how UNKNOWN_INT is used across the codebase and verify it's a valid sentinel

echo "=== UNKNOWN_INT definition ==="
rg -n "const val UNKNOWN_INT" --type=kt

echo -e "\n=== VerifyPurchaseProduct usage (check if UNKNOWN_INT is handled) ==="
rg -n "rewardPuzzleCount" --type=kt -A 2 -B 2 -g '!**/test/**'

Repository: Piece-Puzzly/Piece-Android

Length of output: 242


🏁 Script executed:

#!/bin/bash
# Check how UNKNOWN_INT is used across the codebase and verify it's a valid sentinel

echo "=== UNKNOWN_INT definition ==="
rg -n "const val UNKNOWN_INT" --glob="*.kt"

echo -e "\n=== UNKNOWN_INT all usages ==="
rg -n "UNKNOWN_INT" --glob="*.kt" -B 1 -A 1

echo -e "\n=== VerifyPurchaseProduct class definition ==="
rg -n "class VerifyPurchaseProduct" --glob="*.kt" -A 5

echo -e "\n=== rewardPuzzleCount property usage ==="
rg -n "rewardPuzzleCount" --glob="*.kt" -B 2 -A 2

Repository: Piece-Puzzly/Piece-Android

Length of output: 22812


🏁 Script executed:

#!/bin/bash

echo "=== BillingHelperImpl full context around rewardPuzzleCount ==="
rg -n "rewardPuzzleCount" --glob="*.kt" -B 10 -A 5 core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt

echo -e "\n=== BillingHelperImpl interface/contract ==="
rg -n "interface|fun.*rewardPuzzleCount" --glob="*.kt" core/billing/ -B 2 -A 3

echo -e "\n=== How verifyResult is used after return ==="
rg -n "verifyResult" --glob="*.kt" core/billing/

Repository: Piece-Puzzly/Piece-Android

Length of output: 4439


🏁 Script executed:

#!/bin/bash

echo "=== handleSinglePurchase full implementation ==="
rg -n "handleSinglePurchase" --glob="*.kt" -A 20 core/billing/

echo -e "\n=== Reward usage in context ==="
rg -n "val reward|fetchPurchaseReward" --glob="*.kt" -B 2 -A 8 core/billing/

Repository: Piece-Puzzly/Piece-Android

Length of output: 11213


Address inconsistent handling of UNKNOWN_INT sentinel in purchase reward flow.

The mapper correctly uses UNKNOWN_INT as a fallback, but downstream handling is inconsistent. While batch purchases filter out non-positive rewards (line 221: if (totalReward > 0)), single purchases directly return the reward value without validation (line 118). This means a null rewardPuzzleCount would result in PurchaseResult.Success(-1), which is likely unintended.

Either validate the reward in fetchPurchaseReward() (e.g., filter UNKNOWN_INT before returning) or ensure handleSinglePurchase() handles negative values consistently with batch processing.

🤖 Prompt for AI Agents
In
@core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt
around lines 35 - 38, The mapping uses UNKNOWN_INT as a sentinel for missing
reward (VerifyPurchaseProductResponse.toDomain -> rewardPuzzleCount =
rewardPuzzleCount ?: UNKNOWN_INT) but downstream code treats that value
inconsistently; fix this by normalizing the sentinel before returning final
results: update fetchPurchaseReward() to treat UNKNOWN_INT (and any non-positive
value) as "no reward" and either return 0 or null (so callers can handle it
consistently), or alternatively modify handleSinglePurchase() to check the
reward returned and if reward <= 0 (including UNKNOWN_INT) convert it to the
same behavior as batch purchases (e.g., do not return
PurchaseResult.Success(-1); return Success(0) or the appropriate non-reward
path). Ensure you update the code paths that construct PurchaseResult.Success to
never be passed UNKNOWN_INT and reference
VerifyPurchaseProductResponse.toDomain, fetchPurchaseReward(),
handleSinglePurchase(), and PurchaseResult.Success when making the change.

Comment on lines +25 to 27
fun <T> ApiResponse<T>.unwrapData(): T {
return data ?: Unit as T
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: Unsafe cast will cause runtime crashes.

Line 26 contains an unsafe cast: data ?: Unit as T. When data is null and T is not Unit, this will compile but throw a ClassCastException at runtime.

Example failure:

val response = ApiResponse<String>(status = "ok", message = null, data = null)
val result = response.unwrapData() // Returns Unit cast as String → ClassCastException

Now that this function is public (accessible from the billing module), the risk of misuse increases.

🔒 Proposed fix
-fun <T> ApiResponse<T>.unwrapData(): T {
-    return data ?: Unit as T
+fun <T> ApiResponse<T>.unwrapData(): T {
+    return data ?: throw IllegalStateException("ApiResponse data is null")
 }

Alternative: Return a nullable type or use a Result wrapper to handle the null case safely.

🤖 Prompt for AI Agents
In @core/network/src/main/java/com/puzzle/network/model/ApiResponse.kt around
lines 25 - 27, The unwrapData function performs an unsafe cast (data ?: Unit as
T) that can throw ClassCastException; change its contract to safely propagate
nulls by making the function return a nullable T (fun <T>
ApiResponse<T>.unwrapData(): T? = data) so callers handle absence explicitly, or
alternatively provide an explicit throwing variant (e.g., unwrapDataOrThrow()
that throws a clear IllegalStateException when data is null) and update any
callers of ApiResponse.unwrapData accordingly; locate the function
ApiResponse.unwrapData and replace the unsafe cast with a safe nullable return
or add the explicit throwing helper.

@kkh725 kkh725 added 리뷰 원해요🔥 피어의 리뷰를 기다리는 ing.. 🔥 리팩토링 🧰 동작의 변화는 없지만 가독성, 유지보수 측면에서의 코드 개선 🧰 ㄱㅎ근형 🌙 labels Jan 7, 2026
Copy link
Collaborator

@comst19 comst19 left a comment

Choose a reason for hiding this comment

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

고생하셨습니다.
둘 다 장단점이 있을 거 같긴 한데 제 생각엔 piece 단일 제품이라 현재 구조도 괜찮지 않을까 싶습니다

)

internal fun VerifyPurchaseProductResponse.toDomain(): VerifyPurchaseProduct =
VerifyPurchaseProduct(
Copy link
Collaborator

Choose a reason for hiding this comment

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

최근에 정한 대로 단일 값이면 VerifyPurchaseProduct 만들지 말고 Int로 처리해 주시면 좋을 거 같아요.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

7261d61 수정했습니다

}

if (verifyResult.rewardPuzzleCount > 0) {
analyticsHelper.trackRevenueEvent(
Copy link
Collaborator

Choose a reason for hiding this comment

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

구매 검증 성공 시 로깅은 빼기로 정해졌나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

IOS 는 추가를 일단안한거같기도하고, 정확한 price 가 아니고 퍼즐값이라 일단 제거했어요

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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt (1)

42-45: Analytics tracking for purchase events is missing.

The removal of AnalyticsHelper from the constructor eliminated revenue tracking at the billing layer. While AnalyticsHelper.trackRevenueEvent() is still available in the codebase, it is never called when purchases succeed. The PurchaseResult.Success is collected in StoreViewModel and MatchingViewModel, but neither location triggers any revenue or purchase analytics.

This results in lost visibility into purchase completions and revenue events, which impacts business intelligence. Either add analytics tracking to the ViewModels that handle PurchaseResult.Success, or restore AnalyticsHelper to BillingHelperImpl to track revenue at the source.

🤖 Fix all issues with AI agents
In @core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt:
- Line 8: Change the repository method signature and mapper to use the domain
model VerifyPurchaseProduct instead of Int: update the suspend fun
verifyPurchaseProduct(purchaseProduct: PurchaseProduct) declaration in
PaymentRepository (and any implementing classes) to return
VerifyPurchaseProduct, then modify the mapping in ProductMapper (the conversion
from VerifyPurchaseProductResponse) to produce a VerifyPurchaseProduct instance
rather than an Int, and adjust callers to consume the domain object; ensure all
compile-time types and imports are updated accordingly.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2a9bcd7 and 7261d61.

📒 Files selected for processing (3)
  • core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt
  • core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt
  • core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt (1)

3-4: LGTM! Import refactoring aligns with module independence goal.

The migration of CashProduct and PurchaseProduct from the domain module to the billing module successfully decouples the Billing module from external domain dependencies.

core/billing/src/main/java/com/puzzle/billing/data/BillingHelperImpl.kt (2)

18-24: LGTM! Clean package reorganization.

The import updates reflect the billing module's new independent structure. The namespace changes are consistent with the PR's modularization objectives.


100-113: LGTM! Improved variable naming.

The rename from verifyResult to rewardPuzzleCount makes the variable's purpose clearer. The simplified return logic is also cleaner.

interface PaymentRepository {
suspend fun getAvailableProduct(): CashProduct
suspend fun verifyPurchaseProduct(purchaseProduct: PurchaseProduct): VerifyPurchaseProduct
suspend fun verifyPurchaseProduct(purchaseProduct: PurchaseProduct): Int
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if VerifyPurchaseProduct exists in the billing module
echo "=== Searching for VerifyPurchaseProduct in billing module ==="
find core/billing -type f -name "*.kt" | xargs grep -l "VerifyPurchaseProduct" 2>/dev/null || echo "Not found in files"

# Search for definition of VerifyPurchaseProduct
echo -e "\n=== Searching for VerifyPurchaseProduct definition ==="
rg "class|data class|enum|sealed class|object|typealias.*VerifyPurchaseProduct" --type=kotlin

# Check the PaymentRepository file
echo -e "\n=== Content of PaymentRepository.kt ==="
find core/billing -name "PaymentRepository.kt" -type f -exec cat {} \;

# Search for usages of verifyPurchaseProduct method
echo -e "\n=== Finding callers of verifyPurchaseProduct ==="
rg -n "verifyPurchaseProduct" --type=kotlin -B2 -A2

Repository: Piece-Puzzly/Piece-Android

Length of output: 50382


🏁 Script executed:

#!/bin/bash
# Get the PaymentRepository.kt file content
echo "=== PaymentRepository.kt content ==="
cat core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt

echo -e "\n=== VerifyPurchaseProduct.kt content ==="
cat core/billing/src/main/java/com/puzzle/billing/model/payment/VerifyPurchaseProduct.kt

echo -e "\n=== Usages of verifyPurchaseProduct method ==="
rg "verifyPurchaseProduct" --type=kotlin -B3 -A3

Repository: Piece-Puzzly/Piece-Android

Length of output: 4815


🏁 Script executed:

#!/bin/bash
# Get PaymentRepositoryImpl to see what toDomain() actually does
echo "=== PaymentRepositoryImpl.kt content ==="
cat core/billing/src/main/java/com/puzzle/billing/data/PaymentRepositoryImpl.kt

# Check for toDomain extension functions
echo -e "\n=== Searching for toDomain extension on VerifyPurchaseProductResponse ==="
rg "fun.*VerifyPurchaseProductResponse.*toDomain" --type=kotlin -A5

# Check ProductMapper which likely has the mapping logic
echo -e "\n=== ProductMapper.kt content ==="
cat core/billing/src/main/java/com/puzzle/billing/mapper/payment/ProductMapper.kt

Repository: Piece-Puzzly/Piece-Android

Length of output: 2542


Return type should be VerifyPurchaseProduct instead of Int—this is an architectural inconsistency.

The domain model VerifyPurchaseProduct exists but is not being used. The repository currently returns a raw Int value, which:

  • Loses type safety: Primitive Int lacks semantic meaning; callers cannot distinguish this from other integer values.
  • Violates clean architecture: Domain repositories should return domain models, not primitives.
  • Breaks consistency: Other repository methods (e.g., getAvailableProduct()) correctly return domain models (CashProduct).

The current mapper in ProductMapper.kt explicitly converts VerifyPurchaseProductResponse to Int. Update it to return VerifyPurchaseProduct instead, and change the repository interface return type accordingly.

🤖 Prompt for AI Agents
In @core/billing/src/main/java/com/puzzle/billing/domain/PaymentRepository.kt at
line 8, Change the repository method signature and mapper to use the domain
model VerifyPurchaseProduct instead of Int: update the suspend fun
verifyPurchaseProduct(purchaseProduct: PurchaseProduct) declaration in
PaymentRepository (and any implementing classes) to return
VerifyPurchaseProduct, then modify the mapping in ProductMapper (the conversion
from VerifyPurchaseProductResponse) to produce a VerifyPurchaseProduct instance
rather than an Int, and adjust callers to consume the domain object; ensure all
compile-time types and imports are updated accordingly.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ㄱㅎ근형 🌙 리뷰 원해요🔥 피어의 리뷰를 기다리는 ing.. 🔥 리팩토링 🧰 동작의 변화는 없지만 가독성, 유지보수 측면에서의 코드 개선 🧰

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants