Skip to content

refactor(ads): move reward verification to coresdk#6895

Merged
peterporfy merged 22 commits into
mainfrom
ads-292
Jun 18, 2026
Merged

refactor(ads): move reward verification to coresdk#6895
peterporfy merged 22 commits into
mainfrom
ads-292

Conversation

@peterporfy

@peterporfy peterporfy commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Motivation

Move reward-verification (SSV) logic from the AdMob adapter into core, and expose it as two primitives. The goal with this is to make it available outside the RC AdMob Adapter (for example hybrids, or later non-admob providers). The adapter and future bridges share one core implementation instead of each reimplementing it.

Description

Relocates the non-AdMob-specific SSV logic (the poll loop, outcome/result types) from RevenueCatAdMob into core RevenueCat, and exposes two @_spi(Experimental) methods on Purchases:

  • generateRewardVerificationToken(impressionId:) — mints the SSV token (transaction id + custom-data payload + app
    user id) at ad load.
  • pollRewardVerification(clientTransactionID:) — runs the verification poll loop and invalidates the virtual-currencies
    cache on a verified VC reward.

RevenueCatAdMob becomes a thin wrapper that calls these; its AdMob-specific glue (Setup/State/Dispatcher) stays in the adapter.

Notes

  • No behavior change to the AdMob SSV flow; verified end to end
  • Source-level migration for @_spi consumers: code inspecting the result now needs @_spi(Experimental) import RevenueCat in addition to the adapter import (the result types moved core-ward).
  • Hybrid/KMP @objc wireability is intentionally not in this PR (tracked separately) — these primitives are Swift-shaped for now; the AdMob adapter consumes them today.

Note

Medium Risk
Refactors the rewarded-ad verification and virtual-currency cache path across core and the AdMob adapter; intended behavior parity but any wiring mistake could affect SSV or VC freshness.

Overview
Moves server-side reward verification (SSV) out of RevenueCatAdMob into core RevenueCat, so adapters and future integrations share one implementation.

Core adds @_spi(Experimental) Purchases.generateRewardVerificationToken(impressionId:) (SSV JSON payload + transaction id) and Purchases.pollRewardVerification(clientTransactionID:), which runs the moved RewardVerification.Poller loop, maps to RewardVerificationResult, and invalidates virtual currencies cache on verified virtual-currency rewards. RewardVerificationToken, internal Outcome, and poll logging move to AdsStrings in core.

The AdMob adapter keeps AdMob-only glue (Setup, State, Dispatcher, one-shot main-actor delivery). Load-time setup now goes through TokenProvider → core token minting instead of local apiKey/appUserID JSON encoding; present-time polling delegates to Purchases.shared.pollRewardVerification instead of an in-adapter poller and mapOutcome. Adapter-side VC cache invalidation and poll log strings are removed.

Tests follow the split: poller/outcome coverage lives in core unit tests; adapter tests mock tokens and injectable poll closures. @_spi(Experimental) import RevenueCat is required where RewardVerificationResult / token types are used.

Reviewed by Cursor Bugbot for commit 3433a87. Bugbot is set up for automated code reviews on this repo. Configure here.

@peterporfy

Copy link
Copy Markdown
Contributor Author

This change is part of the following stack:

Change managed by git-spice.

@peterporfy peterporfy requested a review from polmiro June 3, 2026 12:47
@peterporfy peterporfy marked this pull request as ready for review June 3, 2026 12:59
@peterporfy peterporfy requested a review from a team as a code owner June 3, 2026 12:59
Comment thread Sources/Purchasing/Purchases/Purchases.swift Outdated
Comment thread Sources/Purchasing/Purchases/Purchases.swift Outdated
@polmiro polmiro requested a review from ajpallares June 3, 2026 14:00
polmiro added a commit that referenced this pull request Jun 3, 2026
Merge PR #6895 (refactor: move reward verification to coresdk) into the ADS-306
failure-reason work and re-apply the feature onto the relocated layout:

- Poller / Outcome / RewardVerificationResult now live in Sources/Ads/RewardVerification;
  reward-verification log strings live in AdsStrings (core). The feature is re-applied there:
  failure taxonomy (backendRejected(reason:message:), exhaustedPending, exhaustedTransient,
  unexpectedResponse, terminalError, cancelled), last-observed-disposition exhaustion
  classification, reason+message threading with reason fallback, and the cause-specific
  diagnostics.
- Purchases.pollRewardVerificationStatus threads failure_reason/message into the SPI poll status.
- Adapter Dispatcher always delivers on cancellation (no silent drop); dead outcome_cancelled
  string removed.
- Tests relocated to Tests/UnitTests/Ads/RewardVerification re-apply the new assertions; the
  cancellation tests are deterministic (cooperative hang).

Refs ADS-306
@peterporfy peterporfy force-pushed the porfy/fix-tuist-test-targets branch from e1cd1d8 to 5be234b Compare June 5, 2026 08:00
@peterporfy peterporfy force-pushed the porfy/fix-tuist-test-targets branch from 5be234b to 11b49ae Compare June 5, 2026 08:04
Base automatically changed from porfy/fix-tuist-test-targets to main June 5, 2026 10:06
Comment thread Sources/Purchasing/Purchases/Purchases.swift
Comment thread Sources/Purchasing/Purchases/Purchases.swift

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did another pass! We're getting there, I only have some smaller comments.

Also, the PR description still says pollRewardVerification(transactionId:), but the current state is shipped as pollRewardVerification(clientTransactionID:)

Comment thread Sources/Purchasing/Purchases/Purchases.swift Outdated
Comment thread Sources/Ads/RewardVerification/RewardVerificationResult.swift

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 461fe0b. Configure here.

Comment thread Sources/Purchasing/Purchases/Purchases.swift
@polmiro polmiro requested a review from ajpallares June 12, 2026 06:51
public let appUserID: String

/// Creates a token for reward verification
@_spi(Experimental) public init(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this be @_spi(Internal) at most?

Probably internal even, since the SDK would create this to return it but it shouldn't get instantiated from outside

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch! It can't be internal because we are using it from a different module for unit tests - but I switched it to @_spi(Internal) 👍

@ajpallares ajpallares left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work! Just a suggestion to add some API tests, but probably better done in a follow-up PR

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if marked as @_spi(Experimental), I think the new reward-verification APIs (generateRewardVerificationToken, pollRewardVerification, RewardVerificationToken, RewardVerificationResult) should be covered in Tests/APITesters/.

SwiftAPITester/PurchasesAPI.swift already imports @_spi(Experimental), so there's an existing pattern to follow.

Even if we end up modifying these APIs, it will lock the signatures and make changes more intentional, while also making sure these are covered by API tests when we remove the Experimental mark.

Please, let's add these API tests in a follow-up PR so that this one doesn't grow bigger 🙏

@peterporfy

Copy link
Copy Markdown
Contributor Author

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor Author

@RCGitBot please test

@peterporfy

Copy link
Copy Markdown
Contributor Author

@RCGitBot please test

@peterporfy peterporfy merged commit e66ad63 into main Jun 18, 2026
43 checks passed
@peterporfy peterporfy deleted the ads-292 branch June 18, 2026 07:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants