Skip to content
Draft
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,5 @@
Purchases.configure(
PurchasesConfiguration.Builder(this, <api_key>)
.purchasesAreCompletedBy(PurchasesAreCompletedBy.MY_APP)
.build()
)
4 changes: 4 additions & 0 deletions code_blocks/tools/paywalls_custom_config_anonymous_objc.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[RCPurchases configureWithAPIKey:@"<public_sdk_key>"
appUserID:nil
purchasesAreCompletedBy:RCPurchasesAreCompletedByMyApp
storeKitVersion:RCStoreKitVersion2];
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Purchases.configure(
with: .init(withAPIKey: "<public_sdk_key>")
.with(appUserID: "<app_user_id>")
.with(purchasesAreCompletedBy: .myApp, storeKitVersion: .storeKit2)
)
13 changes: 13 additions & 0 deletions code_blocks/tools/paywalls_custom_config_with_user_java.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// If you're targeting only Google Play Store
public class MainApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Purchases.configure(
new PurchasesConfiguration.Builder(this, <public_google_sdk_key>)
.appUserID(<my_app_user_id>)
.purchasesAreCompletedBy(PurchasesAreCompletedBy.MY_APP)
.build()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Purchases.configure(
PurchasesConfiguration.Builder(this, <api_key>)
.appUserID(<my_app_user_id>)
.purchasesAreCompletedBy(PurchasesAreCompletedBy.MY_APP)
.build()
)
4 changes: 4 additions & 0 deletions code_blocks/tools/paywalls_custom_config_with_user_objc.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[RCPurchases configureWithAPIKey:@"<public_sdk_key>"
appUserID:@"<app_user_id>"
purchasesAreCompletedBy:RCPurchasesAreCompletedByMyApp
storeKitVersion:RCStoreKitVersion2];
82 changes: 82 additions & 0 deletions code_blocks/tools/paywalls_custom_purchase_android_1.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import android.app.Activity
import androidx.compose.runtime.*
import com.revenuecat.purchases.CustomerInfo
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCode
import com.revenuecat.purchases.ui.revenuecatui.Paywall
import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogic
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogicResult
import kotlinx.coroutines.suspendCancellableCoroutine

@Composable
fun MyPaywallScreen() {
val myPurchaseLogic = remember {
object : PurchaseLogic {
override suspend fun performPurchase(
activity: Activity,
rcPackage: Package,
): PurchaseLogicResult {
return try {
performCustomPurchase(activity, rcPackage)
PurchaseLogicResult.Success
} catch (e: UserCancelledException) {
PurchaseLogicResult.Cancellation
} catch (e: Exception) {
PurchaseLogicResult.Error(
errorDetails = PurchasesError(
code = PurchasesErrorCode.PurchaseInvalidError,
underlyingErrorMessage = e.message
)
)
}
}

override suspend fun performRestore(
customerInfo: CustomerInfo
): PurchaseLogicResult {
return try {
performCustomRestore()
PurchaseLogicResult.Success
} catch (e: Exception) {
PurchaseLogicResult.Error()
}
}
}
}

val paywallOptions = PaywallOptions.Builder(dismissRequest = {
// Handle dismiss
})
.setPurchaseLogic(myPurchaseLogic)
.build()

Paywall(options = paywallOptions)
}

// MARK: - Your Billing Client Implementation

class UserCancelledException : Exception()

private suspend fun performCustomPurchase(
activity: Activity,
rcPackage: Package
): Unit = suspendCancellableCoroutine { continuation ->
// Implement your Billing Client purchase flow here.
// See Google's documentation: https://developer.android.com/google/play/billing/integrate

// Sync with RevenueCat after purchase completes
Purchases.sharedInstance.syncPurchases()
continuation.resume(Unit) { }
}

private suspend fun performCustomRestore(): Unit = suspendCancellableCoroutine { continuation ->
// Implement your restore flow here.
// See: https://developer.android.com/google/play/billing/integrate#pending

// Sync with RevenueCat after restore completes
Purchases.sharedInstance.syncPurchases()
continuation.resume(Unit) { }
}
79 changes: 79 additions & 0 deletions code_blocks/tools/paywalls_custom_purchase_android_2.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import android.app.Activity
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.revenuecat.purchases.CustomerInfo
import com.revenuecat.purchases.Package
import com.revenuecat.purchases.Purchases
import com.revenuecat.purchases.PurchasesError
import com.revenuecat.purchases.PurchasesErrorCode
import com.revenuecat.purchases.ui.revenuecatui.PaywallOptions
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogicWithCallback
import com.revenuecat.purchases.ui.revenuecatui.PurchaseLogicResult
import com.revenuecat.purchases.ui.revenuecatui.views.PaywallView

// In your Activity or Fragment
class MyActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)

val paywallView = findViewById<PaywallView>(R.id.paywallView)

// Note: For new projects, we recommend using the Compose + suspend function approach.
// This callback-based approach is provided for legacy codebases.

