Skip to content

Upgrade Purchasely SDK to 6.0.0#9

Open
chouaibMo wants to merge 10 commits into
mainfrom
chore/sdk-v6-migration
Open

Upgrade Purchasely SDK to 6.0.0#9
chouaibMo wants to merge 10 commits into
mainfrom
chore/sdk-v6-migration

Conversation

@chouaibMo

@chouaibMo chouaibMo commented May 18, 2026

Copy link
Copy Markdown

Summary

Migrate the Android integration to Purchasely SDK v6 per MIGRATION_V6.md.

  • Replace removed setPaywallActionsInterceptor with per-action Purchasely.interceptAction<…> suspend handlers; bridge them to the existing Observer-mode billing flow via a pendingResult continuation resolved to PLYInterceptResult.{SUCCESS,NOT_HANDLED,FAILED} based on TransactionResult.
  • Switch to allowDeeplink, handleDeeplink, the single-arg start { error -> } callback, and PLYRunningMode.Observer (was PaywallObserver).
  • Move from fetchPresentation / PLYPresentationProperties to the PLYPresentation { placementId(...) }.preload() DSL and map display() / buildView() callbacks to PLYPresentationOutcome (PLYPurchaseResultDisplayResult).
  • Re-anchor presentation imports under io.purchasely.ext.presentation.

Collateral: Purchasely 6.0.0 transitively pulls Google Play Billing 8.x; updated PurchaseManager.queryProductDetailsAsync to the new QueryProductDetailsResult.productDetailsList shape.

mavenLocal() is temporarily added to dependency resolution — remove once 6.0.0 ships to Maven Central.

Test plan

  • ./gradlew :app:testDebugUnitTest — 178/178 passing locally against a publishToMavenLocal build of io.purchasely:core:6.0.0 + io.purchasely:google-play:6.0.0.
  • Smoke test paywall display + Observer-mode purchase + restore on a device.
  • Verify the success_payment chain still fires after a successful purchase.
  • Verify deeplink handling via handleDeeplink.
  • Remove mavenLocal() from settings.gradle.kts once 6.0.0 is on Maven Central, and re-run the test suite.

🤖 Generated with Claude Code


Android v6 Presentation coverage added

  • Removed Paywall wording from Android source in favor of Presentation / Screen naming.
  • Added Android examples for onPresented, onCloseRequested, runtime colors, UI flags, screenId, flowId, and prepared display(...).
  • Verified with cd android && ./gradlew :app:assembleDebug --quiet.

Migrate the Android integration to the v6 public API per MIGRATION_V6.md:

- Replace the removed global setPaywallActionsInterceptor with per-action
  Purchasely.interceptAction<…> suspend handlers; bridge them to the existing
  Observer-mode billing flow with a pendingResult continuation that resolves
  to PLYInterceptResult.SUCCESS / NOT_HANDLED / FAILED based on the emitted
  TransactionResult.
- Switch to allowDeeplink, handleDeeplink, the single-arg start { error -> }
  callback, and PLYRunningMode.Observer (was PaywallObserver).
- Move from fetchPresentation/PLYPresentationProperties to the
  PLYPresentation { placementId(...) }.preload() DSL and map display() /
  buildView() callbacks to PLYPresentationOutcome (PLYPurchaseResult →
  DisplayResult).
- Re-anchor presentation imports under io.purchasely.ext.presentation.

Collateral: Purchasely 6.0.0 transitively pulls Google Play Billing 8.x;
update PurchaseManager.queryProductDetailsAsync to the new
QueryProductDetailsResult.productDetailsList shape.

Add mavenLocal() to dependency resolution (remove once 6.0.0 ships to
Maven Central).

