starti.app
SDK Reference

In-App Purchase

Purchase products, manage subscriptions, and retrieve product information through the native App Store / Google Play in-app purchase flow.

Access: startiapp.InAppPurchase

For step-by-step guides — see Setup In-App Purchases and Setup Subscriptions.

Methods

purchaseProduct(productId, purchaseType): Promise<InAppPurchaseResponse<InAppPurchaseProductResponse>>

Initiates a native purchase flow for the specified product. The returned promise resolves after the user completes or cancels the transaction.

Parameters:

ParameterTypeRequiredDescription
productIdstringYesThe product identifier as configured in App Store Connect or Google Play Console.
purchaseTypeInApPurchasePurchaseTypeYesWhether the product is "consumable" or "nonconsumable".

Returns: Promise<InAppPurchaseResponse<InAppPurchaseProductResponse>> — A response object indicating success (with a transaction identifier) or failure (with an error message).

Example:

const result = await startiapp.InAppPurchase.purchaseProduct(
  "com.example.premium",
  "nonconsumable"
);

if (result.success) {
  console.log("Transaction ID:", result.value.transactionIdentifier);
} else {
  console.error("Purchase failed:", result.errorMessage);
}

getProduct(productId, purchaseType): Promise<InAppPurchaseResponse<InAppPurchaseGetProductResponse>>

Retrieves product metadata (name, description, localized price) from the store without initiating a purchase.

Parameters:

ParameterTypeRequiredDescription
productIdstringYesThe product identifier as configured in App Store Connect or Google Play Console.
purchaseTypeInApPurchasePurchaseTypeYesWhether the product is "consumable" or "nonconsumable".

Returns: Promise<InAppPurchaseResponse<InAppPurchaseGetProductResponse>> — A response object containing product details on success, or an error message on failure.

Example:

const result = await startiapp.InAppPurchase.getProduct(
  "com.example.premium",
  "nonconsumable"
);

if (result.success) {
  console.log(`${result.value.name} - ${result.value.localizedPrice}`);
} else {
  console.error("Failed to load product:", result.errorMessage);
}

subscribe(productId, options?): Promise<InAppPurchaseResponse<SubscribeResponse>>

Initiates a subscription purchase. Also handles upgrades and downgrades when options.oldProductId is provided.

Parameters:

ParameterTypeRequiredDescription
productIdstringYesThe subscription product identifier.
optionsSubscribeOptionsNoOptions for upgrades/downgrades and promotional offers.

Returns: Promise<InAppPurchaseResponse<SubscribeResponse>> — Transaction details on success.

Example:

// New subscription
const result = await startiapp.InAppPurchase.subscribe("com.example.monthly");

if (result.success) {
  console.log("Subscribed! Transaction:", result.value.transactionId);

  // IMPORTANT: Validate on your backend first, then finish the transaction
  const validated = await fetch("/api/validate-subscription", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(result.value)
  }).then(r => r.ok);

  if (validated) {
    await startiapp.InAppPurchase.finishTransaction(result.value.transactionId);
  }
}

You must call finishTransaction() after your backend validates the purchase. If you don't, the store will re-deliver the transaction on next app launch and may eventually issue a refund.

Upgrade example:

// Upgrade from monthly to yearly
const result = await startiapp.InAppPurchase.subscribe("com.example.yearly", {
  oldProductId: "com.example.monthly",
  upgradePolicy: "immediate"
});

finishTransaction(transactionId): Promise<InAppPurchaseResponse<boolean>>

Finishes (acknowledges) a subscription transaction after your backend has validated it. This must be called after every successful subscribe() call once your server confirms the purchase.

If not called, the transaction remains "unfinished" — the store will re-deliver it on next app launch (via getActiveSubscriptions() / restorePurchases()), and may eventually refund the user.

Parameters:

ParameterTypeRequiredDescription
transactionIdstringYesThe transactionId from the SubscribeResponse.

Returns: Promise<InAppPurchaseResponse<boolean>>true if the transaction was acknowledged.

Example:

// After your backend confirms the subscription is valid:
const result = await startiapp.InAppPurchase.finishTransaction(transactionId);

if (result.success) {
  console.log("Transaction acknowledged");
}

