Skip to main content
The AppDNA SDK supports server-driven paywalls that are configured remotely and rendered natively using Jetpack Compose. Paywalls handle the full purchase flow, from presenting product options to completing the transaction.

Present a Paywall

Present a paywall by passing the current Activity, the paywall ID, and an optional context:
import ai.appdna.sdk.AppDNA
import ai.appdna.sdk.paywall.PaywallContext

AppDNA.presentPaywall(
    activity = this,
    id = "premium_paywall",
    context = PaywallContext(placement = "settings"),
    listener = paywallListener
)

Paywall Module

The AppDNA.paywall module provides direct access to paywall functionality:
MethodDescription
present(activity, paywallId, context?)Presents the paywall
setDelegate(delegate: AppDNAPaywallDelegate?)Sets the paywall delegate

Paywall Context

Pass additional context to control paywall behavior and tracking:
import ai.appdna.sdk.paywall.PaywallContext

val context = PaywallContext(
    placement = "settings",
    experiment = "paywall_test",
    variant = "b"
)

AppDNA.paywall.present(
    activity = this,
    paywallId = "premium_paywall",
    context = context
)

PaywallContext Properties

PropertyTypeDescription
placementStringWhere the paywall was triggered (e.g., “settings”, “onboarding”)
experimentString?Experiment identifier for A/B testing
variantString?Variant identifier within the experiment
The placement property is used for analytics segmentation. Use descriptive placement names to track which entry points convert best.

Paywall Delegate

Implement AppDNAPaywallDelegate to receive paywall lifecycle callbacks:
import ai.appdna.sdk.paywall.AppDNAPaywallDelegate
import ai.appdna.sdk.paywall.PaywallAction
import ai.appdna.sdk.billing.TransactionInfo

class MyPaywallDelegate : AppDNAPaywallDelegate {

    override fun onPaywallPresented(paywallId: String) {
        Log.d("Paywall", "Paywall presented: $paywallId")
    }

    override fun onPaywallAction(paywallId: String, action: PaywallAction) {
        Log.d("Paywall", "Action: $action on paywall: $paywallId")
    }

    override fun onPaywallPurchaseStarted(paywallId: String, productId: String) {
        Log.d("Paywall", "Purchase started: $productId")
    }

    override fun onPaywallPurchaseCompleted(
        paywallId: String,
        productId: String,
        transaction: TransactionInfo
    ) {
        Log.d("Paywall", "Purchase completed: $productId")
        // Dismiss paywall or navigate to premium content
    }

    override fun onPaywallPurchaseFailed(paywallId: String, error: Exception) {
        Log.e("Paywall", "Purchase failed on paywall: $paywallId", error)
    }

    override fun onPaywallDismissed(paywallId: String) {
        Log.d("Paywall", "Paywall dismissed: $paywallId")
    }
}

// Set the delegate
AppDNA.paywall.setDelegate(MyPaywallDelegate())

Paywall Action

The PaywallAction enum represents user interactions within the paywall:
ValueDescription
CTA_TAPPEDUser tapped the primary call-to-action button
FEATURE_SELECTEDUser selected a feature to view details
PLAN_CHANGEDUser switched between plan options
LINK_TAPPEDUser tapped a link (e.g., terms, privacy)
CUSTOMCustom action defined in the paywall config

Dismiss Reason

The DismissReason enum indicates how the paywall was closed:
ValueDescription
PURCHASEDPaywall dismissed after a successful purchase
DISMISSEDUser explicitly dismissed the paywall
TAPPED_OUTSIDEUser tapped outside the paywall to close it
PROGRAMMATICPaywall was dismissed programmatically

Auto-Tracked Events

The paywall module automatically tracks the following events:
EventTriggered When
paywall_viewPaywall is presented to the user
paywall_closePaywall is dismissed
purchase_startedUser initiates a purchase from the paywall

Paywall Sections