Rewrite PurchaselyWrapperTest against the new suspend interceptors and
flow ordering. ./gradlew :app:testDebugUnitTest — 178/178 passing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@chouaibMo chouaibMo changed the title chore(android): upgrade Purchasely SDK to 6.0.0 Upgrade Purchasely SDK to 6.0.0 May 18, 2026
chouaibMo and others added 2 commits May 18, 2026 18:43
AGP 9.2.1 requires Gradle ≥ 9.4.1 (MIGRATION_V6.md §7). The committed
wrapper was still 8.13, which broke CI even though local builds passed
because the dev machine had 9.4.1 cached.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ose plugin setup

- Switch Purchasely SDK init from Builder chain to the new `Purchasely { ... }` Kotlin DSL entrypoint (idiomatic v6).
- Drop redundant `kotlin.android` plugin alias: AGP 9.2.1 + `kotlin.plugin.compose` 2.2.10 already register the Kotlin extension (was causing "extension already registered" if re-applied).
- Add `mavenLocal()` to repositories for local SDK snapshot testing.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kherembourg kherembourg marked this pull request as ready for review May 22, 2026 16:13
kherembourg and others added 2 commits May 27, 2026 12:17
Migrate the iOS sample to the v6 API surface:
- fluent init `Purchasely.apiKey(...).…start { error in }` (.storeKit1 on develop)
- `.paywallObserver` -> `.observer`, `readyToOpenDeeplink` -> `allowDeeplink`,
  `isDeeplinkHandled` -> `handleDeeplink`
- typed `interceptAction(.login/.navigate/.purchase/.restore)` returning PLYInterceptResult
- `fetchPresentation` -> `PLYPresentationBuilder.…preload`, dismissal -> PLYPresentationOutcome
- embedded paywall via `PLYPresentationViewController` + UIViewControllerRepresentable
- local Swift package workaround for the unreleased develop snapshot (no root Package.swift)
- docs/migration_v6.md recap

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@kherembourg

kherembourg commented May 28, 2026

Copy link
Copy Markdown
Contributor

CI note after Android v6 Presentation example update:

  • Android job currently fails before compiling app code because io.purchasely:core:6.0.0 and io.purchasely:google-play:6.0.0 are not published to Maven Central/local Maven on GitHub Actions. Local verification passed with the v6 artifacts available in Maven local: cd android && ./gradlew :app:assembleDebug --quiet.
  • iOS job currently fails in ios/LocalPackages/Purchasely/Shims/PLYLottieView.swift on missing PLYLogger; this PR update did not touch iOS per scope.

@kherembourg kherembourg requested a review from Copilot May 28, 2026 21:54
@kherembourg

Copy link
Copy Markdown
Contributor

@greptileai review

@greptile-apps

greptile-apps Bot commented May 28, 2026

Copy link
Copy Markdown

Greptile Summary

Migrates both Android and iOS integrations from Purchasely SDK v5 to v6, replacing the removed setPaywallActionsInterceptor/processAction pattern with typed interceptAction suspend handlers, switching presentation APIs from fetchPresentation/PLYPresentationProperties to the builder DSL with preload(), and updating deeplink and running-mode APIs throughout.

  • Android: PurchaselyWrapper rewired around Purchasely.interceptAction<T> suspend lambdas bridged through awaitPendingResult; PurchaseManager.queryProductDetailsAsync updated for the Billing 8.x QueryProductDetailsResult shape; enum rename from PAYWALL_OBSERVEROBSERVER with legacy storage migration.
  • iOS: Parallel changes to PurchaselyWrapper and EmbeddedScreenBanner; handleObserverAction simplified to direct async/await on @MainActor; loadPresentation adds defensive post-preload callback re-assignment.
  • mavenLocal() remains in settings.gradle.kts (tracked in the PR checklist for removal once 6.0.0 is on Maven Central).

Confidence Score: 4/5

Safe to merge with one fix — the iOS success payment screen may silently skip subscription refresh in some SDK builds.

