InAppify

Offerings, placements and packages

Read current, placed or exact offerings, distinguish current/default and refresh attributes plus offerings within quota.

After configuration and identity, let the SDK select the customer's offer. The first catalog entry is not necessarily appropriate.

Three selection modes

IntentInputBehavior
Customer's current offerNo placementSelected current, otherwise default
Display location such as upgradeplacementIdentifier: 'upgrade'Placement selection with current/default fallback
One exact offeringgetOffering('special_offer')Exact lookup; missing is null, not substitution

A placement is a display location; an offeringIdentifier identifies the offer. Targeting can make current differ from the project default. Mobile uses server selection; Web uses the provider's existing rules/fallback resolver.

Flutter

import 'package:inappify_sdk/inappify_sdk.dart';

Future<Map<String, Object?>> offeringAlternatives() async {
  final catalog = await InappifySdk.getOfferingsResult();
  final current = await InappifySdk.getCurrentOfferingResult();
  final upgrade = await InappifySdk.getCurrentOfferingResult(
    placementIdentifier: 'upgrade',
  );
  final special = await InappifySdk.getOfferingResult('special_offer');
  if (!upgrade.isSuccess) return {}; // Show a retry state.
  final selected = upgrade.data;
  if (selected == null) return {}; // No offering selected.
  final packages = selected.packages ?? [];
  // Render packages; retain the actual PackageModel for purchase.
  // Helpers on a collection already obtained, without another HTTP call:
  return {
    'current': current.data, 'special': special.data, 'packages': packages,
    'cachedCurrent': catalog.data?.current,
    'cachedExact': catalog.data?.getOffering('special_offer'),
    'cachedPlacement': catalog.data?.getCurrentOfferingForPlacement('upgrade'),
  };
}

Convenience getOfferings(), getCurrentOffering(placementIdentifier: ...) and getOffering(id) return nullable models. A successful offering lookup with null data is different from network failure.

Kotlin

import com.inappify.sdk.*

suspend fun offeringAlternatives(client: InappifyV2Client) {
    val catalog = client.getOfferings()
    val current = client.getCurrentOffering()
    val upgrade = client.getCurrentOffering("upgrade")
    val special = client.getOffering("special_offer")
    if (upgrade is InappifyResult.Success) {
        val selected = upgrade.data ?: return
        val packages = selected.availablePackages
        // Render packages; retain the actual InappifyPackage.
    }
    if (catalog is InappifyResult.Success) {
        val selected = catalog.data.current
        val exact = catalog.data.getOffering("special_offer")
        val placed = catalog.data.getCurrentOfferingForPlacement("upgrade")
    }
}

Collection helpers make no HTTP call. Kotlin/Flutter all is an identifier map. Retain the complete package for purchase; don't reconstruct it from name or price.

JavaScript

import type { InAppify } from '@inappifylabs/web-sdk';

export async function offeringAlternatives(client: InAppify) {
  const catalog = await client.getOfferings();
  const current = await client.getCurrentOffering();
  const upgrade = await client.getCurrentOffering('upgrade');
  const exact = await client.getOffering('special_offer');
  return {
    current,
    upgrade,
    exact,
    cachedExact: catalog.getOffering('special_offer'),
    cachedPlacement: catalog.getCurrentOfferingForPlacement('upgrade'),
  };
}

JS catalog.all is an array. The old object form getCurrentOffering({placementIdentifier: 'upgrade'}) remains compatible; prefer the simple form above for new code.

Manual refresh

On Kotlin/Flutter mobile, await syncAttributesAndOfferingsIfNeeded() and select from its returned collection. It synchronizes pending attributes before fresh offerings, including a fresh fetch when the queue is empty and quota permits. See attributes.

Each client admits five executions per rolling 60 seconds. Admitted failures count and account changes do not reset quota. Above the limit there is no network: return current-session cache or RATE_LIMITED_NO_CACHE with retry timing in failure details. Do not recreate the SDK to evade the limit.

Normal reads use cache. Android automatic offering refresh runs in foreground with a five-minute freshness window and thirty-second error backoff; it is separate from manual quota. No additional startup pre-warming is needed.

Missing offers or products

Check placement/offering identifiers, catalog membership, app key, store SKU and targeting conditions. Distinguish failed fetch from a successful empty collection. Do not fabricate packages or silently replace an explicitly requested offering.

Purchase a package · Selected-offering discounts