Paywalls configured in the Console support the following content sections:
SectionDescription
HeaderTitle, subtitle, and optional hero image
Features listList of feature highlights with icons and descriptions
Plan selectionSelectable plan options (e.g., monthly, annual)
CTA buttonPrimary purchase button with dynamic price text
Social proofTestimonials, ratings, or user counts
GuaranteeMoney-back guarantee or free trial messaging
ImageFull-width or sized image with optional corner radius
SpacerConfigurable vertical spacing between sections
TestimonialQuote with author name, role, and optional avatar
CountdownUrgency countdown timer with configurable expiry behavior
LegalTerms of service, privacy policy, and subscription terms
DividerHorizontal separator line with optional label
Sticky footerFixed bottom bar with CTA and price summary
CardRounded card container for grouping related content
CarouselHorizontally scrollable content cards (features, reviews)
TimelineStep-by-step vertical timeline (e.g., trial-to-paid flow)
Icon gridGrid layout of icons with labels (feature highlights)
Comparison tableSide-by-side plan comparison (free vs. premium columns)
Promo inputText field for entering promotional or coupon codes
ToggleOn/off toggle for add-on options (e.g., annual billing)
Reviews carouselHorizontally scrollable user reviews with ratings

Plan Display Styles

The plan selection section supports 12 display styles, configured in the Console. Each style controls how subscription plans are laid out and visually differentiated:
StyleDescription
HORIZONTAL_CARDSSide-by-side cards, one per plan. Best for 2-3 plans.
VERTICAL_STACKStacked rows, each showing plan name, price, and badge.
TOGGLE_SWITCHTwo-option toggle (e.g., Monthly / Annual).
SEGMENTED_CONTROLNative segmented control for 2-4 plan options.
RADIO_LISTRadio button list with plan details per row.
CAROUSELHorizontally scrollable plan cards with snap behavior.
COMPARISON_GRIDFeature comparison grid with checkmarks per plan column.
PILL_SELECTORRounded pill buttons arranged horizontally.
TIER_BLOCKSLarge stacked blocks with feature lists per tier.
MINIMAL_TEXTText-only plan names with prices, no cards or borders.
FEATURED_HIGHLIGHTOne plan visually promoted (larger, badge, glow).
ACCORDIONExpandable/collapsible plan sections with full details.

Card & Badge Customization

Plan cards support per-plan styling configured in the Console:
  • Badge text and color — e.g., “Best Value”, “Most Popular” with custom background
  • Card border and shadow — highlight the selected or recommended plan
  • Save percentage — automatically calculated and displayed on annual plans
  • Trial label — shows “7-day free trial” or custom trial messaging
  • Card background — solid color, gradient, or image per plan card
  • Corner radius — configurable per card

Promo Code Handling

When a paywall includes a Promo input section, implement the promo code delegate method to validate codes against your backend:
class MyPaywallDelegate : AppDNAPaywallDelegate {
    // ... other callbacks ...

    override fun onPromoCodeSubmit(
        paywallId: String,
        code: String,
        completion: (PromoCodeResult) -> Unit
    ) {
        // Validate the code with your backend
        lifecycleScope.launch {
            try {
                val discount = MyAPI.validatePromoCode(code)
                completion(PromoCodeResult.Valid(
                    discount = discount.percentage,
                    label = discount.label
                ))
            } catch (e: Exception) {
                completion(PromoCodeResult.Invalid(
                    message = "This code is not valid or has expired."
                ))
            }
        }
    }
}
Result CaseDescription
Valid(discount, label)Code accepted — the SDK updates displayed prices with the discount
Invalid(message)Code rejected — the SDK shows the error message in the promo input

Toggle State in Purchase Metadata

When a paywall includes Toggle sections (e.g., for annual billing or add-on options), the toggle state is automatically included in the purchase metadata passed to onPaywallPurchaseCompleted:
override fun onPaywallPurchaseCompleted(
    paywallId: String,
    productId: String,
    transaction: TransactionInfo
) {
    val toggles = transaction.metadata["toggles"] as? Map<String, Boolean>
    val annualBilling = toggles?.get("annual_billing") ?: false
    val addonSelected = toggles?.get("premium_addon") ?: false
}
Purchase completion and failure events are tracked by the billing module, not the paywall module. See the Billing documentation for the full list of purchase-related events.

Rich Media in Paywalls

Paywall sections support rich media content configured in the Console:
  • Lottie animations — animated hero images, feature illustrations, or backgrounds
  • Rive animations — interactive state-machine-driven animations
  • Video — inline video in header or feature sections
  • Haptic feedback — triggered on plan selection and CTA taps
  • Particle effects — confetti or celebration effects on purchase completion
  • Per-section styling — background colors, gradients, images, borders, shadows, and corner radius
See the Rich Media guide for full details on supported formats and configuration.
Paywalls are rendered using Jetpack Compose. The SDK handles the full UI lifecycle, including product loading, plan selection, and purchase flow. Your app only needs to set the delegate and handle callbacks.

Next Steps