The Android migration is thorough: interceptors, presentation loading, tests, and the enum rename are all consistent. The iOS side has a gap in showSuccessPaymentScreen — the defensive post-preload callback re-assignment applied carefully in loadPresentation is missing here, which means refreshAfterSuccessPayment won't be invoked when the SDK build doesn't propagate builder-seeded callbacks. Users who successfully purchase will see stale premium state until they manually restart the app.

ios/Shaker/Purchasely/PurchaselyWrapper.swift — specifically the showSuccessPaymentScreen method.

Important Files Changed

Filename Overview
ios/Shaker/Purchasely/PurchaselyWrapper.swift iOS v6 migration: switches to builder DSL, typed interceptors, and async/await observer flow. loadPresentation defensively re-assigns presentation callbacks after preload, but showSuccessPaymentScreen omits this, leaving refreshAfterSuccessPayment unreachable when builder-seeded callbacks don't fire.
android/app/src/main/java/com/purchasely/shaker/purchasely/PurchaselyWrapper.kt Core v6 migration: replaces setPaywallActionsInterceptor + processAction with typed interceptAction suspend handlers bridged through awaitPendingResult; updates SDK init, deeplink, and presentation APIs.
android/app/src/test/java/com/purchasely/shaker/purchasely/PurchaselyWrapperTest.kt Tests updated to match the new suspend-based interceptor API; async/await-style interceptJob pattern correctly resolves pending results and cleans up after each test.
android/app/src/main/java/com/purchasely/shaker/data/purchase/PurchaseManager.kt Updated queryProductDetailsAsync callback to accept the Billing 8.x QueryProductDetailsResult wrapper and extract productDetailsList from it; minimal and correct.
android/app/src/main/java/com/purchasely/shaker/data/PurchaselySdkMode.kt Renames PAYWALL_OBSERVER to OBSERVER and adds LEGACY_PAYWALL_OBSERVER migration constant; fromStorage correctly handles both old and new storage values.
ios/Shaker/Purchasely/EmbeddedScreenBanner.swift Replaces removed PresentationView property with a UIViewControllerRepresentable wrapper around PLYPresentationViewController; clean and correct.
android/settings.gradle.kts Adds mavenLocal() as a temporary dependency resolver for the pre-release Purchasely 6.0.0 SDK; must be removed before merging (explicitly tracked in PR checklist).

Sequence Diagram

sequenceDiagram
    participant SDK as Purchasely SDK v6
    participant IW as interceptAction lambda
    participant PW as PurchaselyWrapper
    participant PM as PurchaseManager
    participant App as App / ViewModel

    Note over SDK,App: Observer-mode purchase flow (v6)
    SDK->>IW: intercept(.purchase) [suspend]
    IW->>PW: handlePurchase(info, action)
    PW->>PW: awaitPendingResult
    PW->>PM: purchaseRequests.emit(PurchaseRequest)
    PM->>PM: Google Play Billing purchase
    PM->>PW: "transactionResult.emit(Success|Cancelled|Error)"
    PW->>PW: pendingResult.invoke(PLYInterceptResult)
    PW-->>IW: PLYInterceptResult (resume continuation)
    IW-->>SDK: return result

    Note over SDK,App: Presentation display flow (v6)
    App->>PW: loadPresentation(placementId)
    PW->>SDK: "PLYPresentation{}.preload()"
    SDK-->>PW: PLYPresentation
    PW->>PW: re-assign onClose/onDismissed defensively
    PW-->>App: FetchResult.Success(handle)
    App->>PW: display(handle, activity)
    SDK-->>PW: PLYPresentationOutcome
    PW->>PW: toDisplayResult()
    alt pendingSuccessfulPurchase
        PW->>PW: showSuccessPaymentScreen()
    end
    PW-->>App: DisplayResult
Loading

Fix All in Claude Code

Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 1
ios/Shaker/Purchasely/PurchaselyWrapper.swift:351-376
**`showSuccessPaymentScreen` skips the defensive callback re-assignment done in `loadPresentation`**

