Ir al contenido
DocuGraph

DocuGraph

Frontend Spec: RevenueCat Pro Paywall Subscription Modal Composable (PaywallModal)

Estado de fase (MASTER-00): MVP — alcance del MVP Shipaton sujeto a resultados de Gate 0 y PoC. Gobernanza: MASTER-00.

Frontend Spec: RevenueCat Pro Paywall Subscription Modal Composable (PaywallModal)

Sección titulada «Frontend Spec: RevenueCat Pro Paywall Subscription Modal Composable (PaywallModal)»

This specification details the subscription paywall dialog (PaywallModal) in :docugraph-app. It renders Pro tier benefits (unlimited projects, unrestricted context depth, cloud sync, automated CI/CD audits), displays monthly vs yearly pricing tiers fetched via RevenueCat Mobile SDK / REST API, manages purchase flows, updates local session tokens upon success, and syncs offline grace limits.

package io.docugraph.app.ui.paywall
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
@Composable
fun PaywallModal(
isOpen: Boolean,
onDismiss: () -> Unit,
onPurchaseSuccess: () -> Unit,
viewModel: PaywallViewModel,
modifier: Modifier = Modifier
)
package io.docugraph.app.ui.paywall
enum class SubscriptionPlan {
MONTHLY_PRO, // $19 / month
YEARLY_PRO // $190 / year (2 months free)
}
data class PaywallUiState(
val selectedPlan: SubscriptionPlan = SubscriptionPlan.YEARLY_PRO,
val isPurchasing: Boolean = false,
val monthlyPriceString: String = "$19.00 / mo",
val yearlyPriceString: String = "$190.00 / yr",
val errorMessage: String? = null,
val purchaseSuccessBanner: Boolean = false
)
Dialog (if isOpen, onDismissRequest = onDismiss)
└── Card (Modifier.width(520.dp), shape = RoundedCornerShape(16.dp))
└── Column (Padding 24.dp)
├── HeaderRow
│ ├── Text ("Upgrade to DocuGraph Pro 🚀", style = HeadlineSmall)
│ └── IconButton (Close -> onDismiss())
├── Text ("Unlock maximum productivity for AI coding agents", style = BodyMedium)
├── Spacer (Height 20.dp)
├── FeatureMatrixColumn
│ ├── FeatureItem (Icon: Check, Text: "Unlimited Active Projects (Free tier capped at 3)")
│ ├── FeatureItem (Icon: Check, Text: "Unrestricted MCP Traversal Depth (Free tier capped at depth=2)")
│ ├── FeatureItem (Icon: Check, Text: "Automated CI/CD Traceability Audits")
│ └── FeatureItem (Icon: Check, Text: "7-Day Offline Grace Mode")
├── Spacer (Height 24.dp)
├── PlanSelectionRow
│ ├── PlanCard (Monthly, Selected = selectedPlan == MONTHLY_PRO, OnClick = selectMonthly)
│ └── PlanCard (Yearly - Best Value Badge, Selected = selectedPlan == YEARLY_PRO, OnClick = selectYearly)
├── Spacer (Height 24.dp)
├── AnimatedVisibility (errorMessage != null)
│ └── ErrorText (errorMessage)
├── Button ("Subscribe Now")
│ ├── Enabled = !isPurchasing
│ └── Content: if (isPurchasing) CircularProgressIndicator else Text("Start Pro Subscription")
├── Spacer (Height 12.dp)
└── TextButton ("Restore Purchases", onClick = restorePurchases)

5. RevenueCat Mobile SDK Purchase Flow & Token Sync

Sección titulada «5. RevenueCat Mobile SDK Purchase Flow & Token Sync»
fun executePurchase(plan: SubscriptionPlan) {
viewModelScope.launch {
_uiState.update { it.copy(isPurchasing = true) }
try {
val packageToPurchase = getRevenueCatPackage(plan)
val purchaseResult = revenueCatSdk.purchasePackage(packageToPurchase)
if (purchaseResult.customerInfo.entitlements["pro_tier"]?.isActive == true) {
// Sync updated entitlement with backend BE-06 checkEntitlement
authRepository.refreshUserTokens()
_uiState.update { it.copy(isPurchasing = false, purchaseSuccessBanner = true) }
onPurchaseSuccess()
}
} catch (e: UserCancelledException) {
_uiState.update { it.copy(isPurchasing = false) }
} catch (e: Exception) {
_uiState.update { it.copy(isPurchasing = false, errorMessage = e.message) }
}
}
}
  • Screenshot Test: Render PaywallModal preview with Yearly plan selected and feature list.
  • UiTest: Tap “Subscribe Now”, mock RevenueCat SDK success response, verify onPurchaseSuccess invoked and token refreshed.