feat(premium): add store-identical Release run scheme and real-purchase priority

- 'Haru_Danim-Store' 공유 스킴 추가: Release 구성으로 실행해 DEBUG 코드
  (프리미엄 테스트 토글·검증용 런치 인자)가 전부 빠진 '스토어에 올릴
  그대로'의 앱을 실기기에서 확인. StoreKit 로컬 구성은 유지되어 실제 결제
  시트가 뜨되 돈은 청구되지 않는다
- 실구매/복원 시 DEBUG 테스트 토글 강제 모드를 자동 해제(실권한 우선),
  개발용 섹션에 '토글 강제 해제' 버튼과 동작 설명 footer 추가
- Localizable.xcstrings는 Xcode 직렬화 포맷 정규화

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013yKDMhuF39GVYy3FMcGHh7
This commit is contained in:
songyc macbook 2026-07-13 16:14:11 +09:00
parent 14fc5cb5bd
commit 040198beb4
5 changed files with 5718 additions and 5605 deletions

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
"스토어에 올릴 그대로" 확인용 스킴.
- Release 구성으로 실행: #if DEBUG 코드(프리미엄 테스트 토글, 검증용 런치 인자)가 전부 빠진다
- StoreKit 로컬 구성(HaruDanim.storekit)은 그대로 붙어서, 실기기에서 실제 결제 시트가 뜨되
돈은 청구되지 않는다 (결제 시트에 [Environment: Xcode] 표시)
-->
<Scheme
LastUpgradeVersion = "2660"
version = "1.7">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES"
buildArchitectures = "Automatic">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "725A9E832FFCC65000846FFB"
BuildableName = "Haru_Danim.app"
BlueprintName = "Haru_Danim"
ReferencedContainer = "container:Haru_Danim.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
shouldAutocreateTestPlan = "YES">
</TestAction>
<LaunchAction
buildConfiguration = "Release"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "725A9E832FFCC65000846FFB"
BuildableName = "Haru_Danim.app"
BlueprintName = "Haru_Danim"
ReferencedContainer = "container:Haru_Danim.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<StoreKitConfigurationFileReference
identifier = "../../HaruDanim.storekit">
</StoreKitConfigurationFileReference>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "725A9E832FFCC65000846FFB"
BuildableName = "Haru_Danim.app"
BlueprintName = "Haru_Danim"
ReferencedContainer = "container:Haru_Danim.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -178,6 +178,10 @@ final class PremiumStore {
/// · : ·
private func didChangeEntitlement() async {
#if DEBUG
// / (DEBUG )
await PremiumManager.shared.clearMockOverride()
#endif
await PremiumManager.shared.refresh()
await refreshEntitlement()
WidgetCenter.shared.reloadAllTimelines()

File diff suppressed because it is too large Load Diff

View File

@ -368,11 +368,24 @@ struct PremiumView: View {
purchaseSection
}
#if DEBUG
Section("개발용") {
Section {
Toggle("프리미엄 상태 (테스트)", isOn: Binding(
get: { premium.isPremium },
set: { premium.setMockPremium($0) }
))
Button {
Task {
await premium.clearMockOverride()
await store.refreshEntitlement()
}
} label: {
Text(verbatim: "토글 강제 해제 — 실제 구매 상태 따르기")
.font(.callout)
}
} header: {
Text(verbatim: "개발용")
} footer: {
Text(verbatim: "이 섹션은 개발(DEBUG) 빌드에서만 보여요. 출시(Release) 빌드와 'Haru_Danim-Store' 스킴 실행에는 나타나지 않아요. 토글을 한 번이라도 쓰면 실제 구매 상태를 무시하므로, 결제 검증 시에는 '토글 강제 해제'를 눌러 주세요.")
}
#endif
}

View File

@ -176,6 +176,13 @@ final class PremiumManager {
AppGroup.defaults.set(true, forKey: Self.mockActiveKey)
apply(value)
}
/// (DEBUG ) StoreKit .
/// · (PremiumStore) .
func clearMockOverride() async {
AppGroup.defaults.removeObject(forKey: Self.mockActiveKey)
await refresh()
}
#endif
/// (StoreKit) . DEBUG