← Hive AxylCONTENT HISTORY
WHAT CHANGED
Update to Hive Axyl
Snapshot Oct 24, 2026 · 13:42 UTC · version 2.1.0
Collection source: downloaded plugin package.
First saved snapshot
No earlier snapshot is available to establish a change.
Save Hive Axyl and check future changes in Following.
Up to 10 plugins free · no card required.
Compare saved observations
Download comparison JSONFull technical diff · 0 changed fields
Full snapshot data
{
"description": "Composes shared Apple/Google subscription startup in Hive Axyl Unity: pre-record, acquisition, save, game-server verification and entitlement, and confirmation. Use for multi-market or unspecified-market subscription flows and common receipt/post invariants; does not implement server renewal jobs.",
"included_files": [
{
"relative_path": "references/apple.md",
"size_in_bytes": 2712
},
{
"relative_path": "references/google.md",
"size_in_bytes": 5196
}
],
"name": "axyl-subscription",
"skill_md_contents": "---\nname: axyl-subscription\ndescription: \"Composes shared Apple/Google subscription startup in Hive Axyl Unity: pre-record, acquisition, save, game-server verification and entitlement, and confirmation. Use for multi-market or unspecified-market subscription flows and common receipt/post invariants; does not implement server renewal jobs.\"\nlicense: Apache-2.0\nmetadata:\n author: Com2uS Platform Corporation\n sdk-version: 1.0.0\n engine: Unity 6000.0+\n---\n\n# Start and confirm a subscription\n\nStarts an Apple or Google subscription: pre-record, checkout, save, game-server\nverification and entitlement, confirmation and store finish. The Installed SDK, SDK results, Payment\nstate and Start and confirm a subscription sections below apply to every step.\n\n**Inputs:** the market, the subscription product, the originating player's session,\ncountry, language and the account UUID namespace.\n**App owns:** checkout UI, pending-state storage, and the game server's\nverification, entitlement ledger, confirmation and renewal processing.\n\nFor one named market, prefer `axyl-purchase-market-apple` or\n`axyl-purchase-market-google`. Otherwise read only that market's procedure:\n\n| Market | Read | Market steps |\n|---|---|---|\n| Apple | [Apple StoreKit](references/apple.md) | AutoRenewable catalog, StoreKit purchase, JWS and ID, finish after post |\n| Google | [Google Play Billing](references/google.md) | Subscription offers and phases, per-launch `Subs` query and offer token, envelope, device acknowledgement after post |\n\n## Verify\n\n- [ ] Pre → store → save → game-server confirmation → post → finish or acknowledge,\n with one original receipt for save, server verification and post; the client\n calls no verify API.\n- [ ] A save conflict or a duplicate post is not a new grant, a missing confirmation\n posts nothing, and a post without `HiveAxylTransactionId` finishes nothing.\n- [ ] Google acknowledges on the device (`AlreadyAcknowledged` counts) and never\n consumes; a failed acknowledgement stays pending and recoverable after restart.\n- [ ] Google is acknowledged by Axyl's server at post and again on the device; the\n receipt-form gap and the app-exit recovery contract are reported, not assumed.\n\n<!-- axyl-shared: core/installed-sdk.md -->\n\n## Installed SDK\n\n- Read `Packages/manifest.json`, the lock file and each resolved `package.json`.\n All `com.com2usplatform.hiveaxyl.*` packages must be the same release (UPM does\n not enforce this); `com.hive.axyl.*` packages are a release before 1.0.0-rc.9.\n- The installed SDK wins for APIs, types and contracts. If its release differs\n from this skill's `sdk-version`, apply a step only after its fields, result\n variants, platform support and failure behaviour match; otherwise stop at that\n step and report what is missing. Never change the installed version.\n- The app owns UI, storage, the device key and orchestration. Sample and recipe\n classes are not SDK APIs.\n\n<!-- /axyl-shared -->\n\n<!-- axyl-shared: core/results.md -->\n\n## SDK results\n\nMatch each call's concrete result and read `Success.Data` only after checking the\nfields the next step needs. The markers live in `Hive.Axyl.Contracts.Result`.\n\n| Result | Handling |\n|---|---|\n| `IUserCanceledOutcome` | The user dismissed OS or provider UI: stop this attempt quietly |\n| `IUnknownOutcome` | Keep `Code` and `RawJson`; never treat it as a known refusal or a success |\n| `IUntypedProblem` | Technical failure in `Problem` (`HiveError`): network, timeout or caller `Cancelled` |\n| Other typed variant | Only that call's documented meaning |\n\nPass `new ApiCallContext { Token = ct }` and check cancellation before each\nfollow-up call; a request cancelled in flight may still have taken effect. Never\nbranch on HTTP status, exception text or raw bodies, and never log tokens or\ncredentials.\n\n<!-- /axyl-shared -->\n\n<!-- axyl-shared: core/payment-state.md -->\n\n## Payment state\n\nResolve `Hive.Axyl.Payments.IPaymentsService`, registered once at startup with\n`AddPayments()` (`https://commerce-api.hiveaxyl.com`), `AddPayments(sandbox: true)`\n(`https://sandbox-commerce-api.hiveaxyl.com`) or `AddPayments(baseUrl)` for a\nprovisioned host, as the app's environment selects. Require the originating\nplayer's authenticated session and the selected market's addon; never initialize\nper purchase. Never log receipts.\n\n### Retained state\n\n`PurchaseOrder`, `PendingPurchase`, `PurchaseDeliveryConfirmation` and the recipe\nsources are sample types: carry the same values in app-owned types through every\nstage.\n\n| State | Values |\n|---|---|\n| Order | Market, originating player, product ID, decimal price, currency, country, language, server ID, app version, IAP payload, account UUID; Steam store player ID |\n| Pending | Record receipt, verification token, finish token, order ID, store transaction ID, the order, any local store verification objection |\n| Confirmed | The game server's delivery confirmation for that pending purchase, with any updated closing receipt or verified subscription product ID |\n\nA missing addon or input, or a pending purchase from another market, stops before\nthe next mutation. Keep receipts and IDs after a failure or cancellation: payment\nor delivery may already have happened. Persist recoverable state under the app's\nstorage policy, and coordinate purchase, recovery and account changes so that no\ntransaction is delivered twice at once or moved to another player.\n\n`AccountUuid` is optional on `PrePurchase`, `PurchaseRequest` and\n`SubscriptionPrePurchaseRequest`; an unset one is left off, so the server cannot\ncompare the account. Set it: derive it as UUIDv5 from the game's fixed namespace UUID\n(from game configuration, the same on every platform) and the invariant decimal\nplayer ID string, in standard network byte order (mind C# `Guid`'s layout). Send the\nsame value as Apple `AppAccountToken`, Google `ObfuscatedAccountId` and in Axyl\nrequests; the SDK does not generate it.\n\n### Catalog\n\nApple/Google: use configured product IDs, or call\n`ListStoreProductIdsAsync(new StoreRequest { ProviderId, Country, Language,\nAppVersion }, ctx)` and select the intended entry of\n`PaymentsListStoreProductIdsResult.Success.Data.Stores`: `Products` for consumables,\n`ProductSubscriptions` for subscriptions (the request has no product type). An\nabsent or empty list calls for configured IDs or a catalog diagnosis; never reuse\nconsumable IDs as subscriptions or invent one. Then query the native store and send\nthe mapped details with the market's `Fetch*ProductsAsync`. Steam and PG fetch their\nserver catalog directly.\n\nEach fetch returns its own result over `ProductResponseData`. Choose the\n`Data.Products` entry by `ProductId` and `ProductType`, and keep its decimal `Price`,\n`Currency` and offers with the response's country and language as the purchase\ninputs. Never parse `DisplayPrice`, rescale a decimal into micros or turn a missing\nprice into zero (a configured zero-price phase is real). A catalog failure, an empty\ncatalog and a failed checkout are different results. Recovery keeps the original\ntransaction's price and uses the catalog only for missing data.\n\n### Receipt identities\n\n| Market | Record receipt | Verification token for the game server | Store finish |\n|---|---|---|---|\n| Apple | Original transaction `JwsRepresentation` | Same JWS | `AppleTransaction.Id` as decimal `ulong` |\n| Google | JSON envelope `{\"purchase_data\": OriginalJson, \"signature\": Signature}` | Consumable: `PurchaseToken` with `ProductId`; subscription: the envelope | Same `PurchaseToken` |\n| Steam | Server `AxylReceipt` from init or restore | Same sealed value | None on the device |\n| PG | Server `AxylReceipt` from restore | Same sealed value | None on the device |\n\nBuild the Google envelope with a JSON serializer around the untouched\n`OriginalJson` string; never parse and reserialize it. A payment URL is never a\nreceipt. Keep the order ID and store transaction ID as separate values: a restore\nentry's `StoreTransactionId` can differ from its order key.\n\nProvider enums are request-specific (`PrePurchaseProviderId`,\n`PurchaseRequestProviderId`, `PurchaseFinalizeRequestProviderId`, ...): use the named\n`Apple`, `Google`, `Steam` or `Pg` member where it exists; never cast between enums\nor default an unknown market.\n\n### Game-server verification, delivery, close\n\nReceipt verification is server to server: the Payments contract marks\n`/payment/v1/purchase/verify` and `/payment/v1/subscription/verify` server-only, so\nthe client SDK has no verify call. After the purchase is recorded, send the pending\npurchase to the game's own authenticated server: market, record receipt,\nverification token, order ID, store transaction ID, order metadata and account, and\nwhether it is a new purchase or a recovery (the server maps that to verify request\ntype 1 or 2).\n\nThe game server verifies with the payment backend, checks account, product and price\n(for a subscription also current refund and expiry eligibility) and grants once\nthrough its durable ledger. It answers with a delivery confirmation for that pending\npurchase, also when the same transaction was already granted, carrying any updated\nclosing receipt (for a subscription, the verified product ID). The endpoint, request\nand response are the app's design. The confirmation is an integration contract, not\nproof of payment: the client never manufactures one, and a rejection, failure,\ntimeout or missing confirmation keeps the pending purchase and closes nothing.\n\nClose only with a confirmation that matches the pending purchase, using the market's\nprocedure; `RequestPurchaseAsync` and `FinalizePurchaseAsync` run after delivery. A\nclose failure leaves a delivered, unfinished transaction: retry the close with the\nsame confirmation, never grant again. Remove the pending record only after the close\nsucceeds.\n\n`ItemResultAsync(ItemResultBody, ctx)` reports actual delivery (`AxylTransactionId`\nfrom the game server's verification; `Status` 1 success, 2 cancelled without\nclawback, 3 cancelled with clawback; `Assets` granted). The recipes do not use it. If\nthe integration requires it, agree its owner with the backend and report the ledger\nresult, never status 1 merely because a purchase was recorded.\n\n<!-- /axyl-shared -->\n\n<!-- axyl-shared: payments/subscription.md -->\n\n## Start and confirm a subscription\n\nApple or Google only: Steam/PG subscriptions have no composition here, so never\nroute them through Apple or infer a flow from an enum member. Before enabling\nGoogle checkout, agree the acknowledgement recovery below.\n\n1. With the catalog product, price, currency, country, language and account UUID,\n call `PrepareSubscriptionAsync(new SubscriptionPrePurchaseRequest { ProviderId,\n ProductId, Price, Currency, Country, Language, ServerId, IapPayload, AccountUuid,\n AppVersion }, ctx)` **before checkout**. It ties later renewal notifications to\n the player and grants nothing.\n2. Acquire a purchased store transaction with the market; keep the original\n receipt, product, account, transaction ID and finish token. Pending, dismissal\n and cancellation stop here.\n3. `PurchaseSubscriptionAsync(new SubscriptionPurchaseRequest { ProviderId,\n AxylReceipt = originalReceipt, StoreTransactionId, Price, Currency, Country,\n Language, ServerId, AppVersion }, ctx)`: continue on `Success` or this call's\n `PaymentResourceConflict` (a repeated save); anything else stops.\n4. Send the pending subscription to the game server and wait for its delivery\n confirmation; the client calls no verify API. The server verifies the receipt,\n checks current refund and expiry eligibility and grants the entitlement once; a\n refund or an earlier grant alone never earns a fresh entitlement. Renewals are\n the server's job.\n5. With that confirmation, `PostSubscriptionAsync(new SubscriptionPurchasePostRequest {\n ProviderId, AxylReceipt = originalReceipt, ProductId = confirmedProductIdOrOriginal,\n Currency, Country, Language, ServerId, AppVersion }, ctx)`, where the product ID is\n the confirmation's verified one when present. **Save, server verification and post\n use the same original receipt**; another receipt can return `Success` without a\n matching subscription.\n6. `PaymentsPostSubscriptionResult.Success` needs a nonempty\n `Data.HiveAxylTransactionId` before anything is confirmed or finished; this\n call's `VerifyDuplicated` is the already-confirmed branch and permits the\n remaining store cleanup.\n7. Apple: `FinishTransactionAsync` with the original transaction ID. Google:\n `AcknowledgePurchaseAsync(originalPurchaseToken, ct)`, where `AlreadyAcknowledged`\n is success and any other result keeps the subscription pending. Never consume a\n subscription.\n\nUse the named `Apple`/`Google` member of `SubscriptionPrePurchaseRequestProviderId`,\n`SubscriptionPurchaseRequestProviderId` and `SubscriptionPurchasePostRequestProviderId`. Keep pending state on every\nfailure, including a post without a confirmed ID and a failed finish or\nacknowledgement; a repeated save or confirmation is not a new grant.\n\nGoogle save, server verification and post keep the JSON envelope. The SDK's\ntoken-form receipt needs a product ID that the save request lacks: never invent the field\nor rewrite the receipt without server confirmation. Google checkout queries the\nproduct as `ProductType.Subs` and takes the offer token from that answer.\n\n### Google acknowledgement responsibility\n\nAs the recipe does, Play is told twice: Axyl's server acknowledges the purchase\nthrough the Play Developer API when `/subscription/post` confirms it, and the device\nthen calls `AcknowledgePurchaseAsync` after post `Success` or `VerifyDuplicated`.\nBoth are idempotent (`AlreadyAcknowledged` counts as success) and the server guide\nasks for both, so one side's failure does not go unnoticed; never skip the device\ncall. A post `Success` without `HiveAxylTransactionId` confirmed nothing, so that\npurchase is still unacknowledged.\n\nGoogle refunds and revokes a new auto-renewing purchase that is not acknowledged\nwithin three days of PURCHASED (not PENDING); renewals need no new acknowledgement,\nand prepaid plans or top-ups under one week allow half the plan duration. See\nGoogle's [processing guide](https://developer.android.com/google/play/billing/integrate#subscriptions)\nand [subscription lifecycle](https://developer.android.com/google/play/billing/lifecycle/subscriptions).\n\nNothing acknowledges the purchase before post succeeds. Persist the receipt,\naccount, entitlement status, post status and acknowledgement result separately. A\nfailed save, confirmation, post or device acknowledgement stays urgent recoverable\nwork even after the entitlement is granted; retry the unfinished step under the\nagreed idempotency policy without granting again. The recipes compose no\nsubscription discovery after restart, so agree a server-owned or other recovery\nthat still works if the app does not reopen before the deadline.\n\n<!-- /axyl-shared -->\n"
}SHA-256 of public snapshot: af995a82e7b907f0211aff790394740990079a0bb00671dd626d069b1ecd7fb0