Skip to content

feat(security): mandatory server-side receipt validation before subscription grant#724

Merged
RUKAYAT-CODER merged 3 commits into
rinafcode:mainfrom
No-bodyq:security/server-receipt-validation
Jun 28, 2026
Merged

feat(security): mandatory server-side receipt validation before subscription grant#724
RUKAYAT-CODER merged 3 commits into
rinafcode:mainfrom
No-bodyq:security/server-receipt-validation

Conversation

@No-bodyq

Copy link
Copy Markdown
Contributor

Summary

  • Removed the development fallback in validateReceipt that returned { valid: true } on any error — receipts are now never accepted client-side
  • validateReceipt now POSTs to /api/payments/validate-receipt; retries up to 3 times (4 total attempts) on network-level failures with exponential back-off (1 s, 2 s, 4 s); throws immediately on server-returned errors (4xx / 5xx) without retrying so a rejected receipt is not retried
  • purchaseUpdatedListener in initialize() now guards entry with receiptValidationPending to prevent duplicate in-flight submissions; calls finishTransaction and setSubscriptionTier only inside the result.valid === true branch; clears the pending flag in a finally block regardless of outcome
  • Added subscriptionTier and receiptValidationPending to useAppStore (src/store/index.ts); logout resets both; _setTier now updates the store in addition to AsyncStorage so subscription state is reactive
  • Unit tests cover: server validation success, valid: false rejection, retry-then-succeed on transient network error, exhausted retries throwing, server error failing immediately without retry, pending flag lifecycle, finishTransaction not called on rejection or network failure, duplicate guard, missing-receipt guard
  • Created docs/payments/receipt-validation-flow.md with flow diagram, key guarantees table, full API request/response contract, and notes on restore behaviour

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Chore / Refactor (no functional changes)

Testing Done

  • Unit Tests
  • Integration Tests
  • Manual Verification (e.g., iOS/Android UI checks)

Security Considerations

  • Does this store user data securely (e.g., avoiding plain AsyncStorage for sensitive data)? — Subscription tier is mirrored to AsyncStorage for cold-start reads only; authoritative state lives in the server-validated store field
  • Is token handling secure (no token exposure in logs or UI)? — Error logs capture only productId; no receipt data, tokens, or response bodies are logged
  • Are all user inputs validated? — N/A; receipt data originates from the platform SDK, not user input
  • Is deep link handling safe from malicious payloads? — N/A to this change

Performance Considerations

  • Are React hooks (useCallback, useMemo) used appropriately to prevent unnecessary renders? — N/A to this change
  • Is FlatList optimized (e.g., using getItemLayout, keyExtractor)? — N/A to this change
  • Are asynchronous patterns handled correctly (e.g., useEffect cleanup to avoid memory leaks)? — receiptValidationPending is always cleared in finally; no dangling promises or unhandled rejections
  • Have bundle size impacts been considered? — No new dependencies added

Checklist

  • I have read the CONTRIBUTING guide.
  • My code follows the style guidelines of this project.
  • I have updated the documentation accordingly. — docs/payments/receipt-validation-flow.md created with full flow diagram and API contract
  • Are there architectural changes? If so, is there an Architectural Decision Record (ADR)? — No architectural changes; validation is additive to the existing IAP listener and service interface

Closes #585

…ription grant

Client-side receipt acceptance allowed receipt replay, fake purchase injection
(Freedom/iAP Cracker), and tampered purchase responses. All IAP receipts now
POST to /api/payments/validate-receipt before any local state is updated.
Subscription tier and finishTransaction are only reached on server valid:true.
Network failures retry up to 3 times (exponential back-off 1s/2s/4s) before
surfacing an error; non-network server errors fail immediately without retry.
A receiptValidationPending guard prevents duplicate in-flight submissions.
@drips-wave

drips-wave Bot commented Jun 28, 2026

Copy link
Copy Markdown

@No-bodyq Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Kindly resolve conflict

@No-bodyq

Copy link
Copy Markdown
Contributor Author

resolved

@RUKAYAT-CODER

Copy link
Copy Markdown
Contributor

Thank you for contributing to the project.

@RUKAYAT-CODER RUKAYAT-CODER merged commit 626d6e4 into rinafcode:main Jun 28, 2026
2 of 14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Security] Payment receipts validated client-side only — server-side verification bypass possible

2 participants