JTDRECIPE

Just The Damn Recipe

Sign up

JTDRecipe – Google Play Billing Integration (TWA Bridge)

Objective

Implement a compliant Google Play Billing flow for the Android TWA version of JTDRecipe, replacing any Stripe or external subscription flows inside the app.

This must:

  • Trigger native Google Play purchase UI from the Android layer
  • Communicate purchase success back to the web app
  • Unlock Pro features immediately after purchase
  • Avoid any Stripe or external payment flow inside the Android app

TWA vs WebView: How it Connects

Trusted Web Activities (TWAs) run in a Chrome Custom Tab, not a standard WebView. Therefore, TWAs do not support Javascript interfaces (window.Android) and they don't use shouldOverrideUrlLoading.

Instead, the web app triggers billing by navigating to a custom deep link: playbilling://purchase?planId=...&userId=...

To catch this in a TWA, your Android app must register an Intent Filter for this custom scheme in the AndroidManifest.xml.

1. AndroidManifest.xml (The TWA Hook)

Add an Activity to handle the billing intent. This catches the playbilling:// URL when the user taps "Go Pro".

<activity android:name=".BillingActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.VIEW" />
        <category android:name="android.intent.category.DEFAULT" />
        <category android:name="android.intent.category.BROWSABLE" />
        <data android:scheme="playbilling" android:host="purchase" />
    </intent-filter>
</activity>

2. BillingActivity.kt (The Native Handler)

When the intent is caught, this Activity extracts the parameters and launches the Google Play Billing flow.

class BillingActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        val uri: Uri? = intent.data
        if (uri != null && uri.scheme == "playbilling") {
            val planId = uri.getQueryParameter("planId")
            val userId = uri.getQueryParameter("userId")
            
            val productMap = mapOf(
                "monthly" to "jtdrecipe_pro_monthly",
                "6month" to "jtdrecipe_pro_6month",
                "yearly" to "jtdrecipe_pro_annual"
            )
            val productId = productMap[planId]

            if (productId != null && userId != null) {
                // Initialize BillingClient and launch Google Play Billing Flow here
                // launchBillingFlow(productId, userId)
            } else {
                finish() // Invalid parameters, close the invisible activity
            }
        }
    }
}

Billing Flow Handling

In MainActivity.kt:

  • Initialize BillingClient
  • Handle onPurchasesUpdated

On successful purchase:

webView.evaluateJavascript(
    "window.onPurchaseSuccess && window.onPurchaseSuccess();",
    null
)

Local Verification & Unlocking Pro

When Android receives the successful purchase response, verify the signature locally using your Licensing Key from the Google Play Console.

Once verified locally, call the Javascript callback:

runOnUiThread {
    webView.evaluateJavascript("if(window.onPurchaseSuccess) window.onPurchaseSuccess();", null)
}

This will trigger the website to automatically unlock Pro and refresh the user's account!

Advertisement