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:
@@ -105,6 +105,43 @@ func (r *SubscriptionRepository) UpdatePurchaseToken(userID uint, token string)
|
||||
Update("google_purchase_token", token).Error
|
||||
}
|
||||
|
||||
// FindByAppleReceiptContains finds a subscription by Apple transaction ID
|
||||
// Used by webhooks to find the user associated with a transaction
|
||||
func (r *SubscriptionRepository) FindByAppleReceiptContains(transactionID string) (*models.UserSubscription, error) {
|
||||
var sub models.UserSubscription
|
||||
// Search for transaction ID in the stored receipt data
|
||||
err := r.db.Where("apple_receipt_data LIKE ?", "%"+transactionID+"%").First(&sub).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sub, nil
|
||||
}
|
||||
|
||||
// FindByGoogleToken finds a subscription by Google purchase token
|
||||
// Used by webhooks to find the user associated with a purchase
|
||||
func (r *SubscriptionRepository) FindByGoogleToken(purchaseToken string) (*models.UserSubscription, error) {
|
||||
var sub models.UserSubscription
|
||||
err := r.db.Where("google_purchase_token = ?", purchaseToken).First(&sub).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &sub, nil
|
||||
}
|
||||
|
||||
// SetCancelledAt sets the cancellation timestamp
|
||||
func (r *SubscriptionRepository) SetCancelledAt(userID uint, cancelledAt time.Time) error {
|
||||
return r.db.Model(&models.UserSubscription{}).
|
||||
Where("user_id = ?", userID).
|
||||
Update("cancelled_at", cancelledAt).Error
|
||||
}
|
||||
|
||||
// ClearCancelledAt clears the cancellation timestamp (user resubscribed)
|
||||
func (r *SubscriptionRepository) ClearCancelledAt(userID uint) error {
|
||||
return r.db.Model(&models.UserSubscription{}).
|
||||
Where("user_id = ?", userID).
|
||||
Update("cancelled_at", nil).Error
|
||||
}
|
||||
|
||||
// === Tier Limits ===
|
||||
|
||||
// GetTierLimits gets the limits for a subscription tier
|
||||
|
||||
Reference in New Issue
Block a user