@onesub/server 0.12.0 → 0.12.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.
Files changed (120) hide show
  1. package/dist/__tests__/webhook-apple.test.d.ts +12 -0
  2. package/dist/__tests__/webhook-apple.test.d.ts.map +1 -0
  3. package/dist/__tests__/webhook-google.test.d.ts +13 -0
  4. package/dist/__tests__/webhook-google.test.d.ts.map +1 -0
  5. package/dist/index.cjs +258 -288
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.ts +0 -1
  8. package/dist/index.d.ts.map +1 -1
  9. package/dist/index.js +259 -286
  10. package/dist/index.js.map +1 -1
  11. package/dist/providers/google.d.ts.map +1 -1
  12. package/dist/routes/webhook-apple.d.ts +6 -0
  13. package/dist/routes/webhook-apple.d.ts.map +1 -0
  14. package/dist/routes/webhook-google.d.ts +7 -0
  15. package/dist/routes/webhook-google.d.ts.map +1 -0
  16. package/dist/routes/webhook.d.ts +4 -17
  17. package/dist/routes/webhook.d.ts.map +1 -1
  18. package/package.json +2 -2
  19. package/dist/__tests__/admin-customer-detail.test.js +0 -146
  20. package/dist/__tests__/admin-customer-detail.test.js.map +0 -1
  21. package/dist/__tests__/admin-list-subscriptions.test.js +0 -192
  22. package/dist/__tests__/admin-list-subscriptions.test.js.map +0 -1
  23. package/dist/__tests__/admin-subscription-detail.test.js +0 -112
  24. package/dist/__tests__/admin-subscription-detail.test.js.map +0 -1
  25. package/dist/__tests__/apple-root-ca.test.js +0 -41
  26. package/dist/__tests__/apple-root-ca.test.js.map +0 -1
  27. package/dist/__tests__/apple-status-api.test.js +0 -345
  28. package/dist/__tests__/apple-status-api.test.js.map +0 -1
  29. package/dist/__tests__/cache.test.js +0 -34
  30. package/dist/__tests__/cache.test.js.map +0 -1
  31. package/dist/__tests__/entitlements.test.js +0 -268
  32. package/dist/__tests__/entitlements.test.js.map +0 -1
  33. package/dist/__tests__/error-codes.test.js +0 -168
  34. package/dist/__tests__/error-codes.test.js.map +0 -1
  35. package/dist/__tests__/fetch-hardening.test.js +0 -192
  36. package/dist/__tests__/fetch-hardening.test.js.map +0 -1
  37. package/dist/__tests__/google-paused.test.js +0 -126
  38. package/dist/__tests__/google-paused.test.js.map +0 -1
  39. package/dist/__tests__/google-price-change.test.js +0 -186
  40. package/dist/__tests__/google-price-change.test.js.map +0 -1
  41. package/dist/__tests__/google-subscriptions-v2.test.js +0 -206
  42. package/dist/__tests__/google-subscriptions-v2.test.js.map +0 -1
  43. package/dist/__tests__/lifecycle-scenarios.test.js +0 -579
  44. package/dist/__tests__/lifecycle-scenarios.test.js.map +0 -1
  45. package/dist/__tests__/lifecycle-states.test.js +0 -423
  46. package/dist/__tests__/lifecycle-states.test.js.map +0 -1
  47. package/dist/__tests__/linked-purchase-token.test.js +0 -227
  48. package/dist/__tests__/linked-purchase-token.test.js.map +0 -1
  49. package/dist/__tests__/metrics.test.js +0 -340
  50. package/dist/__tests__/metrics.test.js.map +0 -1
  51. package/dist/__tests__/mock-provider.test.js +0 -188
  52. package/dist/__tests__/mock-provider.test.js.map +0 -1
  53. package/dist/__tests__/openapi.test.js +0 -32
  54. package/dist/__tests__/openapi.test.js.map +0 -1
  55. package/dist/__tests__/paused-auto-resume.test.js +0 -162
  56. package/dist/__tests__/paused-auto-resume.test.js.map +0 -1
  57. package/dist/__tests__/providers.test.js +0 -321
  58. package/dist/__tests__/providers.test.js.map +0 -1
  59. package/dist/__tests__/redis-store.test.js +0 -117
  60. package/dist/__tests__/redis-store.test.js.map +0 -1
  61. package/dist/__tests__/refund-policy.test.js +0 -317
  62. package/dist/__tests__/refund-policy.test.js.map +0 -1
  63. package/dist/__tests__/schema.test.js +0 -46
  64. package/dist/__tests__/schema.test.js.map +0 -1
  65. package/dist/__tests__/store.test.js +0 -172
  66. package/dist/__tests__/store.test.js.map +0 -1
  67. package/dist/__tests__/test-utils.js +0 -30
  68. package/dist/__tests__/test-utils.js.map +0 -1
  69. package/dist/__tests__/webhook-events.test.js +0 -36
  70. package/dist/__tests__/webhook-events.test.js.map +0 -1
  71. package/dist/__tests__/webhook-queue.test.js +0 -26
  72. package/dist/__tests__/webhook-queue.test.js.map +0 -1
  73. package/dist/__tests__/webhook-refund.test.js +0 -313
  74. package/dist/__tests__/webhook-refund.test.js.map +0 -1
  75. package/dist/cache.js +0 -43
  76. package/dist/cache.js.map +0 -1
  77. package/dist/errors.js +0 -19
  78. package/dist/errors.js.map +0 -1
  79. package/dist/http.js +0 -36
  80. package/dist/http.js.map +0 -1
  81. package/dist/logger.js +0 -20
  82. package/dist/logger.js.map +0 -1
  83. package/dist/openapi.js +0 -196
  84. package/dist/openapi.js.map +0 -1
  85. package/dist/providers/apple-root-ca.js +0 -38
  86. package/dist/providers/apple-root-ca.js.map +0 -1
  87. package/dist/providers/apple.js +0 -510
  88. package/dist/providers/apple.js.map +0 -1
  89. package/dist/providers/google.js +0 -522
  90. package/dist/providers/google.js.map +0 -1
  91. package/dist/providers/mock.js +0 -93
  92. package/dist/providers/mock.js.map +0 -1
  93. package/dist/routes/admin.js +0 -287
  94. package/dist/routes/admin.js.map +0 -1
  95. package/dist/routes/entitlements.js +0 -130
  96. package/dist/routes/entitlements.js.map +0 -1
  97. package/dist/routes/metrics.js +0 -281
  98. package/dist/routes/metrics.js.map +0 -1
  99. package/dist/routes/purchase.js +0 -187
  100. package/dist/routes/purchase.js.map +0 -1
  101. package/dist/routes/status.js +0 -55
  102. package/dist/routes/status.js.map +0 -1
  103. package/dist/routes/validate.js +0 -70
  104. package/dist/routes/validate.js.map +0 -1
  105. package/dist/routes/webhook.js +0 -461
  106. package/dist/routes/webhook.js.map +0 -1
  107. package/dist/store.js +0 -145
  108. package/dist/store.js.map +0 -1
  109. package/dist/stores/postgres.js +0 -329
  110. package/dist/stores/postgres.js.map +0 -1
  111. package/dist/stores/redis.js +0 -255
  112. package/dist/stores/redis.js.map +0 -1
  113. package/dist/stores/schema.js +0 -67
  114. package/dist/stores/schema.js.map +0 -1
  115. package/dist/tracing.js +0 -74
  116. package/dist/tracing.js.map +0 -1
  117. package/dist/webhook-events.js +0 -57
  118. package/dist/webhook-events.js.map +0 -1
  119. package/dist/webhook-queue.js +0 -108
  120. package/dist/webhook-queue.js.map +0 -1
