@onesub/server 0.9.1 → 0.11.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/README.md +86 -1
- package/dist/__tests__/entitlements.test.d.ts +14 -0
- package/dist/__tests__/entitlements.test.d.ts.map +1 -0
- package/dist/__tests__/entitlements.test.js +268 -0
- package/dist/__tests__/entitlements.test.js.map +1 -0
- package/dist/__tests__/metrics.test.d.ts +6 -0
- package/dist/__tests__/metrics.test.d.ts.map +1 -0
- package/dist/__tests__/metrics.test.js +227 -0
- package/dist/__tests__/metrics.test.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/routes/entitlements.d.ts +23 -0
- package/dist/routes/entitlements.d.ts.map +1 -0
- package/dist/routes/entitlements.js +130 -0
- package/dist/routes/entitlements.js.map +1 -0
- package/dist/routes/metrics.d.ts +18 -0
- package/dist/routes/metrics.d.ts.map +1 -0
- package/dist/routes/metrics.js +181 -0
- package/dist/routes/metrics.js.map +1 -0
- package/dist/store.d.ts +26 -0
- package/dist/store.d.ts.map +1 -1
- package/dist/store.js +20 -2
- package/dist/store.js.map +1 -1
- package/dist/stores/postgres.d.ts +9 -0
- package/dist/stores/postgres.d.ts.map +1 -1
- package/dist/stores/postgres.js +24 -0
- package/dist/stores/postgres.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -24,7 +24,15 @@ import { createOneSubMiddleware, PostgresSubscriptionStore, PostgresPurchaseStor
|
|
|
24
24
|
const app = express();
|
|
25
25
|
|
|
26
26
|
app.use(createOneSubMiddleware({
|
|
27
|
-
apple:
|
|
27
|
+
apple: {
|
|
28
|
+
bundleId: 'com.yourapp.id',
|
|
29
|
+
sharedSecret: process.env.APPLE_SHARED_SECRET,
|
|
30
|
+
// Optional — required only for the App Store Server API features below
|
|
31
|
+
// (status fetch fallback, consumption response).
|
|
32
|
+
keyId: process.env.APPLE_KEY_ID,
|
|
33
|
+
issuerId: process.env.APPLE_ISSUER_ID,
|
|
34
|
+
privateKey: process.env.APPLE_PRIVATE_KEY,
|
|
35
|
+
},
|
|
28
36
|
google: { packageName: 'com.yourapp.id', serviceAccountKey: process.env.GOOGLE_SERVICE_ACCOUNT_KEY },
|
|
29
37
|
database: { url: process.env.DATABASE_URL },
|
|
30
38
|
store: new PostgresSubscriptionStore(process.env.DATABASE_URL),
|
|
@@ -32,6 +40,7 @@ app.use(createOneSubMiddleware({
|
|
|
32
40
|
// Optional:
|
|
33
41
|
adminSecret: process.env.ADMIN_SECRET, // enables /onesub/purchase/admin/*
|
|
34
42
|
logger: require('pino')(), // any { info, warn, error } logger
|
|
43
|
+
refundPolicy: 'immediate', // 'immediate' (default) | 'until_expiry'
|
|
35
44
|
}));
|
|
36
45
|
|
|
37
46
|
app.listen(4100);
|
|
@@ -51,10 +60,86 @@ app.listen(4100);
|
|
|
51
60
|
| `POST /onesub/purchase/admin/grant` | Manually grant a purchase (requires `adminSecret`) |
|
|
52
61
|
| `POST /onesub/purchase/admin/transfer` | Reassign a `transactionId` to a new `userId` (requires `adminSecret`) |
|
|
53
62
|
|
|
63
|
+
## Lifecycle states (`0.4.0+`)
|
|
64
|
+
|
|
65
|
+
`SubscriptionInfo.status` carries the full lifecycle. The status route's `active: boolean` is computed as `(active || grace_period) && expiresAt > now` — but the raw status lets you render accurate UX:
|
|
66
|
+
|
|
67
|
+
| Status | active | UX hint |
|
|
68
|
+
|--------|--------|---------|
|
|
69
|
+
| `active` | ✅ | normal |
|
|
70
|
+
| `grace_period` | ✅ | "결제 정보 확인 필요 (계속 사용 가능)" |
|
|
71
|
+
| `on_hold` | ❌ | "결제 정보를 업데이트하세요" |
|
|
72
|
+
| `paused` | ❌ | "재개 예정: \{autoResumeTime\}" |
|
|
73
|
+
| `expired` / `canceled` | ❌ | re-purchase or restore |
|
|
74
|
+
|
|
75
|
+
See [`@onesub/shared` README](../shared/README.md) for the full mapping.
|
|
76
|
+
|
|
77
|
+
## Refund policy (`0.8.0+`)
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
refundPolicy: 'immediate' | 'until_expiry' // default 'immediate'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
- `'immediate'` — subscription refunds (Apple `REFUND`/`REVOKE`, Google voided productType=1) flip status to `canceled` right away. Strict, fraud-resistant.
|
|
84
|
+
- `'until_expiry'` — keep `status`/`expiresAt` untouched, only flip `willRenew = false`. User keeps entitlement until the original expiry. Better UX for goodwill refunds.
|
|
85
|
+
|
|
86
|
+
IAP refunds (consumable / non-consumable) are **always immediate** regardless of policy — they have no expiry concept.
|
|
87
|
+
|
|
88
|
+
## Optional Apple App Store Server API features (`0.8.0+`)
|
|
89
|
+
|
|
90
|
+
Set `apple.keyId` / `apple.issuerId` / `apple.privateKey` (PKCS8 ES256 from App Store Connect → Users and Access → Keys) to unlock:
|
|
91
|
+
|
|
92
|
+
### Status API fallback (automatic)
|
|
93
|
+
|
|
94
|
+
If a webhook arrives for an `originalTransactionId` the store doesn't know (server downtime, queue truncation, fresh install), the webhook handler calls `GET /inApps/v1/subscriptions/{originalTransactionId}` to fetch canonical state from Apple and saves a record under a placeholder `userId`. Subsequent `/onesub/validate` from the host can claim ownership.
|
|
95
|
+
|
|
96
|
+
You can also call it directly:
|
|
97
|
+
|
|
98
|
+
```ts
|
|
99
|
+
import { fetchAppleSubscriptionStatus } from '@onesub/server';
|
|
100
|
+
|
|
101
|
+
const sub = await fetchAppleSubscriptionStatus(originalTxId, config.apple, { sandbox: false });
|
|
102
|
+
// sub: SubscriptionInfo | null — null on missing creds / 404 / network failure
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### CONSUMPTION_REQUEST response hook
|
|
106
|
+
|
|
107
|
+
When Apple sends a `CONSUMPTION_REQUEST` notification (consumable refund review), without a hook Apple has no usage signal and tends to grant the refund. Provide a hook to PUT consumption info back to `/inApps/v1/transactions/consumption/{txId}`:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
apple: {
|
|
111
|
+
// ...
|
|
112
|
+
consumptionInfoProvider: async (ctx) => ({
|
|
113
|
+
customerConsented: true, // required; false makes Apple ignore the response
|
|
114
|
+
consumptionStatus: 3, // 0=undeclared, 1=not consumed, 2=partial, 3=full
|
|
115
|
+
deliveryStatus: 1, // 0=undeclared, 1=delivered & working, 2=quality issue, ...
|
|
116
|
+
refundPreference: 2, // 0=undeclared, 1=grant, 2=decline, 3=no preference
|
|
117
|
+
// see AppleConsumptionRequest for the full set of optional fields
|
|
118
|
+
}),
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Fire-and-forget; failures are logged but don't block the webhook 200.
|
|
123
|
+
|
|
124
|
+
## Optional Google hooks (`0.8.0+`)
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
google: {
|
|
128
|
+
// ...
|
|
129
|
+
// Called when SUBSCRIPTION_PRICE_CHANGE_CONFIRMED (8) arrives — user agreed
|
|
130
|
+
// to a price change; new price applies on next renewal. Useful for analytics.
|
|
131
|
+
onPriceChangeConfirmed: async (ctx) => {
|
|
132
|
+
await analytics.track('price_change_confirmed', ctx);
|
|
133
|
+
},
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
54
137
|
## Schema
|
|
55
138
|
|
|
56
139
|
Canonical Postgres DDL shipped at [`sql/schema.sql`](./sql/schema.sql). Apply with `psql -f` or let `store.initSchema()` run it for you on startup.
|
|
57
140
|
|
|
141
|
+
`store.initSchema()` is **safe to call on every boot** — all DDL is `IF NOT EXISTS`. New columns added in later releases (e.g. `linked_purchase_token`, `auto_resume_time`) ship with `ALTER TABLE IF NOT EXISTS` so existing installs auto-backfill on the next startup.
|
|
142
|
+
|
|
58
143
|
## Security
|
|
59
144
|
|
|
60
145
|
- Apple JWS signature verified end-to-end against **Apple Root CA G3** (as of `0.6.0`)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlements abstraction tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - evaluateEntitlement helper (subscription / purchase / mixed / none /
|
|
6
|
+
* expired sub / wrong status / consumable excluded)
|
|
7
|
+
* - GET /onesub/entitlement single-check route (404 unknown id, 400 bad input)
|
|
8
|
+
* - GET /onesub/entitlements bulk route
|
|
9
|
+
* - Router not mounted when config.entitlements is absent (404)
|
|
10
|
+
* - getAllByUserId returns multi-product subscriptions (regression for the
|
|
11
|
+
* InMemoryStore change that backs entitlements)
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=entitlements.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/entitlements.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG"}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Entitlements abstraction tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers:
|
|
5
|
+
* - evaluateEntitlement helper (subscription / purchase / mixed / none /
|
|
6
|
+
* expired sub / wrong status / consumable excluded)
|
|
7
|
+
* - GET /onesub/entitlement single-check route (404 unknown id, 400 bad input)
|
|
8
|
+
* - GET /onesub/entitlements bulk route
|
|
9
|
+
* - Router not mounted when config.entitlements is absent (404)
|
|
10
|
+
* - getAllByUserId returns multi-product subscriptions (regression for the
|
|
11
|
+
* InMemoryStore change that backs entitlements)
|
|
12
|
+
*/
|
|
13
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
14
|
+
import express from 'express';
|
|
15
|
+
import { SUBSCRIPTION_STATUS, PURCHASE_TYPE } from '@onesub/shared';
|
|
16
|
+
import { createOneSubMiddleware } from '../index.js';
|
|
17
|
+
import { evaluateEntitlement } from '../routes/entitlements.js';
|
|
18
|
+
import { InMemorySubscriptionStore, InMemoryPurchaseStore } from '../store.js';
|
|
19
|
+
// ── helpers ─────────────────────────────────────────────────────────────────
|
|
20
|
+
const futureExpiry = '2099-01-01T00:00:00.000Z';
|
|
21
|
+
const pastExpiry = '2024-01-01T00:00:00.000Z';
|
|
22
|
+
function sub(overrides) {
|
|
23
|
+
return {
|
|
24
|
+
userId: 'u',
|
|
25
|
+
productId: 'pro_monthly',
|
|
26
|
+
platform: 'apple',
|
|
27
|
+
status: 'active',
|
|
28
|
+
expiresAt: futureExpiry,
|
|
29
|
+
originalTransactionId: `orig_${Math.random()}`,
|
|
30
|
+
purchasedAt: '2026-01-01T00:00:00.000Z',
|
|
31
|
+
willRenew: true,
|
|
32
|
+
...overrides,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function purchase(overrides) {
|
|
36
|
+
return {
|
|
37
|
+
userId: 'u',
|
|
38
|
+
productId: 'lifetime_pass',
|
|
39
|
+
platform: 'apple',
|
|
40
|
+
type: 'non_consumable',
|
|
41
|
+
transactionId: `tx_${Math.random()}`,
|
|
42
|
+
purchasedAt: '2026-01-01T00:00:00.000Z',
|
|
43
|
+
quantity: 1,
|
|
44
|
+
...overrides,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function spinUp(handler) {
|
|
48
|
+
return {
|
|
49
|
+
async get(path) {
|
|
50
|
+
const httpServer = handler.listen(0);
|
|
51
|
+
const port = httpServer.address().port;
|
|
52
|
+
try {
|
|
53
|
+
const resp = await fetch(`http://127.0.0.1:${port}${path}`);
|
|
54
|
+
const text = await resp.text();
|
|
55
|
+
let parsed = text;
|
|
56
|
+
try {
|
|
57
|
+
parsed = JSON.parse(text);
|
|
58
|
+
}
|
|
59
|
+
catch { /* keep as text */ }
|
|
60
|
+
return { status: resp.status, body: parsed };
|
|
61
|
+
}
|
|
62
|
+
finally {
|
|
63
|
+
await new Promise((r) => httpServer.close(() => r()));
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function buildServer(opts) {
|
|
69
|
+
const store = new InMemorySubscriptionStore();
|
|
70
|
+
const purchaseStore = new InMemoryPurchaseStore();
|
|
71
|
+
const config = {
|
|
72
|
+
apple: { bundleId: 'com.example.app', skipJwsVerification: true },
|
|
73
|
+
database: { url: '' },
|
|
74
|
+
entitlements: opts.entitlements,
|
|
75
|
+
};
|
|
76
|
+
const app = express();
|
|
77
|
+
// createOneSubMiddleware mounts express.json itself, but the entitlement
|
|
78
|
+
// routes are GET-only so it's fine either way.
|
|
79
|
+
const middleware = createOneSubMiddleware({ ...config, store, purchaseStore });
|
|
80
|
+
app.use(middleware);
|
|
81
|
+
return { store, purchaseStore, server: spinUp(app) };
|
|
82
|
+
}
|
|
83
|
+
const PREMIUM = {
|
|
84
|
+
premium: { productIds: ['pro_monthly', 'pro_yearly', 'lifetime_pass'] },
|
|
85
|
+
};
|
|
86
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
87
|
+
// evaluateEntitlement (unit, no HTTP)
|
|
88
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
89
|
+
describe('evaluateEntitlement', () => {
|
|
90
|
+
let store;
|
|
91
|
+
let purchaseStore;
|
|
92
|
+
beforeEach(() => {
|
|
93
|
+
store = new InMemorySubscriptionStore();
|
|
94
|
+
purchaseStore = new InMemoryPurchaseStore();
|
|
95
|
+
});
|
|
96
|
+
it('active subscription matching one of the productIds → entitled', async () => {
|
|
97
|
+
await store.save(sub({ userId: 'u1', productId: 'pro_yearly' }));
|
|
98
|
+
const result = await evaluateEntitlement('u1', PREMIUM.premium, store, purchaseStore);
|
|
99
|
+
expect(result.active).toBe(true);
|
|
100
|
+
expect(result.source).toBe('subscription');
|
|
101
|
+
expect(result.productId).toBe('pro_yearly');
|
|
102
|
+
expect(result.expiresAt).toBe(futureExpiry);
|
|
103
|
+
});
|
|
104
|
+
it('non-consumable purchase matching one of the productIds → entitled', async () => {
|
|
105
|
+
await purchaseStore.savePurchase(purchase({ userId: 'u2', productId: 'lifetime_pass' }));
|
|
106
|
+
const result = await evaluateEntitlement('u2', PREMIUM.premium, store, purchaseStore);
|
|
107
|
+
expect(result.active).toBe(true);
|
|
108
|
+
expect(result.source).toBe('purchase');
|
|
109
|
+
expect(result.productId).toBe('lifetime_pass');
|
|
110
|
+
expect(result.expiresAt).toBeUndefined();
|
|
111
|
+
});
|
|
112
|
+
it('no matching record → not entitled', async () => {
|
|
113
|
+
const result = await evaluateEntitlement('u_none', PREMIUM.premium, store, purchaseStore);
|
|
114
|
+
expect(result).toEqual({ active: false, source: null });
|
|
115
|
+
});
|
|
116
|
+
it('subscription preferred over purchase when both match', async () => {
|
|
117
|
+
await store.save(sub({ userId: 'u3', productId: 'pro_monthly' }));
|
|
118
|
+
await purchaseStore.savePurchase(purchase({ userId: 'u3', productId: 'lifetime_pass' }));
|
|
119
|
+
const result = await evaluateEntitlement('u3', PREMIUM.premium, store, purchaseStore);
|
|
120
|
+
expect(result.source).toBe('subscription');
|
|
121
|
+
expect(result.productId).toBe('pro_monthly');
|
|
122
|
+
});
|
|
123
|
+
it('expired subscription (expiresAt < now) → not entitled even if status=active', async () => {
|
|
124
|
+
await store.save(sub({ userId: 'u4', productId: 'pro_monthly', expiresAt: pastExpiry }));
|
|
125
|
+
const result = await evaluateEntitlement('u4', PREMIUM.premium, store, purchaseStore);
|
|
126
|
+
expect(result.active).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it('grace_period subscription → entitled (still has valid expiresAt)', async () => {
|
|
129
|
+
await store.save(sub({ userId: 'u5', productId: 'pro_monthly', status: 'grace_period' }));
|
|
130
|
+
const result = await evaluateEntitlement('u5', PREMIUM.premium, store, purchaseStore);
|
|
131
|
+
expect(result.active).toBe(true);
|
|
132
|
+
expect(result.source).toBe('subscription');
|
|
133
|
+
});
|
|
134
|
+
it('on_hold / paused / canceled / expired status → not entitled', async () => {
|
|
135
|
+
for (const status of ['on_hold', 'paused', 'canceled', 'expired']) {
|
|
136
|
+
const localStore = new InMemorySubscriptionStore();
|
|
137
|
+
await localStore.save(sub({ userId: 'u', productId: 'pro_monthly', status }));
|
|
138
|
+
const result = await evaluateEntitlement('u', PREMIUM.premium, localStore, purchaseStore);
|
|
139
|
+
expect(result.active).toBe(false);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
it('consumable purchase is NOT considered for entitlement', async () => {
|
|
143
|
+
await purchaseStore.savePurchase(purchase({
|
|
144
|
+
userId: 'u6',
|
|
145
|
+
productId: 'pro_monthly', // even matching productId
|
|
146
|
+
type: 'consumable',
|
|
147
|
+
}));
|
|
148
|
+
const result = await evaluateEntitlement('u6', PREMIUM.premium, store, purchaseStore);
|
|
149
|
+
expect(result.active).toBe(false);
|
|
150
|
+
});
|
|
151
|
+
it('multi-product subscriptions: returns the first matching active sub', async () => {
|
|
152
|
+
// User has two subs — one for pro_yearly (matches), one for off-premium product (no match)
|
|
153
|
+
await store.save(sub({ userId: 'u7', productId: 'unrelated_product' }));
|
|
154
|
+
await store.save(sub({ userId: 'u7', productId: 'pro_yearly' }));
|
|
155
|
+
const result = await evaluateEntitlement('u7', PREMIUM.premium, store, purchaseStore);
|
|
156
|
+
expect(result.active).toBe(true);
|
|
157
|
+
expect(result.productId).toBe('pro_yearly');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
161
|
+
// HTTP routes
|
|
162
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
163
|
+
describe('GET /onesub/entitlement', () => {
|
|
164
|
+
it('returns active=true when user has matching active sub', async () => {
|
|
165
|
+
const { store, server } = buildServer({ entitlements: PREMIUM });
|
|
166
|
+
await store.save(sub({ userId: 'u_yes', productId: 'pro_monthly' }));
|
|
167
|
+
const resp = await server.get('/onesub/entitlement?userId=u_yes&id=premium');
|
|
168
|
+
expect(resp.status).toBe(200);
|
|
169
|
+
expect(resp.body.id).toBe('premium');
|
|
170
|
+
expect(resp.body.active).toBe(true);
|
|
171
|
+
expect(resp.body.source).toBe('subscription');
|
|
172
|
+
});
|
|
173
|
+
it('returns active=false when no matching record', async () => {
|
|
174
|
+
const { server } = buildServer({ entitlements: PREMIUM });
|
|
175
|
+
const resp = await server.get('/onesub/entitlement?userId=u_none&id=premium');
|
|
176
|
+
expect(resp.status).toBe(200);
|
|
177
|
+
expect(resp.body.active).toBe(false);
|
|
178
|
+
expect(resp.body.source).toBeNull();
|
|
179
|
+
});
|
|
180
|
+
it('404 + ENTITLEMENT_NOT_FOUND for unknown id', async () => {
|
|
181
|
+
const { server } = buildServer({ entitlements: PREMIUM });
|
|
182
|
+
const resp = await server.get('/onesub/entitlement?userId=u&id=enterprise');
|
|
183
|
+
expect(resp.status).toBe(404);
|
|
184
|
+
expect(resp.body.errorCode).toBe('ENTITLEMENT_NOT_FOUND');
|
|
185
|
+
});
|
|
186
|
+
it('400 INVALID_INPUT when userId or id is missing', async () => {
|
|
187
|
+
const { server } = buildServer({ entitlements: PREMIUM });
|
|
188
|
+
const resp = await server.get('/onesub/entitlement?userId=u');
|
|
189
|
+
expect(resp.status).toBe(400);
|
|
190
|
+
expect(resp.body.errorCode).toBe('INVALID_INPUT');
|
|
191
|
+
});
|
|
192
|
+
});
|
|
193
|
+
describe('GET /onesub/entitlements (bulk)', () => {
|
|
194
|
+
it('evaluates every configured entitlement in one round-trip', async () => {
|
|
195
|
+
const config = {
|
|
196
|
+
premium: { productIds: ['pro_monthly', 'pro_yearly'] },
|
|
197
|
+
promode: { productIds: ['dev_tools_addon'] },
|
|
198
|
+
};
|
|
199
|
+
const { store, server } = buildServer({ entitlements: config });
|
|
200
|
+
await store.save(sub({ userId: 'u_bulk', productId: 'pro_yearly' }));
|
|
201
|
+
const resp = await server.get('/onesub/entitlements?userId=u_bulk');
|
|
202
|
+
expect(resp.status).toBe(200);
|
|
203
|
+
expect(resp.body.entitlements.premium.active).toBe(true);
|
|
204
|
+
expect(resp.body.entitlements.premium.productId).toBe('pro_yearly');
|
|
205
|
+
expect(resp.body.entitlements.promode.active).toBe(false);
|
|
206
|
+
});
|
|
207
|
+
it('returns empty entitlement statuses for a user with nothing', async () => {
|
|
208
|
+
const config = {
|
|
209
|
+
premium: { productIds: ['pro_monthly'] },
|
|
210
|
+
addon: { productIds: ['some_addon'] },
|
|
211
|
+
};
|
|
212
|
+
const { server } = buildServer({ entitlements: config });
|
|
213
|
+
const resp = await server.get('/onesub/entitlements?userId=u_empty');
|
|
214
|
+
expect(resp.body.entitlements.premium.active).toBe(false);
|
|
215
|
+
expect(resp.body.entitlements.addon.active).toBe(false);
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
describe('Entitlement routes are NOT mounted without config', () => {
|
|
219
|
+
it('returns 404 when config.entitlements is absent', async () => {
|
|
220
|
+
const { server } = buildServer({}); // no entitlements
|
|
221
|
+
const resp = await server.get('/onesub/entitlement?userId=u&id=premium');
|
|
222
|
+
expect(resp.status).toBe(404);
|
|
223
|
+
const respBulk = await server.get('/onesub/entitlements?userId=u');
|
|
224
|
+
expect(respBulk.status).toBe(404);
|
|
225
|
+
});
|
|
226
|
+
it('returns 404 when config.entitlements is empty {}', async () => {
|
|
227
|
+
const { server } = buildServer({ entitlements: {} });
|
|
228
|
+
const resp = await server.get('/onesub/entitlement?userId=u&id=premium');
|
|
229
|
+
expect(resp.status).toBe(404);
|
|
230
|
+
});
|
|
231
|
+
});
|
|
232
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
233
|
+
// Store regression: getAllByUserId returns multi-product subs
|
|
234
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
235
|
+
describe('InMemorySubscriptionStore — multi-sub per user (entitlement support)', () => {
|
|
236
|
+
it('getAllByUserId returns all subs across productIds, latest first', async () => {
|
|
237
|
+
const store = new InMemorySubscriptionStore();
|
|
238
|
+
await store.save(sub({ userId: 'u', productId: 'pro_monthly', originalTransactionId: 'orig_a' }));
|
|
239
|
+
await store.save(sub({ userId: 'u', productId: 'pro_yearly', originalTransactionId: 'orig_b' }));
|
|
240
|
+
await store.save(sub({ userId: 'u', productId: 'addon', originalTransactionId: 'orig_c' }));
|
|
241
|
+
const all = await store.getAllByUserId('u');
|
|
242
|
+
expect(all.map((s) => s.productId)).toEqual(['addon', 'pro_yearly', 'pro_monthly']);
|
|
243
|
+
});
|
|
244
|
+
it('getByUserId still returns the most recent (legacy contract preserved)', async () => {
|
|
245
|
+
const store = new InMemorySubscriptionStore();
|
|
246
|
+
await store.save(sub({ userId: 'u', productId: 'pro_monthly', originalTransactionId: 'orig_a' }));
|
|
247
|
+
await store.save(sub({ userId: 'u', productId: 'pro_yearly', originalTransactionId: 'orig_b' }));
|
|
248
|
+
const latest = await store.getByUserId('u');
|
|
249
|
+
expect(latest?.productId).toBe('pro_yearly');
|
|
250
|
+
});
|
|
251
|
+
it('save() with same originalTransactionId replaces prior record (no duplicates)', async () => {
|
|
252
|
+
const store = new InMemorySubscriptionStore();
|
|
253
|
+
await store.save(sub({
|
|
254
|
+
userId: 'u', productId: 'pro_monthly', originalTransactionId: 'orig_x',
|
|
255
|
+
status: 'active',
|
|
256
|
+
}));
|
|
257
|
+
await store.save(sub({
|
|
258
|
+
userId: 'u', productId: 'pro_monthly', originalTransactionId: 'orig_x',
|
|
259
|
+
status: 'canceled',
|
|
260
|
+
}));
|
|
261
|
+
const all = await store.getAllByUserId('u');
|
|
262
|
+
expect(all).toHaveLength(1);
|
|
263
|
+
expect(all[0].status).toBe(SUBSCRIPTION_STATUS.CANCELED);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
// keep the import honest
|
|
267
|
+
PURCHASE_TYPE;
|
|
268
|
+
//# sourceMappingURL=entitlements.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entitlements.test.js","sourceRoot":"","sources":["../../src/__tests__/entitlements.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metrics.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/metrics.test.ts"],"names":[],"mappings":"AAAA;;;GAGG"}
|