val myPurchaseLogic = object : PurchaseLogicWithCallback() {
override fun performPurchaseWithCompletion(
activity: Activity,
rcPackage: Package,
completion: (PurchaseLogicResult) -> Unit,
) {
performCustomPurchase(activity, rcPackage) { result ->
completion(result)
}
}

override fun performRestoreWithCompletion(
customerInfo: CustomerInfo,
completion: (PurchaseLogicResult) -> Unit,
) {
performCustomRestore { result ->
completion(result)
}
}
}

val paywallOptions = PaywallOptions.Builder(dismissRequest = {
// Handle dismiss
})
.setPurchaseLogic(myPurchaseLogic)
.build()

paywallView.setPaywallOptions(paywallOptions)
}

// MARK: - Your Billing Client Implementation

private fun performCustomPurchase(
activity: Activity,
rcPackage: Package,
completion: (PurchaseLogicResult) -> Unit
) {
// Implement your Billing Client purchase flow here.
// See Google's documentation: https://developer.android.com/google/play/billing/integrate

// Sync with RevenueCat after purchase completes
Purchases.sharedInstance.syncPurchases()
completion(PurchaseLogicResult.Success)
}

private fun performCustomRestore(completion: (PurchaseLogicResult) -> Unit) {
// Implement your restore flow here.
// See: https://developer.android.com/google/play/billing/integrate#pending

// Sync with RevenueCat after restore completes
Purchases.sharedInstance.syncPurchases()
completion(PurchaseLogicResult.Success)
}
}
58 changes: 58 additions & 0 deletions code_blocks/tools/paywalls_custom_purchase_ios_1.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import SwiftUI
import StoreKit
import RevenueCat
import RevenueCatUI

struct ContentView: View {
@State private var showPaywall = false

var body: some View {
VStack {
Button("Show Paywall") {
showPaywall = true
}
}
.sheet(isPresented: $showPaywall) {
PaywallView(
displayCloseButton: true,
performPurchase: { package in
do {
try await performCustomPurchase(package)
return (userCancelled: false, error: nil)
} catch {
return (userCancelled: false, error: error)
}
},
performRestore: {
do {
try await performCustomRestore()
return (success: true, error: nil)
} catch {
return (success: false, error: error)
}
}
)
.onPurchaseCompleted { customerInfo in
showPaywall = false
}
}
}

// MARK: - Your StoreKit Implementation

private func performCustomPurchase(_ package: Package) async throws {
// Implement your StoreKit purchase flow here.
// See Apple's documentation: https://developer.apple.com/documentation/storekit/in-app-purchase

// Sync with RevenueCat after purchase completes
_ = try? await Purchases.shared.syncPurchases()
}

private func performCustomRestore() async throws {
// Implement your restore flow here.
// See: https://developer.apple.com/documentation/storekit/transaction/currententitlements

// Sync with RevenueCat after restore completes
_ = try? await Purchases.shared.syncPurchases()
}
}
51 changes: 51 additions & 0 deletions code_blocks/tools/paywalls_custom_purchase_ios_2.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import SwiftUI
import StoreKit
import RevenueCat
import RevenueCatUI

struct ContentView: View {
var body: some View {
YourAppContent()
.presentPaywall(
myAppPurchaseLogic: MyAppPurchaseLogic(
performPurchase: { package in
do {
try await performCustomPurchase(package)
return (userCancelled: false, error: nil)
} catch {
return (userCancelled: false, error: error)
}
},
performRestore: {
do {
try await performCustomRestore()
return (success: true, error: nil)
} catch {
return (success: false, error: error)
}
}
),
purchaseCompleted: { customerInfo in
print("Purchase completed")
}
)
}

// MARK: - Your StoreKit Implementation

private func performCustomPurchase(_ package: Package) async throws {
// Implement your StoreKit purchase flow here.
// See Apple's documentation: https://developer.apple.com/documentation/storekit/in-app-purchase

// Sync with RevenueCat after purchase completes
_ = try? await Purchases.shared.syncPurchases()
}

private func performCustomRestore() async throws {
// Implement your restore flow here.
// See: https://developer.apple.com/documentation/storekit/transaction/currententitlements

// Sync with RevenueCat after restore completes
_ = try? await Purchases.shared.syncPurchases()
}
}
4 changes: 4 additions & 0 deletions docs/tools/paywalls.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ You can think of a Paywall as an optional feature of your Offering. An Offering

Therefore, you can create a unique Paywall for each of your Offerings, and can create an unlimited number of Offerings & Paywalls for each variation you want to test with Experiments.

:::info Enterprise: Use Paywalls with Your Own Purchase Infrastructure
If you have existing in-app purchase infrastructure and want to use RevenueCat's paywall designer and rendering while maintaining your own purchase handling, see [Paywalls with Your Own Purchase Infrastructure](/tools/paywalls/custom-purchase-handling).
:::

### Getting Started

Our paywalls use native code to deliver smooth, intuitive experiences to your customers when you're ready to deliver them an Offering; and you can use our Dashboard to build your paywall from any of our existing templates, or start from scratch to create your own. Either way, you'll have full control of the components and their properties to modify the paywall to meet your needs.
Expand Down
Loading