# Mobile purchase claims (https://www.getfounderhq.com/docs/analytics/revenue/mobile-purchase-claims)

Tie an App Store or Google Play purchase to the person who made it, and to the
account it should fund. Allow about fifteen minutes.

<Callout title="Before you start">
Install the FounderHQ SDK ([React Native](/analytics/sdks/react-native),
[iOS](/analytics/sdks/ios), or [Android](/analytics/sdks/android)) and connect a store:
[RevenueCat](/analytics/revenue/connect-revenuecat),
[Superwall](/analytics/revenue/connect-superwall),
[Apple](/analytics/revenue/connect-apple), or
[Google Play](/analytics/revenue/connect-google-play).
</Callout>

A store purchase carries no browser session and no metadata field you can fill.
It carries one UUID. The SDK mints that UUID, records what it stands for, and
hands it to the store. When the store notifies FounderHQ, the UUID leads back
to the buyer.

## 1. Prepare before you open the purchase sheet

```ts
const prepared = await founderhq.preparePurchase({ source: "app_store" });
```

`prepared` gives you the field your store library needs:

| Store | Field to pass | Where it goes |
| --- | --- | --- |
| App Store | `prepared.appAccountToken` | StoreKit purchase option `appAccountToken` |
| Google Play | `prepared.obfuscatedExternalAccountId` | `BillingFlowParams.Builder.setObfuscatedAccountId` |
| Superwall | `prepared.appAccountToken` | Superwall user attribute `fhq_visitor`, plus the store field above |

`preparePurchase` waits a moment for FounderHQ to acknowledge the context, then
returns anyway. Checkout never blocks on the network.

Whatever account is set on the SDK at this moment is frozen into the context.
Changing accounts later does not move this purchase.

## 2. Report the purchase result

```ts
await founderhq.observePurchase({
  prepared,
  purchase: {
    source: "app_store",
    transactionId: transaction.id,
    originalTransactionId: transaction.originalID,
  },
});
```

For Google Play, send `{ source: "google_play", purchaseToken, orderId }`.

The source you observe must match the source you prepared.

Using RevenueCat? Wrap the purchase instead, and the SDK does both steps:

```ts
const result = await founderhq.purchaseWithRevenueCat(() =>
  Purchases.purchasePackage(pkg),
);
```

## 3. Move an existing subscription to an account

Sometimes a person already pays, and later that subscription should fund a
team, family, or workspace. That is a deliberate product action, so it needs a
deliberate call:

```ts
await founderhq.claimSubscription({
  purchase: {
    source: "app_store",
    transactionId: transaction.id,
    originalTransactionId: transaction.originalID,
  },
  confirmation: "move_future_revenue",
});
```

<Callout type="warn" title="A claim moves future revenue only">
Payments that already happened keep the owner they had. The new owner starts at
the moment FounderHQ accepts the claim. MRR moves; history does not. Refunds
always follow the original payment.
</Callout>

Never call this from a listener, a restore, an entitlement refresh, or a
sign-in. Call it from a screen where the person asked for it.

## The RevenueCat identity rule

If you use RevenueCat, the FounderHQ UUID is the RevenueCat App User ID. Let
the SDK configure and log in RevenueCat, and never call
`Purchases.logOut()`. See [Connect RevenueCat](/analytics/revenue/connect-revenuecat).

## The same calls on every platform

| What you do | React Native | iOS | Android |
| --- | --- | --- | --- |
| Prepare | `preparePurchase` | `preparePurchase(source:)` | `preparePurchase(source, completion)` |
| Apply the token | pass `prepared` fields | `purchase(_:options:)` adds it | `applyPurchaseContext(builder, prepared)` |
| Report the result | `observePurchase` | `observePurchase(_:prepared:)` | `observePurchase(purchase, prepared)` |
| Wrap RevenueCat | `purchaseWithRevenueCat` | `purchaseWithRevenueCat` | `revenueCatPurchaseCallback` |
| Move a subscription | `claimSubscription` | `claimSubscription(_:confirmation:)` | `claimSubscription(purchase, confirmation)` |

## From your backend

Your server can do two things with a secret key, through the
[Node SDK](/analytics/sdks/node):

- `accountContextToken({ account, userId, idempotencyKey })` issues short-lived
  proof that this person may act for this account. It moves no money.
- `claimMobileSubscription({ account, userId, purchase, confirmation, idempotencyKey })`
  moves a subscription's future revenue to an account, with the same
  `"move_future_revenue"` confirmation and the same rules as the mobile call.

Neither call creates the account or the membership. Create those first with
`upsertAccount` and `accountMembership`.

## Verify

1. Buy a sandbox subscription in the app.
2. In FounderHQ, open Revenue. The purchase shows the buyer as a contact, and
   the channel that brought them.
3. After a transfer claim, the next renewal counts for the account, and the
   earlier payments still count for the person.

## Troubleshoot

### The purchase has no contact

The app purchased without preparing. Call `preparePurchase` first, and pass the
token to the store library.

### "Observed purchase source does not match its preparation"

You prepared for one store and observed another. Prepare with the same source
you buy with.

### A RevenueCat purchase reports no transaction

Some observer and custom-completion modes return no store transaction. The SDK
will not guess from entitlements. Claim it later with a verified transaction ID
or purchase token.
