@onesub/server 0.14.0 → 0.16.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.
Files changed (140) hide show
  1. package/dist/__tests__/google-subscriptions-v2.test.d.ts +1 -1
  2. package/dist/__tests__/receipt-age.test.d.ts +7 -0
  3. package/dist/__tests__/receipt-age.test.d.ts.map +1 -0
  4. package/dist/__tests__/tracing.test.d.ts +2 -0
  5. package/dist/__tests__/tracing.test.d.ts.map +1 -0
  6. package/dist/cache.d.ts +5 -0
  7. package/dist/cache.d.ts.map +1 -1
  8. package/dist/index.cjs +178 -55
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.js +178 -55
  11. package/dist/index.js.map +1 -1
  12. package/dist/providers/apple.d.ts.map +1 -1
  13. package/dist/providers/google.d.ts.map +1 -1
  14. package/dist/providers/mock.d.ts.map +1 -1
  15. package/dist/routes/admin.d.ts.map +1 -1
  16. package/dist/routes/purchase.d.ts.map +1 -1
  17. package/dist/routes/validate.d.ts.map +1 -1
  18. package/dist/routes/webhook-apple.d.ts.map +1 -1
  19. package/dist/routes/webhook-google.d.ts +1 -1
  20. package/dist/routes/webhook-google.d.ts.map +1 -1
  21. package/dist/store.d.ts.map +1 -1
  22. package/dist/stores/postgres.d.ts.map +1 -1
  23. package/dist/stores/redis.d.ts +1 -0
  24. package/dist/stores/redis.d.ts.map +1 -1
  25. package/dist/tracing.d.ts.map +1 -1
  26. package/dist/webhook-events.d.ts +10 -0
  27. package/dist/webhook-events.d.ts.map +1 -1
  28. package/package.json +5 -3
  29. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  30. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  31. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  32. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  33. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  34. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  35. package/dist/__tests__/apple-root-ca.test.js +0 -41
  36. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  37. package/dist/__tests__/apple-status-api.test.js +0 -345
  38. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  39. package/dist/__tests__/cache.test.js +0 -34
  40. package/dist/__tests__/cache.test.js.map +0 -1
  41. package/dist/__tests__/entitlements.test.js +0 -268
  42. package/dist/__tests__/entitlements.test.js.map +0 -1
  43. package/dist/__tests__/error-codes.test.js +0 -173
  44. package/dist/__tests__/error-codes.test.js.map +0 -1
  45. package/dist/__tests__/fetch-hardening.test.js +0 -192
  46. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  47. package/dist/__tests__/google-paused.test.js +0 -126
  48. package/dist/__tests__/google-paused.test.js.map +0 -1
  49. package/dist/__tests__/google-price-change.test.js +0 -186
  50. package/dist/__tests__/google-price-change.test.js.map +0 -1
  51. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  52. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  53. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  54. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  55. package/dist/__tests__/lifecycle-states.test.js +0 -423
  56. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  57. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  58. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  59. package/dist/__tests__/metrics.test.js +0 -340
  60. package/dist/__tests__/metrics.test.js.map +0 -1
  61. package/dist/__tests__/mock-provider.test.js +0 -188
  62. package/dist/__tests__/mock-provider.test.js.map +0 -1
  63. package/dist/__tests__/openapi.test.js +0 -32
  64. package/dist/__tests__/openapi.test.js.map +0 -1
  65. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  66. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  67. package/dist/__tests__/providers.test.js +0 -321
  68. package/dist/__tests__/providers.test.js.map +0 -1
  69. package/dist/__tests__/redis-store.test.js +0 -166
  70. package/dist/__tests__/redis-store.test.js.map +0 -1
  71. package/dist/__tests__/refund-policy.test.js +0 -317
  72. package/dist/__tests__/refund-policy.test.js.map +0 -1
  73. package/dist/__tests__/schema.test.js +0 -46
  74. package/dist/__tests__/schema.test.js.map +0 -1
  75. package/dist/__tests__/store.test.js +0 -172
  76. package/dist/__tests__/store.test.js.map +0 -1
  77. package/dist/__tests__/test-utils.js +0 -30
  78. package/dist/__tests__/test-utils.js.map +0 -1
  79. package/dist/__tests__/webhook-apple.test.js +0 -247
  80. package/dist/__tests__/webhook-apple.test.js.map +0 -1
  81. package/dist/__tests__/webhook-events.test.js +0 -36
  82. package/dist/__tests__/webhook-events.test.js.map +0 -1
  83. package/dist/__tests__/webhook-google.test.js +0 -255
  84. package/dist/__tests__/webhook-google.test.js.map +0 -1
  85. package/dist/__tests__/webhook-queue.test.js +0 -26
  86. package/dist/__tests__/webhook-queue.test.js.map +0 -1
  87. package/dist/__tests__/webhook-refund.test.js +0 -313
  88. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  89. package/dist/cache.js +0 -39
  90. package/dist/cache.js.map +0 -1
  91. package/dist/errors.js +0 -19
  92. package/dist/errors.js.map +0 -1
  93. package/dist/http.js +0 -36
  94. package/dist/http.js.map +0 -1
  95. package/dist/logger.js +0 -20
  96. package/dist/logger.js.map +0 -1
  97. package/dist/openapi.js +0 -196
  98. package/dist/openapi.js.map +0 -1
  99. package/dist/providers/apple-root-ca.js +0 -38
  100. package/dist/providers/apple-root-ca.js.map +0 -1
  101. package/dist/providers/apple.js +0 -630
  102. package/dist/providers/apple.js.map +0 -1
  103. package/dist/providers/google.js +0 -564
  104. package/dist/providers/google.js.map +0 -1
  105. package/dist/providers/mock.js +0 -93
  106. package/dist/providers/mock.js.map +0 -1
  107. package/dist/routes/admin.js +0 -327
  108. package/dist/routes/admin.js.map +0 -1
  109. package/dist/routes/apple-offer.js +0 -72
  110. package/dist/routes/apple-offer.js.map +0 -1
  111. package/dist/routes/entitlements.js +0 -130
  112. package/dist/routes/entitlements.js.map +0 -1
  113. package/dist/routes/metrics.js +0 -281
  114. package/dist/routes/metrics.js.map +0 -1
  115. package/dist/routes/purchase.js +0 -204
  116. package/dist/routes/purchase.js.map +0 -1
  117. package/dist/routes/status.js +0 -55
  118. package/dist/routes/status.js.map +0 -1
  119. package/dist/routes/validate.js +0 -70
  120. package/dist/routes/validate.js.map +0 -1
  121. package/dist/routes/webhook-apple.js +0 -145
  122. package/dist/routes/webhook-apple.js.map +0 -1
  123. package/dist/routes/webhook-google.js +0 -204
  124. package/dist/routes/webhook-google.js.map +0 -1
  125. package/dist/routes/webhook.js +0 -19
  126. package/dist/routes/webhook.js.map +0 -1
  127. package/dist/store.js +0 -145
  128. package/dist/store.js.map +0 -1
  129. package/dist/stores/postgres.js +0 -329
  130. package/dist/stores/postgres.js.map +0 -1
  131. package/dist/stores/redis.js +0 -297
  132. package/dist/stores/redis.js.map +0 -1
  133. package/dist/stores/schema.js +0 -67
  134. package/dist/stores/schema.js.map +0 -1
  135. package/dist/tracing.js +0 -74
  136. package/dist/tracing.js.map +0 -1
  137. package/dist/webhook-events.js +0 -58
  138. package/dist/webhook-events.js.map +0 -1
  139. package/dist/webhook-queue.js +0 -108
  140. package/dist/webhook-queue.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"openapi.js","sourceRoot":"","sources":["../src/openapi.ts"],"names":[],"mappings":"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"}
