Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ public void onRestoreError(@NonNull PurchasesError error) {}

@Override
public void onRestoreCompleted(@NonNull CustomerInfo customerInfo) {}

@Override
public void onWebCheckoutOpened() {}
};
}
}
1 change: 1 addition & 0 deletions ui/revenuecatui/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ package com.revenuecat.purchases.ui.revenuecatui {
method public default void onRestoreError(com.revenuecat.purchases.PurchasesError error);
method public default void onRestoreInitiated(com.revenuecat.purchases.ui.revenuecatui.utils.Resumable resume);
method public default void onRestoreStarted();
method public default void onWebCheckoutOpened();
}

@androidx.compose.runtime.Immutable @dev.drewhamilton.poko.Poko public final class PaywallOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,6 +395,7 @@ private fun rememberPaywallActionHandler(viewModel: PaywallViewModel): suspend (
} else {
viewModel.invalidateCustomerInfoCache()
context.handleUrlDestination(url, action.openMethod)
viewModel.notifyWebCheckoutOpened()
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
if (action.autoDismiss) {
Logger.d("Auto-dismissing paywall after launching web checkout.")
viewModel.closePaywall()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,10 @@ private class LoadingViewModel(
return null
}

override fun notifyWebCheckoutOpened() {
// no-op
}

override fun invalidateCustomerInfoCache() {
// no-op
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,11 @@ public interface PaywallListener {
public fun onRestoreStarted() {}
public fun onRestoreCompleted(customerInfo: CustomerInfo) {}
public fun onRestoreError(error: PurchasesError) {}

/**
* Called when the user taps a web checkout CTA and the external payment URL was opened.
* This is distinct from cancellation: the user has not cancelled, they left the app to
* complete payment externally.
*/
public fun onWebCheckoutOpened() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,10 @@ internal class PaywallActivity : ComponentActivity() {
userListener?.onRestoreError(error)
setResult(RESULT_OK, createResultIntent(PaywallResult.Error(error)))
}

override fun onWebCheckoutOpened() {
userListener?.onWebCheckoutOpened()
}
}

val edgeToEdge = args?.edgeToEdge == true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,7 @@ internal interface PaywallViewModel {
fun onTransitionComplete(transitionId: Int)

fun getWebCheckoutUrl(launchWebCheckout: PaywallAction.External.LaunchWebCheckout): String?
fun notifyWebCheckoutOpened()
fun invalidateCustomerInfoCache()

/**
Expand Down Expand Up @@ -414,6 +415,10 @@ internal class PaywallViewModelImpl(
return state.resolveWebCheckoutUrlForInteraction(launchWebCheckout)
}

override fun notifyWebCheckoutOpened() {
listener?.onWebCheckoutOpened()
}

override fun invalidateCustomerInfoCache() {
purchases.invalidateVirtualCurrenciesCache()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,7 @@ internal class MockViewModel(
validationWarning: PaywallWarning? = null,
private val allowsPurchases: Boolean = false,
private val shouldErrorOnUnsupportedMethods: Boolean = true,
private val webCheckoutUrl: String? = null,
) : ViewModel(), PaywallViewModel {
override val resourceProvider: ResourceProvider
get() = MockResourceProvider()
Expand Down Expand Up @@ -619,7 +620,13 @@ internal class MockViewModel(
override fun getWebCheckoutUrl(launchWebCheckout: PaywallAction.External.LaunchWebCheckout): String? {
getWebCheckoutUrlCallCount++
getWebCheckoutUrlParams.add(launchWebCheckout)
return null
return webCheckoutUrl
}

var notifyWebCheckoutOpenedCallCount = 0
private set
override fun notifyWebCheckoutOpened() {
notifyWebCheckoutOpenedCallCount++
}

var invalidateCustomerInfoCacheCallCount = 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,49 @@ class PaywallActionTests {
assertEquals(1, viewModel.closePaywallCallCount)
}

@Test
fun `LaunchWebCheckout with autoDismiss notifies listener and closes paywall`(): Unit =
with(composeTestRule) {
// Arrange
val textColor = ColorScheme(ColorInfo.Hex(Color.Black.toArgb()))
val defaultLocale = LocaleId("en_US")
val localizationKey = LocalizationKey("web_checkout")
val localizationData = LocalizationData.Text("web checkout")
val localizations = nonEmptyMapOf(
defaultLocale to nonEmptyMapOf(localizationKey to localizationData),
)
val components = listOf(
PurchaseButtonComponent(
stack = StackComponent(
components = listOf(TextComponent(text = localizationKey, color = textColor)),
),
method = PurchaseButtonComponent.Method.WebCheckout(autoDismiss = true),
),
)
val offering = FakeOffering(components, localizations)
val viewModel = MockViewModel(
offering = offering,
allowsPurchases = true,
webCheckoutUrl = "https://checkout.example.com",
)
val options = PaywallOptions.Builder(dismissRequest = {}).setOffering(offering).build()

// Act
setContent { InternalPaywall(options, viewModel) }
// Buttons appear once in main content and once in sticky footer
onAllNodesWithText(localizationData.value)
.assertCountEquals(2)
.get(0)
.assertIsDisplayed()
.assertHasClickAction()
.performClick()
waitForIdle()

// Assert: listener notified once, paywall dismissed generically (no new result type)
assertEquals(1, viewModel.notifyWebCheckoutOpenedCallCount)
assertEquals(1, viewModel.closePaywallCallCount)
}

@Suppress("TestFunctionName")
private fun FakeOffering(data: PaywallComponentsData): Offering =
Offering(
Expand Down