`loadPresentation` explicitly re-assigns `presentation.onClose` and `presentation.onDismissed` on the loaded presentation object after `preload`, with the comment "builder-seeded callbacks are not fired by all develop snapshots." `showSuccessPaymentScreen` only attaches `onDismissed` on the builder; if the SDK snapshot in use doesn't propagate builder callbacks to the loaded presentation, the `refreshAfterSuccessPayment` call is never reached — subscriptions remain stale after the success screen is dismissed. The defensive re-assignment of `presentation.onDismissed` should be added inside the `preload` closure here, mirroring the pattern in `loadPresentation`.

Reviews (3): Last reviewed commit: "fix: block SDK default flow on Observer-..." | Re-trigger Greptile

Copilot AI 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.

Pull request overview

This PR migrates Shaker’s Android + iOS Purchasely integrations to Purchasely SDK v6, updating running modes, deeplink handling, action interception, and presentation fetching/display APIs, plus adjusting Android Billing for the transitive Billing v8 upgrade.

Changes:

  • Android: upgrade Purchasely to 6.0.0, migrate to v6 Presentation APIs + typed interceptors, and adapt Billing queryProductDetailsAsync usage.
  • iOS: migrate Purchasely initialization + deeplink + presentation flows to v6 APIs, and introduce a local Swift package wrapper for Purchasely sources.
  • Tooling/docs: add temporary mavenLocal() resolution, update Gradle wrapper, and add a migration write-up.

Reviewed changes

