Skip to main content

Roku

⚠️Beta Feature

RevenueCat's Roku support is currently in beta. For interest, please fill out our waitlist form.

General setup

Prerequisites

Setting up your Roku developer account

Follow the First Steps guide to create a Roku developer account, login to your Roku device and enable developer mode on your Roku device.

Setting up your Roku channel

Once you have your developer account created, head to the dashboard

  1. First, create a new Channel
  2. If you want to use this channel for testing, make sure the channel is enabled for billing testing
  3. Under "Monetization" -> "Test users", add a test user with the email associated to your Roku device
  4. Under "Monetization" -> "Product", follow the process to submit the tax documents, and after you're approved, you can create in-channel products

App configuration

  1. Make sure your project has been enabled to create Roku apps. If you're not sure, talk to your RevenueCat contact.
  2. Open the RevenueCat dashboard, select your project, and click on "Add app" > "Roku Store"

  1. Enter your app's name. Navigate to the Roku Pay Web Services page to copy your the Roku Pay API key and paste this to the RevenueCat dashboard under 'Roku Pay API Key'

  1. Find your Roku Channel ID in your Roku Developer Dashboard and enter this value in the RevenueCat dashboard under 'Roku Channel ID'

Remember to select 'SAVE CHANGES'

  1. Once the app is created, click on the "Roku Server to Server notifications settings", copy the "Roku Push Notification URL" which starts with https://api.revenuecat.com/v1/incoming-webhooks/roku-pay-jwt-notification. Navigate back to your Roku Pay Web Services page (where you found the Roku API key) page and scroll down until you see a section called 'Push Notifications' and paste RevenueCat's Push notifications URL in the text box

Remember to select 'SAVE CHANGES'

  1. Back to the RevenueCat dashboard, click on "Public API Key" and copy over the value which should start with "roku_XXXXXX". You will need it to configure the SDK later.

Product configuration

After you have configured the Roku Store app on RevenueCat, you should create your in-channel products and then follow RevenueCat's regular setup of entitlements, products, and offerings.

Installing the SDK

  1. Clone the repository:
git clone https://github.com/RevenueCat/purchases-roku
  1. Copy the components/purchases folder into your app's components folder.
  2. Copy the source/Purchases.brs file into your app's source folder.
  3. Import the SDK in the .xml file of the component where you want to use it:
<!-- Importing the RevenueCat SDK -->
<script type="text/brightscript" uri="pkg:/source/Purchases.brs" />

Configuring the SDK

Important: The SDK should be used only from SceneGraph components. Calling it from the main thread or from a Task component is not supported.

Initialize the SDK with your api key. You typically do this inside the init() method of your main scene.

sub init()
Purchases().configure({
"apiKey": "roku_XXXXX"
})
end sub

Callbacks and error handling

In methods of the SDK which perform async operations, you can get the result by passing a sub routine or a callback name.

  • The first parameter will contain the result.
  • The second parameter will contain an error if there was one, or invalid if there wasnt.

Example:

sub init()
Purchases().logIn(my_user_id, sub(subscriber, error)
if error <> invalid
print "there was en error"
else
print subscriber
end if
end sub)

' To use a function as callback, pass its name as second parameter
Purchases().logIn(my_user_id, "onSubscriberReceived")
end sub

sub onSubscriberReceived(e as object)
data = e.GetData()
if data.error <> invalid
print "there was en error"
else
print data.result
end if
end sub

Models

Subscriber

' The subscriber object is returned from different APIs. Here's an example of what it looks like:
{
activeSubscriptions: ["my_product_id"]
allExpirationDatesByProduct: {
"my_product_id": <Component: roDateTime>
}
allPurchaseDatesByProduct: {
"my_product_id": <Component: roDateTime>
}
allPurchasedProductIds: ["my_product_id"]
entitlements: {
all: {
billingIssueDetectedAt: invalid
expirationDate: <Component: roDateTime>
identifier: "premium"
isActive: false
isSandbox: true
latestPurchaseDate: <Component: roDateTime>
originalPurchaseDate: <Component: roDateTime>
ownershipType: "PURCHASED"
periodType: "normal"
productIdentifier: "my_product_identifier"
productPlanIdentifier: invalid
store: "app_store"
unsubscribeDetectedAt: invalid
willRenew: false
}
active: {}
}
firstSeen: <Component: roDateTime>
lastSeen: <Component: roDateTime>
latestExpirationDate: <Component: roDateTime>
managementUrl: invalid
nonSubscriptionTransactions: [
{
isSandbox: false
originalPurchaseDate: <Component: roDateTime>
purchaseDate: <Component: roDateTime>
store: "roku"
storeTransactionIdentifier: "XXXXXXX"
transactionIdentifier: "XXXXXXX"
productIdentifier: "my_product_id"
}
]
originalAppUserId: "$RCAnonymousID:XXXXXXXXXXXXXXXX"
originalApplicationVersion: "1.0"
originalPurchaseDate: <Component: roDateTime>
requestDate: <Component: roDateTime>
}

Offerings