getSubscriptionProducts(productIds): Promise<InAppPurchaseResponse<SubscriptionProduct[]>>

Retrieves subscription product details including pricing, billing period, and available offers.

Parameters:

ParameterTypeRequiredDescription
productIdsstring[]YesArray of subscription product identifiers.

Returns: Promise<InAppPurchaseResponse<SubscriptionProduct[]>> — Array of subscription product details.

On Android, only the first introductory pricing phase is returned per product. If a subscription has a multi-phase intro (e.g., free trial followed by a discounted period), only the first phase appears in introductoryOffer.

Example:

const result = await startiapp.InAppPurchase.getSubscriptionProducts([
  "com.example.monthly",
  "com.example.yearly"
]);

if (result.success) {
  result.value.forEach(product => {
    console.log(`${product.name}: ${product.localizedPrice}/${product.subscriptionPeriod}`);

    if (product.introductoryOffer) {
      console.log(`  Trial: ${product.introductoryOffer.localizedPrice} for ${product.introductoryOffer.period}`);
    }
  });
}

getActiveSubscriptions(options?): Promise<InAppPurchaseResponse<SubscriptionStatus[]>>

Returns the status of all active subscriptions for the current user. Use this to check if the user has an active subscription and its current state.

Parameters:

ParameterTypeRequiredDescription
options.syncbooleanNoForce sync with App Store / Google Play before returning. Use for restore purchases functionality.

Returns: Promise<InAppPurchaseResponse<SubscriptionStatus[]>> — Array of active subscription statuses.

Example:

const result = await startiapp.InAppPurchase.getActiveSubscriptions();

if (result.success) {
  const active = result.value.filter(s => s.state === "subscribed");
  if (active.length > 0) {
    console.log("User has active subscription:", active[0].productId);
    console.log("Will renew:", active[0].willAutoRenew);
  }
}

restorePurchases(): Promise<InAppPurchaseResponse<SubscriptionStatus[]>>

Syncs with the store and returns all subscriptions — including expired ones. Unlike getActiveSubscriptions() which only returns active subscriptions, restorePurchases() returns the full history so you can re-validate on your backend. Required by App Store guidelines — apps with subscriptions must include a "Restore Purchases" button.

Returns: Promise<InAppPurchaseResponse<SubscriptionStatus[]>> — Array of subscription statuses (may include both "subscribed" and "expired").

Example:

const result = await startiapp.InAppPurchase.restorePurchases();

if (result.success) {
  console.log("Restored", result.value.length, "subscriptions");
}

manageSubscriptions(): Promise<InAppPurchaseResponse<boolean>>

Opens the platform's subscription management screen where the user can cancel, change, or view their subscriptions. On iOS, this opens an in-app management sheet. On Android, this opens the Google Play subscriptions page in an external browser.

Returns: Promise<InAppPurchaseResponse<boolean>>true if the management screen was opened successfully.

Example:

await startiapp.InAppPurchase.manageSubscriptions();

isSubscribed(productId?): Promise<boolean>

Convenience wrapper over getActiveSubscriptions(). Returns true if the user has at least one subscription with state === "subscribed".

Parameters:

ParameterTypeRequiredDescription
productIdstringNoIf provided, checks for this specific product ID only.

Returns: Promise<boolean>true if the user has an active subscription (or the specific product if productId is provided). Returns false on error rather than throwing.

Example:

// Check for any active subscription
if (await startiapp.InAppPurchase.isSubscribed()) {
  unlockPremiumFeatures();
}

// Check for a specific product
const hasYearly = await startiapp.InAppPurchase.isSubscribed("com.example.yearly");

Events

"unfinishedTransaction"

Emitted automatically on app startup for each subscription transaction that was purchased but not yet finished (acknowledged). This handles cases where the app crashed, lost connectivity, or closed before finishTransaction() was called.

The listener can be registered before or after startiapp.initialize() — unfinished transactions are buffered and delivered when the first listener is added.

Event detail: SubscriptionStatus

Example:

await startiapp.initialize();