@@ -1,36 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { CacheWebhookEventStore, InMemoryWebhookEventStore } from '../webhook-events.js';
3
- import { InMemoryCacheAdapter } from '../cache.js';
4
- describe('InMemoryWebhookEventStore', () => {
5
- it('returns true the first time, false on retry', async () => {
6
- const store = new InMemoryWebhookEventStore();
7
- expect(await store.markIfNew('apple', 'uuid-1')).toBe(true);
8
- expect(await store.markIfNew('apple', 'uuid-1')).toBe(false);
9
- });
10
- it('keys by provider so apple/google ids never collide', async () => {
11
- const store = new InMemoryWebhookEventStore();
12
- expect(await store.markIfNew('apple', 'shared')).toBe(true);
13
- expect(await store.markIfNew('google', 'shared')).toBe(true);
14
- });
15
- it('expires entries after the TTL', async () => {
16
- const store = new InMemoryWebhookEventStore(1); // 1-second TTL
17
- expect(await store.markIfNew('apple', 'uuid-1')).toBe(true);
18
- await new Promise((resolve) => setTimeout(resolve, 1100));
19
- // Re-eligible after expiry — Apple/Google retries beyond the window are
20
- // effectively new events, which is the intended behavior.
21
- expect(await store.markIfNew('apple', 'uuid-1')).toBe(true);
22
- });
23
- });
24
- describe('CacheWebhookEventStore', () => {
25
- it('uses any CacheAdapter for cluster-shared dedupe', async () => {
26
- const cache = new InMemoryCacheAdapter();
27
- const store = new CacheWebhookEventStore(cache);
28
- expect(await store.markIfNew('google', 'msg-1')).toBe(true);
29
- expect(await store.markIfNew('google', 'msg-1')).toBe(false);
30
- // A second store backed by the same cache sees the prior write — this is
31
- // the multi-instance idempotency guarantee we ship.
32
- const store2 = new CacheWebhookEventStore(cache);
33
- expect(await store2.markIfNew('google', 'msg-1')).toBe(false);
34
- });
35
- });
36
- //# sourceMappingURL=webhook-events.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-events.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-events.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,sBAAsB,CAAC;AACzF,OAAO,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAEnD,QAAQ,CAAC,2BAA2B,EAAE,GAAG,EAAE;IACzC,EAAE,CAAC,6CAA6C,EAAE,KAAK,IAAI,EAAE;QAC3D,MAAM,KAAK,GAAG,IAAI,yBAAyB,EAAE,CAAC;QAC9C,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,KAAK,GAAG,IAAI,yBAAyB,EAAE,CAAC;QAC9C,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC/D,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,+BAA+B,EAAE,KAAK,IAAI,EAAE;QAC7C,MAAM,KAAK,GAAG,IAAI,yBAAyB,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe;QAC/D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;QAC1D,wEAAwE;QACxE,0DAA0D;QAC1D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9D,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,wBAAwB,EAAE,GAAG,EAAE;IACtC,EAAE,CAAC,iDAAiD,EAAE,KAAK,IAAI,EAAE;QAC/D,MAAM,KAAK,GAAG,IAAI,oBAAoB,EAAE,CAAC;QACzC,MAAM,KAAK,GAAG,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC5D,MAAM,CAAC,MAAM,KAAK,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAE7D,yEAAyE;QACzE,oDAAoD;QACpD,MAAM,MAAM,GAAG,IAAI,sBAAsB,CAAC,KAAK,CAAC,CAAC;QACjD,MAAM,CAAC,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAChE,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,26 +0,0 @@
1
- import { describe, expect, it } from 'vitest';
2
- import { InProcessWebhookQueue } from '../webhook-queue.js';
3
- describe('InProcessWebhookQueue', () => {
4
- it('runs the handler synchronously inside enqueue', async () => {
5
- const queue = new InProcessWebhookQueue();
6
- const seen = [];
7
- queue.setHandler(async (job) => {
8
- seen.push(job);
9
- });
10
- await queue.enqueue({ provider: 'apple', eventId: 'uuid-1', payload: { foo: 'bar' } });
11
- expect(seen).toHaveLength(1);
12
- expect(seen[0].eventId).toBe('uuid-1');
13
- });
14
- it('throws if no handler has been registered', async () => {
15
- const queue = new InProcessWebhookQueue();
16
- await expect(queue.enqueue({ provider: 'apple', eventId: 'uuid-1', payload: {} })).rejects.toThrow(/no handler/);
17
- });
18
- it('propagates handler errors to enqueue', async () => {
19
- const queue = new InProcessWebhookQueue();
20
- queue.setHandler(async () => {
21
- throw new Error('downstream failed');
22
- });
23
- await expect(queue.enqueue({ provider: 'apple', eventId: 'uuid-1', payload: {} })).rejects.toThrow(/downstream failed/);
24
- });
25
- });
26
- //# sourceMappingURL=webhook-queue.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-queue.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-queue.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AAG5D,QAAQ,CAAC,uBAAuB,EAAE,GAAG,EAAE;IACrC,EAAE,CAAC,+CAA+C,EAAE,KAAK,IAAI,EAAE;QAC7D,MAAM,KAAK,GAAG,IAAI,qBAAqB,EAAE,CAAC;QAC1C,MAAM,IAAI,GAAiB,EAAE,CAAC;QAC9B,KAAK,CAAC,UAAU,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;YAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;QACH,MAAM,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;QACvF,MAAM,CAAC,IAAI,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,KAAK,IAAI,EAAE;QACxD,MAAM,KAAK,GAAG,IAAI,qBAAqB,EAAE,CAAC;QAC1C,MAAM,MAAM,CACV,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CACrE,CAAC,OAAO,CAAC,OAAO,CAAC,YAAY,CAAC,CAAC;IAClC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,KAAK,GAAG,IAAI,qBAAqB,EAAE,CAAC;QAC1C,KAAK,CAAC,UAAU,CAAC,KAAK,IAAI,EAAE;YAC1B,MAAM,IAAI,KAAK,CAAC,mBAAmB,CAAC,CAAC;QACvC,CAAC,CAAC,CAAC;QACH,MAAM,MAAM,CACV,KAAK,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,EAAE,EAAE,CAAC,CACrE,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
@@ -1,313 +0,0 @@
1
- /**
2
- * Refund / voided-purchase webhook tests.
3
- *
4
- * Covers:
5
- * - Apple webhook REFUND for IAP (Consumable / Non-Consumable) →
6
- * PurchaseStore row removed, SubscriptionStore untouched.
7
- * - Apple webhook REFUND for subscriptions still updates SubscriptionStore.
8
- * - Google voidedPurchaseNotification productType=2 → IAP row removed.
9
- * - Google voidedPurchaseNotification productType=1 → subscription canceled.
10
- * - decodeGoogleVoidedNotification recognises voided payload, ignores other kinds.
11
- */
12
- import { describe, it, expect, beforeEach } from 'vitest';
13
- import express from 'express';
14
- import { SUBSCRIPTION_STATUS } from '@onesub/shared';
15
- import { createWebhookRouter } from '../routes/webhook.js';
16
- import { InMemorySubscriptionStore, InMemoryPurchaseStore } from '../store.js';
17
- import { decodeGoogleVoidedNotification } from '../providers/google.js';
18
- // ── helpers ─────────────────────────────────────────────────────────────────
19
- function makeJws(payload) {
20
- const header = Buffer.from(JSON.stringify({ alg: 'ES256' })).toString('base64url');
21
- const body = Buffer.from(JSON.stringify(payload)).toString('base64url');
22
- return `${header}.${body}.fakesig`;
23
- }
24
- function appleConfig() {
25
- return {
26
- apple: { bundleId: 'com.example.app', skipJwsVerification: true },
27
- database: { url: '' },
28
- };
29
- }
30
- function googleConfig() {
31
- return {
32
- google: { packageName: 'com.example.app' },
33
- database: { url: '' },
34
- };
35
- }
36
- function buildServer(config, store, purchaseStore) {
37
- const app = express();
38
- app.use(express.json());
39
- app.use(createWebhookRouter(config, store, purchaseStore));
40
- return {
41
- async request(path, body) {
42
- // Use Node http directly for a lightweight test server.
43
- const server = app.listen(0);
44
- const address = server.address();
45
- const port = typeof address === 'object' && address ? address.port : 0;
46
- try {
47
- const resp = await fetch(`http://127.0.0.1:${port}${path}`, {
48
- method: 'POST',
49
- headers: { 'Content-Type': 'application/json' },
50
- body: JSON.stringify(body),
51
- });
52
- const text = await resp.text();
53
- let parsed = text;
54
- try {
55
- parsed = JSON.parse(text);
56
- }
57
- catch { /* keep as text */ }
58
- return { status: resp.status, body: parsed };
59
- }
60
- finally {
61
- await new Promise((resolve) => server.close(() => resolve()));
62
- }
63
- },
64
- };
65
- }
66
- const samplePurchase = (overrides) => ({
67
- userId: 'user_1',
68
- productId: 'credits_100',
69
- platform: 'apple',
70
- type: 'consumable',
71
- transactionId: 'txn_apple_consumable_001',
72
- purchasedAt: '2026-01-01T00:00:00.000Z',
73
- quantity: 1,
74
- ...overrides,
75
- });
76
- const sampleSub = (overrides) => ({
77
- userId: 'user_sub',
78
- productId: 'pro_monthly',
79
- platform: 'google',
80
- status: 'active',
81
- expiresAt: '2027-01-01T00:00:00.000Z',
82
- originalTransactionId: 'token_google_sub_xyz',
83
- purchasedAt: '2026-01-01T00:00:00.000Z',
84
- willRenew: true,
85
- ...overrides,
86
- });
87
- // ── Apple webhook: IAP REFUND ───────────────────────────────────────────────
88
- describe('Apple webhook — IAP REFUND', () => {
89
- let store;
90
- let purchaseStore;
91
- let server;
92
- beforeEach(() => {
93
- store = new InMemorySubscriptionStore();
94
- purchaseStore = new InMemoryPurchaseStore();
95
- server = buildServer(appleConfig(), store, purchaseStore);
96
- });
97
- it('removes the consumable purchase row on REFUND', async () => {
98
- await purchaseStore.savePurchase(samplePurchase({ transactionId: 'txn_consumable_a' }));
99
- const signedTransactionInfo = makeJws({
100
- bundleId: 'com.example.app',
101
- type: 'Consumable',
102
- productId: 'credits_100',
103
- transactionId: 'txn_consumable_a',
104
- originalTransactionId: 'orig_consumable_a',
105
- purchaseDate: Date.now(),
106
- });
107
- const signedRenewalInfo = makeJws({});
108
- const signedPayload = makeJws({
109
- notificationType: 'REFUND',
110
- data: { signedTransactionInfo, signedRenewalInfo },
111
- });
112
- const resp = await server.request('/onesub/webhook/apple', { signedPayload });
113
- expect(resp.status).toBe(200);
114
- expect(await purchaseStore.getPurchaseByTransactionId('txn_consumable_a')).toBeNull();
115
- });
116
- it('removes the non-consumable purchase row on REVOKE', async () => {
117
- await purchaseStore.savePurchase(samplePurchase({
118
- transactionId: 'txn_nc_a',
119
- type: 'non_consumable',
120
- productId: 'premium_unlock',
121
- }));
122
- const signedTransactionInfo = makeJws({
123
- bundleId: 'com.example.app',
124
- type: 'Non-Consumable',
125
- productId: 'premium_unlock',
126
- transactionId: 'txn_nc_a',
127
- originalTransactionId: 'txn_nc_a',
128
- purchaseDate: Date.now(),
129
- });
130
- const signedPayload = makeJws({
131
- notificationType: 'REVOKE',
132
- data: { signedTransactionInfo, signedRenewalInfo: makeJws({}) },
133
- });
134
- const resp = await server.request('/onesub/webhook/apple', { signedPayload });
135
- expect(resp.status).toBe(200);
136
- expect(await purchaseStore.getPurchaseByTransactionId('txn_nc_a')).toBeNull();
137
- });
138
- it('does not touch the SubscriptionStore on IAP REFUND', async () => {
139
- await store.save(sampleSub({ originalTransactionId: 'orig_sub_untouched' }));
140
- await purchaseStore.savePurchase(samplePurchase({ transactionId: 'iap_only' }));
141
- const signedTransactionInfo = makeJws({
142
- bundleId: 'com.example.app',
143
- type: 'Consumable',
144
- productId: 'credits_100',
145
- transactionId: 'iap_only',
146
- originalTransactionId: 'iap_only',
147
- purchaseDate: Date.now(),
148
- });
149
- const signedPayload = makeJws({
150
- notificationType: 'REFUND',
151
- data: { signedTransactionInfo, signedRenewalInfo: makeJws({}) },
152
- });
153
- await server.request('/onesub/webhook/apple', { signedPayload });
154
- expect(await store.getByTransactionId('orig_sub_untouched')).not.toBeNull();
155
- });
156
- it('still routes subscription REFUND through the SubscriptionStore', async () => {
157
- await store.save(sampleSub({
158
- platform: 'apple',
159
- originalTransactionId: 'orig_sub_apple',
160
- status: 'active',
161
- }));
162
- const signedTransactionInfo = makeJws({
163
- bundleId: 'com.example.app',
164
- type: 'Auto-Renewable Subscription',
165
- productId: 'pro_monthly',
166
- transactionId: 'tx_sub_apple_1',
167
- originalTransactionId: 'orig_sub_apple',
168
- purchaseDate: Date.now(),
169
- expiresDate: Date.now() + 86400000,
170
- });
171
- const signedPayload = makeJws({
172
- notificationType: 'REFUND',
173
- data: { signedTransactionInfo, signedRenewalInfo: makeJws({}) },
174
- });
175
- const resp = await server.request('/onesub/webhook/apple', { signedPayload });
176
- expect(resp.status).toBe(200);
177
- const updated = await store.getByTransactionId('orig_sub_apple');
178
- expect(updated?.status).toBe(SUBSCRIPTION_STATUS.CANCELED);
179
- });
180
- });
181
- // ── Google webhook: voidedPurchaseNotification ──────────────────────────────
182
- function makeVoidedPushBody(notification) {
183
- const json = JSON.stringify({
184
- version: '1.0',
185
- eventTimeMillis: String(Date.now()),
186
- ...notification,
187
- });
188
- return {
189
- message: {
190
- data: Buffer.from(json, 'utf-8').toString('base64'),
191
- messageId: '1',
192
- },
193
- subscription: 'projects/x/subscriptions/y',
194
- };
195
- }
196
- describe('Google webhook — voidedPurchaseNotification', () => {
197
- let store;
198
- let purchaseStore;
199
- let server;
200
- beforeEach(() => {
201
- store = new InMemorySubscriptionStore();
202
- purchaseStore = new InMemoryPurchaseStore();
203
- server = buildServer(googleConfig(), store, purchaseStore);
204
- });
205
- it('removes IAP row when productType=2 (one-time product refund)', async () => {
206
- await purchaseStore.savePurchase(samplePurchase({
207
- platform: 'google',
208
- transactionId: 'GPA.refunded_order',
209
- }));
210
- const resp = await server.request('/onesub/webhook/google', makeVoidedPushBody({
211
- packageName: 'com.example.app',
212
- voidedPurchaseNotification: {
213
- purchaseToken: 'tok_x',
214
- orderId: 'GPA.refunded_order',
215
- productType: 2,
216
- refundType: 1,
217
- },
218
- }));
219
- expect(resp.status).toBe(200);
220
- expect(await purchaseStore.getPurchaseByTransactionId('GPA.refunded_order')).toBeNull();
221
- });
222
- it('cancels subscription when productType=1', async () => {
223
- await store.save(sampleSub({ originalTransactionId: 'tok_sub' }));
224
- const resp = await server.request('/onesub/webhook/google', makeVoidedPushBody({
225
- packageName: 'com.example.app',
226
- voidedPurchaseNotification: {
227
- purchaseToken: 'tok_sub',
228
- orderId: 'GPA.sub_order',
229
- productType: 1,
230
- refundType: 1,
231
- },
232
- }));
233
- expect(resp.status).toBe(200);
234
- const updated = await store.getByTransactionId('tok_sub');
235
- expect(updated?.status).toBe(SUBSCRIPTION_STATUS.CANCELED);
236
- });
237
- it('rejects voided notification when packageName mismatches', async () => {
238
- const resp = await server.request('/onesub/webhook/google', makeVoidedPushBody({
239
- packageName: 'com.attacker.app',
240
- voidedPurchaseNotification: {
241
- purchaseToken: 'tok',
242
- orderId: 'GPA.x',
243
- productType: 2,
244
- refundType: 1,
245
- },
246
- }));
247
- expect(resp.status).toBe(400);
248
- });
249
- it('acknowledges with 200 even when the orderId is unknown', async () => {
250
- // Idempotent ack semantics — Google retries until 2xx; we don't want loops
251
- // when the row was already removed.
252
- const resp = await server.request('/onesub/webhook/google', makeVoidedPushBody({
253
- packageName: 'com.example.app',
254
- voidedPurchaseNotification: {
255
- purchaseToken: 'tok',
256
- orderId: 'GPA.unknown',
257
- productType: 2,
258
- refundType: 1,
259
- },
260
- }));
261
- expect(resp.status).toBe(200);
262
- });
263
- });
264
- // ── decodeGoogleVoidedNotification unit ─────────────────────────────────────
265
- describe('decodeGoogleVoidedNotification', () => {
266
- it('returns null for subscription notifications', () => {
267
- const json = JSON.stringify({
268
- packageName: 'com.example.app',
269
- eventTimeMillis: '0',
270
- subscriptionNotification: {
271
- version: '1.0',
272
- notificationType: 4,
273
- purchaseToken: 't',
274
- subscriptionId: 'p',
275
- },
276
- });
277
- const result = decodeGoogleVoidedNotification({
278
- message: { data: Buffer.from(json).toString('base64'), messageId: '1' },
279
- subscription: 's',
280
- });
281
- expect(result).toBeNull();
282
- });
283
- it('extracts voided payload fields', () => {
284
- const json = JSON.stringify({
285
- packageName: 'com.example.app',
286
- eventTimeMillis: '0',
287
- voidedPurchaseNotification: {
288
- purchaseToken: 'tok123',
289
- orderId: 'GPA.xyz',
290
- productType: 2,
291
- refundType: 1,
292
- },
293
- });
294
- const result = decodeGoogleVoidedNotification({
295
- message: { data: Buffer.from(json).toString('base64'), messageId: '1' },
296
- subscription: 's',
297
- });
298
- expect(result).toEqual({
299
- purchaseToken: 'tok123',
300
- orderId: 'GPA.xyz',
301
- productType: 2,
302
- refundType: 1,
303
- packageName: 'com.example.app',
304
- });
305
- });
306
- it('returns null for malformed base64', () => {
307
- expect(decodeGoogleVoidedNotification({
308
- message: { data: '!!!not-base64!!!', messageId: '1' },
309
- subscription: 's',
310
- })).toBeNull();
311
- });
312
- });
313
- //# sourceMappingURL=webhook-refund.test.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"webhook-refund.test.js","sourceRoot":"","sources":["../../src/__tests__/webhook-refund.test.ts"],"names":[],"mappings":"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"}
package/dist/cache.js DELETED
@@ -1,43 +0,0 @@
1
- /**
2
- * Process-local in-memory cache. Default for single-instance deployments.
3
- */
4
- export class InMemoryCacheAdapter {
5
- entries = new Map();
6
- async get(key) {
7
- const entry = this.entries.get(key);
8
- if (!entry)
9
- return null;
10
- if (entry.expiresAt !== 0 && entry.expiresAt < Date.now()) {
11
- this.entries.delete(key);
12
- return null;
13
- }
14
- return entry.value;
15
- }
16
- async set(key, value, ttlSeconds) {
17
- const expiresAt = ttlSeconds && ttlSeconds > 0 ? Date.now() + ttlSeconds * 1000 : 0;
18
- this.entries.set(key, { value, expiresAt });
19
- }
20
- async del(key) {
21
- this.entries.delete(key);
22
- }
23
- /** Test helper — clear everything. Not part of the public CacheAdapter contract. */
24
- clear() {
25
- this.entries.clear();
26
- }
27
- }
28
- /**
29
- * Module-level singleton used by providers when no per-call adapter is
30
- * supplied. Keeps the existing single-instance behavior with zero config.
31
- */
32
- let defaultAdapter = new InMemoryCacheAdapter();
33
- export function getDefaultCache() {
34
- return defaultAdapter;
35
- }
36
- export function setDefaultCache(adapter) {
37
- defaultAdapter = adapter;
38
- }
39
- /** Test helper — reset the default adapter to a fresh in-memory one. */
40
- export function __resetDefaultCacheForTests() {
41
- defaultAdapter = new InMemoryCacheAdapter();
42
- }
43
- //# sourceMappingURL=cache.js.map
package/dist/cache.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cache.js","sourceRoot":"","sources":["../src/cache.ts"],"names":[],"mappings":"AAyBA;;GAEG;AACH,MAAM,OAAO,oBAAoB;IACd,OAAO,GAAG,IAAI,GAAG,EAAiB,CAAC;IAEpD,KAAK,CAAC,GAAG,CAAI,GAAW;QACtB,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,OAAO,IAAI,CAAC;QACxB,IAAI,KAAK,CAAC,SAAS,KAAK,CAAC,IAAI,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YAC1D,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC,KAAU,CAAC;IAC1B,CAAC;IAED,KAAK,CAAC,GAAG,CAAI,GAAW,EAAE,KAAQ,EAAE,UAAmB;QACrD,MAAM,SAAS,GAAG,UAAU,IAAI,UAAU,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;QACpF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;IAC9C,CAAC;IAED,KAAK,CAAC,GAAG,CAAC,GAAW;QACnB,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,CAAC;IAED,oFAAoF;IACpF,KAAK;QACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;CACF;AAED;;;GAGG;AACH,IAAI,cAAc,GAAiB,IAAI,oBAAoB,EAAE,CAAC;AAE9D,MAAM,UAAU,eAAe;IAC7B,OAAO,cAAc,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,OAAqB;IACnD,cAAc,GAAG,OAAO,CAAC;AAC3B,CAAC;AAED,wEAAwE;AACxE,MAAM,UAAU,2BAA2B;IACzC,cAAc,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC9C,CAAC"}
package/dist/errors.js DELETED
@@ -1,19 +0,0 @@
1
- import { ONESUB_ERROR_CODE } from '@onesub/shared';
2
- /**
3
- * Send a structured error response. Every onesub HTTP endpoint uses this
4
- * so consumers can rely on `errorCode` being present on every 4xx/5xx body.
5
- *
6
- * `extra` lets callers inject route-specific defaults (e.g. `purchase: null`
7
- * for ValidatePurchaseResponse shape compatibility).
8
- */
9
- export function sendError(res, status, code, error, extra = {}) {
10
- res.status(status).json({ ...extra, error, errorCode: code });
11
- }
12
- /**
13
- * 400 response for a failed zod parse. The ZodError's per-issue messages
14
- * are joined for the human-readable `error` field.
15
- */
16
- export function sendZodError(res, err, extra = {}) {
17
- sendError(res, 400, ONESUB_ERROR_CODE.INVALID_INPUT, err.issues.map((e) => e.message).join(', '), extra);
18
- }
19
- //# sourceMappingURL=errors.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AAEnD;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CACvB,GAAa,EACb,MAAc,EACd,IAAqB,EACrB,KAAa,EACb,QAAiC,EAAE;IAEnC,GAAG,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;AAChE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAC1B,GAAa,EACb,GAAe,EACf,QAAiC,EAAE;IAEnC,SAAS,CACP,GAAG,EACH,GAAG,EACH,iBAAiB,CAAC,aAAa,EAC/B,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAsB,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAChE,KAAK,CACN,CAAC;AACJ,CAAC"}
package/dist/http.js DELETED
@@ -1,36 +0,0 @@
1
- /**
2
- * Outbound HTTP helpers shared by Apple/Google providers.
3
- *
4
- * Why timeouts: Node's global fetch has no default timeout. If the upstream
5
- * (Apple App Store Server API, Google Play Developer API, Google OAuth) hangs,
6
- * webhook handlers and validate routes hang with it — Apple/Google retry on
7
- * their side, so a hung outbound call cascades into request pile-up here.
8
- */
9
- /** Default timeout for outbound API calls (10s). */
10
- export const DEFAULT_FETCH_TIMEOUT_MS = 10_000;
11
- /**
12
- * fetch with an `AbortController`-based timeout. Throws an `AbortError`-like
13
- * Error when the timer fires. Caller is expected to catch and decide
14
- * (return null, log, propagate as 5xx, etc.).
15
- */
16
- export async function fetchWithTimeout(input, init, timeoutMs = DEFAULT_FETCH_TIMEOUT_MS) {
17
- const controller = new AbortController();
18
- // Allow caller-provided signal to compose with our timeout.
19
- const callerSignal = init?.signal;
20
- if (callerSignal) {
21
- if (callerSignal.aborted)
22
- controller.abort(callerSignal.reason);
23
- else
24
- callerSignal.addEventListener('abort', () => controller.abort(callerSignal.reason), { once: true });
25
- }
26
- const timer = setTimeout(() => {
27
- controller.abort(new Error(`[onesub] fetch timed out after ${timeoutMs}ms`));
28
- }, timeoutMs);
29
- try {
30
- return await fetch(input, { ...init, signal: controller.signal });
31
- }
32
- finally {
33
- clearTimeout(timer);
34
- }
35
- }
36
- //# sourceMappingURL=http.js.map
package/dist/http.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"http.js","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,oDAAoD;AACpD,MAAM,CAAC,MAAM,wBAAwB,GAAG,MAAM,CAAC;AAE/C;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,KAAwB,EACxB,IAAkB,EAClB,SAAS,GAAG,wBAAwB;IAEpC,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,4DAA4D;IAC5D,MAAM,YAAY,GAAG,IAAI,EAAE,MAAM,CAAC;IAClC,IAAI,YAAY,EAAE,CAAC;QACjB,IAAI,YAAY,CAAC,OAAO;YAAE,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC;;YAC3D,YAAY,CAAC,gBAAgB,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,YAAY,CAAC,MAAM,CAAC,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3G,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE;QAC5B,UAAU,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,kCAAkC,SAAS,IAAI,CAAC,CAAC,CAAC;IAC/E,CAAC,EAAE,SAAS,CAAC,CAAC;IAEd,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IACpE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC"}
package/dist/logger.js DELETED
@@ -1,20 +0,0 @@
1
- /**
2
- * Process-wide logger used by @onesub/server's providers and routes.
3
- *
4
- * Defaults to `console`. `createOneSubMiddleware()` calls `setLogger()` once
5
- * during setup if `config.logger` is provided. All internal call sites should
6
- * import `log` from this module instead of calling `console.*` directly so
7
- * operators can redirect logs (pino / winston / bunyan) with a single config
8
- * setting.
9
- */
10
- let current = console;
11
- export function setLogger(logger) {
12
- if (logger)
13
- current = logger;
14
- }
15
- export const log = {
16
- info: (...args) => current.info(...args),
17
- warn: (...args) => current.warn(...args),
18
- error: (...args) => current.error(...args),
19
- };
20
- //# sourceMappingURL=logger.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AAEH,IAAI,OAAO,GAAiB,OAAO,CAAC;AAEpC,MAAM,UAAU,SAAS,CAAC,MAAgC;IACxD,IAAI,MAAM;QAAE,OAAO,GAAG,MAAM,CAAC;AAC/B,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAiB;IAC/B,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,IAAI,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IACxC,KAAK,EAAE,CAAC,GAAG,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;CAC3C,CAAC"}