@entitlehub/sdk 0.2.1 → 0.3.1
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 +37 -9
- package/README.md +43 -6
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +267 -15
- package/dist/index.d.ts +267 -15
- package/dist/index.js +1 -1
- package/package.json +13 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,30 +2,58 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https://semver.org/).
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.3.1 (2026-09-20)
|
|
6
6
|
|
|
7
7
|
### Added
|
|
8
|
-
-
|
|
8
|
+
- `isReceiptRevoked(err)`: was this purchase report refused because the receipt has been refunded?
|
|
9
|
+
Reporting a refunded receipt answers `409 receipt_revoked`: no grant is written, and the
|
|
10
|
+
entitlement it had granted is revoked server-side. Shipped as a function because the obvious
|
|
11
|
+
guess, `err.status === 409`, is wrong: the catalog endpoints answer 409 for their own conflicts
|
|
12
|
+
(`product_exists`, `entitlement_in_use`, …).
|
|
13
|
+
|
|
14
|
+
### Notes
|
|
15
|
+
- If you are on **0.2.0 or earlier**, `err.body` and `err.apiCode` do not exist, so a check written
|
|
16
|
+
against them silently never matches. Upgrade to 0.3.0+ before relying on either.
|
|
17
|
+
- `err.code` remains the SDK-level category (`config` | `auth` | `http` | `network` | `no_fetch`).
|
|
18
|
+
The API's own code is `err.apiCode`, and has been since 0.3.0. Branch on `apiCode`, or just call
|
|
19
|
+
`isReceiptRevoked`.
|
|
20
|
+
|
|
21
|
+
## 0.3.0 (2026-09-19)
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- Catalog API on `EntitleHubServer`: `eh.entitlements`, `eh.products`, `eh.offerings`. Create,
|
|
25
|
+
update, archive and delete entitlements, products and offerings; attach/detach mappings from
|
|
26
|
+
either side; manage paywall packages. Refs take ids or the key / SKU / identifier you already
|
|
27
|
+
have. See [the Catalog API docs](https://entitlehub.com/docs/catalog-api).
|
|
28
|
+
- `ensure(...)` on entitlements and products: create, or return the existing row, so a catalog
|
|
29
|
+
setup script can run on every deploy.
|
|
30
|
+
- `EntitleHubError.apiCode` and `.body`: the API's machine-readable error code (e.g.
|
|
31
|
+
`product_exists`, `ambiguous_product`, `rate_limited`) and the full error payload.
|
|
32
|
+
|
|
33
|
+
## 0.2.0 (2026-07-18)
|
|
34
|
+
|
|
35
|
+
### Added
|
|
36
|
+
- Client `EntitleHub.reportPurchase(...)`: report a **validated** purchase (Apple JWS / Google
|
|
9
37
|
token / Stripe subscription) directly from the app with your publishable key; updates the cache
|
|
10
38
|
and notifies listeners. Validation makes a forged receipt impossible, so no secret key is needed
|
|
11
39
|
on the client. (The trusted server-report mode still requires a secret key.)
|
|
12
40
|
- Powers the new [`@entitlehub/react-native`](https://www.npmjs.com/package/@entitlehub/react-native)
|
|
13
41
|
purchase SDK.
|
|
14
42
|
|
|
15
|
-
## 0.1.3
|
|
43
|
+
## 0.1.3 (2026-07-18)
|
|
16
44
|
|
|
17
45
|
### Added
|
|
18
|
-
- `reportPurchase` accepts `stripeSubscriptionId
|
|
46
|
+
- `reportPurchase` accepts `stripeSubscriptionId`, EntitleHub validates the subscription against
|
|
19
47
|
your Stripe secret key (active/trialing → granted, current period end as the expiry).
|
|
20
48
|
|
|
21
|
-
## 0.1.2
|
|
49
|
+
## 0.1.2 (2026-07-18)
|
|
22
50
|
|
|
23
51
|
### Changed
|
|
24
52
|
- The SDK now lives in its own public repository:
|
|
25
53
|
[github.com/DanCue44/entitlehub-sdk](https://github.com/DanCue44/entitlehub-sdk) (MIT). Open-source
|
|
26
54
|
SDK, closed backend. `repository` / `bugs` metadata point there.
|
|
27
55
|
|
|
28
|
-
## 0.1.1
|
|
56
|
+
## 0.1.1 (2026-07-18)
|
|
29
57
|
|
|
30
58
|
### Added
|
|
31
59
|
- README badges (npm version, types, zero-deps, license) and docs links.
|
|
@@ -36,14 +64,14 @@ All notable changes to `@entitlehub/sdk`. Adheres to [Semantic Versioning](https
|
|
|
36
64
|
+ `isSubscription`), Apple StoreKit 2 (validated via `signedTransaction`), and trusted
|
|
37
65
|
server-report.
|
|
38
66
|
|
|
39
|
-
## 0.1.0
|
|
67
|
+
## 0.1.0 (2026-07-18)
|
|
40
68
|
|
|
41
69
|
Initial release.
|
|
42
70
|
|
|
43
|
-
- **`EntitleHub`** (client, publishable key)
|
|
71
|
+
- **`EntitleHub`** (client, publishable key): `getCustomerInfo`, `isEntitled`,
|
|
44
72
|
`checkEntitlement`, `getOfferings`, `logIn` / `logOut`, `addCustomerInfoUpdateListener`.
|
|
45
73
|
Cached CustomerInfo; safe for browsers, React Native, and Expo.
|
|
46
|
-
- **`EntitleHubServer`** (server, secret key)
|
|
74
|
+
- **`EntitleHubServer`** (server, secret key): `reportPurchase` (Apple JWS / Google Play
|
|
47
75
|
token / trusted), `grantEntitlement`, `getCustomerInfo`, `check`.
|
|
48
76
|
- **`CustomerInfo`** ergonomic wrapper; **`EntitleHubError`** with `.status` / `.code`.
|
|
49
77
|
- Zero dependencies; ESM + CJS + bundled type declarations.
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@entitlehub/sdk)
|
|
6
6
|
[](./LICENSE)
|
|
7
7
|
|
|
8
|
-
One entitlement API across **App Store, Google Play, Stripe, and web
|
|
8
|
+
One entitlement API across **App Store, Google Play, Stripe, and web**, for browsers, React Native, Expo, and Node. Stop checking raw product IDs; ask *"what is this user entitled to right now?"*
|
|
9
9
|
|
|
10
10
|
> Docs: **[entitlehub.com/docs](https://entitlehub.com/docs)** · [REST API](https://entitlehub.com/docs/api) · [Migrating from RevenueCat](https://entitlehub.com/docs/migrating-from-revenuecat)
|
|
11
11
|
|
|
@@ -15,7 +15,7 @@ npm install @entitlehub/sdk
|
|
|
15
15
|
|
|
16
16
|
Zero dependencies. Uses the global `fetch` (Node 18+, all modern browsers, React Native, Expo).
|
|
17
17
|
|
|
18
|
-
## Client (in your app)
|
|
18
|
+
## Client (in your app) (publishable key)
|
|
19
19
|
|
|
20
20
|
Reads only. Safe to ship in your app. **Never put a secret (`sk_`) key here.**
|
|
21
21
|
|
|
@@ -66,7 +66,7 @@ eh.logOut(); // forget cached info
|
|
|
66
66
|
| `getOfferings()` | `Offerings` | Entitlements + products, for building a paywall. |
|
|
67
67
|
| `addCustomerInfoUpdateListener(fn)` | `() => void` | Returns an unsubscribe. |
|
|
68
68
|
|
|
69
|
-
## Server (in your backend)
|
|
69
|
+
## Server (in your backend) (secret key)
|
|
70
70
|
|
|
71
71
|
Report purchases and grant entitlements. **Server only.**
|
|
72
72
|
|
|
@@ -76,17 +76,17 @@ import { EntitleHubServer } from "@entitlehub/sdk";
|
|
|
76
76
|
const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
77
77
|
|
|
78
78
|
// After the store purchase is confirmed on-device, report it from your server.
|
|
79
|
-
// Google Play
|
|
79
|
+
// Google Play: validated (EntitleHub verifies the token with your Play service account):
|
|
80
80
|
const info = await eh.reportPurchase(userId, {
|
|
81
81
|
store: "play", storeProductId: "pro_monthly",
|
|
82
82
|
purchaseToken: playToken, isSubscription: true,
|
|
83
83
|
});
|
|
84
84
|
info.isActive("pro"); // → true
|
|
85
85
|
|
|
86
|
-
// Apple StoreKit 2
|
|
86
|
+
// Apple StoreKit 2: validated (product / environment read from the signed JWS):
|
|
87
87
|
await eh.reportPurchase(userId, { store: "app_store", storeProductId: "", signedTransaction: jws });
|
|
88
88
|
|
|
89
|
-
// Trusted server-report (no store validation
|
|
89
|
+
// Trusted server-report (no store validation, only if you validated the receipt elsewhere):
|
|
90
90
|
await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
91
91
|
|
|
92
92
|
// Grant directly (promo / comp / support), no purchase:
|
|
@@ -99,6 +99,41 @@ await eh.grantEntitlement(userId, "pro", { durationDays: 30 });
|
|
|
99
99
|
| `grantEntitlement(userId, id, { durationDays?, isSandbox? })` | `CustomerInfo` |
|
|
100
100
|
| `getCustomerInfo(userId)` / `check(userId, id)` | `CustomerInfo` / `CheckResult` |
|
|
101
101
|
|
|
102
|
+
### Catalog: entitlements, products, offerings
|
|
103
|
+
|
|
104
|
+
Set up and change your catalog from code. Refs take the id **or** the key / SKU / identifier you
|
|
105
|
+
already have. Every create has an `ensure` twin that returns the existing row instead of failing,
|
|
106
|
+
so a setup script can run on every deploy.
|
|
107
|
+
|
|
108
|
+
```ts
|
|
109
|
+
await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
110
|
+
|
|
111
|
+
await eh.products.ensure({
|
|
112
|
+
store: "stripe", storeProductId: "price_1Q...", type: "subscription", duration: "P1M",
|
|
113
|
+
priceMicros: 9_990_000, entitlements: ["pro"],
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
// Remap (future purchases only; access already granted is untouched):
|
|
117
|
+
await eh.products.update({ store: "play", storeProductId: "pro_monthly" }, { entitlements: ["pro", "team"] });
|
|
118
|
+
await eh.entitlements.attachProducts("team", ["price_1Q..."]);
|
|
119
|
+
|
|
120
|
+
// Retire a SKU you no longer sell. Existing subscribers keep access and renewals:
|
|
121
|
+
await eh.products.archive("price_old");
|
|
122
|
+
|
|
123
|
+
await eh.offerings.create({
|
|
124
|
+
identifier: "default", isCurrent: true, metadata: { headline: "Go Pro" },
|
|
125
|
+
packages: [{ identifier: "monthly", product: "price_1Q..." }],
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
| Namespace | Methods |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `eh.entitlements` | `list` `get` `create` `ensure` `update` `delete` `products` `attachProducts` `detachProducts` `archive` `unarchive` |
|
|
132
|
+
| `eh.products` | `list` `get` `create` `ensure` `update` `delete` `attachEntitlements` `detachEntitlements` `archive` `unarchive` |
|
|
133
|
+
| `eh.offerings` | `list` `get` `create` `update` `delete` `archive` `unarchive` `packages` `addPackage` `updatePackage` `deletePackage` |
|
|
134
|
+
|
|
135
|
+
Full reference: [entitlehub.com/docs/catalog-api](https://entitlehub.com/docs/catalog-api).
|
|
136
|
+
|
|
102
137
|
## Already have an IAP setup?
|
|
103
138
|
|
|
104
139
|
Keep making the actual store purchase with your existing IAP flow (StoreKit / Play Billing /
|
|
@@ -108,6 +143,8 @@ with `getCustomerInfo()`. See the full guide at [entitlehub.com/docs](https://en
|
|
|
108
143
|
## Errors
|
|
109
144
|
|
|
110
145
|
Every failure throws an `EntitleHubError` with `.status` and `.code` (`auth` | `http` | `network` | `config`).
|
|
146
|
+
When the API sends its own machine-readable code it is on `.apiCode` (e.g. `product_exists`,
|
|
147
|
+
`ambiguous_product`, `rate_limited`), with the full payload on `.body`.
|
|
111
148
|
|
|
112
149
|
```ts
|
|
113
150
|
import { EntitleHubError } from "@entitlehub/sdk";
|
package/dist/index.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
"use strict";var
|
|
1
|
+
"use strict";var I=Object.defineProperty;var H=Object.getOwnPropertyDescriptor;var U=Object.getOwnPropertyNames;var A=Object.prototype.hasOwnProperty;var D=(r,t)=>{for(var e in t)I(r,e,{get:t[e],enumerable:!0})},q=(r,t,e,n)=>{if(t&&typeof t=="object"||typeof t=="function")for(let s of U(t))!A.call(r,s)&&s!==e&&I(r,s,{get:()=>t[s],enumerable:!(n=H(t,s))||n.enumerable});return r};var N=r=>q(I({},"__esModule",{value:!0}),r);var L={};D(L,{CustomerInfo:()=>c,EntitleHub:()=>y,EntitleHubError:()=>a,EntitleHubServer:()=>b,EntitlementsApi:()=>l,OfferingsApi:()=>m,ProductsApi:()=>g,isReceiptRevoked:()=>x});module.exports=N(L);var c=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let n of this.entitlements)e[n.entitlement]=n;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var S="0.3.0";var a=class extends Error{constructor(t,e,n="error",s){super(t),this.name="EntitleHubError",this.status=e,this.code=n,this.body=s,this.apiCode=typeof s?.code=="string"?s.code:void 0}};function x(r){if(!r||typeof r!="object")return!1;let t=r;return t.apiCode==="receipt_revoked"||t.body?.code==="receipt_revoked"}var K=r=>new Promise(t=>setTimeout(t,r));async function i(r,t,e,n){let s=r.fetchImpl??globalThis.fetch;if(!s)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let C=`${r.baseUrl.replace(/\/$/,"")}${e}`,E=t==="GET"?r.retries??2:0,_;for(let f=0;f<=E;f++){let w=new AbortController,T=r.timeoutMs?setTimeout(()=>w.abort(),r.timeoutMs):void 0;try{let u=await s(C,{method:t,headers:{Authorization:`Bearer ${r.apiKey}`,"X-EntitleHub-Client":r.client??`${"js"}/${S}`,...n?{"Content-Type":"application/json"}:{}},body:n?JSON.stringify(n):void 0,signal:w.signal}),v=await u.text(),h=v?j(v):{};if(!u.ok){let O=`HTTP ${u.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(O=String(P.error)),new a(O,u.status,u.status===401||u.status===403?"auth":"http",P)}return h}catch(u){if(_=u,u instanceof a)throw u;f<E&&await K(250*(f+1))}finally{T&&clearTimeout(T)}}throw new a(`Network request failed: ${String(_?.message??_)}`,0,"network")}function j(r){try{return JSON.parse(r)}catch{return{}}}var y=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let n=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),s=new c(n);this.cached={info:s,at:Date.now()};for(let C of this.listeners)try{C(s)}catch{}return s}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),n=new c(e);this.cached={info:n,at:Date.now()};for(let s of this.listeners)try{s(n)}catch{}return n}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var o=encodeURIComponent;function p(r){return typeof r=="string"?r:"id"in r?{id:r.id}:{store:r.store,store_product_id:r.storeProductId}}function d(r){return typeof r=="string"?{seg:o(r),qs:""}:"id"in r?{seg:o(r.id),qs:""}:{seg:o(r.storeProductId),qs:`?store=${o(r.store)}`}}var k=r=>r?.includeArchived?"include_archived=true":"";async function $(r,t){try{return await r()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=k(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${o(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(p)})}ensure(t){return $(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${o(t)}`,{name:e.name,description:e.description,products:e.products?.map(p)})}delete(t){return i(this.http,"DELETE",`/entitlements/${o(t)}`)}async products(t,e){let n=k(e);return(await i(this.http,"GET",`/entitlements/${o(t)}/products${n?`?${n}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/attach_products`,{products:e.map(p)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${o(t)}/detach_products`,{products:e.map(p)})}archive(t){return i(this.http,"POST",`/entitlements/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${o(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let n=e.toString();return(await i(this.http,"GET",`/products${n?`?${n}`:""}`)).products}get(t){let{seg:e,qs:n}=d(t);return i(this.http,"GET",`/products/${e}${n}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return $(()=>this.create(t),"product_exists")}update(t,e){let{seg:n,qs:s}=d(t);return i(this.http,"PATCH",`/products/${n}${s}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:n}=d(t);return i(this.http,"DELETE",`/products/${e}${n}`)}attachEntitlements(t,e){let{seg:n,qs:s}=d(t);return i(this.http,"POST",`/products/${n}/attach_entitlements${s}`,{entitlements:e})}detachEntitlements(t,e){let{seg:n,qs:s}=d(t);return i(this.http,"POST",`/products/${n}/detach_entitlements${s}`,{entitlements:e})}archive(t){let{seg:e,qs:n}=d(t);return i(this.http,"POST",`/products/${e}/archive${n}`)}unarchive(t){let{seg:e,qs:n}=d(t);return i(this.http,"POST",`/products/${e}/unarchive${n}`)}},m=class{constructor(t){this.http=t}async list(t){let e=k(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${o(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(R)})}update(t,e){return i(this.http,"PATCH",`/offerings/${o(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${o(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${o(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${o(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${o(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${o(t)}/packages`,R(e))}updatePackage(t,e,n){return i(this.http,"PATCH",`/offerings/${o(t)}/packages/${o(e)}`,{display_name:n.displayName,position:n.position,product:n.product===void 0?void 0:p(n.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${o(t)}/packages/${o(e)}`)}};function R(r){return{identifier:r.identifier,product:p(r.product),position:r.position,display_name:r.displayName}}var b=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let n=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new c(n)}async grantEntitlement(t,e,n={}){let s=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:n.durationDays??0,is_sandbox:!!n.isSandbox});return new c(s)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new c(e)}async check(t,e,n={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!n.sandbox})}};0&&(module.exports={CustomerInfo,EntitleHub,EntitleHubError,EntitleHubServer,EntitlementsApi,OfferingsApi,ProductsApi,isReceiptRevoked});
|
package/dist/index.d.cts
CHANGED
|
@@ -80,7 +80,7 @@ interface PurchaseInput {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Ergonomic wrapper around a subscriber's active entitlements
|
|
83
|
+
* Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
|
|
84
84
|
*
|
|
85
85
|
* const info = await eh.getCustomerInfo();
|
|
86
86
|
* if (info.isActive("pro")) showProFeatures();
|
|
@@ -121,7 +121,7 @@ interface EntitleHubOptions {
|
|
|
121
121
|
type CustomerInfoListener = (info: CustomerInfo) => void;
|
|
122
122
|
type FetchPolicy = "cache-first" | "network-only";
|
|
123
123
|
/**
|
|
124
|
-
* The client-side EntitleHub SDK
|
|
124
|
+
* The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
|
|
125
125
|
* Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
|
|
126
126
|
*
|
|
127
127
|
* const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
|
|
@@ -151,7 +151,7 @@ declare class EntitleHub {
|
|
|
151
151
|
checkEntitlement(entitlementId: string, opts?: {
|
|
152
152
|
sandbox?: boolean;
|
|
153
153
|
}): Promise<CheckResult>;
|
|
154
|
-
/** The project's entitlements + products
|
|
154
|
+
/** The project's entitlements + products: for building a paywall. */
|
|
155
155
|
getOfferings(): Promise<Offerings>;
|
|
156
156
|
/**
|
|
157
157
|
* Report a VALIDATED store purchase for the current user and return the updated entitlements.
|
|
@@ -174,30 +174,288 @@ declare class EntitleHub {
|
|
|
174
174
|
addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
declare class EntitleHubError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
/** SDK-level category: config | auth | http | network | no_fetch. */
|
|
180
|
+
readonly code: string;
|
|
181
|
+
/**
|
|
182
|
+
* The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
|
|
183
|
+
* "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
|
|
184
|
+
*/
|
|
185
|
+
readonly apiCode?: string;
|
|
186
|
+
/** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
|
|
187
|
+
readonly body?: Record<string, unknown>;
|
|
188
|
+
constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Was this purchase report refused because the receipt has been REFUNDED?
|
|
192
|
+
*
|
|
193
|
+
* Reporting a refunded receipt answers 409 `receipt_revoked`: no grant is written, and EntitleHub
|
|
194
|
+
* revokes the entitlement it had granted. Apple stamps revocationDate permanently and StoreKit
|
|
195
|
+
* keeps handing the transaction back, so this is the signal that a purchase went away because the
|
|
196
|
+
* money was returned, rather than for some unexplained reason.
|
|
197
|
+
*
|
|
198
|
+
* Shipped as a function so nobody has to write the check themselves: reaching for `status === 409`
|
|
199
|
+
* is the obvious guess and the wrong one, because the catalog endpoints answer 409 for their own
|
|
200
|
+
* conflicts (`product_exists`, `entitlement_in_use`, …). Matching on the API code is exact.
|
|
201
|
+
*
|
|
202
|
+
* Duck-typed rather than `instanceof`, so it still works when the error crosses a bundle boundary.
|
|
203
|
+
*/
|
|
204
|
+
declare function isReceiptRevoked(err: unknown): boolean;
|
|
205
|
+
interface HttpOptions {
|
|
206
|
+
baseUrl: string;
|
|
207
|
+
apiKey: string;
|
|
208
|
+
/** Network-error retries for idempotent reads (default 2). */
|
|
209
|
+
retries?: number;
|
|
210
|
+
timeoutMs?: number;
|
|
211
|
+
fetchImpl?: typeof fetch;
|
|
212
|
+
/** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
|
|
213
|
+
client?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
type ProductType = "subscription" | "non_consumable" | "consumable";
|
|
217
|
+
type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
|
|
218
|
+
/** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
|
|
219
|
+
type ProductRef = string | {
|
|
220
|
+
id: string;
|
|
221
|
+
} | {
|
|
222
|
+
store: Store;
|
|
223
|
+
storeProductId: string;
|
|
224
|
+
};
|
|
225
|
+
interface CatalogEntitlement {
|
|
226
|
+
object: "entitlement";
|
|
227
|
+
id: string;
|
|
228
|
+
key: string;
|
|
229
|
+
name: string;
|
|
230
|
+
description: string;
|
|
231
|
+
products: {
|
|
232
|
+
id: string;
|
|
233
|
+
store: Store;
|
|
234
|
+
store_product_id: string;
|
|
235
|
+
}[];
|
|
236
|
+
active_grants: number;
|
|
237
|
+
created_at: string | null;
|
|
238
|
+
archived_at: string | null;
|
|
239
|
+
}
|
|
240
|
+
interface CatalogProduct {
|
|
241
|
+
object: "product";
|
|
242
|
+
id: string;
|
|
243
|
+
store: Store;
|
|
244
|
+
store_product_id: string;
|
|
245
|
+
type: ProductType;
|
|
246
|
+
duration: Duration | null;
|
|
247
|
+
display_name: string;
|
|
248
|
+
price_micros: number;
|
|
249
|
+
currency: string;
|
|
250
|
+
entitlements: string[];
|
|
251
|
+
active_grants: number;
|
|
252
|
+
archived_at: string | null;
|
|
253
|
+
}
|
|
254
|
+
interface CatalogPackage {
|
|
255
|
+
object: "package";
|
|
256
|
+
id: string;
|
|
257
|
+
identifier: string;
|
|
258
|
+
display_name: string;
|
|
259
|
+
position: number;
|
|
260
|
+
store: Store;
|
|
261
|
+
store_product_id: string;
|
|
262
|
+
product: CatalogProduct | null;
|
|
263
|
+
}
|
|
264
|
+
interface CatalogOffering {
|
|
265
|
+
object: "offering";
|
|
266
|
+
id: string;
|
|
267
|
+
identifier: string;
|
|
268
|
+
display_name: string;
|
|
269
|
+
is_current: boolean;
|
|
270
|
+
metadata: Record<string, unknown> | null;
|
|
271
|
+
packages: CatalogPackage[];
|
|
272
|
+
created_at: string;
|
|
273
|
+
archived_at: string | null;
|
|
274
|
+
}
|
|
275
|
+
/** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
|
|
276
|
+
interface ListedOffering {
|
|
277
|
+
id: string;
|
|
278
|
+
identifier: string;
|
|
279
|
+
display_name: string;
|
|
280
|
+
is_current: boolean;
|
|
281
|
+
metadata: Record<string, unknown> | null;
|
|
282
|
+
archived_at: string | null;
|
|
283
|
+
packages: {
|
|
284
|
+
id: string;
|
|
285
|
+
identifier: string;
|
|
286
|
+
display_name: string;
|
|
287
|
+
position: number;
|
|
288
|
+
store: Store;
|
|
289
|
+
store_product_id: string;
|
|
290
|
+
product: {
|
|
291
|
+
display_name: string;
|
|
292
|
+
type: string;
|
|
293
|
+
price_micros: number;
|
|
294
|
+
currency: string;
|
|
295
|
+
entitlements: string[];
|
|
296
|
+
} | null;
|
|
297
|
+
}[];
|
|
298
|
+
}
|
|
299
|
+
interface Deleted {
|
|
300
|
+
object: string;
|
|
301
|
+
id: string;
|
|
302
|
+
deleted: true;
|
|
303
|
+
}
|
|
304
|
+
interface CreateEntitlementInput {
|
|
305
|
+
/** The exact string your app checks with isEntitled(). Can't change later. */
|
|
306
|
+
key: string;
|
|
307
|
+
name?: string;
|
|
308
|
+
description?: string;
|
|
309
|
+
/** Products that grant it, attached in the same call. */
|
|
310
|
+
products?: ProductRef[];
|
|
311
|
+
}
|
|
312
|
+
interface CreateProductInput {
|
|
313
|
+
store: Store;
|
|
314
|
+
storeProductId: string;
|
|
315
|
+
type: ProductType;
|
|
316
|
+
/** Required for subscriptions. */
|
|
317
|
+
duration?: Duration;
|
|
318
|
+
displayName?: string;
|
|
319
|
+
/** Integer micros (4.99 → 4_990_000). */
|
|
320
|
+
priceMicros?: number;
|
|
321
|
+
currency?: string;
|
|
322
|
+
/** Entitlement keys this product grants. */
|
|
323
|
+
entitlements?: string[];
|
|
324
|
+
}
|
|
325
|
+
interface PackageInput {
|
|
326
|
+
identifier: string;
|
|
327
|
+
product: ProductRef;
|
|
328
|
+
position?: number;
|
|
329
|
+
displayName?: string;
|
|
330
|
+
}
|
|
331
|
+
declare class EntitlementsApi {
|
|
332
|
+
private http;
|
|
333
|
+
constructor(http: HttpOptions);
|
|
334
|
+
list(opts?: {
|
|
335
|
+
includeArchived?: boolean;
|
|
336
|
+
}): Promise<CatalogEntitlement[]>;
|
|
337
|
+
/** By id or key. */
|
|
338
|
+
get(ref: string): Promise<CatalogEntitlement>;
|
|
339
|
+
create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
340
|
+
/** Create, or return the entitlement that already has this key. */
|
|
341
|
+
ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
342
|
+
/** `products`, when given, REPLACES the set of products that grant it. */
|
|
343
|
+
update(ref: string, patch: {
|
|
344
|
+
name?: string;
|
|
345
|
+
description?: string;
|
|
346
|
+
products?: ProductRef[];
|
|
347
|
+
}): Promise<CatalogEntitlement>;
|
|
348
|
+
/** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
|
|
349
|
+
delete(ref: string): Promise<Deleted>;
|
|
350
|
+
products(ref: string, opts?: {
|
|
351
|
+
includeArchived?: boolean;
|
|
352
|
+
}): Promise<CatalogProduct[]>;
|
|
353
|
+
attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
354
|
+
detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
355
|
+
/** Hide it from listings. Never takes access away from anyone who holds it. */
|
|
356
|
+
archive(ref: string): Promise<CatalogEntitlement>;
|
|
357
|
+
unarchive(ref: string): Promise<CatalogEntitlement>;
|
|
358
|
+
}
|
|
359
|
+
declare class ProductsApi {
|
|
360
|
+
private http;
|
|
361
|
+
constructor(http: HttpOptions);
|
|
362
|
+
list(opts?: {
|
|
363
|
+
store?: Store;
|
|
364
|
+
entitlement?: string;
|
|
365
|
+
includeArchived?: boolean;
|
|
366
|
+
}): Promise<CatalogProduct[]>;
|
|
367
|
+
get(ref: ProductRef): Promise<CatalogProduct>;
|
|
368
|
+
create(input: CreateProductInput): Promise<CatalogProduct>;
|
|
369
|
+
/** Create, or return the product that already has this store + SKU. */
|
|
370
|
+
ensure(input: CreateProductInput): Promise<CatalogProduct>;
|
|
371
|
+
/** `entitlements`, when given, REPLACES what it grants (future purchases only). */
|
|
372
|
+
update(ref: ProductRef, patch: {
|
|
373
|
+
displayName?: string;
|
|
374
|
+
priceMicros?: number;
|
|
375
|
+
currency?: string;
|
|
376
|
+
entitlements?: string[];
|
|
377
|
+
}): Promise<CatalogProduct>;
|
|
378
|
+
/** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
|
|
379
|
+
delete(ref: ProductRef): Promise<Deleted>;
|
|
380
|
+
attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
381
|
+
detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
382
|
+
/** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
|
|
383
|
+
archive(ref: ProductRef): Promise<CatalogProduct>;
|
|
384
|
+
unarchive(ref: ProductRef): Promise<CatalogProduct>;
|
|
385
|
+
}
|
|
386
|
+
declare class OfferingsApi {
|
|
387
|
+
private http;
|
|
388
|
+
constructor(http: HttpOptions);
|
|
389
|
+
/** Every offering. Archived ones only with includeArchived. */
|
|
390
|
+
list(opts?: {
|
|
391
|
+
includeArchived?: boolean;
|
|
392
|
+
}): Promise<ListedOffering[]>;
|
|
393
|
+
/** By id or identifier. */
|
|
394
|
+
get(ref: string): Promise<CatalogOffering>;
|
|
395
|
+
create(input: {
|
|
396
|
+
identifier: string;
|
|
397
|
+
displayName?: string;
|
|
398
|
+
isCurrent?: boolean;
|
|
399
|
+
metadata?: Record<string, unknown>;
|
|
400
|
+
packages?: PackageInput[];
|
|
401
|
+
}): Promise<CatalogOffering>;
|
|
402
|
+
/** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
|
|
403
|
+
update(ref: string, patch: {
|
|
404
|
+
displayName?: string;
|
|
405
|
+
isCurrent?: true;
|
|
406
|
+
metadata?: Record<string, unknown> | null;
|
|
407
|
+
}): Promise<CatalogOffering>;
|
|
408
|
+
delete(ref: string): Promise<Deleted>;
|
|
409
|
+
archive(ref: string): Promise<CatalogOffering>;
|
|
410
|
+
unarchive(ref: string): Promise<CatalogOffering>;
|
|
411
|
+
packages(ref: string): Promise<CatalogPackage[]>;
|
|
412
|
+
addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
|
|
413
|
+
/** `pkg` is the package id, or its identifier when that is used on one store only. */
|
|
414
|
+
updatePackage(ref: string, pkg: string, patch: {
|
|
415
|
+
displayName?: string;
|
|
416
|
+
position?: number;
|
|
417
|
+
product?: ProductRef;
|
|
418
|
+
}): Promise<CatalogPackage>;
|
|
419
|
+
deletePackage(ref: string, pkg: string): Promise<Deleted>;
|
|
420
|
+
}
|
|
421
|
+
|
|
177
422
|
interface EntitleHubServerOptions {
|
|
178
|
-
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY
|
|
423
|
+
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
|
|
179
424
|
apiKey: string;
|
|
180
425
|
baseUrl?: string;
|
|
181
426
|
fetchImpl?: typeof fetch;
|
|
182
427
|
}
|
|
183
428
|
/**
|
|
184
|
-
* The server-side EntitleHub SDK
|
|
429
|
+
* The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
|
|
185
430
|
* Keep this on your server; it uses a secret key.
|
|
186
431
|
*
|
|
187
432
|
* const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
188
433
|
* await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
434
|
+
*
|
|
435
|
+
* Manage the catalog from code too (every create has an `ensure` twin that returns the existing
|
|
436
|
+
* row instead of failing, so a setup script can run on every deploy):
|
|
437
|
+
*
|
|
438
|
+
* await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
439
|
+
* await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
|
|
440
|
+
* duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
|
|
189
441
|
*/
|
|
190
442
|
declare class EntitleHubServer {
|
|
191
443
|
private http;
|
|
444
|
+
/** Catalog API: entitlements. */
|
|
445
|
+
readonly entitlements: EntitlementsApi;
|
|
446
|
+
/** Catalog API: products and their entitlement mappings. */
|
|
447
|
+
readonly products: ProductsApi;
|
|
448
|
+
/** Catalog API: paywall offerings and packages. */
|
|
449
|
+
readonly offerings: OfferingsApi;
|
|
192
450
|
constructor(opts: EntitleHubServerOptions);
|
|
193
451
|
/**
|
|
194
452
|
* Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
|
|
195
453
|
*
|
|
196
454
|
* Three modes (pick one):
|
|
197
455
|
* • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
|
|
198
|
-
* • Apple (validated): { signedTransaction }
|
|
199
|
-
* • Stripe (validated): { stripeSubscriptionId }
|
|
200
|
-
* • Trusted server-report: { store, storeProductId }
|
|
456
|
+
* • Apple (validated): { signedTransaction } - store/product are read from the JWS
|
|
457
|
+
* • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
|
|
458
|
+
* • Trusted server-report: { store, storeProductId }, no store validation; only when you've
|
|
201
459
|
* already validated the receipt elsewhere.
|
|
202
460
|
*/
|
|
203
461
|
reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
|
|
@@ -214,10 +472,4 @@ declare class EntitleHubServer {
|
|
|
214
472
|
}): Promise<CheckResult>;
|
|
215
473
|
}
|
|
216
474
|
|
|
217
|
-
|
|
218
|
-
readonly status: number;
|
|
219
|
-
readonly code: string;
|
|
220
|
-
constructor(message: string, status: number, code?: string);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
|
|
475
|
+
export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store, isReceiptRevoked };
|
package/dist/index.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ interface PurchaseInput {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
/**
|
|
83
|
-
* Ergonomic wrapper around a subscriber's active entitlements
|
|
83
|
+
* Ergonomic wrapper around a subscriber's active entitlements, the object your UI reads.
|
|
84
84
|
*
|
|
85
85
|
* const info = await eh.getCustomerInfo();
|
|
86
86
|
* if (info.isActive("pro")) showProFeatures();
|
|
@@ -121,7 +121,7 @@ interface EntitleHubOptions {
|
|
|
121
121
|
type CustomerInfoListener = (info: CustomerInfo) => void;
|
|
122
122
|
type FetchPolicy = "cache-first" | "network-only";
|
|
123
123
|
/**
|
|
124
|
-
* The client-side EntitleHub SDK
|
|
124
|
+
* The client-side EntitleHub SDK: configure once, then ask "what is this user entitled to?".
|
|
125
125
|
* Safe for browsers, React Native, and Expo (publishable key only; never ship a secret key).
|
|
126
126
|
*
|
|
127
127
|
* const eh = new EntitleHub({ apiKey: "pk_live_…", appUserId: user.id });
|
|
@@ -151,7 +151,7 @@ declare class EntitleHub {
|
|
|
151
151
|
checkEntitlement(entitlementId: string, opts?: {
|
|
152
152
|
sandbox?: boolean;
|
|
153
153
|
}): Promise<CheckResult>;
|
|
154
|
-
/** The project's entitlements + products
|
|
154
|
+
/** The project's entitlements + products: for building a paywall. */
|
|
155
155
|
getOfferings(): Promise<Offerings>;
|
|
156
156
|
/**
|
|
157
157
|
* Report a VALIDATED store purchase for the current user and return the updated entitlements.
|
|
@@ -174,30 +174,288 @@ declare class EntitleHub {
|
|
|
174
174
|
addCustomerInfoUpdateListener(listener: CustomerInfoListener): () => void;
|
|
175
175
|
}
|
|
176
176
|
|
|
177
|
+
declare class EntitleHubError extends Error {
|
|
178
|
+
readonly status: number;
|
|
179
|
+
/** SDK-level category: config | auth | http | network | no_fetch. */
|
|
180
|
+
readonly code: string;
|
|
181
|
+
/**
|
|
182
|
+
* The API's own machine-readable code, when it sent one (e.g. "entitlement_exists",
|
|
183
|
+
* "ambiguous_product", "rate_limited"). Branch on this, not on the message text.
|
|
184
|
+
*/
|
|
185
|
+
readonly apiCode?: string;
|
|
186
|
+
/** The full error body, for context the API attaches: `existing`, `candidates`, `unknown`, `grants`… */
|
|
187
|
+
readonly body?: Record<string, unknown>;
|
|
188
|
+
constructor(message: string, status: number, code?: string, body?: Record<string, unknown>);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Was this purchase report refused because the receipt has been REFUNDED?
|
|
192
|
+
*
|
|
193
|
+
* Reporting a refunded receipt answers 409 `receipt_revoked`: no grant is written, and EntitleHub
|
|
194
|
+
* revokes the entitlement it had granted. Apple stamps revocationDate permanently and StoreKit
|
|
195
|
+
* keeps handing the transaction back, so this is the signal that a purchase went away because the
|
|
196
|
+
* money was returned, rather than for some unexplained reason.
|
|
197
|
+
*
|
|
198
|
+
* Shipped as a function so nobody has to write the check themselves: reaching for `status === 409`
|
|
199
|
+
* is the obvious guess and the wrong one, because the catalog endpoints answer 409 for their own
|
|
200
|
+
* conflicts (`product_exists`, `entitlement_in_use`, …). Matching on the API code is exact.
|
|
201
|
+
*
|
|
202
|
+
* Duck-typed rather than `instanceof`, so it still works when the error crosses a bundle boundary.
|
|
203
|
+
*/
|
|
204
|
+
declare function isReceiptRevoked(err: unknown): boolean;
|
|
205
|
+
interface HttpOptions {
|
|
206
|
+
baseUrl: string;
|
|
207
|
+
apiKey: string;
|
|
208
|
+
/** Network-error retries for idempotent reads (default 2). */
|
|
209
|
+
retries?: number;
|
|
210
|
+
timeoutMs?: number;
|
|
211
|
+
fetchImpl?: typeof fetch;
|
|
212
|
+
/** Identifies the calling SDK, e.g. "react-native/0.1.5". Reported to the dashboard. */
|
|
213
|
+
client?: string;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
type ProductType = "subscription" | "non_consumable" | "consumable";
|
|
217
|
+
type Duration = "P1W" | "P1M" | "P3M" | "P6M" | "P1Y";
|
|
218
|
+
/** A product by id, by SKU (when it is on one store), or by explicit store + SKU. */
|
|
219
|
+
type ProductRef = string | {
|
|
220
|
+
id: string;
|
|
221
|
+
} | {
|
|
222
|
+
store: Store;
|
|
223
|
+
storeProductId: string;
|
|
224
|
+
};
|
|
225
|
+
interface CatalogEntitlement {
|
|
226
|
+
object: "entitlement";
|
|
227
|
+
id: string;
|
|
228
|
+
key: string;
|
|
229
|
+
name: string;
|
|
230
|
+
description: string;
|
|
231
|
+
products: {
|
|
232
|
+
id: string;
|
|
233
|
+
store: Store;
|
|
234
|
+
store_product_id: string;
|
|
235
|
+
}[];
|
|
236
|
+
active_grants: number;
|
|
237
|
+
created_at: string | null;
|
|
238
|
+
archived_at: string | null;
|
|
239
|
+
}
|
|
240
|
+
interface CatalogProduct {
|
|
241
|
+
object: "product";
|
|
242
|
+
id: string;
|
|
243
|
+
store: Store;
|
|
244
|
+
store_product_id: string;
|
|
245
|
+
type: ProductType;
|
|
246
|
+
duration: Duration | null;
|
|
247
|
+
display_name: string;
|
|
248
|
+
price_micros: number;
|
|
249
|
+
currency: string;
|
|
250
|
+
entitlements: string[];
|
|
251
|
+
active_grants: number;
|
|
252
|
+
archived_at: string | null;
|
|
253
|
+
}
|
|
254
|
+
interface CatalogPackage {
|
|
255
|
+
object: "package";
|
|
256
|
+
id: string;
|
|
257
|
+
identifier: string;
|
|
258
|
+
display_name: string;
|
|
259
|
+
position: number;
|
|
260
|
+
store: Store;
|
|
261
|
+
store_product_id: string;
|
|
262
|
+
product: CatalogProduct | null;
|
|
263
|
+
}
|
|
264
|
+
interface CatalogOffering {
|
|
265
|
+
object: "offering";
|
|
266
|
+
id: string;
|
|
267
|
+
identifier: string;
|
|
268
|
+
display_name: string;
|
|
269
|
+
is_current: boolean;
|
|
270
|
+
metadata: Record<string, unknown> | null;
|
|
271
|
+
packages: CatalogPackage[];
|
|
272
|
+
created_at: string;
|
|
273
|
+
archived_at: string | null;
|
|
274
|
+
}
|
|
275
|
+
/** An offering as the SDK-facing /v1/offerings/all returns it: packages joined with their product. */
|
|
276
|
+
interface ListedOffering {
|
|
277
|
+
id: string;
|
|
278
|
+
identifier: string;
|
|
279
|
+
display_name: string;
|
|
280
|
+
is_current: boolean;
|
|
281
|
+
metadata: Record<string, unknown> | null;
|
|
282
|
+
archived_at: string | null;
|
|
283
|
+
packages: {
|
|
284
|
+
id: string;
|
|
285
|
+
identifier: string;
|
|
286
|
+
display_name: string;
|
|
287
|
+
position: number;
|
|
288
|
+
store: Store;
|
|
289
|
+
store_product_id: string;
|
|
290
|
+
product: {
|
|
291
|
+
display_name: string;
|
|
292
|
+
type: string;
|
|
293
|
+
price_micros: number;
|
|
294
|
+
currency: string;
|
|
295
|
+
entitlements: string[];
|
|
296
|
+
} | null;
|
|
297
|
+
}[];
|
|
298
|
+
}
|
|
299
|
+
interface Deleted {
|
|
300
|
+
object: string;
|
|
301
|
+
id: string;
|
|
302
|
+
deleted: true;
|
|
303
|
+
}
|
|
304
|
+
interface CreateEntitlementInput {
|
|
305
|
+
/** The exact string your app checks with isEntitled(). Can't change later. */
|
|
306
|
+
key: string;
|
|
307
|
+
name?: string;
|
|
308
|
+
description?: string;
|
|
309
|
+
/** Products that grant it, attached in the same call. */
|
|
310
|
+
products?: ProductRef[];
|
|
311
|
+
}
|
|
312
|
+
interface CreateProductInput {
|
|
313
|
+
store: Store;
|
|
314
|
+
storeProductId: string;
|
|
315
|
+
type: ProductType;
|
|
316
|
+
/** Required for subscriptions. */
|
|
317
|
+
duration?: Duration;
|
|
318
|
+
displayName?: string;
|
|
319
|
+
/** Integer micros (4.99 → 4_990_000). */
|
|
320
|
+
priceMicros?: number;
|
|
321
|
+
currency?: string;
|
|
322
|
+
/** Entitlement keys this product grants. */
|
|
323
|
+
entitlements?: string[];
|
|
324
|
+
}
|
|
325
|
+
interface PackageInput {
|
|
326
|
+
identifier: string;
|
|
327
|
+
product: ProductRef;
|
|
328
|
+
position?: number;
|
|
329
|
+
displayName?: string;
|
|
330
|
+
}
|
|
331
|
+
declare class EntitlementsApi {
|
|
332
|
+
private http;
|
|
333
|
+
constructor(http: HttpOptions);
|
|
334
|
+
list(opts?: {
|
|
335
|
+
includeArchived?: boolean;
|
|
336
|
+
}): Promise<CatalogEntitlement[]>;
|
|
337
|
+
/** By id or key. */
|
|
338
|
+
get(ref: string): Promise<CatalogEntitlement>;
|
|
339
|
+
create(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
340
|
+
/** Create, or return the entitlement that already has this key. */
|
|
341
|
+
ensure(input: CreateEntitlementInput): Promise<CatalogEntitlement>;
|
|
342
|
+
/** `products`, when given, REPLACES the set of products that grant it. */
|
|
343
|
+
update(ref: string, patch: {
|
|
344
|
+
name?: string;
|
|
345
|
+
description?: string;
|
|
346
|
+
products?: ProductRef[];
|
|
347
|
+
}): Promise<CatalogEntitlement>;
|
|
348
|
+
/** Refused (409 `entitlement_in_use`) once anyone has been granted it. Archive instead. */
|
|
349
|
+
delete(ref: string): Promise<Deleted>;
|
|
350
|
+
products(ref: string, opts?: {
|
|
351
|
+
includeArchived?: boolean;
|
|
352
|
+
}): Promise<CatalogProduct[]>;
|
|
353
|
+
attachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
354
|
+
detachProducts(ref: string, products: ProductRef[]): Promise<CatalogEntitlement>;
|
|
355
|
+
/** Hide it from listings. Never takes access away from anyone who holds it. */
|
|
356
|
+
archive(ref: string): Promise<CatalogEntitlement>;
|
|
357
|
+
unarchive(ref: string): Promise<CatalogEntitlement>;
|
|
358
|
+
}
|
|
359
|
+
declare class ProductsApi {
|
|
360
|
+
private http;
|
|
361
|
+
constructor(http: HttpOptions);
|
|
362
|
+
list(opts?: {
|
|
363
|
+
store?: Store;
|
|
364
|
+
entitlement?: string;
|
|
365
|
+
includeArchived?: boolean;
|
|
366
|
+
}): Promise<CatalogProduct[]>;
|
|
367
|
+
get(ref: ProductRef): Promise<CatalogProduct>;
|
|
368
|
+
create(input: CreateProductInput): Promise<CatalogProduct>;
|
|
369
|
+
/** Create, or return the product that already has this store + SKU. */
|
|
370
|
+
ensure(input: CreateProductInput): Promise<CatalogProduct>;
|
|
371
|
+
/** `entitlements`, when given, REPLACES what it grants (future purchases only). */
|
|
372
|
+
update(ref: ProductRef, patch: {
|
|
373
|
+
displayName?: string;
|
|
374
|
+
priceMicros?: number;
|
|
375
|
+
currency?: string;
|
|
376
|
+
entitlements?: string[];
|
|
377
|
+
}): Promise<CatalogProduct>;
|
|
378
|
+
/** Refused (409 `product_in_use`) once anyone has bought it. Archive instead. */
|
|
379
|
+
delete(ref: ProductRef): Promise<Deleted>;
|
|
380
|
+
attachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
381
|
+
detachEntitlements(ref: ProductRef, entitlements: string[]): Promise<CatalogProduct>;
|
|
382
|
+
/** Retire a SKU: gone from listings and paywalls; existing subscribers keep access and renewals. */
|
|
383
|
+
archive(ref: ProductRef): Promise<CatalogProduct>;
|
|
384
|
+
unarchive(ref: ProductRef): Promise<CatalogProduct>;
|
|
385
|
+
}
|
|
386
|
+
declare class OfferingsApi {
|
|
387
|
+
private http;
|
|
388
|
+
constructor(http: HttpOptions);
|
|
389
|
+
/** Every offering. Archived ones only with includeArchived. */
|
|
390
|
+
list(opts?: {
|
|
391
|
+
includeArchived?: boolean;
|
|
392
|
+
}): Promise<ListedOffering[]>;
|
|
393
|
+
/** By id or identifier. */
|
|
394
|
+
get(ref: string): Promise<CatalogOffering>;
|
|
395
|
+
create(input: {
|
|
396
|
+
identifier: string;
|
|
397
|
+
displayName?: string;
|
|
398
|
+
isCurrent?: boolean;
|
|
399
|
+
metadata?: Record<string, unknown>;
|
|
400
|
+
packages?: PackageInput[];
|
|
401
|
+
}): Promise<CatalogOffering>;
|
|
402
|
+
/** `isCurrent: true` makes it the offering /current serves. `metadata: null` clears it. */
|
|
403
|
+
update(ref: string, patch: {
|
|
404
|
+
displayName?: string;
|
|
405
|
+
isCurrent?: true;
|
|
406
|
+
metadata?: Record<string, unknown> | null;
|
|
407
|
+
}): Promise<CatalogOffering>;
|
|
408
|
+
delete(ref: string): Promise<Deleted>;
|
|
409
|
+
archive(ref: string): Promise<CatalogOffering>;
|
|
410
|
+
unarchive(ref: string): Promise<CatalogOffering>;
|
|
411
|
+
packages(ref: string): Promise<CatalogPackage[]>;
|
|
412
|
+
addPackage(ref: string, input: PackageInput): Promise<CatalogPackage>;
|
|
413
|
+
/** `pkg` is the package id, or its identifier when that is used on one store only. */
|
|
414
|
+
updatePackage(ref: string, pkg: string, patch: {
|
|
415
|
+
displayName?: string;
|
|
416
|
+
position?: number;
|
|
417
|
+
product?: ProductRef;
|
|
418
|
+
}): Promise<CatalogPackage>;
|
|
419
|
+
deletePackage(ref: string, pkg: string): Promise<Deleted>;
|
|
420
|
+
}
|
|
421
|
+
|
|
177
422
|
interface EntitleHubServerOptions {
|
|
178
|
-
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY
|
|
423
|
+
/** Your secret key (sk_live_… / sk_test_…). SERVER ONLY, never ship this to a client. */
|
|
179
424
|
apiKey: string;
|
|
180
425
|
baseUrl?: string;
|
|
181
426
|
fetchImpl?: typeof fetch;
|
|
182
427
|
}
|
|
183
428
|
/**
|
|
184
|
-
* The server-side EntitleHub SDK
|
|
429
|
+
* The server-side EntitleHub SDK: report purchases and grant entitlements from your backend.
|
|
185
430
|
* Keep this on your server; it uses a secret key.
|
|
186
431
|
*
|
|
187
432
|
* const eh = new EntitleHubServer({ apiKey: process.env.ENTITLEHUB_SECRET_KEY! });
|
|
188
433
|
* await eh.reportPurchase(userId, { store: "app_store", storeProductId: "pro_monthly" });
|
|
434
|
+
*
|
|
435
|
+
* Manage the catalog from code too (every create has an `ensure` twin that returns the existing
|
|
436
|
+
* row instead of failing, so a setup script can run on every deploy):
|
|
437
|
+
*
|
|
438
|
+
* await eh.entitlements.ensure({ key: "pro", name: "Pro" });
|
|
439
|
+
* await eh.products.ensure({ store: "stripe", storeProductId: "price_1Q…", type: "subscription",
|
|
440
|
+
* duration: "P1M", priceMicros: 9_990_000, entitlements: ["pro"] });
|
|
189
441
|
*/
|
|
190
442
|
declare class EntitleHubServer {
|
|
191
443
|
private http;
|
|
444
|
+
/** Catalog API: entitlements. */
|
|
445
|
+
readonly entitlements: EntitlementsApi;
|
|
446
|
+
/** Catalog API: products and their entitlement mappings. */
|
|
447
|
+
readonly products: ProductsApi;
|
|
448
|
+
/** Catalog API: paywall offerings and packages. */
|
|
449
|
+
readonly offerings: OfferingsApi;
|
|
192
450
|
constructor(opts: EntitleHubServerOptions);
|
|
193
451
|
/**
|
|
194
452
|
* Report a completed store purchase → writes the mapped entitlement grant(s). Returns updated info.
|
|
195
453
|
*
|
|
196
454
|
* Three modes (pick one):
|
|
197
455
|
* • Google (validated): { store:"play", storeProductId, purchaseToken, isSubscription }
|
|
198
|
-
* • Apple (validated): { signedTransaction }
|
|
199
|
-
* • Stripe (validated): { stripeSubscriptionId }
|
|
200
|
-
* • Trusted server-report: { store, storeProductId }
|
|
456
|
+
* • Apple (validated): { signedTransaction } - store/product are read from the JWS
|
|
457
|
+
* • Stripe (validated): { stripeSubscriptionId }, validated via your Stripe secret key
|
|
458
|
+
* • Trusted server-report: { store, storeProductId }, no store validation; only when you've
|
|
201
459
|
* already validated the receipt elsewhere.
|
|
202
460
|
*/
|
|
203
461
|
reportPurchase(appUserId: string, purchase: PurchaseInput): Promise<CustomerInfo>;
|
|
@@ -214,10 +472,4 @@ declare class EntitleHubServer {
|
|
|
214
472
|
}): Promise<CheckResult>;
|
|
215
473
|
}
|
|
216
474
|
|
|
217
|
-
|
|
218
|
-
readonly status: number;
|
|
219
|
-
readonly code: string;
|
|
220
|
-
constructor(message: string, status: number, code?: string);
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
export { type ActiveEntitlement, type CheckResult, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, type FetchPolicy, type OfferingEntitlement, type OfferingProduct, type Offerings, type PurchaseInput, type Store };
|
|
475
|
+
export { type ActiveEntitlement, type CatalogEntitlement, type CatalogOffering, type CatalogPackage, type CatalogProduct, type CheckResult, type CreateEntitlementInput, type CreateProductInput, CustomerInfo, type CustomerInfoListener, type CustomerInfoResponse, type Deleted, type Duration, EntitleHub, EntitleHubError, type EntitleHubOptions, EntitleHubServer, type EntitleHubServerOptions, type EntitlementStatus, EntitlementsApi, type FetchPolicy, type ListedOffering, type OfferingEntitlement, type OfferingProduct, type Offerings, OfferingsApi, type PackageInput, type ProductRef, type ProductType, ProductsApi, type PurchaseInput, type Store, isReceiptRevoked };
|
package/dist/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=class{constructor(t){this.appUserId=t.app_user_id,this.entitlements=t.active_entitlements??[];let e={};for(let r of this.entitlements)e[r.entitlement]=r;this.active=e}isActive(t){return!!this.active[t]}get activeEntitlementIds(){return Object.keys(this.active)}expirationDate(t){let e=this.active[t];if(e)return e.expires_at?new Date(e.expires_at):null}};var O="0.3.0";var a=class extends Error{constructor(t,e,r="error",o){super(t),this.name="EntitleHubError",this.status=e,this.code=r,this.body=o,this.apiCode=typeof o?.code=="string"?o.code:void 0}};function $(n){if(!n||typeof n!="object")return!1;let t=n;return t.apiCode==="receipt_revoked"||t.body?.code==="receipt_revoked"}var H=n=>new Promise(t=>setTimeout(t,n));async function i(n,t,e,r){let o=n.fetchImpl??globalThis.fetch;if(!o)throw new a("No fetch implementation available. On Node <18, pass fetchImpl.",0,"no_fetch");let y=`${n.baseUrl.replace(/\/$/,"")}${e}`,k=t==="GET"?n.retries??2:0,b;for(let f=0;f<=k;f++){let E=new AbortController,w=n.timeoutMs?setTimeout(()=>E.abort(),n.timeoutMs):void 0;try{let c=await o(y,{method:t,headers:{Authorization:`Bearer ${n.apiKey}`,"X-EntitleHub-Client":n.client??`${"js"}/${O}`,...r?{"Content-Type":"application/json"}:{}},body:r?JSON.stringify(r):void 0,signal:E.signal}),T=await c.text(),h=T?U(T):{};if(!c.ok){let v=`HTTP ${c.status}`,P=h&&typeof h=="object"?h:void 0;throw P&&"error"in P&&(v=String(P.error)),new a(v,c.status,c.status===401||c.status===403?"auth":"http",P)}return h}catch(c){if(b=c,c instanceof a)throw c;f<k&&await H(250*(f+1))}finally{w&&clearTimeout(w)}}throw new a(`Network request failed: ${String(b?.message??b)}`,0,"network")}function U(n){try{return JSON.parse(n)}catch{return{}}}var C=class{constructor(t){this.listeners=new Set;if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.appUserId)throw new a("appUserId is required.",0,"config");if(t.apiKey.startsWith("sk_"))throw new a("Never use a secret (sk_) key in the client SDK. Use your publishable (pk_) key.",0,"config");this.appUserId=t.appUserId,this.cacheTtlMs=t.cacheTtlMs??5*6e4,this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3,client:t.client}}get currentAppUserId(){return this.appUserId}async logIn(t){return this.appUserId=t,this.cached=void 0,this.getCustomerInfo({fetchPolicy:"network-only"})}logOut(){this.cached=void 0}async getCustomerInfo(t={}){if((t.fetchPolicy??"cache-first")==="cache-first"&&this.cached&&Date.now()-this.cached.at<this.cacheTtlMs)return this.cached.info;let r=await i(this.http,"GET",`/subscribers/${encodeURIComponent(this.appUserId)}`),o=new u(r);this.cached={info:o,at:Date.now()};for(let y of this.listeners)try{y(o)}catch{}return o}async isEntitled(t){return(await this.getCustomerInfo()).isActive(t)}async checkEntitlement(t,e={}){return i(this.http,"POST","/check",{app_user_id:this.appUserId,entitlement:t,sandbox:!!e.sandbox})}async getOfferings(){return i(this.http,"GET","/offerings")}async reportPurchase(t){let e=await i(this.http,"POST",`/subscribers/${encodeURIComponent(this.appUserId)}/purchases`,{store:t.store??"",store_product_id:t.storeProductId??"",is_sandbox:!!t.isSandbox,purchase_token:t.purchaseToken??"",is_subscription:!!t.isSubscription,signed_transaction:t.signedTransaction??"",stripe_subscription_id:t.stripeSubscriptionId??""}),r=new u(e);this.cached={info:r,at:Date.now()};for(let o of this.listeners)try{o(r)}catch{}return r}addCustomerInfoUpdateListener(t){return this.listeners.add(t),()=>this.listeners.delete(t)}};var s=encodeURIComponent;function p(n){return typeof n=="string"?n:"id"in n?{id:n.id}:{store:n.store,store_product_id:n.storeProductId}}function d(n){return typeof n=="string"?{seg:s(n),qs:""}:"id"in n?{seg:s(n.id),qs:""}:{seg:s(n.storeProductId),qs:`?store=${s(n.store)}`}}var _=n=>n?.includeArchived?"include_archived=true":"";async function x(n,t){try{return await n()}catch(e){if(e instanceof a&&e.apiCode===t&&e.body?.existing)return e.body.existing;throw e}}var l=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/entitlements${e?`?${e}`:""}`)).entitlements}get(t){return i(this.http,"GET",`/entitlements/${s(t)}`)}create(t){return i(this.http,"POST","/entitlements",{key:t.key,name:t.name,description:t.description,products:t.products?.map(p)})}ensure(t){return x(()=>this.create(t),"entitlement_exists")}update(t,e){return i(this.http,"PATCH",`/entitlements/${s(t)}`,{name:e.name,description:e.description,products:e.products?.map(p)})}delete(t){return i(this.http,"DELETE",`/entitlements/${s(t)}`)}async products(t,e){let r=_(e);return(await i(this.http,"GET",`/entitlements/${s(t)}/products${r?`?${r}`:""}`)).products}attachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/attach_products`,{products:e.map(p)})}detachProducts(t,e){return i(this.http,"POST",`/entitlements/${s(t)}/detach_products`,{products:e.map(p)})}archive(t){return i(this.http,"POST",`/entitlements/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/entitlements/${s(t)}/unarchive`)}},g=class{constructor(t){this.http=t}async list(t){let e=new URLSearchParams;t?.store&&e.set("store",t.store),t?.entitlement&&e.set("entitlement",t.entitlement),t?.includeArchived&&e.set("include_archived","true");let r=e.toString();return(await i(this.http,"GET",`/products${r?`?${r}`:""}`)).products}get(t){let{seg:e,qs:r}=d(t);return i(this.http,"GET",`/products/${e}${r}`)}create(t){return i(this.http,"POST","/products",{store:t.store,store_product_id:t.storeProductId,type:t.type,duration:t.duration,display_name:t.displayName,price_micros:t.priceMicros,currency:t.currency,entitlements:t.entitlements})}ensure(t){return x(()=>this.create(t),"product_exists")}update(t,e){let{seg:r,qs:o}=d(t);return i(this.http,"PATCH",`/products/${r}${o}`,{display_name:e.displayName,price_micros:e.priceMicros,currency:e.currency,entitlements:e.entitlements})}delete(t){let{seg:e,qs:r}=d(t);return i(this.http,"DELETE",`/products/${e}${r}`)}attachEntitlements(t,e){let{seg:r,qs:o}=d(t);return i(this.http,"POST",`/products/${r}/attach_entitlements${o}`,{entitlements:e})}detachEntitlements(t,e){let{seg:r,qs:o}=d(t);return i(this.http,"POST",`/products/${r}/detach_entitlements${o}`,{entitlements:e})}archive(t){let{seg:e,qs:r}=d(t);return i(this.http,"POST",`/products/${e}/archive${r}`)}unarchive(t){let{seg:e,qs:r}=d(t);return i(this.http,"POST",`/products/${e}/unarchive${r}`)}},m=class{constructor(t){this.http=t}async list(t){let e=_(t);return(await i(this.http,"GET",`/offerings/all${e?`?${e}`:""}`)).offerings}get(t){return i(this.http,"GET",`/offerings/${s(t)}`)}create(t){return i(this.http,"POST","/offerings",{identifier:t.identifier,display_name:t.displayName,is_current:t.isCurrent,metadata:t.metadata,packages:t.packages?.map(S)})}update(t,e){return i(this.http,"PATCH",`/offerings/${s(t)}`,{display_name:e.displayName,is_current:e.isCurrent,metadata:e.metadata})}delete(t){return i(this.http,"DELETE",`/offerings/${s(t)}`)}archive(t){return i(this.http,"POST",`/offerings/${s(t)}/archive`)}unarchive(t){return i(this.http,"POST",`/offerings/${s(t)}/unarchive`)}async packages(t){return(await i(this.http,"GET",`/offerings/${s(t)}/packages`)).packages}addPackage(t,e){return i(this.http,"POST",`/offerings/${s(t)}/packages`,S(e))}updatePackage(t,e,r){return i(this.http,"PATCH",`/offerings/${s(t)}/packages/${s(e)}`,{display_name:r.displayName,position:r.position,product:r.product===void 0?void 0:p(r.product)})}deletePackage(t,e){return i(this.http,"DELETE",`/offerings/${s(t)}/packages/${s(e)}`)}};function S(n){return{identifier:n.identifier,product:p(n.product),position:n.position,display_name:n.displayName}}var I=class{constructor(t){if(!t.apiKey)throw new a("apiKey is required.",0,"config");if(!t.apiKey.startsWith("sk_"))throw new a("EntitleHubServer needs a secret (sk_) key. For client reads, use the EntitleHub class with a pk_ key.",0,"config");this.http={baseUrl:t.baseUrl??"https://entitlehub.com/v1",apiKey:t.apiKey,fetchImpl:t.fetchImpl,timeoutMs:15e3},this.entitlements=new l(this.http),this.products=new g(this.http),this.offerings=new m(this.http)}async reportPurchase(t,e){let r=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/purchases`,{store:e.store,store_product_id:e.storeProductId,is_sandbox:!!e.isSandbox,transaction_id:e.transactionId??"",purchase_token:e.purchaseToken??"",is_subscription:!!e.isSubscription,signed_transaction:e.signedTransaction??"",stripe_subscription_id:e.stripeSubscriptionId??""});return new u(r)}async grantEntitlement(t,e,r={}){let o=await i(this.http,"POST",`/subscribers/${encodeURIComponent(t)}/grant`,{entitlement:e,duration_days:r.durationDays??0,is_sandbox:!!r.isSandbox});return new u(o)}async getCustomerInfo(t){let e=await i(this.http,"GET",`/subscribers/${encodeURIComponent(t)}`);return new u(e)}async check(t,e,r={}){return i(this.http,"POST","/check",{app_user_id:t,entitlement:e,sandbox:!!r.sandbox})}};export{u as CustomerInfo,C as EntitleHub,a as EntitleHubError,I as EntitleHubServer,l as EntitlementsApi,m as OfferingsApi,g as ProductsApi,$ as isReceiptRevoked};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@entitlehub/sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "EntitleHub SDK
|
|
3
|
+
"version": "0.3.1",
|
|
4
|
+
"description": "EntitleHub SDK: one entitlement API across App Store, Google Play, Stripe, and web. Works in browsers, React Native, Expo, and Node.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"entitlehub",
|
|
7
7
|
"entitlements",
|
|
@@ -12,10 +12,17 @@
|
|
|
12
12
|
"stripe"
|
|
13
13
|
],
|
|
14
14
|
"homepage": "https://entitlehub.com",
|
|
15
|
-
"repository": {
|
|
16
|
-
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/DanCue44/entitlehub-sdk.git"
|
|
18
|
+
},
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/DanCue44/entitlehub-sdk/issues"
|
|
21
|
+
},
|
|
17
22
|
"license": "MIT",
|
|
18
|
-
"publishConfig": {
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
19
26
|
"type": "module",
|
|
20
27
|
"main": "./dist/index.cjs",
|
|
21
28
|
"module": "./dist/index.js",
|
|
@@ -37,7 +44,7 @@
|
|
|
37
44
|
"scripts": {
|
|
38
45
|
"build": "tsup src/index.ts --format esm,cjs --dts --clean --minify",
|
|
39
46
|
"typecheck": "tsc --noEmit",
|
|
40
|
-
"test": "node --test",
|
|
47
|
+
"test": "node --test \"test/*.test.mjs\"",
|
|
41
48
|
"prepublishOnly": "npm run build"
|
|
42
49
|
},
|
|
43
50
|
"engines": {
|