@@ -1,38 +0,0 @@
1
- /**
2
- * Apple Root CA certificates used to verify StoreKit 2 signed transactions.
3
- *
4
- * Apple signs JWS transactions with an ECDSA key; the signing certificate
5
- * chain is embedded in the JWS `x5c` header. To trust the leaf key we must
6
- * validate the chain all the way up to one of Apple's published root CAs.
7
- *
8
- * Source: https://www.apple.com/certificateauthority/
9
- * These certificates are public and static. Update this list if Apple
10
- * publishes a new root — existing JWS signed with older roots continue to
11
- * validate against their original root.
12
- */
13
- /**
14
- * Apple Root CA - G3 (current root for StoreKit 2 / App Store Server API).
15
- * SHA-256 fingerprint: 63343ABF B89A6A03 EB994052 C5ADBBF8 A87E2C46 81F6F0FC C8A4DA48 95F0FBBF
16
- * Valid: 2014-04-30 — 2039-04-30
17
- */
18
- export const APPLE_ROOT_CA_G3_PEM = `-----BEGIN CERTIFICATE-----
19
- MIICQzCCAcmgAwIBAgIILcX8iNLFS5UwCgYIKoZIzj0EAwMwZzEbMBkGA1UEAwwS
20
- QXBwbGUgUm9vdCBDQSAtIEczMSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
21
- IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UEBhMCVVMwHhcN
22
- MTQwNDMwMTgxOTA2WhcNMzkwNDMwMTgxOTA2WjBnMRswGQYDVQQDDBJBcHBsZSBS
23
- b290IENBIC0gRzMxJjAkBgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9y
24
- aXR5MRMwEQYDVQQKDApBcHBsZSBJbmMuMQswCQYDVQQGEwJVUzB2MBAGByqGSM49
25
- AgEGBSuBBAAiA2IABJjpLz1AcqTtkyJygRMc3RCV8cWjTnHcFBbZDuWmBSp3ZHtf
26
- TjjTuxxEtX/1H7YyYl3J6YRbTzBPEVoA/VhYDKX1DyxNB0cTddqXl5dvMVztK517
27
- IDvYuVTZXpmkOlEKMaNCMEAwHQYDVR0OBBYEFLuw3qFYM4iapIqZ3r6966/ayySr
28
- MA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEGMAoGCCqGSM49BAMDA2gA
29
- MGUCMQCD6cHEFl4aXTQY2e3v9GwOAEZLuN+yRhHFD/3meoyhpmvOwgPUnPWTxnS4
30
- at+qIxUCMG1mihDK1A3UT82NQz60imOlM27jbdoXt2QfyFMm+YhidDkLF1vLUagM
31
- 6BgD56KyKA==
32
- -----END CERTIFICATE-----`;
33
- /**
34
- * All Apple root CAs accepted for StoreKit 2 JWS validation.
35
- * Currently only G3; add G4 here when Apple publishes it.
36
- */
37
- export const APPLE_ROOT_CA_PEMS = [APPLE_ROOT_CA_G3_PEM];
38
- //# sourceMappingURL=apple-root-ca.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"apple-root-ca.js","sourceRoot":"","sources":["../../src/providers/apple-root-ca.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG;;;;;;;;;;;;;;0BAcV,CAAC;AAE3B;;;GAGG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,oBAAoB,CAAC,CAAC"}
@@ -1,630 +0,0 @@
1
- import { decodeJwt, decodeProtectedHeader, importPKCS8, importX509, jwtVerify, SignJWT } from 'jose';
2
- import { X509Certificate, createSign, randomUUID } from 'node:crypto';
3
- import { SUBSCRIPTION_STATUS } from '@onesub/shared';
4
- import { APPLE_ROOT_CA_PEMS } from './apple-root-ca.js';
5
- import { log } from '../logger.js';
6
- import { fetchWithTimeout } from '../http.js';
7
- import { getDefaultCache } from '../cache.js';
8
- import { mockValidateAppleSubscription, mockValidateAppleProduct, } from './mock.js';
9
- /** Maximum age for consumable receipts (72 hours). Older receipts may indicate replay attacks. */
10
- const MAX_CONSUMABLE_RECEIPT_AGE_MS = 72 * 60 * 60 * 1000;
11
- function derBase64ToPem(der) {
12
- return ('-----BEGIN CERTIFICATE-----\n' +
13
- (der.match(/.{1,64}/g) ?? []).join('\n') +
14
- '\n-----END CERTIFICATE-----');
15
- }
16
- const APPLE_ROOT_CERTS = APPLE_ROOT_CA_PEMS.map((pem) => new X509Certificate(pem));
17
- /**
18
- * Validate that the x5c chain from the JWS terminates at one of Apple's
19
- * bundled root CAs. Each cert in the chain must be signed by the next (or
20
- * the bundled root), and all certs must currently be within their validity
21
- * window.
22
- *
23
- * Returns the leaf certificate PEM on success. Throws on any failure.
24
- */
25
- function verifyAppleCertChain(x5c) {
26
- if (x5c.length === 0) {
27
- throw new Error('[onesub/apple] empty x5c');
28
- }
29
- const chain = x5c.map((der) => new X509Certificate(derBase64ToPem(der)));
30
- const now = new Date();
31
- // Validity window + leaf→intermediate→... signature chain
32
- for (let i = 0; i < chain.length; i++) {
33
- const cert = chain[i];
34
- if (new Date(cert.validFrom) > now || new Date(cert.validTo) < now) {
35
- throw new Error(`[onesub/apple] cert[${i}] outside validity window`);
36
- }
37
- if (i + 1 < chain.length) {
38
- if (!cert.checkIssued(chain[i + 1]) || !cert.verify(chain[i + 1].publicKey)) {
39
- throw new Error(`[onesub/apple] cert[${i}] not signed by cert[${i + 1}]`);
40
- }
41
- }
42
- }
43
- // Final cert in chain (typically intermediate) must be signed by an Apple root.
44
- // Accept either (a) explicit signature match or (b) the cert itself being one
45
- // of our bundled Apple roots (happens when Apple embeds the full chain).
46
- const top = chain[chain.length - 1];
47
- const topDer = top.raw.toString('base64');
48
- const trustsRoot = APPLE_ROOT_CERTS.some((root) => {
49
- if (root.raw.toString('base64') === topDer)
50
- return true;
51
- if (!top.checkIssued(root))
52
- return false;
53
- try {
54
- return top.verify(root.publicKey);
55
- }
56
- catch {
57
- return false;
58
- }
59
- });
60
- if (!trustsRoot) {
61
- throw new Error('[onesub/apple] cert chain does not terminate at a trusted Apple root');
62
- }
63
- return chain[0].toString();
64
- }
65
- /**
66
- * Decode and verify a StoreKit 2 signed transaction JWS.
67
- *
68
- * 1. Extract the x5c certificate chain from the JWS header.
69
- * 2. Validate the chain terminates at a bundled Apple Root CA (G3).
70
- * 3. Use the leaf certificate's public key to verify the JWS signature.
71
- *
72
- * skipVerification=true skips all of the above and just decodes the payload
73
- * (dev/test only).
74
- */
75
- export async function decodeJws(jws, skipVerification = false) {
76
- if (skipVerification) {
77
- if (process.env['NODE_ENV'] === 'production') {
78
- log.warn('[onesub/apple] WARNING: skipJwsVerification is enabled in production. ' +
79
- 'JWS signatures are NOT being verified. This is a security risk.');
80
- }
81
- return decodeJwt(jws);
82
- }
83
- const header = decodeProtectedHeader(jws);
84
- const x5c = header.x5c;
85
- if (!x5c || x5c.length === 0) {
86
- throw new Error('[onesub/apple] JWS header missing x5c certificate chain');
87
- }
88
- const leafPem = verifyAppleCertChain(x5c);
89
- const alg = header.alg ?? 'ES256';
90
- const key = await importX509(leafPem, alg);
91
- const { payload } = await jwtVerify(jws, key);
92
- return payload;
93
- }
94
- /**
95
- * Derive a SubscriptionStatus from the transaction + renewal payloads.
96
- */
97
- function deriveStatus(tx, renewal) {
98
- if (tx.revocationDate)
99
- return SUBSCRIPTION_STATUS.CANCELED;
100
- const now = Date.now();
101
- const expires = tx.expiresDate ?? 0;
102
- if (expires > now)
103
- return SUBSCRIPTION_STATUS.ACTIVE;
104
- // Expired — check if it was voluntarily canceled
105
- if (renewal?.autoRenewStatus === 0)
106
- return SUBSCRIPTION_STATUS.CANCELED;
107
- return SUBSCRIPTION_STATUS.EXPIRED;
108
- }
109
- /**
110
- * Validate an Apple receipt string.
111
- *
112
- * StoreKit 2 receipts are JWS-encoded signed transactions (the `signedTransaction`
113
- * field from the client). For legacy base64 receipts from StoreKit 1, you would
114
- * POST to verifyReceipt — that path is left as a stub here because Apple is
115
- * deprecating it.
116
- */
117
- export async function validateAppleReceipt(receipt, config) {
118
- if (config.mockMode)
119
- return mockValidateAppleSubscription(receipt);
120
- let tx;
121
- try {
122
- tx = await decodeJws(receipt, config.skipJwsVerification);
123
- }
124
- catch (err) {
125
- const preview = receipt.slice(0, 60);
126
- const parts = receipt.split('.').length;
127
- log.warn('[onesub/apple] Failed to decode receipt as JWS:', err?.message ?? err, `| preview: "${preview}..." (len=${receipt.length}, parts=${parts})`);
128
- return null;
129
- }
130
- if (!tx.originalTransactionId || !tx.productId || !tx.expiresDate) {
131
- return null;
132
- }
133
- // Validate bundle ID — missing or mismatched both rejected
134
- if (!tx.bundleId || tx.bundleId !== config.bundleId) {
135
- log.warn('[onesub/apple] Bundle ID mismatch:', tx.bundleId, '!==', config.bundleId);
136
- return null;
137
- }
138
- // Reject Sandbox receipts in production unless explicitly allowed.
139
- // Set ONESUB_ALLOW_SANDBOX=true to permit TestFlight sandbox receipts on
140
- // production servers (useful during QA before App Store release).
141
- if (process.env['NODE_ENV'] === 'production' &&
142
- tx.environment !== 'Production' &&
143
- process.env['ONESUB_ALLOW_SANDBOX'] !== 'true') {
144
- log.warn('[onesub/apple] Sandbox receipt rejected in production:', tx.environment);
145
- return null;
146
- }
147
- const status = deriveStatus(tx, null);
148
- const purchasedAt = tx.originalPurchaseDate ?? tx.purchaseDate ?? Date.now();
149
- return {
150
- userId: '', // caller fills this in from the request body
151
- productId: tx.productId,
152
- platform: 'apple',
153
- status,
154
- expiresAt: new Date(tx.expiresDate).toISOString(),
155
- originalTransactionId: tx.originalTransactionId,
156
- purchasedAt: new Date(purchasedAt).toISOString(),
157
- willRenew: status === SUBSCRIPTION_STATUS.ACTIVE, // refined by renewal info in webhook
158
- };
159
- }
160
- /**
161
- * Validate an Apple StoreKit 2 JWS signedTransaction for a consumable or
162
- * non-consumable product.
163
- *
164
- * Differences from validateAppleReceipt (subscriptions):
165
- * - Checks tx.type is 'Consumable' or 'Non-Consumable' (not a subscription)
166
- * - Uses tx.transactionId (per-purchase unique) instead of originalTransactionId
167
- * - Enforces a 72-hour receipt age limit to block replay attacks
168
- * - Does NOT check expiresDate (one-time purchases don't expire)
169
- */
170
- export async function validateAppleConsumableReceipt(signedTransaction, config, expectedProductId) {
171
- if (config.mockMode)
172
- return mockValidateAppleProduct(signedTransaction, expectedProductId);
173
- let tx;
174
- try {
175
- tx = await decodeJws(signedTransaction, config.skipJwsVerification);
176
- }
177
- catch (err) {
178
- const preview = signedTransaction.slice(0, 60);
179
- const parts = signedTransaction.split('.').length;
180
- const looksLikeJws = parts === 3;
181
- log.warn('[onesub/apple] Failed to decode consumable JWS:', err?.message ?? err, `| receipt preview: "${preview}..." (len=${signedTransaction.length}, parts=${parts}, looksLikeJws=${looksLikeJws})`);
182
- return null;
183
- }
184
- // bundleId must be present and match
185
- if (!tx.bundleId || tx.bundleId !== config.bundleId) {
186
- log.warn('[onesub/apple] Bundle ID mismatch:', tx.bundleId, '!==', config.bundleId);
187
- return null;
188
- }
189
- // Must be a one-time purchase type (not a subscription)
190
- if (tx.type !== 'Consumable' && tx.type !== 'Non-Consumable') {
191
- log.warn('[onesub/apple] Invalid purchase type for product validation:', tx.type);
192
- return null;
193
- }
194
- // Reject Sandbox receipts in production unless explicitly allowed.
195
- // Set ONESUB_ALLOW_SANDBOX=true to permit TestFlight sandbox receipts on
196
- // production servers (useful during QA before App Store release).
197
- if (process.env['NODE_ENV'] === 'production' &&
198
- tx.environment !== 'Production' &&
199
- process.env['ONESUB_ALLOW_SANDBOX'] !== 'true') {
200
- log.warn('[onesub/apple] Sandbox receipt rejected in production:', tx.environment);
201
- return null;
202
- }
203
- if (!tx.productId) {
204
- log.warn('[onesub/apple] No productId in transaction');
205
- return null;
206
- }
207
- if (expectedProductId && tx.productId !== expectedProductId) {
208
- log.warn('[onesub/apple] Product ID mismatch:', tx.productId, '!==', expectedProductId);
209
- return null;
210
- }
211
- // Reject refunded purchases
212
- if (tx.revocationDate) {
213
- log.warn('[onesub/apple] Purchase was revoked/refunded');
214
- return null;
215
- }
216
- // Reject receipts older than 72 hours (replay attack prevention)
217
- if (tx.purchaseDate && Date.now() - tx.purchaseDate > MAX_CONSUMABLE_RECEIPT_AGE_MS) {
218
- log.warn('[onesub/apple] Consumable receipt too old (>72h)');
219
- return null;
220
- }
221
- // For consumables, transactionId is per-purchase unique.
222
- // originalTransactionId is shared across re-purchases, so it must not be used
223
- // as the deduplication key for consumables.
224
- const transactionId = tx.transactionId ?? tx.originalTransactionId;
225
- if (!transactionId) {
226
- log.warn('[onesub/apple] No transactionId in consumable transaction');
227
- return null;
228
- }
229
- return {
230
- transactionId,
231
- productId: tx.productId,
232
- purchasedAt: tx.purchaseDate
233
- ? new Date(tx.purchaseDate).toISOString()
234
- : new Date().toISOString(),
235
- };
236
- }
237
- /**
238
- * Decode an Apple Server Notification V2 payload.
239
- * Returns the derived subscription update to be merged into the store.
240
- */
241
- export async function decodeAppleNotification(payload, skipJwsVerification = false) {
242
- const { signedTransactionInfo, signedRenewalInfo } = payload.data;
243
- let tx;
244
- let renewal = null;
245
- try {
246
- tx = await decodeJws(signedTransactionInfo, skipJwsVerification);
247
- }
248
- catch {
249
- return null;
250
- }
251
- try {
252
- renewal = await decodeJws(signedRenewalInfo, skipJwsVerification);
253
- }
254
- catch {
255
- // renewal info is optional for some notification types
256
- }
257
- // expiresDate is only required for subscription notifications.
258
- // Consumable / non-consumable refund notifications (REFUND for IAP) have no expiry.
259
- if (!tx.originalTransactionId)
260
- return null;
261
- const status = deriveStatus(tx, renewal);
262
- const willRenew = renewal?.autoRenewStatus === 1;
263
- // environment is on the notification data envelope but Apple also includes
264
- // it inside the signed transaction payload. Prefer the JWS-protected one.
265
- const txEnv = tx.environment;
266
- const dataEnv = payload.data.environment;
267
- const environment = txEnv === 'Sandbox' || dataEnv === 'Sandbox' ? 'Sandbox' : 'Production';
268
- return {
269
- originalTransactionId: tx.originalTransactionId,
270
- transactionId: tx.transactionId ?? null,
271
- type: tx.type ?? null,
272
- productId: tx.productId ?? null,
273
- bundleId: tx.bundleId ?? payload.data.bundleId ?? null,
274
- environment,
275
- status,
276
- willRenew,
277
- expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
278
- appAccountToken: tx.appAccountToken ?? null,
279
- inAppOwnershipType: tx.inAppOwnershipType ?? null,
280
- };
281
- }
282
- // ───────────────────────────────────────────────────────────────────────────
283
- // App Store Server API — outbound calls (consumption response, etc.)
284
- // ───────────────────────────────────────────────────────────────────────────
285
- /**
286
- * JWT cache for the App Store Server API. Apple caps token TTL at 20 minutes;
287
- * we mint with that TTL and re-use until 60 seconds before expiry (so
288
- * long-running webhook bursts don't pay the ECDSA-sign cost on every request).
289
- *
290
- * Cache backend: `getDefaultCache()` (in-memory by default, swappable with
291
- * Redis / Memcached via `setDefaultCache()`). When the cache is shared across
292
- * cluster nodes only one node mints per TTL window.
293
- *
294
- * Keyed by `${issuerId}|${keyId}` since rotating either invalidates the JWT.
295
- * Concurrent mints inside a single process are deduped via `appleJwtMintPromises`.
296
- */
297
- const appleJwtMintPromises = new Map();
298
- const APPLE_JWT_TTL_MS = 20 * 60 * 1000;
299
- const APPLE_JWT_REFRESH_BEFORE_MS = 60 * 1000;
300
- /**
301
- * Mint a JWT for the App Store Server API (audience `appstoreconnect-v1`).
302
- * Requires keyId, issuerId, and privateKey (PKCS8 ES256 from App Store Connect).
303
- * Token TTL is capped at 20 minutes per Apple's spec.
304
- */
305
- async function makeAppleApiJwt(config) {
306
- const { issuerId, keyId, privateKey, bundleId } = config;
307
- if (!issuerId || !keyId || !privateKey) {
308
- throw new Error('[onesub/apple] App Store Server API requires issuerId, keyId, and privateKey');
309
- }
310
- const cache = getDefaultCache();
311
- const cacheKey = `apple:jwt:${issuerId}|${keyId}`;
312
- const now = Date.now();
313
- const cached = await cache.get(cacheKey);
314
- if (cached && cached.expiresAt - now > APPLE_JWT_REFRESH_BEFORE_MS) {
315
- return cached.token;
316
- }
317
- const inflight = appleJwtMintPromises.get(cacheKey);
318
- if (inflight)
319
- return inflight;
320
- const promise = (async () => {
321
- const key = await importPKCS8(privateKey, 'ES256');
322
- const issuedAt = Math.floor(Date.now() / 1000);
323
- const token = await new SignJWT({ bid: bundleId })
324
- .setProtectedHeader({ alg: 'ES256', kid: keyId, typ: 'JWT' })
325
- .setIssuer(issuerId)
326
- .setAudience('appstoreconnect-v1')
327
- .setIssuedAt(issuedAt)
328
- .setExpirationTime(issuedAt + Math.floor(APPLE_JWT_TTL_MS / 1000))
329
- .sign(key);
330
- const expiresAt = Date.now() + APPLE_JWT_TTL_MS;
331
- // TTL slightly under the 20m spec so cache eviction matches token expiry.
332
- await cache.set(cacheKey, { token, expiresAt }, 19 * 60);
333
- return token;
334
- })().finally(() => {
335
- appleJwtMintPromises.delete(cacheKey);
336
- });
337
- appleJwtMintPromises.set(cacheKey, promise);
338
- return promise;
339
- }
340
- /** Test-only: clear the Apple JWT cache. Not exported. */
341
- function clearAppleJwtCacheForTests() {
342
- const cache = getDefaultCache();
343
- // Best-effort wipe — tests use the in-memory adapter, which exposes clear().
344
- if ('clear' in cache && typeof cache.clear === 'function') {
345
- cache.clear();
346
- }
347
- appleJwtMintPromises.clear();
348
- }
349
- // Expose for the test suite without polluting the public API surface.
350
- export const __testing = { clearAppleJwtCacheForTests };
351
- /**
352
- * PUT a ConsumptionRequest to Apple's
353
- * /inApps/v1/transactions/consumption/{transactionId} endpoint.
354
- *
355
- * Apple sends CONSUMPTION_REQUEST notifications when a customer asks for a
356
- * refund on a consumable. Without a response Apple has no usage signal and
357
- * tends to grant the refund. This call provides the data Apple uses to weigh
358
- * the refund decision.
359
- *
360
- * Fire-and-forget: failures are logged, never thrown — the webhook should
361
- * still 200 to Apple even if our outbound call fails.
362
- */
363
- export async function sendAppleConsumptionResponse(transactionId, body, config, options) {
364
- if (config.mockMode)
365
- return;
366
- let jwt;
367
- try {
368
- jwt = await makeAppleApiJwt(config);
369
- }
370
- catch (err) {
371
- log.warn('[onesub/apple] Cannot send consumption response — JWT mint failed:', err);
372
- return;
373
- }
374
- const host = options?.sandbox
375
- ? 'api.storekit-sandbox.itunes.apple.com'
376
- : 'api.storekit.itunes.apple.com';
377
- const url = `https://${host}/inApps/v1/transactions/consumption/${encodeURIComponent(transactionId)}`;
378
- try {
379
- const resp = await fetchWithTimeout(url, {
380
- method: 'PUT',
381
- headers: {
382
- Authorization: `Bearer ${jwt}`,
383
- 'Content-Type': 'application/json',
384
- },
385
- body: JSON.stringify(body),
386
- });
387
- if (!resp.ok) {
388
- const text = await resp.text();
389
- log.warn(`[onesub/apple] Consumption response API error ${resp.status}: ${text}`);
390
- }
391
- }
392
- catch (err) {
393
- log.warn('[onesub/apple] Consumption response network error:', err);
394
- }
395
- }
396
- /**
397
- * Apple's status code in the GET /inApps/v1/subscriptions/{originalTxId} response.
398
- * https://developer.apple.com/documentation/appstoreserverapi/status
399
- */
400
- const APPLE_SUBSCRIPTION_STATUS_CODE = {
401
- ACTIVE: 1,
402
- EXPIRED: 2,
403
- BILLING_RETRY: 3,
404
- GRACE_PERIOD: 4,
405
- REVOKED: 5,
406
- };
407
- function mapAppleStatusCode(code) {
408
- switch (code) {
409
- case APPLE_SUBSCRIPTION_STATUS_CODE.ACTIVE:
410
- return SUBSCRIPTION_STATUS.ACTIVE;
411
- case APPLE_SUBSCRIPTION_STATUS_CODE.GRACE_PERIOD:
412
- return SUBSCRIPTION_STATUS.GRACE_PERIOD;
413
- case APPLE_SUBSCRIPTION_STATUS_CODE.BILLING_RETRY:
414
- return SUBSCRIPTION_STATUS.ON_HOLD;
415
- case APPLE_SUBSCRIPTION_STATUS_CODE.REVOKED:
416
- return SUBSCRIPTION_STATUS.CANCELED;
417
- case APPLE_SUBSCRIPTION_STATUS_CODE.EXPIRED:
418
- default:
419
- return SUBSCRIPTION_STATUS.EXPIRED;
420
- }
421
- }
422
- /**
423
- * Fetch the current state of a subscription directly from Apple's App Store
424
- * Server API — the canonical source of truth when webhooks have been missed,
425
- * delivered out of order, or the local store has no record at all.
426
- *
427
- * GET /inApps/v1/subscriptions/{originalTransactionId}
428
- * https://developer.apple.com/documentation/appstoreserverapi/get_all_subscription_statuses
429
- *
430
- * Returns null on:
431
- * - Missing API credentials (issuerId/keyId/privateKey)
432
- * - Network or auth failure
433
- * - Empty response (transaction not found)
434
- *
435
- * Hosts can call this directly (e.g. from a reconciliation cron) or it runs
436
- * automatically as the unknown-transaction fallback inside the Apple webhook.
437
- */
438
- export async function fetchAppleSubscriptionStatus(originalTransactionId, config, options) {
439
- if (config.mockMode)
440
- return null;
441
- let jwt;
442
- try {
443
- jwt = await makeAppleApiJwt(config);
444
- }
445
- catch (err) {
446
- log.warn('[onesub/apple] Cannot fetch subscription status — JWT mint failed:', err);
447
- return null;
448
- }
449
- const host = options?.sandbox
450
- ? 'api.storekit-sandbox.itunes.apple.com'
451
- : 'api.storekit.itunes.apple.com';
452
- const url = `https://${host}/inApps/v1/subscriptions/${encodeURIComponent(originalTransactionId)}`;
453
- let body;
454
- try {
455
- const resp = await fetchWithTimeout(url, {
456
- headers: { Authorization: `Bearer ${jwt}` },
457
- });
458
- if (!resp.ok) {
459
- const text = await resp.text();
460
- log.warn(`[onesub/apple] Status API error ${resp.status}: ${text}`);
461
- return null;
462
- }
463
- body = (await resp.json());
464
- }
465
- catch (err) {
466
- log.warn('[onesub/apple] Status API network error:', err);
467
- return null;
468
- }
469
- // Find the lastTransactions entry matching the requested originalTransactionId.
470
- // Apple groups by subscriptionGroupIdentifier; one originalTxId is in exactly one group.
471
- const entry = body.data
472
- ?.flatMap((g) => g.lastTransactions ?? [])
473
- .find((t) => t.originalTransactionId === originalTransactionId);
474
- if (!entry || entry.status == null || !entry.signedTransactionInfo) {
475
- log.warn('[onesub/apple] Status API returned no matching transaction for', originalTransactionId);
476
- return null;
477
- }
478
- let tx;
479
- try {
480
- tx = await decodeJws(entry.signedTransactionInfo, config.skipJwsVerification);
481
- }
482
- catch (err) {
483
- log.warn('[onesub/apple] Failed to decode signedTransactionInfo from Status API:', err);
484
- return null;
485
- }
486
- let renewal = null;
487
- if (entry.signedRenewalInfo) {
488
- try {
489
- renewal = await decodeJws(entry.signedRenewalInfo, config.skipJwsVerification);
490
- }
491
- catch {
492
- // renewal info is optional
493
- }
494
- }
495
- if (!tx.productId || !tx.expiresDate) {
496
- log.warn('[onesub/apple] Status API transaction missing productId or expiresDate');
497
- return null;
498
- }
499
- const status = mapAppleStatusCode(entry.status);
500
- const purchasedAt = tx.originalPurchaseDate ?? tx.purchaseDate ?? Date.now();
501
- return {
502
- userId: '', // caller fills this in
503
- productId: tx.productId,
504
- platform: 'apple',
505
- status,
506
- expiresAt: new Date(tx.expiresDate).toISOString(),
507
- originalTransactionId,
508
- purchasedAt: new Date(purchasedAt).toISOString(),
509
- willRenew: renewal?.autoRenewStatus === 1,
510
- };
511
- }
512
- /**
513
- * Fetch all transactions for an originalTransactionId from the App Store
514
- * Transaction History API (GET /inApps/v2/history/{originalTransactionId}).
515
- *
516
- * Paginates automatically until `hasMore` is false. Returns all decoded
517
- * transaction records, oldest-first (Apple's default sort).
518
- *
519
- * Returns null when API credentials are missing or the call fails.
520
- */
521
- export async function fetchAppleTransactionHistory(originalTransactionId, config, options) {
522
- if (config.mockMode)
523
- return null;
524
- let jwt;
525
- try {
526
- jwt = await makeAppleApiJwt(config);
527
- }
528
- catch (err) {
529
- log.warn('[onesub/apple] Cannot fetch transaction history — JWT mint failed:', err);
530
- return null;
531
- }
532
- const host = options?.sandbox
533
- ? 'api.storekit-sandbox.itunes.apple.com'
534
- : 'api.storekit.itunes.apple.com';
535
- const results = [];
536
- let revision;
537
- for (;;) {
538
- const url = new URL(`https://${host}/inApps/v2/history/${encodeURIComponent(originalTransactionId)}`);
539
- if (revision)
540
- url.searchParams.set('revision', revision);
541
- let page;
542
- try {
543
- const resp = await fetchWithTimeout(url.toString(), {
544
- headers: { Authorization: `Bearer ${jwt}` },
545
- });
546
- if (!resp.ok) {
547
- const text = await resp.text();
548
- log.warn(`[onesub/apple] Transaction History API error ${resp.status}: ${text}`);
549
- return null;
550
- }
551
- page = (await resp.json());
552
- }
553
- catch (err) {
554
- log.warn('[onesub/apple] Transaction History API network error:', err);
555
- return null;
556
- }
557
- for (const signed of page.signedTransactions ?? []) {
558
- try {
559
- const tx = await decodeJws(signed, config.skipJwsVerification);
560
- if (!tx.originalTransactionId || !tx.transactionId || !tx.productId)
561
- continue;
562
- results.push({
563
- originalTransactionId: tx.originalTransactionId,
564
- transactionId: tx.transactionId,
565
- productId: tx.productId,
566
- type: tx.type ?? 'Unknown',
567
- purchasedAt: tx.purchaseDate ? new Date(tx.purchaseDate).toISOString() : new Date().toISOString(),
568
- expiresAt: tx.expiresDate ? new Date(tx.expiresDate).toISOString() : null,
569
- appAccountToken: tx.appAccountToken ?? null,
570
- inAppOwnershipType: tx.inAppOwnershipType ?? null,
571
- revocationDate: tx.revocationDate ? new Date(tx.revocationDate).toISOString() : null,
572
- });
573
- }
574
- catch {
575
- // skip malformed JWS entries
576
- }
577
- }
578
- if (!page.hasMore)
579
- break;
580
- revision = page.revision;
581
- }
582
- return results;
583
- }
584
- /**
585
- * Sign an Apple Promotional Offer payload with ES256 (ECDSA P-256).
586
- *
587
- * The message to sign is:
588
- * appBundleId + '⁣' + keyIdentifier + '⁣' + productIdentifier + '⁣'
589
- * + offerIdentifier + '⁣' + applicationUsername + '⁣'
590
- * + nonce (lowercase) + '⁣' + timestamp (ms, string)
591
- *
592
- * Requires `config.offerKeyId` and `config.offerPrivateKey` (PEM ES256 key from
593
- * App Store Connect → Subscriptions → Promotional Offers → Keys).
594
- *
595
- * Throws if the required keys are not configured.
596
- */
597
- export async function signApplePromotionalOffer(input, config) {
598
- const { offerKeyId, offerPrivateKey } = config;
599
- if (!offerKeyId || !offerPrivateKey) {
600
- throw new Error('[onesub/apple] Promotional Offer signing requires config.apple.offerKeyId and config.apple.offerPrivateKey');
601
- }
602
- const nonce = randomUUID().toLowerCase();
603
- const timestamp = Date.now();
604
- const separator = '⁣';
605
- const message = [
606
- input.bundleId,
607
- offerKeyId,
608
- input.productId,
609
- input.offerId,
610
- input.applicationUsername,
611
- nonce,
612
- String(timestamp),
613
- ].join(separator);
614
- if (!offerPrivateKey.includes('BEGIN') || !offerPrivateKey.includes('END')) {
615
- throw new Error('[onesub/apple] offerPrivateKey does not appear to be PEM-encoded');
616
- }
617
- let signatureBuffer;
618
- try {
619
- const sign = createSign('SHA256');
620
- sign.update(message);
621
- sign.end();
622
- signatureBuffer = sign.sign(offerPrivateKey);
623
- }
624
- catch (err) {
625
- throw new Error(`[onesub/apple] Failed to sign promotional offer — check that offerPrivateKey is a valid ES256 PEM key: ${err.message}`);
626
- }
627
- const signature = signatureBuffer.toString('base64');
628
- return { keyId: offerKeyId, nonce, timestamp, signature };
629
- }
630
- //# sourceMappingURL=apple.js.map