Report and restore purchases

Report purchases from your existing billing integration to Voidhash.

The initial release runs in observer mode. Your existing billing integration starts purchases, restores access, and finishes or acknowledges store transactions. Voidhash reports the purchases it discovers without taking over those responsibilities. SDK-started purchases and hosted paywalls are temporarily unavailable; purchase() returns an Err with READ_ONLY_PURCHASE_NOT_ALLOWED before opening a store purchase flow.

Report a completed purchase or restore

Call reportTransaction(...) after each successful purchase, including purchases from another SDK's paywall. Report each transaction returned by a restore callback as well. Configure Voidhash and identify the purchasing user before starting the host purchase flow.

Only the store identifier is required: an Apple transaction ID or a Google Play purchase token. Voidhash supplies its configured bundle/package and captures the current SDK identity automatically. The backend fetches and verifies the purchase with the store; timestamps, quantities, product metadata, receipts, signatures and purchaser arguments are unnecessary.

// Apple: keep the transaction ID as a string, preserving every digit.
await voidhash.client.reportTransaction({ platform: "ios", transactionId });

// Google Play: the purchase token, not the order ID.
await voidhash.client.reportTransaction({ platform: "android", purchaseToken });

Wait for useVoidhash().status === "ready" before calling the client. If the host exposes original store data instead, the same method accepts a StoreKit JWS string or a Play bridge object with originalJson. Voidhash extracts the identifier internally and discards the remaining payload; no receipt-decoding dependency is needed in your application.

Legacy ReportedTransaction metadata such as productId, purchaseDate, quantity, receipt and appAccountToken is optional and discarded. Android's optional purchaseState can mark a report as pending or unspecified; those reports are ignored until reported as purchased. The original Play JSON's pending state is converted internally. Multi-product bridge purchases are rejected because the current purchase processing supports one product per purchase.

reportTransaction() returns Result<void, VoidhashError>. Missing or malformed required identifiers return FAILED_TO_REPORT_TRANSACTION; an unavailable client returns VOIDHASH_CLIENT_NOT_INITIALIZED. Network and delivery failures leave the report queued and return Ok.

Reporting works after the host has finished or consumed a purchase because it does not scan the store. It never finishes, acknowledges or consumes transactions. Keep your billing integration's finalization and access checks in place.

A successful report is captured in the durable outbox before returning; delivery runs in the background. Success does not imply immediate backend acceptance. SDK diagnostics describe deferred delivery. Duplicate reports and observer callbacks use the same store identifier and retain the original captured identity through retries and app relaunches.

Recovery scans and restore callbacks

Use syncPurchases() for silent recovery when the host callback exposes no usable store transaction values, including after its restore flow. It reports purchases the store still exposes, skips receipts already accepted by Voidhash, and refreshes the current person. It does not prompt for store authentication. Store-read failures reach the caller; delivery failures remain queued.

const result = await voidhash.client.syncPurchases();
if (result.isErr()) console.warn("Voidhash purchase scan failed", result.error);

Initialization installs the observer and scans purchases in the background. Foreground and connectivity recovery also scan with a one-minute throttle shared across both triggers. In observer mode, scans can report unconsumed or unfinished consumables that remain visible without finalizing them. They cannot recover consumed or finished consumables, or arbitrary expired subscription history. iOS scans pending transactions and current entitlements; Android queries currently owned purchases.

syncPurchases() is a fallback, not a delivery guarantee. If the host hides transaction values and finishes a consumable before Voidhash captures it, a later callback or scan cannot recover it. Keep explicit reporting in the successful purchase callback whenever your host SDK exposes the values.

Explicit restoration

Connect your Restore Purchases button to restorePurchases(). It revalidates restorable purchases for the identity that requested the restore, even when Voidhash already accepted those receipts. The project's transfer policy controls whether ownership changes. A queued purchase retains its original buyer and must be delivered before the restore can request another owner.

const result = await voidhash.client.restorePurchases();
if (result.isErr()) console.warn("Voidhash restore failed", result.error);

On iOS, this calls AppStore.sync() and reads verified non-consumable transaction history, including expired or revoked transactions. App Store authentication may appear, so call it only from a user action. Android queries currently owned Play purchases; it cannot retrieve consumed purchases or arbitrary expired subscription history. Known consumables are excluded from explicit restoration. A store error or a receipt that cannot be accepted fails the restore; deferred receipts stay queued for retry. An empty store can restore successfully. Success does not imply an active entitlement: use the person's current grants to decide whether to unlock access.

Reporting and silent sync after a host restore discover purchases. Use explicit restoration when Voidhash must revalidate cached receipts or apply its ownership policy for the current user.

Load products

useProducts() returns the store-backed products configured in your project. Prices come already formatted for the customer's storefront, so you can render them directly.

const { data: products, error, isLoading } = voidhash.useProducts();
const monthly = products.get("monthly");

data is a Map keyed by the project's product slugs. monthly is undefined when the store does not return that product. That usually means the product is not available in the current sandbox account, country, app version, or provider configuration.

Once you have a product, render its store-provided displayPrice.

if (!monthly) return null;

return <Text>{monthly.displayPrice}</Text>;

Transactions missing from the dashboard

  • Confirm successful purchase callbacks call reportTransaction(...) with the original store values. Use syncPurchases() for restore-only callbacks that expose no transaction values.
  • Verify that Voidhash uses the intended project key and purchasing user, and that the dashboard is showing the matching project, person, and store environment.
  • Check SDK diagnostics for store-read or delivery failures. Analytics events arriving does not prove that a store transaction was discovered or submitted.
  • For scan-only integrations, confirm the purchase is still exposed by the store. A finished or consumed consumable requires captured transaction values; a successful host purchase alone is insufficient for a later scan.

Next steps