startiapp.InAppPurchase.addEventListener("unfinishedTransaction", async (event) => {
  const transaction = event.detail;

  // Validate on your backend, then finish the transaction
  const validated = await fetch("/api/validate-subscription", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(transaction)
  }).then(r => r.ok);

  if (validated) {
    await startiapp.InAppPurchase.finishTransaction(transaction.transactionId);
  }
});

On Android, Google Play gives you 3 days to acknowledge a purchase before it is automatically refunded.

Types

InApPurchasePurchaseType

Union type specifying the kind of in-app purchase product.

type InApPurchasePurchaseType = "nonconsumable" | "consumable";
ValueDescription
"nonconsumable"A one-time purchase that does not expire (e.g., unlock a feature).
"consumable"A purchase that can be bought multiple times (e.g., in-game currency).

InAppPurchaseResponse<T>

Discriminated union returned by all purchase methods. Check the success field to determine which shape you have.

type InAppPurchaseResponse<T> =
  | { success: true;  value: T }
  | { success: false; errorMessage: string };

InAppPurchaseProductResponse

Payload returned on a successful purchaseProduct call.

type InAppPurchaseProductResponse = {
  transactionIdentifier: string;
};

InAppPurchaseGetProductResponse

Payload returned on a successful getProduct call.

type InAppPurchaseGetProductResponse = {
  name: string;
  description: string;
  localizedPrice: string;
  currencyCode: string;
};

SubscribeOptions

Options for the subscribe method. All fields are optional.

type SubscribeOptions = {
  oldProductId?: string;
  upgradePolicy?: "immediate" | "deferred";
  offerId?: string;
  resolveOffer?: (productId: string, offerId: string) => Promise<OfferSignature>;
};
FieldDescription
oldProductIdProduct ID of the subscription being replaced. Android only — on iOS, StoreKit 2 handles same-group upgrades automatically. On Android, the SDK auto-detects the active subscription unless your app has multiple subscription groups.
upgradePolicy"immediate" charges now and switches. "deferred" switches at end of current period. Android only — iOS handles transitions automatically within subscription groups.
offerIdPromotional offer identifier as configured in App Store Connect or Google Play Console. On iOS, pair with resolveOffer for promotional offers that require a server-signed signature. On Android, this is passed as the offer token.
resolveOfferAsync callback that returns the offer signature from your backend. Only called when needed (iOS promotional offers). Ignored on Android.

OfferSignature

Returned by the resolveOffer callback. Your backend must generate these values using your App Store Connect subscription key.

type OfferSignature = {
  signature: string;
  nonce: string;
  timestamp: string;
  keyId: string;
};

SubscribeResponse

Payload returned on a successful subscribe call. Important: The transaction is not yet finished — you must call finishTransaction() after your backend validates it.

type SubscribeResponse = {
  /** Pass this to finishTransaction() after backend validation. */
  transactionId: string;
  originalTransactionId: string;
  productId: string;
  purchaseDate: string;
};

SubscriptionProduct

Subscription product details returned by getSubscriptionProducts.

type SubscriptionProduct = {
  productId: string;
  name: string;
  description: string;
  localizedPrice: string;
  currencyCode: string;
  /** Human-readable billing period, e.g. "1 month", "1 year", "3 months". null if not available. */
  subscriptionPeriod: string | null;
  introductoryOffer?: SubscriptionOffer;
  promotionalOffers?: SubscriptionOffer[];
};

SubscriptionOffer

Details of a subscription offer (introductory or promotional).

type SubscriptionOffer = {
  id?: string;
  localizedPrice: string;
  period: string;
  periodCount: number;
  paymentMode: "freeTrial" | "payAsYouGo" | "payUpFront";
};
paymentModeDescription
"freeTrial"Free for the offer period, then regular price.
"payAsYouGo"Discounted price charged each period for periodCount periods.
"payUpFront"Discounted price charged once up front for the full offer duration.

SubscriptionStatus

Status of an active or expired subscription.

type SubscriptionStatus = {
  productId: string;
  transactionId: string;
  originalTransactionId: string;
  purchaseDate: string;
  state: "subscribed" | "expired";
  willAutoRenew: boolean;
};
stateDescription
"subscribed"Active and in good standing. Includes users in grace period or billing retry — the SDK handles this automatically.
"expired"Subscription has ended, been revoked, or payment failed beyond grace period.

On this page