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

@@ -0,0 +1,247 @@
# Subscription Webhook Setup
This document explains how to configure Apple App Store Server Notifications and Google Real-time Developer Notifications for server-to-server subscription status updates.
## Overview
The webhook endpoints allow Apple and Google to notify your server when subscription events occur:
- New subscriptions
- Renewals
- Cancellations
- Refunds
- Expirations
- Grace period events
**Webhook Endpoints:**
- Apple: `POST /api/subscription/webhook/apple/`
- Google: `POST /api/subscription/webhook/google/`
## Apple App Store Server Notifications v2
### 1. Configure in App Store Connect
1. Log in to [App Store Connect](https://appstoreconnect.apple.com)
2. Navigate to **My Apps** > Select your app
3. Go to **App Information** (under General)
4. Scroll to **App Store Server Notifications**
5. Enter your webhook URLs:
- **Production Server URL**: `https://your-domain.com/api/subscription/webhook/apple/`
- **Sandbox Server URL**: `https://your-domain.com/api/subscription/webhook/apple/` (or separate staging URL)
6. Select **Version 2** for the notification version
### 2. Environment Variables
Set these environment variables on your server:
```bash
# Apple IAP Configuration (optional but recommended for validation)
APPLE_IAP_KEY_PATH=/path/to/AuthKey_XXXXX.p8 # Your App Store Connect API key
APPLE_IAP_KEY_ID=XXXXXXXXXX # Key ID from App Store Connect
APPLE_IAP_ISSUER_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx # Issuer ID from App Store Connect
APPLE_IAP_BUNDLE_ID=com.your.app.bundleid # Your app's bundle ID
APPLE_IAP_SANDBOX=true # Set to false for production
```
### 3. Generate App Store Connect API Key
To enable server-side receipt validation:
1. Go to [App Store Connect > Users and Access > Keys](https://appstoreconnect.apple.com/access/api)
2. Click **Generate API Key**
3. Give it a name (e.g., "Casera IAP Server")
4. Select **App Manager** role (minimum required for IAP)
5. Download the `.p8` file - **you can only download it once!**
6. Note the **Key ID** and **Issuer ID**
### 4. Apple Notification Types
Your server will receive these notification types:
| Type | Description | Action |
|------|-------------|--------|
| `SUBSCRIBED` | New subscription | Upgrade user to Pro |
| `DID_RENEW` | Subscription renewed | Extend expiry date |
| `DID_CHANGE_RENEWAL_STATUS` | Auto-renew toggled | Update auto_renew flag |
| `DID_FAIL_TO_RENEW` | Billing failed | User may be in grace period |
| `EXPIRED` | Subscription expired | Downgrade to Free |
| `REFUND` | User got refund | Downgrade to Free |
| `REVOKE` | Access revoked | Downgrade to Free |
| `GRACE_PERIOD_EXPIRED` | Grace period ended | Downgrade to Free |
---
## Google Real-time Developer Notifications
Google uses Cloud Pub/Sub to deliver subscription notifications. Setup requires creating a Pub/Sub topic and subscription.
### 1. Create a Pub/Sub Topic
1. Go to [Google Cloud Console](https://console.cloud.google.com/)
2. Select your project (or create one)
3. Navigate to **Pub/Sub** > **Topics**
4. Click **Create Topic**
5. Name it (e.g., `casera-subscriptions`)
6. Note the full topic name: `projects/your-project-id/topics/casera-subscriptions`
### 2. Create a Push Subscription
1. In the Pub/Sub Topics list, click on your topic
2. Click **Create Subscription**
3. Configure:
- **Subscription ID**: `casera-subscription-webhook`
- **Delivery type**: Push
- **Endpoint URL**: `https://your-domain.com/api/subscription/webhook/google/`
- **Acknowledgment deadline**: 60 seconds
4. Click **Create**
### 3. Link to Google Play Console
1. Go to [Google Play Console](https://play.google.com/console)
2. Select your app
3. Navigate to **Monetization** > **Monetization setup**
4. Under **Real-time developer notifications**:
- Enter your topic name: `projects/your-project-id/topics/casera-subscriptions`
5. Click **Save**
6. Click **Send test notification** to verify the connection
### 4. Create a Service Account for Validation
1. Go to [Google Cloud Console > IAM & Admin > Service Accounts](https://console.cloud.google.com/iam-admin/serviceaccounts)
2. Click **Create Service Account**
3. Name it (e.g., `casera-iap-validator`)
4. Grant roles:
- `Pub/Sub Subscriber` (for webhook handling)
5. Click **Done**
6. Click on the service account > **Keys** > **Add Key** > **Create new key**
7. Select **JSON** and download the key file
### 5. Link Play Console to Cloud Project
1. Go to **Google Play Console** > **Setup** > **API access**
2. Link your Google Cloud project
3. Under **Service accounts**, grant your service account access:
- `View financial data` (for subscription validation)
### 6. Environment Variables
```bash
# Google IAP Configuration
GOOGLE_IAP_SERVICE_ACCOUNT_PATH=/path/to/service-account.json
GOOGLE_IAP_PACKAGE_NAME=com.your.app.packagename
```
### 7. Google Notification Types
Your server will receive these notification types:
| Type Code | Type | Description | Action |
|-----------|------|-------------|--------|
| 1 | `SUBSCRIPTION_RECOVERED` | Recovered from hold | Re-upgrade to Pro |
| 2 | `SUBSCRIPTION_RENEWED` | Subscription renewed | Extend expiry date |
| 3 | `SUBSCRIPTION_CANCELED` | User canceled | Mark as canceled, will expire |
| 4 | `SUBSCRIPTION_PURCHASED` | New purchase | Upgrade to Pro |
| 5 | `SUBSCRIPTION_ON_HOLD` | Account hold | User may lose access soon |
| 6 | `SUBSCRIPTION_IN_GRACE_PERIOD` | In grace period | User still has access |
| 7 | `SUBSCRIPTION_RESTARTED` | User resubscribed | Re-upgrade to Pro |
| 12 | `SUBSCRIPTION_REVOKED` | Subscription revoked | Downgrade to Free |
| 13 | `SUBSCRIPTION_EXPIRED` | Subscription expired | Downgrade to Free |
---
## Testing
### Test Apple Webhooks
1. Use the **Sandbox** environment in App Store Connect
2. Create a Sandbox tester account
3. Make test purchases on a test device
4. Monitor your server logs for webhook events
### Test Google Webhooks
1. In Google Play Console > **Monetization setup**
2. Click **Send test notification**
3. Your server should receive a test notification
4. Check server logs for: `"Google Webhook: Received test notification"`
### Local Development Testing
For local testing, use a tunnel service like ngrok:
```bash
# Start ngrok
ngrok http 8000
# Use the ngrok URL for webhook configuration
# e.g., https://abc123.ngrok.io/api/subscription/webhook/apple/
```
---
## Security Considerations
### Apple Webhook Verification
The webhook handler includes optional JWS signature verification. Apple signs all notifications using their certificate chain. While the current implementation decodes the payload without full verification, you can enable verification by:
1. Downloading Apple's root certificates from https://www.apple.com/certificateauthority/
2. Calling `VerifyAppleSignature()` before processing
### Google Webhook Security
Options for securing Google webhooks:
1. **IP Whitelisting**: Google publishes [Pub/Sub IP ranges](https://cloud.google.com/pubsub/docs/reference/service_apis_overview#ip-ranges)
2. **Push Authentication**: Configure your Pub/Sub subscription to include an authentication header
3. **OIDC Token**: Have Pub/Sub include an OIDC token that your server verifies
### General Security
- Always use HTTPS for webhook endpoints
- Validate the bundle ID / package name in each notification
- Log all webhook events for debugging and audit
- Return 200 OK even if processing fails (to prevent retries flooding your server)
---
## Troubleshooting
### Apple Webhooks Not Arriving
1. Verify the URL is correct in App Store Connect
2. Check your server is reachable from the internet
3. Ensure you're testing with a Sandbox account
4. Check server logs for any processing errors
### Google Webhooks Not Arriving
1. Verify the topic name format: `projects/PROJECT_ID/topics/TOPIC_NAME`
2. Check the Pub/Sub subscription is active
3. Test with "Send test notification" in Play Console
4. Check Cloud Logging for Pub/Sub delivery errors
### User Not Found for Webhook
This typically means:
- The user made a purchase but the receipt/token wasn't stored
- The transaction ID or purchase token doesn't match any stored data
The handler logs these cases but returns success to prevent Apple/Google from retrying.
---
## Database Schema
Relevant fields in `user_subscriptions` table:
```sql
apple_receipt_data TEXT -- Stores receipt/transaction ID for Apple
google_purchase_token TEXT -- Stores purchase token for Google
cancelled_at TIMESTAMP -- When user canceled (will expire at end of period)
auto_renew BOOLEAN -- Whether subscription will auto-renew
```
These fields are used by webhook handlers to:
1. Find the user associated with a transaction
2. Track cancellation state
3. Update renewal preferences