Repository: kickstarter/android-oss
Pull request #2303: MBL-2319: Open a Backing Details webview for users who have gone through PM: Messages Activity
Author: login: leighdouglas
Created: 2025-04-17T16:37:53Z | Merged: 2025-04-17T19:46:08Z

# 📲 What

Changed entry point in Messages for view pledge to backing details webview for backers that have completed pledge management. 

# 🤔 Why

Rerouting users to the mobile web while we build out pledge management natively in "View Your Pledge" screen. Backer's need to be able to see their receipts in the meantime. 

# 🛠 How

- Created new feature flag
- Check for order on `Backing` object, and check if checkout is complete
- Updated v1 REST api call `fetchBacking` to graphql `getProjectBacking` query for one use case. `Order` has only been updated on the `Backing` struct in graph, and not on v1
- Created a few helper methods to check the Backing object for an Order, and if the order is complete, as well as an Activity extension function to open BackingDetails web view. Will update PPO to use this ext function in subsequent PRs.

# 👀 See



# 📋 QA

See https://kickstarter.atlassian.net/browse/MBL-2313 for accounts you can log into for QA
Test with project Sustainable Cooler 2.0

# Story 📖

[MBL-2319: Open a Backing Details webview for users who have gone through PM: Messages Activity](https://kickstarter.atlassian.net/browse/MBL-2319)



Diff:
diff --git a/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt b/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt
index cf6f12181d..af255e7d04 100644
--- a/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt
+++ b/app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt
@@ -82,6 +82,7 @@ enum class FlagKey(val key: String) {
     ANDROID_PLEDGED_PROJECTS_OVERVIEW_V2("android_pledged_projects_overview_v2"),
     ANDROID_SEARCH_FILTER("android_search_filter"),
     ANDROID_REWARD_SHIPMENT_TRACKING("android_reward_shipment_tracking"),
+    ANDROID_COMPLETED_PM_CHECKOUT_WEBVIEW("android_completed_pm_checkout_webview")
 }
 
 fun FeatureFlagClient.getFetchInterval(): Long =
diff --git a/app/src/main/java/com/kickstarter/libs/utils/extensions/BackingExt.kt b/app/src/main/java/com/kickstarter/libs/utils/extensions/BackingExt.kt
index 9bf06e0a65..95d3c70fae 100644
--- a/app/src/main/java/com/kickstarter/libs/utils/extensions/BackingExt.kt
+++ b/app/src/main/java/com/kickstarter/libs/utils/extensions/BackingExt.kt
@@ -4,6 +4,7 @@ package com.kickstarter.libs.utils.extensions
 import com.kickstarter.libs.utils.RewardUtils.isNoReward
 import com.kickstarter.libs.utils.RewardUtils.isShippable
 import com.kickstarter.models.Backing
+import com.kickstarter.models.Order
 import com.kickstarter.models.Project
 import com.kickstarter.models.Reward
 
@@ -21,6 +22,10 @@ fun Backing.isShippable(): Boolean {
     return isShippable(reward)
 }
 
+fun Backing.isOrderPresentAndComplete(): Boolean {
+    return this.order().isNotNull() && this.order()?.checkoutState() == Order.CheckoutStateEnum.COMPLETE
+}
+
 fun Backing.backedReward(project: Project): Reward? {
     val rewards = project.rewards() ?: return null
     for (reward in rewards) {
diff --git a/app/src/main/java/com/kickstarter/mock/factories/BackingFactory.kt b/app/src/main/java/com/kickstarter/mock/factories/BackingFactory.kt
index e178394ee1..d6c5da2fa0 100644
--- a/app/src/main/java/com/kickstarter/mock/factories/BackingFactory.kt
+++ b/app/src/main/java/com/kickstarter/mock/factories/BackingFactory.kt
@@ -5,6 +5,7 @@ import com.kickstarter.mock.factories.ProjectFactory.project
 import com.kickstarter.mock.factories.UserFactory.user
 import com.kickstarter.models.Backing
 import com.kickstarter.models.Backing.Companion.builder
+import com.kickstarter.models.Order
 import com.kickstarter.models.Project
 import com.kickstarter.models.Reward
 import com.kickstarter.models.User
@@ -85,4 +86,28 @@ object BackingFactory {
             .status(status)
             .build()
     }
+
+    @JvmStatic
+    @JvmOverloads
+    fun backingWithCompletePmOrder(url: String): Backing {
+        val project = project()
+        val reward = RewardFactory.reward()
+        val order = Order.builder().checkoutState(Order.CheckoutStateEnum.COMPLETE).build()
+        return Backing.builder()
+            .amount(10.0)
+            .backerId(IdFactory.id().toLong())
+            .backingDetailsPageRoute(url)
+            .cancelable(true)
+            .id(IdFactory.id().toLong())
+            .sequence(1)
+            .order(order)
+            .reward(reward)
+            .rewardId(reward.id())
+            .paymentSource(PaymentSourceFactory.visa())
+            .pledgedAt(DateTime.now())
+            .projectId(project.id())
+            .shippingAmount(0.0f)
+            .status(Backing.STATUS_PLEDGED)
+            .build()
+    }
 }
diff --git a/app/src/main/java/com/kickstarter/ui/activities/MessagesActivity.kt b/app/src/main/java/com/kickstarter/ui/activities/MessagesActivity.kt
index ddbcc67941..bffe289554 100644
--- a/app/src/main/java/com/kickstarter/ui/activities/MessagesActivity.kt
+++ b/app/src/main/java/com/kickstarter/ui/activities/MessagesActivity.kt
@@ -36,6 +36,7 @@ import com.kickstarter.ui.adapters.MessagesAdapter
 import com.kickstarter.ui.extensions.finishWithAnimation
 import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
 import com.kickstarter.ui.extensions.startActivityWithTransition
+import com.kickstarter.ui.extensions.startBackingDetailsWebViewActivity
 import com.kickstarter.viewmodels.MessagesViewModel
 import io.reactivex.android.schedulers.AndroidSchedulers
 import io.reactivex.disposables.CompositeDisposable
@@ -212,6 +213,11 @@ class MessagesActivity : AppCompatActivity() {
             .subscribe { startProjectPageActivity(it) }
             .addToDisposable(disposables)
 
+        viewModel.outputs.startBackingDetailsWebActivity()
+            .observeOn(AndroidSchedulers.mainThread())
+            .subscribe { startBackingDetailsWebViewActivity(it) }
+            .addToDisposable(disposables)
+
         viewModel.outputs.toolbarIsExpanded()
             .observeOn(AndroidSchedulers.mainThread())
             .subscribe { binding.messagesAppBarLayout.setExpanded(it) }
diff --git a/app/src/main/java/com/kickstarter/ui/activities/ProjectPageActivity.kt b/app/src/main/java/com/kickstarter/ui/activities/ProjectPageActivity.kt
index 4e3cd009f9..c0feac388d 100644
--- a/app/src/main/java/com/kickstarter/ui/activities/ProjectPageActivity.kt
+++ b/app/src/main/java/com/kickstarter/ui/activities/ProjectPageActivity.kt
@@ -47,7 +47,6 @@ import com.google.android.material.tabs.TabLayoutMediator
 import com.google.firebase.crashlytics.FirebaseCrashlytics
 import com.kickstarter.R
 import com.kickstarter.databinding.ActivityProjectPageBinding
-import com.kickstarter.features.pledgedprojectsoverview.ui.BackingDetailsActivity
 import com.kickstarter.libs.ActivityRequestCodes
 import com.kickstarter.libs.Either
 import com.kickstarter.libs.Environment
@@ -82,6 +81,7 @@ import com.kickstarter.ui.extensions.selectPledgeFragment
 import com.kickstarter.ui.extensions.setUpConnectivityStatusCheck
 import com.kickstarter.ui.extensions.showErrorToast
 import com.kickstarter.ui.extensions.showSnackbar
+import com.kickstarter.ui.extensions.startBackingDetailsWebViewActivity
 import com.kickstarter.ui.extensions.startDisclaimerChromeTab
 import com.kickstarter.ui.extensions.startPledgeRedemption
 import com.kickstarter.ui.extensions.startRootCommentsActivity
@@ -459,7 +459,7 @@ class ProjectPageActivity :
 
         this.viewModel.outputs.openBackingDetailsWebview()
             .observeOn(AndroidSchedulers.mainThread())
-            .subscribe { openBackingDetailsWebView(it) }
+            .subscribe { startBackingDetailsWebViewActivity(it) }
             .addToDisposable(disposables)
 
         this.viewModel.outputs.projectMedia()
@@ -1243,12 +1243,6 @@ class ProjectPageActivity :
         )
     }
 
-    private fun openBackingDetailsWebView(url: String) {
-        val intent = Intent(this, BackingDetailsActivity::class.java)
-            .putExtra(IntentKey.URL, url)
-        startActivity(intent)
-    }
-
     private fun styleProjectActionButton(detailsAreVisible: Boolean) {
         val buttonParams =
             binding.pledgeContainerLayout.pledgeActionButton.layoutParams as LinearLayout.LayoutParams
diff --git a/app/src/main/java/com/kickstarter/ui/extensions/ActivityExt.kt b/app/src/main/java/com/kickstarter/ui/extensions/ActivityExt.kt
index 45efd4be95..2793685d13 100644
--- a/app/src/main/java/com/kickstarter/ui/extensions/ActivityExt.kt
+++ b/app/src/main/java/com/kickstarter/ui/extensions/ActivityExt.kt
@@ -17,6 +17,7 @@ import androidx.lifecycle.Lifecycle
 import com.google.android.play.core.review.ReviewInfo
 import com.google.android.play.core.review.ReviewManagerFactory
 import com.kickstarter.R
+import com.kickstarter.features.pledgedprojectsoverview.ui.BackingDetailsActivity
 import com.kickstarter.libs.Environment
 import com.kickstarter.libs.MessagePreviousScreenType
 import com.kickstarter.libs.RefTag
@@ -322,3 +323,10 @@ fun Activity.startCreatorMessageActivity(project: Project, previousScreen: Messa
             .putExtra(IntentKey.BACKING, project.backing())
     )
 }
+
+fun Activity.startBackingDetailsWebViewActivity(url: String) {
+    startActivity(
+        Intent(this, BackingDetailsActivity::class.java)
+            .putExtra(IntentKey.URL, url)
+    )
+}
diff --git a/app/src/main/java/com/kickstarter/viewmodels/MessagesViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/MessagesViewModel.kt
index b05b65ff2f..a595648428 100644
--- a/app/src/main/java/com/kickstarter/viewmodels/MessagesViewModel.kt
+++ b/app/src/main/java/com/kickstarter/viewmodels/MessagesViewModel.kt
@@ -10,11 +10,14 @@ import com.kickstarter.libs.Either.Left
 import com.kickstarter.libs.Either.Right
 import com.kickstarter.libs.Environment
 import com.kickstarter.libs.MessagePreviousScreenType
+import com.kickstarter.libs.featureflag.FeatureFlagClientType
+import com.kickstarter.libs.featureflag.FlagKey
 import com.kickstarter.libs.rx.transformers.Transformers
 import com.kickstarter.libs.utils.ListUtils
 import com.kickstarter.libs.utils.extensions.addToDisposable
 import com.kickstarter.libs.utils.extensions.isNonZero
 import com.kickstarter.libs.utils.extensions.isNotNull
+import com.kickstarter.libs.utils.extensions.isOrderPresentAndComplete
 import com.kickstarter.libs.utils.extensions.isPresent
 import com.kickstarter.libs.utils.extensions.negate
 import com.kickstarter.models.Backing
@@ -24,6 +27,7 @@ import com.kickstarter.models.MessageThread
 import com.kickstarter.models.Project
 import com.kickstarter.models.User
 import com.kickstarter.services.ApiClientTypeV2
+import com.kickstarter.services.ApolloClientTypeV2
 import com.kickstarter.services.apiresponses.ErrorEnvelope
 import com.kickstarter.services.apiresponses.MessageThreadEnvelope
 import com.kickstarter.ui.IntentKey
@@ -116,6 +120,9 @@ interface MessagesViewModel {
         /** Emits when we should start the [ProjectPageActivity].  */
         fun startProjectPageActivity(): Observable<Project>
 
+        /** Emits when we should start the [BackingDetailsActivity].  */
+        fun startBackingDetailsWebActivity(): Observable<String>
+
         /** Emits when the thread has been marked as read.  */
         fun successfullyMarkedAsRead(): Observable<Unit>
 
@@ -130,8 +137,10 @@ interface MessagesViewModel {
         ViewModel(),
         Inputs,
         Outputs {
-        private val client: ApiClientTypeV2
+        private val apiClient: ApiClientTypeV2
+        private val apolloClient: ApolloClientTypeV2
         private val currentUser: CurrentUserTypeV2
+        private val featureFlagClient: FeatureFlagClientType
         private val disposables = CompositeDisposable()
 
         private fun intent() = intent?.let { Observable.just(it) } ?: Observable.empty()
@@ -169,6 +178,7 @@ interface MessagesViewModel {
         private val setMessageEditText: Observable<String>
         private val startBackingActivity = PublishSubject.create<BackingWrapper>()
         private val startProjectPageActivity = PublishSubject.create<Project>()
+        private val startBackingDetailsWebActivity = PublishSubject.create<String>()
         private val successfullyMarkedAsRead = BehaviorSubject.create<Unit>()
         private val toolbarIsExpanded: Observable<Boolean>
         private val viewPledgeButtonIsGone = BehaviorSubject.create<Boolean>()
@@ -223,6 +233,7 @@ interface MessagesViewModel {
         override fun setMessageEditText(): Observable<String> = setMessageEditText
         override fun startBackingActivity(): Observable<BackingWrapper> = startBackingActivity
         override fun startProjectPageActivity(): Observable<Project> = startProjectPageActivity
+        override fun startBackingDetailsWebActivity(): Observable<String> = startBackingDetailsWebActivity
         override fun successfullyMarkedAsRead(): Observable<Unit> = successfullyMarkedAsRead
         override fun toolbarIsExpanded(): Observable<Boolean> = toolbarIsExpanded
         override fun viewPledgeButtonIsGone(): Observable<Boolean> = viewPledgeButtonIsGone
@@ -230,7 +241,8 @@ interface MessagesViewModel {
         companion object {
             private fun backingAndProjectFromData(
                 data: MessagesData,
-                client: ApiClientTypeV2
+                apiClient: ApiClientTypeV2,
+                apolloClient: ApolloClientTypeV2
             ): Observable<Pair<Backing?, Project>> {
                 val backingAndProjectObs = data.backingOrThread.either(
                     ifLeft = {
@@ -238,10 +250,10 @@ interface MessagesViewModel {
                     },
                     ifRight = {
                         val backingNotification =
-                            if (data.project.isBacking()) client.fetchProjectBacking(
-                                data.project,
-                                data.currentUser
-                            ).materialize().share() else client.fetchProjectBacking(
+                            if (data.project.isBacking()) apolloClient.getProjectBacking(
+                                data.project.slug() ?: ""
+                            ).materialize().share()
+                            else apiClient.fetchProjectBacking(
                                 data.project,
                                 data.participant
                             ).materialize().share()
@@ -260,9 +272,10 @@ interface MessagesViewModel {
         }
 
         init {
-            client = requireNotNull(environment.apiClientV2())
+            apolloClient = requireNotNull(environment.apolloClientV2())
+            apiClient = requireNotNull(environment.apiClientV2())
             currentUser = requireNotNull(environment.currentUserV2())
-
+            featureFlagClient = requireNotNull(environment.featureFlagClient())
             val configData = intent()
                 .map { i: Intent ->
                     val messageThread =
@@ -290,18 +303,18 @@ interface MessagesViewModel {
                 .filter { it.isNotNull() && it.left().isNotNull() }
                 .map { requireNotNull(it.left()) }
 
-            val backingOrThread: Observable<Either<Backing, MessageThread>> = Observable.merge<Either<Backing, MessageThread>?>(
-                configBacking.filter { it.isNotNull() }.map { Left(it) },
-                configThread.filter { it.isNotNull() }.map { Right(it) }
-            )
+            val backingOrThread: Observable<Either<Backing, MessageThread>> =
+                Observable.merge<Either<Backing, MessageThread>?>(
+                    configBacking.filter { it.isNotNull() }.map { Left(it) },
+                    configThread.filter { it.isNotNull() }.map { Right(it) }
+                )
 
             val messageIsSending = PublishSubject.create<Boolean>()
             val messagesAreLoading = PublishSubject.create<Boolean>()
 
             val project = configData
                 .map { data: Either<MessageThread, Pair<Project?, Backing?>> ->
-                    data.either({ obj: MessageThread -> obj.project() }) {
-                            projectAndBacking: Pair<Project?, Backing?> ->
+                    data.either({ obj: MessageThread -> obj.project() }) { projectAndBacking: Pair<Project?, Backing?> ->
                         projectAndBacking.first
                     }
                 }.filter { it.isNotNull() }
@@ -311,10 +324,10 @@ interface MessagesViewModel {
                 .switchMap {
                     it.either(
                         ifLeft = { backing ->
-                            client.fetchMessagesForBacking(backing)
+                            apiClient.fetchMessagesForBacking(backing)
                         },
                         ifRight = { messageThread ->
-                            client.fetchMessagesForThread(messageThread)
+                            apiClient.fetchMessagesForThread(messageThread)
                         }
                     )
                         .doOnSubscribe { messagesAreLoading.onNext(true) }
@@ -390,7 +403,7 @@ interface MessagesViewModel {
                     )
                 )
                 .switchMap {
-                    client.sendMessage(
+                    apiClient.sendMessage(
                         it.first, it.second
                     )
                         .doOnSubscribe { messageIsSending.onNext(true) }
@@ -406,11 +419,11 @@ interface MessagesViewModel {
                 .compose(Transformers.takeWhenV2(messageSent))
                 .switchMap {
                     it.either({ backing: Backing ->
-                        client.fetchMessagesForBacking(
+                        apiClient.fetchMessagesForBacking(
                             backing
                         )
                     }) { messageThread: MessageThread ->
-                        client.fetchMessagesForThread(
+                        apiClient.fetchMessagesForThread(
                             messageThread
                         )
                     }
@@ -431,7 +444,7 @@ interface MessagesViewModel {
                 .filter { it.isNotNull() && it.messageThread().isNotNull() }
                 .map { requireNotNull(it.messageThread()) }
                 .switchMap {
-                    client.markAsRead(
+                    apiClient.markAsRead(
                         it
                     )
                 }
@@ -496,7 +509,7 @@ interface MessagesViewModel {
 
             messagesData
                 .switchMap {
-                    backingAndProjectFromData(it, client)
+                    backingAndProjectFromData(it, apiClient, apolloClient)
                 }
                 .filter { it.isNotNull() }
                 .subscribe { backingAndProject ->
@@ -575,13 +588,18 @@ interface MessagesViewModel {
                             .map { requireNotNull(it) }
                     )
                 )
-                .filter { it.second.first.isNotNull() }
                 .map {
                     BackingWrapper(
                         requireNotNull(it.second.first), it.first.second, it.first.first
                     )
+                }.subscribe {
+                    if (featureFlagClient?.getBoolean(FlagKey.ANDROID_COMPLETED_PM_CHECKOUT_WEBVIEW) ?: false && it.backing.isOrderPresentAndComplete())
+                        it.backing.backingDetailsPageRoute()?.let {
+                            startBackingDetailsWebActivity.onNext(it)
+                        } else {
+                        startBackingActivity.onNext(it)
+                    }
                 }
-                .subscribe { v: BackingWrapper -> startBackingActivity.onNext(v) }
                 .addToDisposable(disposables)
 
             // Set only the initial padding once to counteract the appbar offset.
diff --git a/app/src/main/java/com/kickstarter/viewmodels/projectpage/ProjectPageViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/projectpage/ProjectPageViewModel.kt
index ef4afb50c6..8991b5206d 100644
--- a/app/src/main/java/com/kickstarter/viewmodels/projectpage/ProjectPageViewModel.kt
+++ b/app/src/main/java/com/kickstarter/viewmodels/projectpage/ProjectPageViewModel.kt
@@ -39,6 +39,7 @@ import com.kickstarter.libs.utils.extensions.isErrored
 import com.kickstarter.libs.utils.extensions.isFalse
 import com.kickstarter.libs.utils.extensions.isNonZero
 import com.kickstarter.libs.utils.extensions.isNotNull
+import com.kickstarter.libs.utils.extensions.isOrderPresentAndComplete
 import com.kickstarter.libs.utils.extensions.isTrue
 import com.kickstarter.libs.utils.extensions.isUIEmptyValues
 import com.kickstarter.libs.utils.extensions.metadataForProject
@@ -47,7 +48,6 @@ import com.kickstarter.libs.utils.extensions.showLatePledgeFlow
 import com.kickstarter.libs.utils.extensions.updateProjectWith
 import com.kickstarter.libs.utils.extensions.userIsCreator
 import com.kickstarter.models.Backing
-import com.kickstarter.models.Order
 import com.kickstarter.models.Project
 import com.kickstarter.models.Reward
 import com.kickstarter.models.User
@@ -771,9 +771,8 @@ interface ProjectPageViewModel {
             this.nativeProjectActionButtonClicked
                 .withLatestFrom(currentProject) { _, project -> project }
                 .subscribe { project ->
-                    val order = project.backing()?.order()
-                    val completedPM = order != null && order.checkoutState() == Order.CheckoutStateEnum.COMPLETE
-                    if (completedPM) {
+                    val netNewBackFeatureFlag = featureFlagClient?.getBoolean(FlagKey.ANDROID_COMPLETED_PM_CHECKOUT_WEBVIEW) ?: false
+                    if (project.backing()?.isOrderPresentAndComplete() ?: false && netNewBackFeatureFlag) {
                         // Open webview to backing/survey_responses endpoint instead of backing/details
                         // endpoint to avoid prompting user to re-login
                         project.backing()?.backingDetailsPageRoute()?.let {
diff --git a/app/src/test/java/com/kickstarter/viewmodels/MessagesViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/MessagesViewModelTest.kt
index 804943fc26..6c0e1557c2 100644
--- a/app/src/test/java/com/kickstarter/viewmodels/MessagesViewModelTest.kt
+++ b/app/src/test/java/com/kickstarter/viewmodels/MessagesViewModelTest.kt
@@ -7,9 +7,12 @@ import com.kickstarter.libs.CurrentUserTypeV2
 import com.kickstarter.libs.Environment
 import com.kickstarter.libs.MessagePreviousScreenType
 import com.kickstarter.libs.MockCurrentUserV2
+import com.kickstarter.libs.featureflag.FlagKey
 import com.kickstarter.libs.utils.extensions.addToDisposable
 import com.kickstarter.libs.utils.extensions.reduceProjectPayload
+import com.kickstarter.mock.MockFeatureFlagClient
 import com.kickstarter.mock.factories.ApiExceptionFactory
+import com.kickstarter.mock.factories.BackingFactory
 import com.kickstarter.mock.factories.BackingFactory.backing
 import com.kickstarter.mock.factories.MessageFactory.message
 import com.kickstarter.mock.factories.MessageThreadEnvelopeFactory.empty
@@ -57,6 +60,7 @@ class MessagesViewModelTest : KSRobolectricTestCase() {
     private val setMessageEditText = TestSubscriber<String>()
     private val showMessageErrorToast = TestSubscriber<String>()
     private val startBackingActivity = TestSubscriber<BackingWrapper>()
+    private val startBackingDetailsWebViewActivity = TestSubscriber<String>()
     private val startProjectPageActivity = TestSubscriber<Project>()
     private val successfullyMarkedAsRead = TestSubscriber<Unit>()
     private val toolbarIsExpanded = TestSubscriber<Boolean>()
@@ -91,6 +95,7 @@ class MessagesViewModelTest : KSRobolectricTestCase() {
         vm.outputs.successfullyMarkedAsRead().subscribe { successfullyMarkedAsRead.onNext(it) }.addToDisposable(disposables)
         vm.outputs.toolbarIsExpanded().subscribe { toolbarIsExpanded.onNext(it) }.addToDisposable(disposables)
         vm.outputs.viewPledgeButtonIsGone().subscribe { viewPledgeButtonIsGone.onNext(it) }.addToDisposable(disposables)
+        vm.outputs.startBackingDetailsWebActivity().subscribe { startBackingDetailsWebViewActivity.onNext(it) }.addToDisposable(disposables)
     }
 
     @Test
@@ -681,6 +686,96 @@ class MessagesViewModelTest : KSRobolectricTestCase() {
         viewPledgeButtonIsGone.assertValues(false)
     }
 
+    @Test
+    fun `test open backing details web view when order complete and feature flag on`() {
+        val url = "https://ksr.com/backing/survey_responses"
+        val user = user()
+        val project = project().toBuilder().isBacking(true).build()
+        val backing = BackingFactory.backingWithCompletePmOrder(url)
+        val apiClient: MockApiClientV2 = object : MockApiClientV2() {
+            override fun fetchProjectBacking(project: Project, user: User): Observable<Backing> {
+                return Observable.just(backing)
+            }
+        }
+
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return true
+            }
+        }
+
+        setUpEnvironment(
+            environment().toBuilder().apiClientV2(apiClient).currentUserV2(MockCurrentUserV2(user)).featureFlagClient(mockFeatureFlagClient)
+                .build(),
+            backerModalContextIntent(backing, project)
+        )
+
+        vm.inputs.viewPledgeButtonClicked()
+
+        this.startBackingActivity.assertNoValues()
+        this.startBackingDetailsWebViewActivity.assertValue(url)
+    }
+
+    @Test
+    fun `test open native view pledge when order complete and feature flag off`() {
+        val url = "https://ksr.com/backing/survey_responses"
+        val user = user()
+        val project = project().toBuilder().isBacking(true).build()
+        val backing = BackingFactory.backingWithCompletePmOrder(url)
+        val apiClient: MockApiClientV2 = object : MockApiClientV2() {
+            override fun fetchProjectBacking(project: Project, user: User): Observable<Backing> {
+                return Observable.just(backing)
+            }
+        }
+
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return false
+            }
+        }
+
+        setUpEnvironment(
+            environment().toBuilder().apiClientV2(apiClient).currentUserV2(MockCurrentUserV2(user)).featureFlagClient(mockFeatureFlagClient)
+                .build(),
+            backerModalContextIntent(backing, project)
+        )
+
+        vm.inputs.viewPledgeButtonClicked()
+
+        this.startBackingActivity.assertValue(BackingWrapper(backing, user, project))
+        this.startBackingDetailsWebViewActivity.assertNoValues()
+    }
+
+    @Test
+    fun `test open native view pledge when order null and feature flag on`() {
+        val url = "https://ksr.com/backing/survey_responses"
+        val user = user()
+        val project = project().toBuilder().isBacking(true).build()
+        val backing = backing()
+        val apiClient: MockApiClientV2 = object : MockApiClientV2() {
+            override fun fetchProjectBacking(project: Project, user: User): Observable<Backing> {
+                return Observable.just(backing)
+            }
+        }
+
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return true
+            }
+        }
+
+        setUpEnvironment(
+            environment().toBuilder().apiClientV2(apiClient).currentUserV2(MockCurrentUserV2(user)).featureFlagClient(mockFeatureFlagClient)
+                .build(),
+            backerModalContextIntent(backing, project)
+        )
+
+        vm.inputs.viewPledgeButtonClicked()
+
+        this.startBackingActivity.assertValue(BackingWrapper(backing, user, project))
+        this.startBackingDetailsWebViewActivity.assertNoValues()
+    }
+
     companion object {
         private fun backerModalContextIntent(backing: Backing, project: Project): Intent {
             return Intent()
diff --git a/app/src/test/java/com/kickstarter/viewmodels/ProjectPageViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/ProjectPageViewModelTest.kt
index 1c0355d5fb..3d0f6b4e72 100644
--- a/app/src/test/java/com/kickstarter/viewmodels/ProjectPageViewModelTest.kt
+++ b/app/src/test/java/com/kickstarter/viewmodels/ProjectPageViewModelTest.kt
@@ -2061,8 +2061,13 @@ class ProjectPageViewModelTest : KSRobolectricTestCase() {
     }
 
     @Test
-    fun testOpenBackingDetailsWebview() { // The testExpandPledgeSheet_ suite of tests covers projects that do not have PM checkout
-        setUpEnvironment(environment().toBuilder().apolloClientV2(apolloClientSuccessfulGetProject()).build())
+    fun `test open Backing Details Webview when order not null and feature flag on`() { // The testExpandPledgeSheet_ suite of tests covers projects that do not have PM checkout
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return true
+            }
+        }
+        setUpEnvironment(environment().toBuilder().apolloClientV2(apolloClientSuccessfulGetProject()).featureFlagClient(mockFeatureFlagClient).build())
 
         // Start the view model with a backed project with PM checkout order
         val backedProject = ProjectFactory.backedProjectWithPMCheckoutOrder()
@@ -2071,9 +2076,51 @@ class ProjectPageViewModelTest : KSRobolectricTestCase() {
         this.vm.inputs.nativeProjectActionButtonClicked()
 
         val url = "https://ksr.com/backing/survey_responses"
+
+        this.expandPledgeSheet.assertNoValues()
         this.openBackingDetailsWebview.assertValue(url)
     }
 
+    @Test
+    fun `test open pledge sheet when order not null but feature flag off`() {
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return false
+            }
+        }
+        setUpEnvironment(environment().toBuilder().apolloClientV2(apolloClientSuccessfulGetProject()).featureFlagClient(mockFeatureFlagClient).build())
+
+        // Start the view model with a backed project with PM checkout order
+        val backedProject = ProjectFactory.backedProjectWithPMCheckoutOrder()
+        this.vm.configureWith(Intent().putExtra(IntentKey.PROJECT, backedProject))
+
+        this.vm.inputs.nativeProjectActionButtonClicked()
+
+        val url = "https://ksr.com/backing/survey_responses"
+        this.openBackingDetailsWebview.assertNoValues()
+        this.expandPledgeSheet.assertValue(Pair(true, true))
+    }
+
+    @Test
+    fun `test open pledge sheet when order null but feature flag on`() {
+        val mockFeatureFlagClient = object : MockFeatureFlagClient() {
+            override fun getBoolean(FlagKey: FlagKey): Boolean {
+                return false
+            }
+        }
+        setUpEnvironment(environment().toBuilder().apolloClientV2(apolloClientSuccessfulGetProject()).featureFlagClient(mockFeatureFlagClient).build())
+
+        // Start the view model with a backed project with PM checkout order
+        val backedProject = ProjectFactory.backedProject()
+        this.vm.configureWith(Intent().putExtra(IntentKey.PROJECT, backedProject))
+
+        this.vm.inputs.nativeProjectActionButtonClicked()
+
+        val url = "https://ksr.com/backing/survey_responses"
+        this.openBackingDetailsWebview.assertNoValues()
+        this.expandPledgeSheet.assertValue(Pair(true, true))
+    }
+
     @Test
     fun testProjectData_whenRefreshProjectIsCalled() {
         setUpEnvironment(environment().toBuilder().apolloClientV2(apolloClientSuccessfulGetProject()).build())


Reviews:
user: login: ycheng-kickstarter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T17:31:16Z
user: login: ycheng-kickstarter
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T18:34:10Z
user: login: leighdouglas
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:11:40Z
user: login: ycheng-kickstarter
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-17T19:18:35Z

Review comments:
diff_hunk: @@ -82,6 +82,7 @@ enum class FlagKey(val key: String) {
     ANDROID_PLEDGED_PROJECTS_OVERVIEW_V2("android_pledged_projects_overview_v2"),
     ANDROID_SEARCH_FILTER("android_search_filter"),
     ANDROID_REWARD_SHIPMENT_TRACKING("android_reward_shipment_tracking"),
+    ANDROID_NET_NEW_BACKERS_WEBVIEW("android_net_new_backers_webview")
path: app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt
user: login: ycheng-kickstarter
body: Would android_completed_pm_checkout_webview would be more inclusive?
created_at: 2025-04-17T17:31:16Z
author_association: CONTRIBUTOR
diff_hunk: @@ -21,6 +22,10 @@ fun Backing.isShippable(): Boolean {
     return isShippable(reward)
 }
 
+fun Backing.isOrderPresentAndComplete(): Boolean {
path: app/src/main/java/com/kickstarter/libs/utils/extensions/BackingExt.kt
user: login: ycheng-kickstarter
body: 💯
created_at: 2025-04-17T18:34:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -82,6 +82,7 @@ enum class FlagKey(val key: String) {
     ANDROID_PLEDGED_PROJECTS_OVERVIEW_V2("android_pledged_projects_overview_v2"),
     ANDROID_SEARCH_FILTER("android_search_filter"),
     ANDROID_REWARD_SHIPMENT_TRACKING("android_reward_shipment_tracking"),
+    ANDROID_NET_NEW_BACKERS_WEBVIEW("android_net_new_backers_webview")
path: app/src/main/java/com/kickstarter/libs/featureflag/FeatureFlagClient.kt
user: login: leighdouglas
body: updated to your naming suggestion, was keeping parity with ios but i agree that this better reflects the bucket of users that fall into this case
created_at: 2025-04-17T19:11:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: codecov-commenter
created_at: 2025-04-17T17:03:07Z
body: :warning: Please install the !['codecov app svg image'](https://github.com/codecov/engineering-team/assets/152432831/e90313f4-9d3a-4b63-8b54-cfe14e7ec20d) to ensure uploads and comments are reliably processed by Codecov.

## [Codecov](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) Report
Attention: Patch coverage is `70.96774%` with `9 lines` in your changes missing coverage. Please review.
> Project coverage is 66.52%. Comparing base [(`d745656`)](https://app.codecov.io/gh/kickstarter/android-oss/commit/d7456562330b4c459b83981a9c13f691a40ba028?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) to head [(`b6703bf`)](https://app.codecov.io/gh/kickstarter/android-oss/commit/b6703bf56aa5961cafc965c0eface609e956a761?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter).

| [Files with missing lines](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?dropdown=coverage&src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) | Patch % | Lines |
|---|---|---|
| [...va/com/kickstarter/viewmodels/MessagesViewModel.kt](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&filepath=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fkickstarter%2Fviewmodels%2FMessagesViewModel.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter#diff-YXBwL3NyYy9tYWluL2phdmEvY29tL2tpY2tzdGFydGVyL3ZpZXdtb2RlbHMvTWVzc2FnZXNWaWV3TW9kZWwua3Q=) | 74.07% | [1 Missing and 6 partials :warning: ](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) |
| [...om/kickstarter/libs/utils/extensions/BackingExt.kt](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&filepath=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fkickstarter%2Flibs%2Futils%2Fextensions%2FBackingExt.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter#diff-YXBwL3NyYy9tYWluL2phdmEvY29tL2tpY2tzdGFydGVyL2xpYnMvdXRpbHMvZXh0ZW5zaW9ucy9CYWNraW5nRXh0Lmt0) | 0.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) |
| [...ter/viewmodels/projectpage/ProjectPageViewModel.kt](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&filepath=app%2Fsrc%2Fmain%2Fjava%2Fcom%2Fkickstarter%2Fviewmodels%2Fprojectpage%2FProjectPageViewModel.kt&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter#diff-YXBwL3NyYy9tYWluL2phdmEvY29tL2tpY2tzdGFydGVyL3ZpZXdtb2RlbHMvcHJvamVjdHBhZ2UvUHJvamVjdFBhZ2VWaWV3TW9kZWwua3Q=) | 50.00% | [0 Missing and 1 partial :warning: ](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter) |

:exclamation: Your organization needs to install the [Codecov GitHub app](https://github.com/apps/codecov/installations/select_target) to enable full functionality.

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #2303      +/-   ##
============================================
- Coverage     66.53%   66.52%   -0.01%     
- Complexity     2246     2248       +2     
============================================
  Files           365      365              
  Lines         25210    25221      +11     
  Branches       3749     3753       +4     
============================================
+ Hits          16774    16779       +5     
  Misses         6505     6505              
- Partials       1931     1937       +6     
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/kickstarter/android-oss/pull/2303?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=kickstarter).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>
author_association: NONE
user: login: ycheng-kickstarter
created_at: 2025-04-17T18:35:29Z
body: QA looked great 🥰 just a renaming suggestion to avoid confusion in future
author_association: CONTRIBUTOR