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

@@ -115,17 +115,18 @@ func (h *SubscriptionHandler) ProcessPurchase(c *gin.Context) {
switch req.Platform {
case "ios":
if req.ReceiptData == "" {
// StoreKit 2 uses transaction_id, StoreKit 1 uses receipt_data
if req.TransactionID == "" && req.ReceiptData == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.receipt_data_required")})
return
}
subscription, err = h.subscriptionService.ProcessApplePurchase(user.ID, req.ReceiptData)
subscription, err = h.subscriptionService.ProcessApplePurchase(user.ID, req.ReceiptData, req.TransactionID)
case "android":
if req.PurchaseToken == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": i18n.LocalizedMessage(c, "error.purchase_token_required")})
return
}
subscription, err = h.subscriptionService.ProcessGooglePurchase(user.ID, req.PurchaseToken)
subscription, err = h.subscriptionService.ProcessGooglePurchase(user.ID, req.PurchaseToken, req.ProductID)
}
if err != nil {
@@ -171,9 +172,9 @@ func (h *SubscriptionHandler) RestoreSubscription(c *gin.Context) {
switch req.Platform {
case "ios":
subscription, err = h.subscriptionService.ProcessApplePurchase(user.ID, req.ReceiptData)
subscription, err = h.subscriptionService.ProcessApplePurchase(user.ID, req.ReceiptData, req.TransactionID)
case "android":
subscription, err = h.subscriptionService.ProcessGooglePurchase(user.ID, req.PurchaseToken)
subscription, err = h.subscriptionService.ProcessGooglePurchase(user.ID, req.PurchaseToken, req.ProductID)
}
if err != nil {