@onesub/server 0.18.0 → 0.18.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/README.md CHANGED
@@ -44,7 +44,7 @@ app.use(createOneSubMiddleware({
44
44
  store: new PostgresSubscriptionStore(process.env.DATABASE_URL),
45
45
  purchaseStore: new PostgresPurchaseStore(process.env.DATABASE_URL),
46
46
  // Optional:
47
- adminSecret: process.env.ADMIN_SECRET, // enables /onesub/purchase/admin/*
47
+ adminSecret: process.env.ADMIN_SECRET, // enables admin + metrics routes
48
48
  logger: require('pino')(), // any { info, warn, error } logger
49
49
  refundPolicy: 'immediate', // 'immediate' (default) | 'until_expiry'
50
50
  }));
@@ -52,6 +52,33 @@ app.use(createOneSubMiddleware({
52
52
  app.listen(4100);
53
53
  ```
54
54
 
55
+ ### Serving multiple apps
56
+
57
+ Keep the top-level `apple`/`google` fields for a single app. To validate several bundle IDs and
58
+ package names from one process, configure `apps` and send `appId` in validation requests:
59
+
60
+ ```ts
61
+ app.use(createOneSubMiddleware({
62
+ database: { url: process.env.DATABASE_URL },
63
+ apps: [
64
+ { id: 'coffee', apple: { bundleId: 'gg.pryzm.coffee' } },
65
+ {
66
+ id: 'penguinrun',
67
+ apple: { bundleId: 'gg.pryzm.penguinrun' },
68
+ google: {
69
+ packageName: 'gg.pryzm.penguinrun',
70
+ serviceAccountKey: process.env.PENGUINRUN_GOOGLE_SERVICE_ACCOUNT_KEY,
71
+ },
72
+ },
73
+ ],
74
+ defaultAppId: 'coffee',
75
+ }));
76
+ ```
77
+
78
+ Apple receipts carry their bundle ID and can be resolved automatically. Google purchase tokens do
79
+ not carry a package name, so requests for a non-default Google app must include `appId`. Unknown
80
+ explicit app IDs fail closed and are never validated with another app's credentials.
81
+
55
82
  ## Endpoints
56
83
 
57
84
  | Route | Purpose |
@@ -65,8 +92,19 @@ app.listen(4100);
65
92
  | `DELETE /onesub/purchase/admin/:userId/:productId` | Wipe a non-consumable (requires `adminSecret`) |
66
93
  | `POST /onesub/purchase/admin/grant` | Manually grant a purchase (requires `adminSecret`) |
67
94
  | `POST /onesub/purchase/admin/transfer` | Reassign a `transactionId` to a new `userId` (requires `adminSecret`) |
95
+ | `GET /onesub/admin/subscriptions` | Filter and paginate subscriptions (requires `adminSecret`) |
96
+ | `GET /onesub/admin/subscriptions/:transactionId` | Get subscription detail (requires `adminSecret`) |
97
+ | `GET /onesub/admin/customers/:userId` | Get a customer profile (requires `adminSecret`) |
98
+ | `POST /onesub/admin/sync-apple/:originalTransactionId` | Refresh a subscription from Apple (requires `adminSecret`) |
68
99
  | `GET /onesub/admin/webhook-deadletters` | List failed webhook jobs in DLQ (requires `adminSecret` + BullMQ) |
69
100
  | `POST /onesub/admin/webhook-replay/:id` | Replay a dead-letter job (requires `adminSecret` + BullMQ) |
101
+ | `GET /onesub/entitlement?userId=&id=` | Evaluate one configured entitlement |
102
+ | `GET /onesub/entitlements?userId=` | Evaluate all configured entitlements |
103
+ | `GET /onesub/metrics/active` | Current entitled counts (requires `adminSecret`) |
104
+ | `GET /onesub/metrics/started` | Subscriptions started in a range (requires `adminSecret`) |
105
+ | `GET /onesub/metrics/expired` | Subscriptions ended in a range (requires `adminSecret`) |
106
+ | `GET /onesub/metrics/purchases/started` | Non-consumables started in a range (requires `adminSecret`) |
107
+ | `POST /onesub/apple/offer-signature` | Sign an Apple promotional offer when offer credentials are configured |
70
108
  | `GET /openapi.json` | OpenAPI 3.1 spec (opt-in via `openapiHandler()`) |
71
109
 
72
110
  ## Lifecycle states (`0.4.0+`)
@@ -111,6 +149,20 @@ const sub = await fetchAppleSubscriptionStatus(originalTxId, config.apple, { san
111
149
  // sub: SubscriptionInfo | null — null on missing creds / 404 / network failure
112
150
  ```
113
151
 
152
+ ### Transaction History API client
153
+
154
+ `fetchAppleTransactionHistory()` follows Apple's revision pagination and returns verified transaction
155
+ records. It is a low-level recovery/migration primitive; the host decides how to reconcile returned
156
+ records with its stores.
157
+
158
+ ```ts
159
+ import { fetchAppleTransactionHistory } from '@onesub/server';
160
+
161
+ const transactions = await fetchAppleTransactionHistory(originalTxId, config.apple, {
162
+ sandbox: false,
163
+ });
164
+ ```
165
+
114
166
  ### CONSUMPTION_REQUEST response hook
115
167
 
116
168
  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}`:
@@ -130,6 +182,13 @@ apple: {
130
182
 
131
183
  Fire-and-forget; failures are logged but don't block the webhook 200.
132
184
 
185
+ ### Promotional offer signing
186
+
187
+ Configure the separate subscription-offer key as `apple.offerKeyId` and `apple.offerPrivateKey` to
188
+ mount `POST /onesub/apple/offer-signature`. If `adminSecret` is configured, callers must send the
189
+ same value in `X-Onesub-Offer-Secret`. The endpoint returns StoreKit's `keyId`, `nonce`, `timestamp`,
190
+ and `signature`; never expose the private key to the client.
191
+
133
192
  ## Optional Google hooks (`0.8.0+`)
134
193
 
135
194
  ```ts