Copilot reviewed 34 out of 34 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
settings.gradle.kts Adds mavenLocal() to resolve Purchasely v6 artifacts temporarily.
android/settings.gradle.kts Adds mavenLocal() for Android-only build resolution.
android/gradle/wrapper/gradle-wrapper.properties Updates Gradle wrapper distribution URL.
android/gradle/libs.versions.toml Bumps Purchasely + Billing versions and adjusts test dependency versions.
android/build.gradle.kts Updates plugin aliases applied across Android build.
android/app/build.gradle.kts Updates app module plugins and test dependencies; removes explicit Kotlin options.
android/app/src/main/java/com/purchasely/shaker/purchasely/PurchaselyWrapper.kt Migrates to v6 DSL init, typed interceptors, Presentation preload/display APIs, and updated callback types.
android/app/src/main/java/com/purchasely/shaker/purchasely/PresentationHandle.kt Re-anchors PLYPresentation import to v6 presentation package.
android/app/src/main/java/com/purchasely/shaker/data/RunningModeRepository.kt Updates observer mode mapping for v6 running mode naming.
android/app/src/main/java/com/purchasely/shaker/data/PurchaselySdkMode.kt Renames/migrates SDK mode storage/labels to “Observer/Presentation Observer”.
android/app/src/main/java/com/purchasely/shaker/data/purchase/PurchaseManager.kt Adapts to new Billing v8 QueryProductDetailsResult.productDetailsList.
android/app/src/main/java/com/purchasely/shaker/ui/screen/settings/SettingsViewModel.kt Renames paywall flows to presentation flows for onboarding placement.
android/app/src/main/java/com/purchasely/shaker/ui/screen/settings/SettingsScreen.kt Updates UI wiring to new presentation display flows and observer naming.
android/app/src/main/java/com/purchasely/shaker/ui/screen/home/HomeViewModel.kt Renames paywall triggers/callbacks to presentation equivalents.
android/app/src/main/java/com/purchasely/shaker/ui/screen/home/HomeScreen.kt Updates presentation collection + dismissal callback wiring.
android/app/src/main/java/com/purchasely/shaker/ui/screen/favorites/FavoritesViewModel.kt Renames paywall triggers/callbacks to presentation equivalents.
android/app/src/main/java/com/purchasely/shaker/ui/screen/favorites/FavoritesScreen.kt Updates presentation collection + unlock action wiring.
android/app/src/main/java/com/purchasely/shaker/ui/screen/detail/DetailViewModel.kt Renames paywall triggers/callbacks to presentation equivalents; updates comments.
android/app/src/main/java/com/purchasely/shaker/ui/screen/detail/DetailScreen.kt Updates presentation collection + unlock action wiring.
android/app/src/test/java/com/purchasely/shaker/purchasely/PurchaselyWrapperTest.kt Updates tests for v6 interceptor + presentation types and new suspend-based interception model.
android/app/src/test/java/com/purchasely/shaker/data/RunningModeRepositoryTest.kt Updates tests for v6 observer mode mapping.
android/app/src/test/java/com/purchasely/shaker/data/PurchaselySdkModeTest.kt Updates tests for v6 running mode mapping.
ios/Shaker/Screens/Settings/SettingsViewModel.swift Updates stored running mode from .paywallObserver to .observer.
ios/Shaker/Screens/Onboarding/OnboardingScreen.swift Avoids fetching onboarding presentation if onboarding is already completed.
ios/Shaker/Purchasely/PurchaselyWrapper.swift Migrates initialization, deeplinks, action interceptors, and presentation preload/display to v6 APIs.
ios/Shaker/Purchasely/EmbeddedScreenBanner.swift Replaces removed embedded view property with a UIViewControllerRepresentable wrapper.
ios/Shaker/Data/RunningModeRepository.swift Updates stored running mode mapping to .observer.
ios/Shaker/Data/PurchaselySDKMode.swift Maps app “paywall observer” selection to v6 .observer running mode.
ios/project.yml Switches Purchasely dependency to local package and adjusts test strict concurrency setting.
ios/LocalPackages/Purchasely/Package.swift Adds local SPM package wrapper definition for Purchasely sources.
ios/LocalPackages/Purchasely/Shims/Exports.swift Adds exported imports shim for Purchasely sources in SPM.
ios/LocalPackages/Purchasely/Shims/PLYLottieView.swift Adds Swift shim for PLYLottieView to avoid mixed ObjC/Swift target issues in SwiftPM.
docs/migration_v6.md Adds a repo-specific migration guide for Purchasely v6 (Android + iOS).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread settings.gradle.kts
Comment thread android/settings.gradle.kts
Comment thread android/app/build.gradle.kts
Comment thread android/build.gradle.kts
Comment thread android/app/src/main/java/com/purchasely/shaker/purchasely/PurchaselyWrapper.kt Outdated
Comment thread ios/Shaker/Purchasely/PurchaselyWrapper.swift
Comment thread ios/Shaker/Purchasely/PurchaselyWrapper.swift Outdated
Comment thread ios/Shaker/Purchasely/PurchaselyWrapper.swift Outdated
Comment thread ios/LocalPackages/Purchasely/Package.swift
Comment thread android/app/src/test/java/com/purchasely/shaker/data/RunningModeRepositoryTest.kt Outdated
Comment thread android/app/src/main/java/com/purchasely/shaker/data/PurchaselySdkMode.kt Outdated
Comment thread android/app/src/main/java/com/purchasely/shaker/data/RunningModeRepository.kt Outdated
Comment thread android/settings.gradle.kts
- PurchaselySdkModeTest/SettingsViewModelTest: replace removed PAYWALL_OBSERVER
  enum refs with OBSERVER and fix storageValue/label assertions (compile error)
- RunningModeRepositoryTest: assert persisted value is "observer" not "paywallObserver"
- PurchaselySdkMode: replace "pay" + "wallObserver" split with named
  LEGACY_PAYWALL_OBSERVER constant
- RunningModeRepository: drop dead LEGACY_OBSERVER branch (fromStorage handles it)
- PurchaselyWrapper (Android+iOS): return SUCCESS instead of NOT_HANDLED for
  Observer-mode cancellation/ignored actions so the SDK does not run its own flow
…ptors)

Return SUCCESS instead of NOT_HANDLED for Observer-mode purchase/restore
cancellations and ignored duplicate actions, so the SDK does not run its own
purchase/restore flow (the v6 equivalent of v5 processAction(false)).

