SuperAPI AI Directory

alternative-billing

General & other

View repository

Updated 10-16 07:58

About this skill

Alternative Billing and External Offers

Google Play exposes several programs that let you present payment paths outside Google Play billing. This skill walks you through picking the right program for your region, wiring the PBL APIs, and reporting transactions to the externaltransactions API within 24 hours.

Phase 1: Discovery

Confirm program eligibility before you touch any code. Eligibility is driven by region, product type, and Play Console enrollment.

Check enrollment

  1. Open Play Console and confirm you accepted the program terms for each program you plan to use.
  2. Register external URLs, subscription management links, and countries per program.
  3. Record the PBL version in your build.gradle. Features map to PBL versions:
Program Minimum PBL
User choice billing 5.2 (6.1 renamed APIs)
Alternative billing only 6.1
External offers (unified API) 8.2.1
External content links 8.2.1
External payment links 8.3.0

Map regions to programs

Region Available programs
South Korea User choice billing
EEA User choice billing, alternative billing only, external offers, external content links, external payment links
India User choice billing
United States User choice billing, external content links, external payment links

Check runtime availability

Do not hard code country lists. The PBL surfaces eligibility.

billingClient.isBillingProgramAvailableAsync(
    BillingProgram.EXTERNAL_OFFER,
    listener
)

For alternative billing only, use isAlternativeBillingOnlyAvailableAsync(). A BILLING_UNAVAILABLE result means the user or account is ineligible.

Discovery questions to answer

  • Which program(s) does your app need per region?
  • Do you serve subscriptions, one time products, or both?
  • Is PBL at a version that supports the target program?
  • Does your backend have a job that can report transactions within 24 hours?

Phase 2: Plan

Pick the program variant that matches your goals and region coverage.

Program comparison

Program Flow BillingClient setup Token source
User choice billing Google renders a choice screen enableUserChoiceBilling(listener) UserChoiceDetails.externalTransactionToken
Alternative billing only No Google Play billing at all, info dialog required enableAlternativeBillingOnly() createAlternativeBillingOnlyReportingDetailsAsync()
External offers Link out to a website for deals enableBillingProgram(EXTERNAL_OFFER) createBillingProgramReportingDetailsAsync()
External content links Link out for digital content (US) enableBillingProgram(EXTERNAL_CONTENT_LINK) createBillingProgramReportingDetailsAsync()
External payment links Choice screen with developer option enableBillingProgram(EXTERNAL_PAYMENTS, devListener) createBillingProgramReportingDetailsAsync()

Token versus ID

  • externalTransactionToken: generated by the PBL, tied to one user and device context, attached to the initial report only. Generate a new token per transaction.
  • externalTransactionId: string you generate, uniquely identifies the transaction in your system. Each renewal uses a new ID referencing the first via initialExternalTransactionId. Do not put PII in the ID.

Backend plan

Before you write client code, plan these backend pieces:

  1. Endpoint that receives the token from the app and starts payment in your processor.
  2. Worker that calls POST /androidpublisher/v3/applications/{packageName}/externalTransactions within 24 hours of charge.
  3. Renewal reporter that reuses initialExternalTransactionId without the token.
  4. Refund reporter that calls :refund on the original transaction ID.
  5. Quota planning: createexternaltransaction and refundexternaltransaction share 1,200 QPM. Stagger migrations.

User choice billing flow plan

  1. User taps buy.
  2. You call launchBillingFlow().
  3. Google renders the choice screen for eligible users.
  4. Google Play path: standard PurchasesUpdatedListener fires.
  5. Your billing path: UserChoiceBillingListener fires with token and products.
  6. You run your payment flow, then report through externaltransactions.

Alternative billing only flow plan

  1. Call isAlternativeBillingOnlyAvailableAsync().
  2. Call showAlternativeBillingOnlyInformationDialog(). On USER_CANCELED, retry next attempt.
  3. Call createAlternativeBillingOnlyReportingDetailsAsync() to get a fresh token.
  4. Send token plus product data to your backend.
  5. Complete payment in your processor.
  6. Report the transaction within 24 hours.

Phase 3: Execute

User choice billing client

val userChoiceListener = UserChoiceBillingListener { details ->
    backend.startExternalPurchase(
        details.externalTransactionToken,
        details.products
    )
}

val billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases(
        PendingPurchasesParams.newBuilder().build()
    )
    .enableUserChoiceBilling(userChoiceListener)
    .build()

For subscription upgrades that stay in your billing system, set setOriginalExternalTransactionId() on SubscriptionUpdateParams to skip the choice screen.

Alternative billing only token

billingClient.createAlternativeBillingOnlyReportingDetailsAsync(
    object : AlternativeBillingOnlyReportingDetailsListener {
        override fun onAlternativeBillingOnlyTokenResponse(
            result: BillingResult,
            details: AlternativeBillingOnlyReportingDetails?
        ) {
            if (result.responseCode != BillingResponseCode.OK) return
            backend.send(details?.externalTransactionToken)
        }
    }
)

External offers or content links (PBL 8.2.1+)

val params = BillingProgramReportingDetailsParams.newBuilder()
    .setBillingProgram(BillingProgram.EXTERNAL_OFFER)
    .build()

billingClient.createBillingProgramReportingDetailsAsync(
    params,
    reportingListener
)

Launch the link after you persist the token:

val linkParams = LaunchExternalLinkParams.newBuilder()
    .setBillingProgram(BillingProgram.EXTERNAL_OFFER)
    .setLinkUri(Uri.parse("https://myapp.com/offer"))
    .setLaunchMode(
        LaunchExternalLinkParams.LaunchMode
            .LAUNCH_IN_EXTERNAL_BROWSER_OR_APP
    )
    .build()
billingClient.launchExternalLink(activity, linkParams, listener)

External payment links (PBL 8.3+)

val devBillingListener = DeveloperProvidedBillingListener { details ->
    backend.handleExternalPayment(details)
}

val billingClient = BillingClient.newBuilder(context)
    .setListener(purchasesUpdatedListener)
    .enablePendingPurchases(
        PendingPurchasesParams.newBuilder().build()
    )
    .enableBillingProgram(
        EnableBillingProgramParams.newBuilder()
            .setBillingProgram(BillingProgram.EXTERNAL_PAYMENTS)
            .setDeveloperProvidedBillingListener(devBillingListener)
            .build()
    )
    .build()

Attach a DeveloperBillingOptionParams to your BillingFlowParams when you call launchBillingFlow(). The choice screen renders only when the user is in a supported country, the program is enabled on the client, and you passed a developer option.

PBL 9 change: DeveloperProvidedBillingDetails.getLinkUri() is now @Nullable. The external payment link may not be resolved at selection time, so guard against both null and empty string before parsing it as a Uri:

val linkUri = details.linkUri
if (!linkUri.isNullOrEmpty()) {
    startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(linkUri)))
} else {
    showExternalPaymentUnavailableState()
}

Report the initial transaction

POST /androidpublisher/v3/applications/{packageName}/externalTransactions?externalTransactionId=txn-1
{
  "originalPreTaxAmount": { "priceMicros": "4990000", "currency": "USD" },
  "originalTaxAmount": { "priceMicros": "449100", "currency": "USD" },
  "transactionTime": "2026-01-15T10:30:00Z",
  "recurringTransaction": {
    "externalTransactionToken": "token_from_pbl",
    "externalSubscription": { "subscriptionType": "RECURRING" }
  },
  "userTaxAddress": { "regionCode": "US" }
}

Amounts use priceMicros. Multiply by 1,000,000. For India, include administrativeArea in userTaxAddress because tax rates vary by state.

Report renewals

Drop the token, add initialExternalTransactionId:

{
  "recurringTransaction": {
    "initialExternalTransactionId": "txn-1",
    "externalSubscription": { "subscriptionType": "RECURRING" }
  }
}

Report refunds

POST /androidpublisher/v3/applications/{packageName}/externalTransactions/{externalTransactionId}:refund

Supply the pre tax amount for partial refunds.

Verification checklist

  • Report every transaction within 24 hours of the charge.
  • Generate a new token per transaction. Never cache or reuse.
  • Confirm PBL 8.2.1 or later if you use createBillingProgramReportingDetailsAsync(). 8.2.0 has a bug.
  • Strip PII from externalTransactionId and from any external link Uri.
  • Payment method image meets the 192dp x 20dp spec before you upload to Play Console.
  • Remove any internal state before launching an external link. Users may not return to your app.

References