@nosslabs/iap 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +114 -0
- package/LICENSE +21 -0
- package/README.md +108 -0
- package/dist/index.cjs +2074 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1134 -0
- package/dist/index.d.ts +1134 -0
- package/dist/index.js +2067 -0
- package/dist/index.js.map +1 -0
- package/package.json +90 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to `@nosslabs/iap` will be documented here.
|
|
4
|
+
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow [Semantic Versioning](https://semver.org/).
|
|
5
|
+
|
|
6
|
+
## [Unreleased]
|
|
7
|
+
|
|
8
|
+
## [0.3.0] — 2026-05-06
|
|
9
|
+
|
|
10
|
+
### Changed (BREAKING)
|
|
11
|
+
|
|
12
|
+
- **Package renamed: `@nossdev/iap` → `@nosslabs/iap`.** Update your install
|
|
13
|
+
and import sites:
|
|
14
|
+
```
|
|
15
|
+
npm uninstall @nossdev/iap
|
|
16
|
+
npm install @nosslabs/iap
|
|
17
|
+
```
|
|
18
|
+
```ts
|
|
19
|
+
// before
|
|
20
|
+
import { createIAP } from '@nossdev/iap';
|
|
21
|
+
// after
|
|
22
|
+
import { createIAP } from '@nosslabs/iap';
|
|
23
|
+
```
|
|
24
|
+
Behavior is unchanged. The rename disambiguates registry routing for
|
|
25
|
+
consumers that point `@nossdev:*` at a private registry — previously,
|
|
26
|
+
any `.npmrc` mapping `@nossdev` to a private feed would also intercept
|
|
27
|
+
the public `@nossdev/iap` lookup. The `@nossdev/iap` package on npm
|
|
28
|
+
remains installable at its existing versions but receives no further
|
|
29
|
+
updates under that name.
|
|
30
|
+
|
|
31
|
+
- **Default storage namespace: `nossdev_iap` → `nosslabs_iap`.** On
|
|
32
|
+
upgrade, prior cached entitlements are not read. The library refetches
|
|
33
|
+
from the backend on first `getEntitlements()` / `restorePurchases()`
|
|
34
|
+
call, so no manual migration is required. If you depend on cache
|
|
35
|
+
continuity across the upgrade, set
|
|
36
|
+
`storage: { namespace: 'nossdev_iap' }` explicitly in your IAP config
|
|
37
|
+
to keep reading the old key.
|
|
38
|
+
|
|
39
|
+
- **Logger console prefix: `[@nossdev/iap]` → `[@nosslabs/iap]`.** Update
|
|
40
|
+
any log-grep dashboards or filters keyed on the old prefix.
|
|
41
|
+
|
|
42
|
+
### Migration
|
|
43
|
+
|
|
44
|
+
No API changes. Drop-in once the package name and (optionally) the
|
|
45
|
+
storage namespace override are updated.
|
|
46
|
+
|
|
47
|
+
## [0.2.0] — 2026-05-06
|
|
48
|
+
|
|
49
|
+
### Changed (BREAKING)
|
|
50
|
+
|
|
51
|
+
- **`purchase()` signature is now an options object.** Replace `iap.purchase('premium_monthly')` with `iap.purchase({ productId: 'premium_monthly' })`. The new shape is required for the additive `appUserId` field below and any future per-purchase options. Search-and-replace migration; one mechanical edit per call site. See [Migration § v0.1 → v0.2](https://iap.nossdev.com/migration#v0-1-v0-2-breaking-purchase-signature).
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **Pre-attached `appUserId` for the verify/webhook user-mapping path.** New optional `appUserId` field on `PurchaseOptions` accepts either a UUID v4 string or an async fetcher (`() => Promise<string>`). When supplied, the resolved value is validated as a UUID v4 and forwarded to StoreKit's `appAccountToken` (iOS) / Play Billing's `obfuscatedAccountId` (Android) — making it available to the consumer's backend on Attesto's verify response and outbound webhook payload as a top-level `appUserId` field. Eliminates the verify/webhook race for purchases where the user is signed in. Fetcher is invoked fresh per purchase; no iap-side caching (backend owns the mint-or-lookup idempotency). See [Getting started § Pre-attaching a user identifier](https://iap.nossdev.com/guide/getting-started#pre-attaching-a-user-identifier-optional).
|
|
56
|
+
- **`AppUserId` and `PurchaseOptions` types** exported from the package root for consumers who type their own helpers around `purchase(...)`.
|
|
57
|
+
- **Two new error codes**:
|
|
58
|
+
- `INVALID_APP_USER_ID` — supplied value (literal or fetcher-returned) isn't a valid UUID v4. Thrown synchronously / via Promise rejection, before reaching the native adapter.
|
|
59
|
+
- `APP_USER_ID_FETCH_FAILED` — async fetcher threw or rejected. Original error is attached as `cause` for introspection.
|
|
60
|
+
|
|
61
|
+
## [0.1.3] — 2026-05-06
|
|
62
|
+
|
|
63
|
+
### Fixed
|
|
64
|
+
|
|
65
|
+
- **Restore response no longer requires a `transaction` envelope.** `HttpBackendAdapter.restore()` previously validated against the same schema as `verifyApple` / `verifyGoogle`, which required `transaction: { id, productId, ... }` on success. The orchestrator never reads `response.transaction` on the restore path — `iap.restorePurchases()` returns `{ restored, entitlements }` and the field was never surfaced. Backends may now respond with `{ valid: true, entitlements: [...] }` and the library accepts it. Backends that include `transaction` aren't broken — the field is preserved (passthrough) but no longer validated.
|
|
66
|
+
- **Top-level response envelopes now passthrough unknown keys.** Every backend response schema (`verifyResponseSchema`, the new `restoreResponseSchema`, `entitlementsResponseSchema`, `productManifestResponseSchema`) used `z.object()`'s strip-unknown default, silently dropping consumer-defined extras (analytics ids, debug fields, server timestamps, custom flags). Inner schemas (`passthroughEntitlementSchema`, `verifiedTransactionSchema`) already passed through; this patch closes the top-level gap so backend metadata rides through end-to-end. Consumer code can read these extras via a runtime cast — the library validates only the named fields it owns.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- **`BackendAdapter.restore()` return type** is now `RestoreResponse<T>` rather than `VerifyResponse<T>`. The success branch omits the `transaction` field; the failure branch is unchanged. Existing custom adapters returning `VerifyResponse` from `restore()` remain structurally compatible — `{ valid: true; entitlements; transaction }` is assignable to `{ valid: true; entitlements }`. Update your typings opportunistically.
|
|
71
|
+
- **`transaction.verifiedAt` no longer validated** in the runtime schema. The library never read it; consumers that send it still see it preserved via the existing `verifiedTransactionSchema.passthrough()`.
|
|
72
|
+
|
|
73
|
+
## [0.1.2] — 2026-05-05
|
|
74
|
+
|
|
75
|
+
### Fixed
|
|
76
|
+
|
|
77
|
+
- **`androidPlanId` no longer required for subscription products** — the schema previously enforced `androidPlanId` cross-platform via a `.refine()` on `configuredProductSchema`, blocking iOS-only consumers and single-plan Android subscriptions from validating their config or backend manifest. The field is now consistently optional. The Android native adapter already falls back to `native.getOffer()` (the default offer) when it's missing, so the runtime is unaffected. Set `androidPlanId` explicitly only when an Android subscription has multiple base plans and you need to disambiguate. iOS ignores it.
|
|
78
|
+
- **`verifyApple` / `verifyGoogle` are individually optional** — previously both were required at the schema level even for single-platform builds. Now at least one of them must be set; the other can be omitted. iOS-only consumers can drop `verifyGoogle`, Android-only consumers can drop `verifyApple`. The HTTP adapter throws `IAPError(INVALID_CONFIG)` with a clear message if the runtime ever dispatches to a missing endpoint — but in practice the orchestrator only calls the endpoint matching the active native transaction's platform.
|
|
79
|
+
|
|
80
|
+
## [0.1.1] — 2026-05-05
|
|
81
|
+
|
|
82
|
+
### Fixed
|
|
83
|
+
|
|
84
|
+
- **HTTP client URL normalization** — `HttpClient` now forgives mismatched slashes between `backend.baseUrl` and `backend.endpoints.*`. Previously, only a trailing slash on `baseUrl` was stripped; an endpoint path without a leading slash silently produced a malformed URL (`https://api.example.comiap/verify`). Both sides are now normalized: `baseUrl` trailing slashes (including `//`) are stripped and a leading `/` is added to the endpoint path if missing. No behavior change for correctly-configured consumers.
|
|
85
|
+
|
|
86
|
+
## [0.1.0] — 2026-04-29
|
|
87
|
+
|
|
88
|
+
First public release. Capacitor 5 IAP orchestrator that defers acknowledgement to a backend you control.
|
|
89
|
+
|
|
90
|
+
### Added
|
|
91
|
+
|
|
92
|
+
- **`createIAP({ products, backend })` factory** — Promise-based public API. Returns an instance with `initialize()`, `purchase()`, `restore()`, `refresh()`, `getEntitlements()`, `hasEntitlement()`, and a typed event emitter.
|
|
93
|
+
- **Capacitor 5 native adapter** — wraps [`cordova-plugin-purchase`](https://github.com/j3k0/cordova-plugin-purchase) `^13.x`. Defers `Transaction.finish()` until backend verification succeeds (the "never grant before backend confirms" guarantee). Web stub no-ops gracefully.
|
|
94
|
+
- **Backend HTTP client** — fetch wrapper with timeout, stepped retry on 5xx (1 s / 2 s / 4 s, no retry on 4xx), pluggable `getAuthHeaders`, request/response transforms, and structured error mapping to `IAPError` with `IAPErrorCode` enum.
|
|
95
|
+
- **Backend abstraction** — `BackendAdapter` interface with optional methods (`verifyApple`, `verifyGoogle`, `entitlements`, `restore`, `listProducts`). Bring your own adapter (Firebase, Supabase, GraphQL) or use the built-in HTTP adapter.
|
|
96
|
+
- **Backend-driven product manifest** — `createIAP({ products })` is optional. Set `backend.endpoints.products` (HTTP) or implement `listProducts()` on a custom adapter to have the backend curate the SKU list. Hard caveat: every SKU must still be pre-registered in App Store Connect / Google Play Console.
|
|
97
|
+
- **Purchase flow orchestration** — captures the cdv `Transaction` in `pendingFinish`, calls backend `verifyApple`/`verifyGoogle`, only then triggers `nativeAdapter.acknowledge()`. Concurrency lock prevents double-purchase. Emits `purchase-started`, `purchase-success`, `purchase-failed`, `purchase-cancelled`, `purchase-pending`, `verification-failed`.
|
|
98
|
+
- **Restore flow** — `iap.restore()` re-fetches owned items, surfaces newly granted entitlements, deduplicates against the local cache.
|
|
99
|
+
- **Refresh + recovery** — `iap.refresh()` reconciles `unfinished_transactions` storage on app resume / launch; recovery on `initialize()` re-attempts verification for transactions that died between native success and ack.
|
|
100
|
+
- **Entitlement cache** — Capacitor Preferences-backed (with in-memory fallback for tests/web). Survives app restarts. Sync reads via `getEntitlements()` / `hasEntitlement()` for fast UI.
|
|
101
|
+
- **Configurable logger** — `Logger` interface with a default console-backed implementation. Inject a structured logger (Sentry, Datadog) for production observability.
|
|
102
|
+
- **Documentation site** — VitePress at [iap.nossdev.com](https://iap.nossdev.com): installation, configuration, backend contract, API reference, and recipes for Vue + Quasar, React, and Pinia store.
|
|
103
|
+
- **CI** — typecheck + lint + test + build on Node 20 and 22, matrix run on every PR and push to `main`.
|
|
104
|
+
|
|
105
|
+
### Notes for early adopters
|
|
106
|
+
|
|
107
|
+
- API may have breaking changes through the 0.x line as production usage exposes rough edges. Pin the minor (`^0.1.0`) and watch this CHANGELOG.
|
|
108
|
+
- Capacitor 7 migration is preserved in git history (commit `f1d20ed`); the v7 native adapter ships as a separate major (`1.x`) when the consumer ecosystem catches up.
|
|
109
|
+
|
|
110
|
+
### Future
|
|
111
|
+
|
|
112
|
+
- Upgrade `zod` from 3 to 4 once the wider ecosystem catches up.
|
|
113
|
+
- Migrate from `NPM_TOKEN` to npm OIDC trusted publishers.
|
|
114
|
+
- Capacitor 7 + `@capgo/native-purchases v7.x` adapter.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 nossdev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# @nosslabs/iap
|
|
2
|
+
|
|
3
|
+
> Thin Capacitor IAP orchestrator. Server-side validation via [Attesto](https://attesto.nossdev.com).
|
|
4
|
+
|
|
5
|
+
**Status: 0.2.0 — published.** API may have breaking changes through the 0.x line as it's exercised in production apps. Pin the minor version (`^0.2.0`) and watch the [CHANGELOG](./CHANGELOG.md).
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @nosslabs/iap cordova-plugin-purchase
|
|
9
|
+
npx cap sync
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { createIAP } from '@nosslabs/iap';
|
|
14
|
+
|
|
15
|
+
const iap = createIAP({
|
|
16
|
+
products: [
|
|
17
|
+
{ id: 'premium_monthly', type: 'subscription', androidPlanId: 'monthly-plan' },
|
|
18
|
+
],
|
|
19
|
+
backend: {
|
|
20
|
+
baseUrl: 'https://api.your-app.com',
|
|
21
|
+
endpoints: {
|
|
22
|
+
verifyApple: '/api/iap/verify/apple',
|
|
23
|
+
verifyGoogle: '/api/iap/verify/google',
|
|
24
|
+
entitlements: '/api/iap/entitlements',
|
|
25
|
+
restore: '/api/iap/restore',
|
|
26
|
+
},
|
|
27
|
+
getAuthHeaders: async () => ({
|
|
28
|
+
Authorization: `Bearer ${await getAuthToken()}`,
|
|
29
|
+
}),
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
await iap.initialize();
|
|
34
|
+
|
|
35
|
+
const result = await iap.purchase({ productId: 'premium_monthly' });
|
|
36
|
+
if (result.status === 'success') {
|
|
37
|
+
// backend has validated; entitlements are cached
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// (optional) Pre-attach a UUID so it travels through StoreKit/Play Billing
|
|
41
|
+
// and reaches your backend on both the verify response and the eventual
|
|
42
|
+
// webhook — eliminates the verify/webhook race for purchases where the
|
|
43
|
+
// user is signed in. Either pass a string you already have or an async
|
|
44
|
+
// fetcher that hits your backend (which mints+saves on first call,
|
|
45
|
+
// returns the existing UUID on subsequent calls).
|
|
46
|
+
await iap.purchase({
|
|
47
|
+
productId: 'premium_monthly',
|
|
48
|
+
appUserId: async () => {
|
|
49
|
+
const r = await fetch('/api/iap/uuid', { headers: authHeaders() });
|
|
50
|
+
return (await r.json()).uuid;
|
|
51
|
+
},
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Documentation
|
|
56
|
+
|
|
57
|
+
**📘 [iap.nossdev.com](https://iap.nossdev.com)** — installation, configuration, framework recipes, API reference.
|
|
58
|
+
|
|
59
|
+
- [Getting started](https://iap.nossdev.com/guide/getting-started) — first purchase in 30 minutes
|
|
60
|
+
- [Backend contract](https://iap.nossdev.com/guide/backend-contract) — four endpoints your backend implements
|
|
61
|
+
- [Architecture](https://iap.nossdev.com/guide/architecture) — three-tier model
|
|
62
|
+
- [Vue + Quasar recipe](https://iap.nossdev.com/recipes/vue-quasar) / [React recipe](https://iap.nossdev.com/recipes/react)
|
|
63
|
+
|
|
64
|
+
## Why this library
|
|
65
|
+
|
|
66
|
+
`@nosslabs/iap` does **one thing**: orchestrate the purchase flow on the client. It
|
|
67
|
+
|
|
68
|
+
- wraps `cordova-plugin-purchase` for native purchase + restore,
|
|
69
|
+
- POSTs to **your** backend (which calls Attesto) for receipt validation,
|
|
70
|
+
- acknowledges native transactions only **after** the backend confirms (no phantom grants),
|
|
71
|
+
- caches entitlements locally for instant, reactive UI reads,
|
|
72
|
+
- recovers unfinished transactions across app launches.
|
|
73
|
+
|
|
74
|
+
It does **not**: talk to Attesto directly, define entitlement business logic, manage user auth, or ship paywall UI. Those belong to your app and your backend.
|
|
75
|
+
|
|
76
|
+
## Capacitor support matrix
|
|
77
|
+
|
|
78
|
+
| `@nosslabs/iap` | Capacitor | Plugin | Status |
|
|
79
|
+
|---|---|---|---|
|
|
80
|
+
| 0.x | 5.x | `cordova-plugin-purchase ^13.x` | **Current** |
|
|
81
|
+
| 1.x | 7.x | TBD (Capacitor-native plugin) | Roadmap |
|
|
82
|
+
|
|
83
|
+
## Optional peer dependency
|
|
84
|
+
|
|
85
|
+
If you want auto-refresh on app resume (default behavior):
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install @capacitor/app
|
|
89
|
+
npx cap sync
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Or disable the listener with `options.refreshOnResume: false`. See [installation guide](https://iap.nossdev.com/guide/installation#optional-app-resume-listener).
|
|
93
|
+
|
|
94
|
+
## Development
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
mise install # Node 22 + npm 10
|
|
98
|
+
npm install
|
|
99
|
+
npm run typecheck # tsc --noEmit
|
|
100
|
+
npm run lint # biome check
|
|
101
|
+
npm test # vitest run
|
|
102
|
+
npm run build # tsup → dist/index.{js,cjs,d.ts}
|
|
103
|
+
npm run docs:dev # vitepress dev (http://localhost:5173)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## License
|
|
107
|
+
|
|
108
|
+
MIT — see [LICENSE](./LICENSE).
|