About rc-one-time-products
A RevenueCat skill for Android developers selling one-time products. It describes purchase flows and how the SDK verifies receipts, acknowledges or consumes tokens, and handles pending payments and errors.
Key features
- 01Fetches products and starts purchases
- 02Verifies purchase receipts through RevenueCat
- 03Automatically acknowledges or consumes tokens
- 04Separates cancellations, pending payments, and errors
- 05Checks access through entitlements or transactions
Use cases
- 01Add one-time purchases to an Android app
- 02Grant access based on entitlements
- 03React when pending payments complete
- 04Set up server-side purchase handling with webhooks
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ revenuecat/ ai-toolkit --skill rc-one-time-productsAfter installing, type/rc-one-time-productsin Claude Code to confirm it loaded.
Details
From the project READMEOne Time Products with RevenueCat
Sell one time products on Android without writing BillingClient glue. The RevenueCat SDK fetches products, launches the purchase, verifies the receipt, and acknowledges or consumes the token for you.
Phase 1: Scope
Answer these before touching code.
| Question | Decision |
|---|---|
| Is the product consumable or non consumable? | Mark consumables in the RevenueCat dashboard so the SDK calls consumeAsync for you. |
| Do you read access through entitlements or transactions? | Prefer entitlements so access logic stays decoupled from product IDs. |
| Do you need server notification of the purchase? | Use RevenueCat webhooks. The INITIAL_PURCHASE event fires for every new non subscription purchase. |
Do not call BillingClient.queryProductDetailsAsync, acknowledgePurchase, or consumeAsync yourself once RevenueCat is integrated. The SDK owns the BillingClient instance.
Phase 2: Prepare
- Configure the product in Google Play Console as a one time product.
- In the RevenueCat dashboard, attach the product to an offering and package, and flag it as consumable if it should be consumed on purchase.
- Confirm
Purchases.configureruns once on app start with your Android API key and the current App User ID.
Phase 3: Execute
Fetch offerings, launch the purchase, then read the result off CustomerInfo.
val offerings = Purchases.sharedInstance.awaitOfferings()
val pkg = offerings.current?.availablePackages?.first() ?: return
Launch the purchase with awaitPurchase. It suspends until the RevenueCat backend verifies the token.
val result = Purchases.sharedInstance.awaitPurchase(
PurchaseParams.Builder(activity, pkg).build()
)
val info = result.customerInfo
Read access through entitlements, or fall back to the non subscription transactions list.
val hasAccess = info.entitlements["lifetime_access"]?.isActive == true
val owned = info.nonSubscriptionTransactions
.any { it.productIdentifier == "lifetime_product_id" }
The SDK acknowledges non consumables and consumes consumables automatically once the backend verifies the purchase. You do not call acknowledgePurchase or consumeAsync.
Phase 4: Handle Edge Cases
Wrap awaitPurchase to separate cancellation, pending payments, and real errors.
try { /* awaitPurchase */ } catch (e: PurchasesTransactionException) {
when {
e.error.code == PurchasesErrorCode.PaymentPendingError -> showPending()
e.userCancelled -> Unit
else -> showError(e.error.message)
}
}
| Outcome | SDK signal | Action |
|---|---|---|
| Success | awaitPurchase returns with updated CustomerInfo | Grant access from entitlements or nonSubscriptionTransactions. |
| User cancel | PurchasesTransactionException, userCancelled == true | Swallow silently. |
| Pending payment | PurchasesTransactionException, PaymentPendingError | Show a pending message. The SDK updates the entitlement later through UpdatedCustomerInfoListener. |
| Other error | PurchasesTransactionException, other codes | Surface e.error.message to you. |
Phase 5: Verify
- Trust
result.customerInfofromawaitPurchase. Do not grant access from optimistic local state. - Pending purchases do not grant entitlements. Register an
UpdatedCustomerInfoListenerso you react when the payment completes. - For server side provisioning, subscribe to the RevenueCat
INITIAL_PURCHASEwebhook. Do not reimplement Google Play Developer API verification.