{
current: {
identifier: "my_id",
metadata: { }, ' Metadata set in the Offering configuration
description: "Offering description",
annual: {}, ' The configured Annual package, if available
monthly: {}, ' The configured Monthly package, if available
' A list of all available packages
availablePackages: [
{
identifier: "package_identifier",
packageType: "custom",
' The raw Roku store product
storeProduct: {
code: "yearly_subscription_product"
cost: "$1.99"
description: "Yearly Subscription"
freeTrialQuantity: 0
freeTrialType: "None"
HDPosterUrl: ""
id: "00000000-0000-0000-0000-000000000000"
inStock: "true"
name: "Yearly Subscription"
offerEndDate: "
offerStartDate: ""
productImagePortrait: ""
productImageUrl: ""
productType: "YearlySub"
qty: 0
SDPosterUrl: ""
trialCost: "$0.99"
trialQuantity: 12
trialType: "Months"
}
}
],
},
all: [
' An array for all offerings, with the same schema as above
]
}

Purchase

Making a purchase

As a parameter to the purchase() method, you can pass an associative array containing one of the following keys:

  • code: A string containing the product id.
  • product: From the getOfferings result: e.g. offerings.current.annual.storeProduct
  • package: From the getOfferings result: e.g. offerings.current.annual

Additionally,you can pass the following optional parameters:

  • action: To perform a product change. Valid values: Upgrade or Downgrade

Sync purchases

This method will post all purchases associated with the current Roku account to RevenueCat and become associated with the current User ID. It should only be used if you're migrating from using your own Roku Pay implementation and want to track previous purchases in RevenueCat.

{
amount: "$0.00"
code: "yearly_subscription_product"
description: "Yearly Subscription"
externalCode: ""
freeTrialQuantity: 0
freeTrialType: "None"
name: "Yearly Subscription"
originalAmount: "0"
productType: "YearlySub"
promotionApplied: false
purchaseId: "00000000-0000-0000-0000-000000000000"
qty: 1
replacedOffers: []
replacedSubscriptionId: ""
rokuCustomerId: "00000000-0000-0000-0000-000000000000"
total: "$0.00"
trialCost: "$0.99"
trialQuantity: 1
trialType: "Years"
}

Error

The error model constains two fields: code and message

{
code: 1234,
message: "There as an error",
}

Tying everything together

For most apps, the usage of the SDK would look like this:

  1. Initialise the SDK
  2. Log in the user
  3. Check if the entitlement is active
  4. Fetch offerings and show your paywall UI
  5. Make a purchase
sub init()
' Initialize the SDK
Purchases().configure({
"apiKey": "roku_XXXXX",
})
' Login the user
Purchases().logIn(m.my_user_id, sub(subscriber, error)
if error = invalid
' If my entitlement is not active, fetch offerings to show the paywall
if subscriber.entitlements.my_entitlement.isActive = false
fetchOfferings()
end if
end if
end sub)
end sub

sub fetchOfferings()
Purchases().getOfferings(sub(offerings, error)
if error = invalid
' Use offerings to build your paywall UI.
' Then call purchasePackage with the one selected by the user
purchasePackage(offerings.current.annual)
end if
end sub)
end sub

' Call purchasePackage when the user decides to initiate a purchase
sub purchasePackage(package)
Purchases().purchase({ package: package }, sub(result, error)
if error = invalid
print "Purchase successful"
print result.transaction
print result.subscriber
end if
end sub)
end sub

Testing

⚠️

Only the "root account user" can test billing on device. If you get added as a collaborator to someone else's developer account, billing testing will not work. You'll need to create your own developer account.

Roku transactions do not have an "associated" environment. Both types of transactions arrive via push notifications, however, there are key differences between them.

  • Price differentiation:
    • Test purchases: Always have a price of 0
    • Production purchases: Will have a non-zero price (unless it's a trial)
  • Charges:
    • Test purchases: The end customer will not be charged for a test purchase
    • Production purchases: The end customer will be charged according to the product details (e.g: pricing tier, trials, offers, etc)

In order to perform a test purchase, a test user must be created and associated with the channel.

After that, the test user will be able to make test purchases on the channel, regardless of the installation method: Public, Beta or Sideloaded (Billing Testing must be enabled). However, you can only use one Roku channel at a time for testing.

RevenueCat limitations

Since Roku transactions do not have an associated environment, RevenueCat defines a "sandbox" purchase as any purchase made in a Sideloaded channel. A "production" purchase is defined as any purchase made in either a beta or public channel, regardless of whether the end customer was actually charged or not for it.

This also means for any events RevenueCat dispatches containing an environment field, the Roku channel where the purchase was made will determine whether the value is set to SANDBOX or PRODUCTION.

To ensure a clear separation between your "testing" and "production" purchases, we recommend creating a separate channel specifically for testing purchases and creating a new RevenueCat Roku Store app for that channel.

Testing with a beta channel

Roku's beta channels are a special channel type to assist with testing your channel in a production-like environment before publishing.

Beta channel rules

  • 120 days: A beta channel can exist for only 120 days after you create it. After 120 days, the channel will be (1) deleted and removed from your Developer Dashboard and (2) disabled for all users who have it installed
  • 10 channels: There is a maximum of 10 beta channels at a time
  • 20 test users: There can only be 20 beta test users per beta channel at any given time

Beta limitations

This feature is currently in beta and has a number of known limitations.

RevenueCat features not yet supported:

Functionality not yet supported:

  • OTPs (one-time purchases)
  • Consumable purchases
  • Detecting price changes
  • Chargebacks
  • Purchase transfers
  • Extending subscriptions
  • Identifying customers upon configuration

If your use case is not supported above, reach out to RevenueCat Support so we can discuss more on how to support you!