SIAX Technology (sax3l)

@siax/mobile-billing (0.1.1)

Published 2026-08-10 12:03:21 +00:00 by admin

Installation

@siax:registry=https://git.cloud.siax.io/api/packages/sax3l/npm/
npm install @siax/mobile-billing@0.1.1
"@siax/mobile-billing": "0.1.1"

About this package

@siax/mobile-billing

Entitlements + ytan för StoreKit 2 / Play Billing. Appen frågar aldrig butiken om rättigheter (IAP-06 🔴).

Status: klienten är implementerad och testad (43 tester). Tjänsten den talar med finns inte änservices/entitlements/ (prompt M8) är blockerad av PAY0. Klienten är byggd komplett mot kontraktet med fetch injicerbar och körs mot en stub tills tjänsten reses.

De två reglerna som bär paketet

IAP-06 (🔴) Rättigheter kommer från GET /me/entitlements. Källan — Apple, Google, Revolut, manuell tilldelning, kampanj, avtal — är osynlig för appen. Det är därför en avtalskund och en Apple-prenumerant ser exakt samma app utan en rad specialkod. Klientens modell har inget source-fält, och parsern släpper igenom endast de fält kontraktet definierar: ett source eller receipt i serverns svar når aldrig appens datamodell (verifierat i test).

IAP-16 (🔴) Ingen entitlement-logik i appen. Appen renderar rättigheter, den beslutar dem inte. Det finns därför ingen funktion i paketet som härleder en rättighet ur ett kvitto, ett datum eller ett butikssvar — och ett test misslyckas om någon exporterar en.

Konkret vad det betyder:

  • isGranted() läser serverns granted. Ett passerat expiresAt upphäver inte ett granted: true (servern kan ha gett respit eller ha ett avtal), och ett framtida expiresAt ger inte rättighet när servern sagt nej.
  • En rättighet utan ett uttryckligt booleskt granted kastas vid parsning. Välvilligt gissande är beslutsfattande i förklädnad.
  • En butiksadapter som returnerar granted, isPro, entitlements, unlocked, expiresAt … avvisas med store_overreachinnan något skickas till servern. Den enda anledningen att lägga ett isPro-fält i ett butikssvar är att någon tänker rendera på det.

Vad som faktiskt är byggt

Fil Innehåll
src/client.ts BillingClientgetEntitlements(), can(), purchase(), products(), signOut()
src/cache.ts EntitlementCache med maxålder, klockskyddskontroll, subjekt- och miljöbindning
src/entitlements.ts Uppslagning och presentation — isGranted, describeState, sandboxBanner
src/store.ts StoreAdapter-kontraktet + IAP-16-spärrarna
src/errors.ts Problem Details + BillingError med maskinläsbara koder

Cache: offline utan evig giltighet

Ordningen är färsk cache → servern → gammal-men-giltig cache. Den sista grenen gör appen användbar i tunnelbanan; maxåldern gör att den inte blir en permanent gratisbiljett. Standard: färsk i 5 minuter, användbar i 72 timmar. Ytor med hög kostnad (AI-krediter) bör sätta betydligt kortare.

Cachen avvisar dessutom:

  • poster som hör till en annan användare (subject jämförs mot inloggad),
  • sandbox-poster i produktion och tvärtom (IAP-14),
  • poster hämtade "i framtiden" — dvs. enhetens klocka ställd bakåt, det enklaste sättet att försöka förlänga en cache,
  • trasig JSON och gammal schemaversion.

Inga kvitton, transaktions-id eller butiksdata sparas — endast serverns färdiga beslut. signOut() måste anropas vid utloggning, annars ärver nästa användare rättigheterna.

När rättigheten inte kan verifieras är can() fail-closed. Att fail-open här vore att ge bort produkten varje gång tjänsten hostar.

Köpordningen är säkerhetskritisk

  1. butiken tar betalt och lämnar en ogenomskinlig referens,
  2. servern validerar den mot App Store Server API / Google Play Developer API (IAP-03) — klientsidig kvittovalidering räknas som ingen validering,
  3. först därefter kvitteras transaktionen mot butiken,
  4. rättigheterna hämtas om från servern, aldrig ur köpsvaret.

Kvitteras transaktionen före steg 2 är den förlorad och användaren har betalat för något hen inte fick. Ett 202 från servern ger pending — transaktionen behålls så att butiken fortsätter påminna (IAP-05, idempotent omförsök). Ett 422 ger validation_rejected och ingen kvittens.

Sandbox kan inte förväxlas med produktion (IAP-14)

Miljön kommer från butiksadaptern, inte från en byggflagga — en TestFlight-build är inte __DEV__ men handlar mot sandbox. Klienten vägrar konstrueras om adaptern och konfigurationen är oense; ett sandbox-svar avvisas hårt i en produktionsapp; en sandbox-cache läses inte i produktionsläge; och sandboxBanner() ger den etikett som måste synas i UI:t.

Vad som INTE är gjort

  • Entitlement-tjänsten finns inte. GET /me/entitlements och POST /me/purchases är kontrakt, inte körande endpoints. Ingenting här har talat med en riktig server. Prompt M8 är blockerad av PAY0 (K2:s E11 / K3:s B11).
  • Ingen StoreKit- eller Play Billing-kod. Paketet definierar StoreAdapter och innehåller ingen native-implementation. Apprepot kopplar in expo-in-app-purchases, react-native-iap eller en egen modul. Avsiktligt: SDK:t måste kunna byggas och testas i Node, och butiksbiblioteken byts oftare än kontraktet.
  • Ingen server-side kvittovalidering. Den hör hemma i PAY0 (M8), inte här. Det här paketet kan bara vägra att göra den på klienten.
  • Ingen B00K-bokföring, ingen CL0UD-capability-koppling. IAP-07 och IAP-08 uppfylls av tjänsten, inte av klienten.
  • Ingen återställning av köp ("restore purchases"). Inte implementerad.
  • Ingen prenumerationshantering (uppgradering, byte av plan, uppsägning) — de flödena går via butikens egna skärmar och är inte inkapslade här.
  • Cachen är inte signerad. En rotad enhet kan skriva om den lokala posten. Skyddet mot det är serverkontrollen vid nästa hämtning och maxåldern — inte kryptografi på klienten.
  • Ingen typkontroll har körts (repot saknar byggberoenden).

Tester

cd packages/mobile-billing && node --test     # eller: npm test

43 tester, alla gröna. Kräver Node ≥ 22.18, inga beroenden.

Details
npm
2026-08-10 12:03:21 +00:00
1
UNLICENSED
latest
15 KiB
Assets (1)
Versions (2) View all
0.1.1 2026-08-10
0.1.0 2026-08-10