Add Apple/Google IAP validation and subscription webhooks

- Add Apple App Store Server API integration for receipt/transaction validation
- Add Google Play Developer API integration for purchase token validation
- Add webhook endpoints for server-to-server subscription notifications
  - POST /api/subscription/webhook/apple/ (App Store Server Notifications v2)
  - POST /api/subscription/webhook/google/ (Real-time Developer Notifications)
- Support both StoreKit 1 (receipt_data) and StoreKit 2 (transaction_id)
- Add repository methods to find users by transaction ID or purchase token
- Add configuration for IAP credentials (APPLE_IAP_*, GOOGLE_IAP_*)
- Add setup documentation for configuring webhooks

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2025-12-14 13:58:37 -06:00
parent 81885c4ea3
commit c58aaa5d5f
10 changed files with 1909 additions and 52 deletions

View File

@@ -23,6 +23,8 @@ type Config struct {
Storage StorageConfig
AppleAuth AppleAuthConfig
GoogleAuth GoogleAuthConfig
AppleIAP AppleIAPConfig
GoogleIAP GoogleIAPConfig
}
type ServerConfig struct {
@@ -85,6 +87,21 @@ type GoogleAuthConfig struct {
IOSClientID string // iOS client ID (optional, for audience verification)
}
// AppleIAPConfig holds Apple App Store Server API configuration
type AppleIAPConfig struct {
KeyPath string // Path to .p8 private key file
KeyID string // Key ID from App Store Connect
IssuerID string // Issuer ID from App Store Connect
BundleID string // App bundle ID (e.g., com.tt.casera)
Sandbox bool // Use sandbox environment for testing
}
// GoogleIAPConfig holds Google Play Developer API configuration
type GoogleIAPConfig struct {
ServiceAccountPath string // Path to service account JSON file
PackageName string // Android package name (e.g., com.tt.casera)
}
type WorkerConfig struct {
// Scheduled job times (UTC)
TaskReminderHour int
@@ -206,6 +223,17 @@ func Load() (*Config, error) {
AndroidClientID: viper.GetString("GOOGLE_ANDROID_CLIENT_ID"),
IOSClientID: viper.GetString("GOOGLE_IOS_CLIENT_ID"),
},
AppleIAP: AppleIAPConfig{
KeyPath: viper.GetString("APPLE_IAP_KEY_PATH"),
KeyID: viper.GetString("APPLE_IAP_KEY_ID"),
IssuerID: viper.GetString("APPLE_IAP_ISSUER_ID"),
BundleID: viper.GetString("APPLE_IAP_BUNDLE_ID"),
Sandbox: viper.GetBool("APPLE_IAP_SANDBOX"),
},
GoogleIAP: GoogleIAPConfig{
ServiceAccountPath: viper.GetString("GOOGLE_IAP_SERVICE_ACCOUNT_PATH"),
PackageName: viper.GetString("GOOGLE_IAP_PACKAGE_NAME"),
},
}
// Validate required fields
@@ -267,6 +295,11 @@ func setDefaults() {
viper.SetDefault("STORAGE_BASE_URL", "/uploads")
viper.SetDefault("STORAGE_MAX_FILE_SIZE", 10*1024*1024) // 10MB
viper.SetDefault("STORAGE_ALLOWED_TYPES", "image/jpeg,image/png,image/gif,image/webp,application/pdf")
// Apple IAP defaults
viper.SetDefault("APPLE_IAP_SANDBOX", true) // Default to sandbox for safety
// Google IAP defaults - no defaults needed, will fail gracefully if not configured
}
func validate(cfg *Config) error {