Summary
We are implementing Google Play's Alternative billing without user choice ("Alternative Billing Only") per the official flow:
https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app#information-dialog
Google's four steps are:
- Check alternative billing availability
- Show the information dialog to users (in-app, Google-rendered)
- Process the payment with your own payment system (in-app, no external link)
- Create and report a token to Google Play
react-native-iap exposes functions that map exactly to this flow:
checkAlternativeBillingAvailabilityAndroid() — "Step 1 of alternative billing flow"
showAlternativeBillingDialogAndroid() — "Step 2 ...", Google's in-app information dialog
createAlternativeBillingTokenAndroid() — "Step 3 ..."
initialized via initConnection({ alternativeBillingModeAndroid: 'alternative-only' }).
Per the official docs, all three are now deprecated, each pointing at a Billing Programs replacement:
| Deprecated (Alternative Billing Only) |
Docs say "use ... instead" |
checkAlternativeBillingAvailabilityAndroid() |
isBillingProgramAvailableAndroid() |
showAlternativeBillingDialogAndroid() |
launchExternalLinkAndroid() |
createAlternativeBillingTokenAndroid() |
createBillingProgramReportingDetailsAndroid() |
This is the crux of the problem: the in-app information dialog (showAlternativeBillingDialogAndroid) is deprecated in favor of launchExternalLinkAndroid, which sends the user out of the app. The Billing Programs replacement (8.2.0+) only covers external offers / content links / external payments; there is no alternative-billing-only program value and no in-app information-dialog function. So the replacement path cannot express Google's Alternative Billing Only flow, which is entirely in-app with a Google-rendered dialog and no external link.
On top of that:
- Step 1 still throws
SERVICE_DISCONNECTED (-1) in 14.7.20. checkAlternativeBillingAvailabilityAndroid() (native isAlternativeBillingOnlyAvailableAsync) throws even though initConnection resolves true (see Path A below).
- The replacement program is a different Play program.
enableBillingProgramAndroid('external-offer') selects External Offers, a separate Play Console enrollment whose flow uses launchExternalLinkAndroid to send the user off-app. The alternativeBillingModeAndroid field's own deprecation note says:
Use enableBillingProgramAndroid instead. Use USER_CHOICE_BILLING for user choice billing, EXTERNAL_OFFER for alternative only.
which routes "alternative only" to External Offers, not to an in-app Alternative Billing Only flow.
So there appears to be no supported way to run Google's Alternative Billing Only flow: the dedicated functions are deprecated and error out (-1), and every documented replacement either targets a different program or replaces the in-app dialog with an off-app link.
Environment
|
|
react-native-iap |
14.7.20 |
react-native-nitro-modules |
0.35.10 |
react-native |
0.81.4 |
expo |
54.0.31 |
| Platform |
Android (device, installed from Play Internal testing track) |
| Play Console |
App enrolled in Alternative billing only (without user choice), US |
Path A — the dedicated Alternative Billing Only functions (throws -1)
await initConnection({ alternativeBillingModeAndroid: "alternative-only" });
// initConnection resolves: true
await new Promise((r) => setTimeout(r, 300)); // let the binding settle
const isAvailable = await checkAlternativeBillingAvailabilityAndroid(); // THROWS
Runtime log (3 attempts, each reconnects first and still throws):
initConnection → mode: alternative-only
initConnection resolved: true
step 1 attempt 1 THREW: Error: {"code":"service-error","message":"Billing service is unavailable","responseCode":-1,"debugMessage":"com.android.billingclient.api.c.isAlternativeBillingOnlyAvailableAsync [interface c6.c]"}
initConnection resolved: true
step 1 attempt 2 THREW: (same -1)
initConnection resolved: true
step 1 attempt 3 THREW: (same -1)
We added a retry loop that resets the connection promise and reconnects with backoff (300ms settle, then 700ms/1400ms) between attempts. initConnection resolves true every time, but checkAlternativeBillingAvailabilityAndroid() throws -1 on every attempt.
Path B — the documented replacement external-offer (connects fine, but wrong program)
On the same device and build, using the Billing Programs API:
enableBillingProgramAndroid("external-offer");
await initConnection();
const result = await isBillingProgramAvailableAndroid("external-offer");
Log:
enableBillingProgramAndroid('external-offer') → initConnection
initConnection resolved: true
isBillingProgramAvailable (attempt 1) → false
→ UNAVAILABLE (app may not be enrolled in External Offers, ineligible country, or not a license tester)
This is important: isBillingProgramAvailableAndroid('external-offer') returns cleanly (false, because we are not enrolled in External Offers) with no -1. So the BillingClient connection itself is healthy. The -1 is specific to the isAlternativeBillingOnlyAvailableAsync (alternative-billing-only) endpoint.
But even if we enrolled in External Offers, external-offer is not the Alternative Billing Only flow: it targets a different Play program and expects launchExternalLinkAndroid (sending the user out of the app), whereas Alternative Billing Only is an entirely in-app flow.
The core problem: no way to show the required information dialog without leaving the app
Google's Alternative Billing Only flow requires showing an in-app, Google-rendered information dialog before we take payment (Step 2 in the guide). In the library:
- The dedicated function for this is
showAlternativeBillingDialogAndroid() ("Step 2 of alternative billing flow", returns whether the user accepted). It is the correct in-app dialog, but (a) it belongs to the alternative-billing-only path whose Step 1 (checkAlternativeBillingAvailabilityAndroid) throws -1, so we can never reach it, and (b) it is now deprecated with "Use launchExternalLinkAndroid() instead".
launchExternalLinkAndroid is not an in-app dialog. It navigates the user out of the app to an external URL. So the official replacement for the in-app information dialog is an off-app link.
- The Billing Programs API has no in-app information-dialog function at all. There is no
showBillingProgramInformationDialogAndroid (or equivalent).
So the deprecation path directly removes the only in-app disclosure mechanism and substitutes an external link:
- The path that has the correct in-app dialog (
showAlternativeBillingDialogAndroid) is both unreachable (Step 1 -1s) and deprecated.
- Its documented replacement (
launchExternalLinkAndroid) forces the user off-app.
- The
external-offer program that connects has no in-app dialog either.
We therefore have no supported way to satisfy Google's mandatory in-app information dialog for Alternative Billing Only without linking off the app, which is not part of, and is contrary to, the Alternative Billing Only flow.
Question
What is the intended path to implement Alternative Billing only?
What we expected
A supported way to run Google's four Alternative Billing Only steps entirely in-app (availability → info dialog → our payment → token), matching the Android guide, without being redirected to the External Offers program or launchExternalLinkAndroid.
Summary
We are implementing Google Play's Alternative billing without user choice ("Alternative Billing Only") per the official flow:
https://developer.android.com/google/play/billing/alternative/alternative-billing-without-user-choice-in-app#information-dialog
Google's four steps are:
react-native-iapexposes functions that map exactly to this flow:checkAlternativeBillingAvailabilityAndroid()— "Step 1 of alternative billing flow"showAlternativeBillingDialogAndroid()— "Step 2 ...", Google's in-app information dialogcreateAlternativeBillingTokenAndroid()— "Step 3 ..."initialized via
initConnection({ alternativeBillingModeAndroid: 'alternative-only' }).Per the official docs, all three are now deprecated, each pointing at a Billing Programs replacement:
checkAlternativeBillingAvailabilityAndroid()isBillingProgramAvailableAndroid()showAlternativeBillingDialogAndroid()launchExternalLinkAndroid()createAlternativeBillingTokenAndroid()createBillingProgramReportingDetailsAndroid()This is the crux of the problem: the in-app information dialog (
showAlternativeBillingDialogAndroid) is deprecated in favor oflaunchExternalLinkAndroid, which sends the user out of the app. The Billing Programs replacement (8.2.0+) only covers external offers / content links / external payments; there is noalternative-billing-onlyprogram value and no in-app information-dialog function. So the replacement path cannot express Google's Alternative Billing Only flow, which is entirely in-app with a Google-rendered dialog and no external link.On top of that:
SERVICE_DISCONNECTED (-1)in14.7.20.checkAlternativeBillingAvailabilityAndroid()(nativeisAlternativeBillingOnlyAvailableAsync) throws even thoughinitConnectionresolvestrue(see Path A below).enableBillingProgramAndroid('external-offer')selects External Offers, a separate Play Console enrollment whose flow useslaunchExternalLinkAndroidto send the user off-app. ThealternativeBillingModeAndroidfield's own deprecation note says:So there appears to be no supported way to run Google's Alternative Billing Only flow: the dedicated functions are deprecated and error out (
-1), and every documented replacement either targets a different program or replaces the in-app dialog with an off-app link.Environment
react-native-iap14.7.20react-native-nitro-modules0.35.10react-native0.81.4expo54.0.31Path A — the dedicated Alternative Billing Only functions (throws
-1)Runtime log (3 attempts, each reconnects first and still throws):
We added a retry loop that resets the connection promise and reconnects with backoff (300ms settle, then 700ms/1400ms) between attempts.
initConnectionresolvestrueevery time, butcheckAlternativeBillingAvailabilityAndroid()throws-1on every attempt.Path B — the documented replacement
external-offer(connects fine, but wrong program)On the same device and build, using the Billing Programs API:
Log:
This is important:
isBillingProgramAvailableAndroid('external-offer')returns cleanly (false, because we are not enrolled in External Offers) with no-1. So the BillingClient connection itself is healthy. The-1is specific to theisAlternativeBillingOnlyAvailableAsync(alternative-billing-only) endpoint.But even if we enrolled in External Offers,
external-offeris not the Alternative Billing Only flow: it targets a different Play program and expectslaunchExternalLinkAndroid(sending the user out of the app), whereas Alternative Billing Only is an entirely in-app flow.The core problem: no way to show the required information dialog without leaving the app
Google's Alternative Billing Only flow requires showing an in-app, Google-rendered information dialog before we take payment (Step 2 in the guide). In the library:
showAlternativeBillingDialogAndroid()("Step 2 of alternative billing flow", returns whether the user accepted). It is the correct in-app dialog, but (a) it belongs to the alternative-billing-only path whose Step 1 (checkAlternativeBillingAvailabilityAndroid) throws-1, so we can never reach it, and (b) it is now deprecated with "UselaunchExternalLinkAndroid()instead".launchExternalLinkAndroidis not an in-app dialog. It navigates the user out of the app to an external URL. So the official replacement for the in-app information dialog is an off-app link.showBillingProgramInformationDialogAndroid(or equivalent).So the deprecation path directly removes the only in-app disclosure mechanism and substitutes an external link:
showAlternativeBillingDialogAndroid) is both unreachable (Step 1-1s) and deprecated.launchExternalLinkAndroid) forces the user off-app.external-offerprogram that connects has no in-app dialog either.We therefore have no supported way to satisfy Google's mandatory in-app information dialog for Alternative Billing Only without linking off the app, which is not part of, and is contrary to, the Alternative Billing Only flow.
Question
What is the intended path to implement Alternative Billing only?
What we expected
A supported way to run Google's four Alternative Billing Only steps entirely in-app (availability → info dialog → our payment → token), matching the Android guide, without being redirected to the External Offers program or
launchExternalLinkAndroid.