In-app purchases

Run App Store and Google Play purchase and consume flows, then read the result from an event.

Read results from events, not the call

Awaiting purchaseIos or purchaseAndroid only confirms the command was sent — the result arrives later on purchaseSuccess or purchaseFailed. Subscribe before you call. For the rest of the iap namespace, use the envelope-based helpers on Sell products & subscriptions.

Every purchase and consume takes { id, type }: id is the App Store / Play product identifier, and type is "product" (one-time / consumable) or "subscription". Use the Ios / Android method for the device you're on.

The payload key is id, not product_id.

Results from the device are unverified. Treat purchaseSuccess as "the store reported a transaction", then verify the receipt server-side before granting entitlements. See Verify in-app purchases.

Buy a product on iOS

Opens the App Store purchase sheet. The result lands on purchaseSuccess, or purchaseFailed on cancel or error — both carry { platform, data }.

import { createBdkNative } from "@bdk/native/browser";

const bdk = createBdkNative();

bdk.on("purchaseSuccess", ({ platform, data }) => {
  // data is the raw, UNVERIFIED store payload — send it to your server to verify.
  console.log("purchased on", platform, data);
});

bdk.on("purchaseFailed", ({ data }) => {
  console.warn("purchase failed or cancelled", data);
});

await bdk.iap.purchaseIos({ id: "pro_monthly", type: "subscription" });

Buy a product on Android

Opens the Google Play purchase sheet. Results surface on the same purchaseSuccess / purchaseFailed events; the platform field tells you which store they came from.

bdk.on("purchaseSuccess", ({ platform, data }) => {
  if (platform === "android") void verifyOnServer(data);
});

await bdk.iap.purchaseAndroid({ id: "coins_100", type: "product" });

Buy from one shared codebase

Pick the right method by platform when you ship to both stores. The call resolves with no effect off-device, so add a web fallback.

async function buy(id: string, type: "product" | "subscription") {
  const os = bdk.getDeviceInfo()?.deviceOS?.toLowerCase();
  if (os === "ios") return bdk.iap.purchaseIos({ id, type });
  if (os === "android") return bdk.iap.purchaseAndroid({ id, type });
  // Not running natively — show a web fallback / upsell.
}

await buy("pro_monthly", "subscription");

Consume a product so it can be bought again

Use for consumables (coins, lives, refills) — a consumable can't be repurchased until it's consumed.

Consuming on the device only updates local state. The authoritative consume for Google Play happens server-side — see Verify in-app purchases.

await bdk.iap.consumeAndroidPurchase({ id: "coins_100", type: "product" });

IAP events reference

Which event answers which call. Each carries the platform ("ios" or "android") and a data object with the store's purchase details — product id, transaction id, receipt data, and result/error codes. Treat data as the raw store payload and verify it server-side. bdk.on(...) returns an unsubscribe function.

EventFired by
purchaseSuccesspurchaseIos / purchaseAndroid succeeded
purchaseFailedpurchase cancelled or errored
receiptReceiveda store receipt / token was returned
const offs = [
  bdk.on("purchaseSuccess", (e) => console.log("success", e.platform, e.data)),
  bdk.on("purchaseFailed", (e) => console.log("failed", e.data)),
  bdk.on("receiptReceived", (e) => console.log("receipt", e.data))
];

// later
offs.forEach((off) => off());

Verify on the server

The step that actually grants entitlements. Send the data from purchaseSuccess / receiptReceived to your backend and validate it. See Verify in-app purchases for verifyIosReceipt, verifyAndroidReceipt, readAndroidReceipt, and server-side consumeAndroidPurchase.

import { verifyAndroidReceipt } from "@bdk/native/server/iap";

// In your API handler, with the purchaseToken forwarded from the client:
const validation = await verifyAndroidReceipt({
  packageName: "com.example.app",
  productId: "pro_monthly",
  purchaseToken,
  productType: "subscription"
});

if (validation.isValid) {
  // Inspect validation.payload / validation.raw before granting entitlements.
}