feat!: v6-only paywall API (remove v5) + single native module#243
feat!: v6-only paywall API (remove v5) + single native module#243kherembourg wants to merge 14 commits into
Conversation
…ome, interceptor)
Introduces the TypeScript surface for the v6 bridge contract:
- `PresentationBuilder.placement(id) | screen(id) | default()` chain with
`onLoaded`, `onPresented`, `onCloseRequested`, `onDismissed`.
- `PresentationRequest.preload()` and `display()` (resolves at dismiss).
- `PresentationOutcome` (5 fields: presentation, purchaseResult, plan,
closeReason, error) with exclusion rule error ⇒ closeReason == null.
- `Transition`, `InterceptorInfo`, `InterceptResult`, `PresentationActionKind`,
typed `ActionPayload` union.
- `PurchaselyBuilder` start chain (`apiKey().runningMode().allowDeeplink()…`)
exposed via `Purchasely.builder(apiKey)`.
- `Purchasely.interceptAction`, `removeActionInterceptor`,
`removeAllActionInterceptors`.
Legacy v5 APIs (`fetchPresentation`, `setPaywallActionInterceptor`,
`readyToOpenDeeplink`, `setPaywallActionInterceptorCallback`, `start({...})`)
are kept and annotated `@deprecated`.
Bumps the SDK version to 6.0.0 and updates the related test expectations.
All 139 existing tests still pass.
Ref: reports/v6-presentation-comparison-v3-claude/BRIDGE-CONTRACT.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…interceptor)
Adds a new `PurchaselyV6Bridge` helper that maps the v6 cross-platform contract
to the underlying Android v6 SDK:
- `v6Preload(requestId, payload)` / `v6Display(requestId, payload, transition)`
build a `PLYPresentationBase.Prepared` from the JS payload, attach the
`onPresented` / `onCloseRequested` / `onDismissed` callbacks and emit them
through the existing `RCTDeviceEventEmitter` as
`PURCHASELY_V6_{LOADED,PRESENTED,CLOSE_REQUESTED,DISMISSED}`.
- `v6Close(requestId)` / `v6Back(requestId)` provide programmatic control over
the live presentation.
- `v6RegisterInterceptor(kind)` uses the new typed
`Purchasely.interceptAction(actionType, callback)` (Java/`Class<>` overload)
to expose every concrete `PLYPresentationAction` subclass and forwards the
typed payload to JS through `PURCHASELY_V6_ACTION_INTERCEPTED`.
- `v6CompleteInterceptor(callbackId, result)` resolves the suspended
`CompletableDeferred` with the JS-supplied `PLYInterceptResult`.
- `v6UnregisterInterceptor(kind)` calls `Purchasely.removeActionInterceptor`.
- `v6ApplyStartOptions({allowDeeplink, allowCampaigns})` chains the v6 start
options onto the existing `start(...)` native method.
The legacy v5 bridge methods (`fetchPresentation`, `presentPresentation*`,
`setPaywallActionInterceptor`, `onProcessAction`) — whose underlying SDK APIs
are removed in v6 — now reject with a `v6_migration_required` message that
points consumers at the v6 builder. Internal `sendPurchaseResult` is rewritten
on top of `PLYPresentationOutcome` (`sendPurchaseResultV6`). `PLYProductActivity`
is reduced to a stub kept only for the AndroidManifest, and
`PurchaselyViewManager` is rewritten to preload + buildView with v6 APIs.
Bumps the native SDK dependencies (`core`, `google-play`, `huawei-services`,
`amazon`, `player`) to 6.0.0.
Known follow-ups:
- The Android SDK 6.0.0 must be published to Maven before the example app
can build natively.
- `v6Back` is currently a no-op log; the SDK does not expose a per-request
back API yet.
Ref: reports/v6-presentation-comparison-v3-claude/BRIDGE-CONTRACT.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
WIP scaffolding for the iOS v6 bridge (PurchaselyRNV6.h declares the category on top of PurchaselyRN). Implementation comes next. Also ignores local caches that polluted git status. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Implements the v6 cross-platform bridge contract on iOS using the existing Purchasely 5.7.4 APIs while the native v6 SDK lands. Adds: - v6Preload / v6Display / v6Close / v6Back exported methods - v6RegisterInterceptor / v6UnregisterInterceptor / v6CompleteInterceptor using the single global setPaywallActionsInterceptor + a kind dispatcher - v6ApplyStartOptions for allowDeeplink/allowCampaigns chain Synthesizes the 5-field outcome (presentation, purchaseResult, plan, closeReason, error) and onPresented(error?) callbacks per the contract workarounds P0.2 / P0.4 / P1.1 — closeReason stays null on iOS until the native pipeline exposes it. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exposes the 5 v6 lifecycle event names (PURCHASELY_V6_LOADED, PRESENTED, CLOSE_REQUESTED, DISMISSED, ACTION_INTERCEPTED) through the RCTEventEmitter supportedEvents array and pulls in the new V6 category header so the methods are linked into the main module. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a v6 builder showcase to the example: PurchaselyBuilder.apiKey() chained start, PresentationBuilder.placement() with onLoaded / onPresented / onCloseRequested / onDismissed callbacks, and a typed 'purchase' interceptor. The legacy v5 setupPurchasely() flow stays the default — the new setupPurchaselyV6() entry is wired but commented out in useEffect so users opt in explicitly. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- README: add a "Migration to v6.x" section with before/after snippets for init, paywall display, action interceptor and the 5-field outcome - CHANGELOG (new file): document the v6.0.0-beta.0 release contents, the dual API strategy, the iOS workarounds and the deprecated v5 entry points - package.json: bump version to 6.0.0-beta.0 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
10 tests validating: - PresentationBuilder.placement/screen → v6Preload payload format - screenId → presentationId mapping (P1.1) - display() resolves at DISMISS not at trigger (P0.3) - onPresented synthesizes (null, error) on render fail (P0.4) - Outcome carries 5 fields with closeReason / error mutually exclusive (P0.2) - Action interceptor registry + cross-kind isolation - Orphan events not auto-resolved (native handles timeout) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
| Filename | Overview |
|---|---|
| packages/purchasely/android/src/main/java/com/reactnativepurchasely/v6/PurchaselyV6Module.kt | New v6 Android bridge: preload/display/close/interceptor wiring with ConcurrentHashMap state, 30s interceptor timeout, and color-parsing guards. Solid implementation with prior review items addressed. |
| packages/purchasely/android/src/main/java/com/reactnativepurchasely/PurchaselyModule.kt | v5 presentation methods (fetchPresentation, presentPresentation, etc.) now hard-reject with 'v6_migration_required' — contradicting the PR's stated 'no removal yet' policy and silently breaking existing Android v5 consumers. |
| packages/purchasely/ios/PurchaselyRNV6.m | New iOS v6 bridge (665 lines): presentation lifecycle, interceptor, and start options implemented as a category on PurchaselyRN. Missing timeout on interceptor callbacks (unlike the Android 30s guard), which can permanently block native SDK actions. |
| packages/purchasely/src/v6/presentation.ts | PresentationBuilder/PresentationRequest JS facade: clean lifecycle event binding, subscription cleanup, and display() Promise semantics. No issues found. |
| packages/purchasely/src/v6/interceptor.ts | Action interceptor JS layer: per-kind registry, native registration/unregistration, and async handler dispatch. Dead branch in normalizePayload (P2 style). |
| packages/purchasely/src/v6/types.ts | Well-typed v6 contract types — Presentation, PresentationOutcome, ActionPayload union, InterceptorHandler. No issues found. |
| packages/purchasely/src/v6/startBuilder.ts | PurchaselyBuilder chain for v6 start: maps string enums to legacy ordinals and delegates to existing native start() + new v6ApplyStartOptions(). Clean implementation. |
| packages/purchasely/src/tests/v6.integration.test.ts | 10 new integration tests covering preload, display, interceptor lifecycle, and timeout. Good event-driven mock harness. |
Sequence Diagram
sequenceDiagram
participant JS as JS (React Native)
participant Bridge as Native Bridge (Android/iOS)
participant SDK as Purchasely SDK
Note over JS,SDK: display() flow
JS->>Bridge: v6Display(requestId, payload, transition)
Bridge-->>JS: Promise.resolve(true)
Bridge->>SDK: prepared.display() / fetchPresentationFor:
SDK-->>Bridge: onPresented(presentation)
Bridge-->>JS: PURCHASELY_V6_PRESENTED event
JS->>JS: onPresented callback
SDK-->>Bridge: onDismissed(outcome)
Bridge-->>JS: PURCHASELY_V6_DISMISSED event
JS->>JS: resolve(PresentationOutcome)
Note over JS,SDK: interceptAction() flow
JS->>Bridge: v6RegisterInterceptor(kind)
Bridge->>SDK: "setPaywallActionsInterceptor / interceptAction<T>"
SDK-->>Bridge: action triggered
Bridge-->>JS: PURCHASELY_V6_ACTION_INTERCEPTED (callbackId)
JS->>JS: "handler(info, payload) -> result"
JS->>Bridge: v6CompleteInterceptor(callbackId, result)
Bridge->>SDK: complete(PLYInterceptResult)
Prompt To Fix All With AI
Fix the following 3 code review issues. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 3
packages/purchasely/android/src/main/java/com/reactnativepurchasely/PurchaselyModule.kt:293-320
**v5 presentation methods hard-removed on Android despite "no removal yet" claim**
`fetchPresentation`, `presentPresentation`, `presentPresentationWithIdentifier`, `presentPresentationForPlacement`, `presentProductWithIdentifier`, `presentPlanWithIdentifier`, `setPaywallActionInterceptor`, and `onProcessAction` all now immediately reject with `"v6_migration_required"` on Android. The PR description explicitly states _"v5 APIs remain (`@deprecated`). Once the iOS native fixes land we'll cut a 6.0.0 GA and remove v5."_, but the Android native implementation has already removed these methods. Any app calling the `@deprecated` JS wrappers (which still delegate to `NativeModules.Purchasely.fetchPresentation(...)`) will receive a runtime rejection on Android, silently breaking existing integrations that haven't migrated yet.
### Issue 2 of 3
packages/purchasely/ios/PurchaselyRNV6.m:506-514
**iOS interceptor callbacks have no timeout — SDK action can hang indefinitely**
The `kV6InterceptorCallbacks` dictionary stores each `callbackId → onProcessActionHandler` block with no expiry. If the JS side never calls `v6CompleteInterceptor` (e.g. the RN bridge is reloaded, the event listener is torn down, or the handler throws before completing), `onProcessActionHandler(proceed)` is never invoked and the Purchasely SDK action is frozen for the lifetime of the process. The Android bridge addressed this with `withTimeoutOrNull(INTERCEPTOR_TIMEOUT_MS = 30_000L)` that falls back to `NOT_HANDLED`. The iOS side needs an equivalent: a GCD `dispatch_after` (or an `NSTimer`) that fires after ~30 s, invokes the stored callback with `"notHandled"` and removes the entry from `kV6InterceptorCallbacks`.
### Issue 3 of 3
packages/purchasely/src/v6/interceptor.ts:49-60
Dead branch in the early-return guard — both the inner `if` and the fall-through return `null`, making the kind-check unreachable.
```suggestion
if (!raw) {
return null;
}
```
Reviews (3): Last reviewed commit: "fix(v6): bound Android interceptor wait;..." | Re-trigger Greptile
- gitignore: split the corrupted `.nx/workspace-datajest_dx/` line back into `.nx/workspace-data` + `jest_dx/` (merge dropped the trailing newline). - android: stop double-firing onDismissed on display errors — reject only and let the JS .catch synthesize the dismissed outcome (matches iOS error path). - ios: PresentationBuilder.default() now reads the `isDefault` flag and fetches the default presentation via fetchPresentationWith:nil (fixes 400 in preload + display). - ios: serialise all access to the shared kV6* mutable collections behind @synchronized(kV6StateLock) to avoid RN-thread/main-queue data races. - v6 close(): document + warn that the native SDK has no per-request close yet, so closeAllScreens() dismisses every displayed presentation.
|
All 5 Greptile findings addressed in
Note on #2: I applied the inverse of the literal suggestion (drop event, keep reject rather than drop reject, keep event). Both eliminate the double-fire, but keeping the reject routes through the purpose-built JS Generated by Claude Code |
|
@greptileai review Generated by Claude Code |
Address the two open Greptile findings on the second review pass of PurchaselyV6Module.kt: - Interceptor timeout (P1, real): wrap `deferred.await()` in `withTimeoutOrNull(INTERCEPTOR_TIMEOUT_MS = 30s)` so the coroutine never suspends indefinitely when JS never calls `completeInterceptor` (e.g. after a bridge reload). On timeout we default to NOT_HANDLED and drop the `pendingInterceptors` entry, so neither the SDK action nor the `complete` lambda is held alive. This fulfils the "native must time out" contract already documented in v6.integration.test.ts. - isDefault on Android (no behaviour change): an empty builder already resolves the default presentation — PLYPresentationManager routes a request with null placementId+presentationId to apiService.getPresentation(null), which substitutes "ply_default". This is the exact mirror of iOS fetchPresentationWith:nil; documented the intentional implicit handling in buildPrepared so it isn't re-flagged. - Tests: lock `default()` -> `isDefault:true` with null placement/presentation ids (guards the iOS isDefault branch added in fbc99b6). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
@greptileai review |
| NSString *callbackId = [[NSUUID UUID] UUIDString]; | ||
| @synchronized (kV6StateLock) { | ||
| kV6InterceptorCallbacks[callbackId] = ^(NSString *result) { | ||
| // Map InterceptResult → bool the native interceptor expects. | ||
| // - success / failed → JS handled the action: don't proceed natively. | ||
| // - notHandled → let the SDK perform its default behavior. | ||
| BOOL proceed = [result isEqualToString:@"notHandled"]; | ||
| onProcessActionHandler(proceed); | ||
| }; |
There was a problem hiding this comment.
iOS interceptor callbacks have no timeout — SDK action can hang indefinitely
The kV6InterceptorCallbacks dictionary stores each callbackId → onProcessActionHandler block with no expiry. If the JS side never calls v6CompleteInterceptor (e.g. the RN bridge is reloaded, the event listener is torn down, or the handler throws before completing), onProcessActionHandler(proceed) is never invoked and the Purchasely SDK action is frozen for the lifetime of the process. The Android bridge addressed this with withTimeoutOrNull(INTERCEPTOR_TIMEOUT_MS = 30_000L) that falls back to NOT_HANDLED. The iOS side needs an equivalent: a GCD dispatch_after (or an NSTimer) that fires after ~30 s, invokes the stored callback with "notHandled" and removes the entry from kV6InterceptorCallbacks.
Prompt To Fix With AI
This is a comment left during a code review.
Path: packages/purchasely/ios/PurchaselyRNV6.m
Line: 506-514
Comment:
**iOS interceptor callbacks have no timeout — SDK action can hang indefinitely**
The `kV6InterceptorCallbacks` dictionary stores each `callbackId → onProcessActionHandler` block with no expiry. If the JS side never calls `v6CompleteInterceptor` (e.g. the RN bridge is reloaded, the event listener is torn down, or the handler throws before completing), `onProcessActionHandler(proceed)` is never invoked and the Purchasely SDK action is frozen for the lifetime of the process. The Android bridge addressed this with `withTimeoutOrNull(INTERCEPTOR_TIMEOUT_MS = 30_000L)` that falls back to `NOT_HANDLED`. The iOS side needs an equivalent: a GCD `dispatch_after` (or an `NSTimer`) that fires after ~30 s, invokes the stored callback with `"notHandled"` and removes the entry from `kV6InterceptorCallbacks`.
How can I resolve this? If you propose a fix, please make it concise.BREAKING CHANGE: the legacy v5 paywall API is removed (not deprecated).
There is no soft-transition / dual mode anymore. Paywalls are displayed and
intercepted exclusively through the v6 builders. Version-agnostic core
methods (user, products, subscriptions, attributes, listeners,
presentSubscriptions, clientPresentation*) and the embedded PLYPresentationView
are UNCHANGED.
Removed (TS + iOS + Android):
- start({...}) → Purchasely.builder(apiKey)...start()
- fetchPresentation → presentation.placement(id).build().preload()
- presentPresentation(*) → presentation.placement|screen(id).build().display()
- presentProductWithIdentifier / presentPlanWithIdentifier
→ presentation.screen(id).contentId(c).build().display()
- show/hide/closePresentation → request.display() / request.close()
- setPaywallActionInterceptor(Callback) / onProcessAction
→ interceptAction(kind, handler)
- setDefaultPresentationResultCallback/Handler (TS + iOS)
→ request.onDismissed(outcome => …)
- readyToOpenDeeplink (JS wrapper) → builder(apiKey).allowDeeplink(true).start()
Kept native primitives the v6 layer depends on: native start &
readyToOpenDeeplink (called by the v6 start builder on both platforms);
Android setDefaultPresentationResultHandler (the embedded view manager's
defaultPurchasePromise fallback). iOS removed its variant since the iOS view
uses purchaseResolve directly.
Details:
- TS (src/index.ts): dropped the 16 v5 paywall declarations + now-unused
imports; v6 façade (builder/presentation/interceptAction) is the only
paywall API. Pruned 19 obsolete tests in index.test.ts.
- iOS: removed 12 v5 paywall RCT methods + their exclusive private helpers
and 4 header properties from PurchaselyRN.m/.h; v6 category & view intact;
supportedEvents keeps the merged core+v6 event list.
- Android: removed the v5 paywall @ReactMethods + the orphaned ProductActivity
inner class; deleted PLYProductActivity.kt, its manifest entry and proguard
keep rule; transformPlanToMap & the v6 bridge intact.
- example/: rewritten to the v6 builder/presentation/interceptAction API.
- docs: added MIGRATION-v6.md (old→new mapping) and updated README,
sdk_public_doc.md, CLAUDE.md and CHANGELOG.
Verified: yarn test (133 ✓), yarn typecheck ✓, yarn lint ✓. Native code is
not compilable in this environment (native 6.0.0 SDKs unpublished) and was
verified structurally (grep/brace-balance) + adversarial review.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ingle module Removed the standalone `PurchaselyV6Bridge` object (PurchaselyV6Module.kt) and inlined its logic directly into `PurchaselyModule` so the Android side exposes a single native module. - The 8 v6 @ReactMethod entry points (v6Preload/v6Display/v6Close/v6Back/ v6RegisterInterceptor/v6UnregisterInterceptor/v6CompleteInterceptor/ v6ApplyStartOptions) now hold the implementation directly instead of delegating to PurchaselyV6Bridge — the JS contract is unchanged. - v6 helpers (buildV6Prepared, wireV6Callbacks, toV6Map/toV6Payload/toV6String/ toV6Ordinal) are now private members; they reuse the module's existing `sendEvent` and companion `transformPlanToMap` (no duplication). - Event-name constants, the interceptor timeout, and per-request state (activeV6Requests, pendingV6Interceptors) live in the companion object to preserve the process-global semantics the former object singleton had. - Deleted packages/.../reactnativepurchasely/v6/PurchaselyV6Module.kt and the now-empty v6/ package directory. No remaining references to PurchaselyV6Bridge. Verified: brace balance 276/276, no orphaned references, imports complete; yarn test (133 ✓) / typecheck ✓ / lint ✓. Kotlin not compilable in this environment (native 6.0.0 SDK unpublished) — verified structurally. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Mirror the Android single-module merge on iOS and remove the "v6" branding from the whole codebase now that v6 is the only API. iOS — single bridge: - Merged the PurchaselyRN (V6) category into the main @implementation in PurchaselyRN.m (statics, C helpers, private methods and the 8 RCT bridge methods). Deleted PurchaselyRNV6.h / PurchaselyRNV6.m. One @implementation, braces 298/298. TypeScript — no more v6 folder: - Moved src/v6/{events,interceptor,presentation,startBuilder}.ts to src/ and src/v6/types.ts to src/presentationTypes.ts (avoids the src/types.ts clash). Folded the src/v6/index.ts barrel into src/index.ts. Renamed __tests__/v6.integration.test.ts -> presentation.integration.test.ts. No "v6" mention left in code — synchronized rename across TS + iOS + Android + example + tests (verified: zero [Vv]6 tokens in *.ts/tsx/kt/java/m/h/swift): - Bridge methods: v6Preload->preloadPresentation, v6Display->displayPresentation, v6Close->closePresentation, v6Back->goBackToPreviousScreen, v6RegisterInterceptor->registerActionInterceptor, v6UnregisterInterceptor->unregisterActionInterceptor, v6CompleteInterceptor->completeActionInterceptor, v6ApplyStartOptions->applyStartOptions. - Events: PURCHASELY_V6_LOADED/PRESENTED/CLOSE_REQUESTED/DISMISSED -> PURCHASELY_PRESENTATION_*, PURCHASELY_V6_ACTION_INTERCEPTED -> PURCHASELY_ACTION_INTERCEPTED, PURCHASELY_V6_EVENTS -> PURCHASELY_PRESENTATION_EVENTS, purchaselyV6EventEmitter -> presentationEventEmitter. - All internal v6/V6-prefixed identifiers (iOS kV6*/V6*, Android *V6*, TS V6LifecycleEvent/V6InterceptorEvent) renamed; v6 log tags -> [Purchasely]; NSError domain io.purchasely.v6 -> io.purchasely.presentation. Wire names verified present & matching across TS/iOS/Android. The public API (PresentationBuilder, PurchaselyBuilder, interceptAction, PLYPresentationView) is unchanged. yarn test (133 ✓) / typecheck ✓ / lint ✓. Native verified structurally (brace balance, single @implementation, wire-name sync) — the 6.0.0 SDK is unpublished so it cannot be compiled here. Docs still reference "v6"/6.0.0 as the version/migration concept (out of scope of the code-only rename). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Summary
Migrates the React Native SDK to the v6 paywall API as the single, only paywall surface and unifies the native layer into one module per platform.
What this PR now does (updated — the branch evolved past the original "façade alongside v5" approach):
PurchaselyBuilder(start),PresentationBuilder→PresentationRequest(preload/display/close/back) with a 5‑fieldPresentationOutcome, and typedinterceptAction. There is no dual/soft‑transition mode.presentSubscriptions, client‑presentation tracking, and the embeddedPLYPresentationViewall keep their existing API.PurchaselyRN.m;PurchaselyRNV6.h/.mare deleted. One Objective‑C bridge.PurchaselyV6Bridgewas merged intoPurchaselyModule.kt; thev6/package is gone. One Kotlin module."v6"naming left in the code. Now that v6 is the only API, thev6branding was removed everywhere (TypeScriptsrc/v6/folder dissolved intosrc/, bridge method/event names renamed, internal identifiers and log tags cleaned).v6survives only as the SDK version (6.0.0-beta.0) and in the migration guide. The public API names are unchanged.PresentationBuilder/interceptActionAPI.MIGRATION-v6.mdmaps every removed v5 paywall method to its replacement (the Purchasely AI plugin/skills can assist the migration).Breaking changes
The v5 paywall methods are removed:
start({...}),fetchPresentation,presentPresentation*,presentProductWithIdentifier,presentPlanWithIdentifier,show/hide/closePresentation,setPaywallActionInterceptor(Callback),onProcessAction,setDefaultPresentationResultCallback/Handler,readyToOpenDeeplink. SeeMIGRATION-v6.mdfor the old → new mapping. Core (user/products/subscriptions/attributes/listeners) is not affected.Architecture
Purchaselyper platform (PurchaselyRN/PurchaselyModule); the paywall bridge logic lives directly inside it.iOS limitations (until the native iOS 6.0.0 SDK ships)
closeReasonis not yet surfaced by native iOS (absent/button).back()(goBackToPreviousScreen) is a no‑op + warning log — the legacy iOS SDK exposes noback()primitive.webCheckoutProviderenum mapping is written against the 6.0.0 pod and will only compile once that pod is published (see CI note below).Test plan
yarn typecheck— 0 errorsyarn lint— 0 errorsyarn test— 133/133 pass (5 suites; v5 paywall tests removed,default()contract tests added)build-android/build-ios— blocked by an external dependency, not by this PR: the native Purchasely 6.0.0 SDKs are not published yet, so Gradle cannot resolveio.purchasely:core:6.0.0and the iOS pod is missing the v6 symbols (PLYWebCheckoutProvider*,asDictionary). These jobs have failed identically on every commit of this branch (pre‑existing) and will go green once the native 6.0.0 SDKs are released. The native changes here were verified structurally (single@implementation/module, balanced braces, wire‑name sync) since they cannot be compiled in CI yet.Reference
MIGRATION-v6.mdpackages/purchasely/CHANGELOG.md🤖 Generated with Claude Code