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(product:) throws
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.
try await voidhash.reportTransaction(transactionId: transactionId)The transaction ID is a string; preserve every digit. You can also pass a StoreKit.Transaction
or VerificationResult<StoreKit.Transaction> directly to reportTransaction(...). The SDK extracts
only the ID and discards other fields, raw JSON and signed receipts. The verified-result overload
rejects unverified results. The direct transaction overload leaves local verification with the host.
Existing VoidhashTransaction inputs remain supported; only transactionId is required. Other
metadata is optional and discarded when reporting. Pending results are ignored. Invalid IDs throw
INVALID_TRANSACTION. Reporting does not require a StoreKit connection; delivery failures stay
queued without throwing into the host purchase callback.
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.
try await voidhash.syncPurchases()Initialization installs the observer and scans purchases in the background. Foreground and connectivity recovery also scan with a one-minute throttle per trigger. 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.
Load products
getProducts() returns the store-backed products configured in your project. Prices come already
formatted for the customer's storefront, so you can render them directly.
let products = try await voidhash.getProducts()
let monthly = products.first(where: { $0.slug == "monthly" })
if let monthly {
Text(monthly.displayPrice)
}If a product is missing from the result, the store did not return it. That happens when the product is not available on the current store account, environment, or provider configuration.
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.
try await voidhash.restorePurchases()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.
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.
Transactions missing from the dashboard
- Confirm successful purchase callbacks call
reportTransaction(...)with the original store values. UsesyncPurchases()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.