Copy link
Copy Markdown
Contributor

Addressed the review findings across 97d74a9 (tests + data) and f801a15 (interceptors). Note: the Android suite isn't runnable in CI yet (io.purchasely:core:6.0.0 isn't on Maven Central), so the test fixes were verified by inspection against the actual enum shape.

# Finding Disposition
1 PurchaselySdkModeTest references removed PAYWALL_OBSERVER + stale storageValue/label assertions (compile error) Fixed 97d74a9 — all refs → OBSERVER, storageValue"observer", label"Presentation Observer". Also fixed a second stale ref the report missed: SettingsViewModelTest.kt:296 (same compile error).
2 RunningModeRepositoryTest asserts persisted "paywallObserver" but setter writes "observer" Fixed 97d74a9 — assertion → "observer", test renamed.
3 PurchaselySdkMode "pay" + "wallObserver" split literal Fixed 97d74a9 — named LEGACY_PAYWALL_OBSERVER = "paywallObserver" constant.
4 RunningModeRepository LEGACY_OBSERVER branch is dead code Fixed 97d74a9 — branch + constant removed; fromStorage() already maps "observer" and legacy "paywallObserver" to OBSERVER.
5 mavenLocal() must be removed before merging to main Kept (tracked)6.0.0 isn't on Maven Central yet, so removing it breaks the build now. Tracked in the PR checklist; inline threads left open as the pre-merge reminder.

Additional Copilot findings also addressed inline:

  • Observer-mode interceptor returns (Android + iOS): cancellation / ignored-duplicate now return SUCCESS/.success instead of NOT_HANDLED/.notHandled, so the SDK no longer runs its own purchase flow (v6 equivalent of v5 processAction(false), per docs/paywall-observer-reference.md). Fixed in f801a15.
  • Kotlin plugin removal: intentional — AGP 9.2.1 ships built-in Kotlin (verified :app:compileDebugUnitTestKotlin is registered). Not a break.
  • iOS forced StoreKit 1 and local SPM symlink: documented pre-release accommodations; left open for visibility, same as mavenLocal().

Generated by Claude Code

Copy link
Copy Markdown
Contributor

@greptileai review


Generated by Claude Code

@kherembourg

Copy link
Copy Markdown
Contributor

@greptileai review

kherembourg and others added 3 commits May 29, 2026 12:49
…SS on cancel)

Complements 97d74a9 (which only fixed PAYWALL_OBSERVER refs) and f801a15:
- Detail/Favorites/Home/Settings ViewModelTest: show*Paywall -> show*Presentation,
  onPaywallDismissed -> onPresentationDismissed (remaining compile errors)
- DetailViewModelTest: named args for loadPresentation (v6 inserts screenId at
  position 1, shifting contentId)
- PurchaselyWrapperTest: Cancelled now resolves with SUCCESS, matching the v6
  Observer-mode block of the SDK default flow introduced in f801a15

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… work

Opening the repo root in Android Studio uses the root Gradle build, which
had drifted from the canonical android/ build during the v6/AGP 9 migration:

- build.gradle.kts referenced libs.plugins.kotlin.android, an alias the
  version catalog no longer defines -> sync failed with
  "Unresolved reference 'android'".
- gradle.properties carried AGP-9 opt-out flags absent from
  android/gradle.properties, notably android.builtInKotlin=false, which
  disabled AGP's built-in Kotlin support. With no kotlin-android plugin
  applied, no compile*Kotlin tasks were created: assembleDebug produced an
  APK without compiling Kotlin and testDebugUnitTest reported NO-SOURCE.

Aligns both root files with the proven android/ configuration. Verified from
the root: sync (help) OK, compileDebugKotlin present, assembleDebug compiles
Kotlin, and testDebugUnitTest runs 180 tests with 0 failures.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

3 participants