@donotdev/functions 0.0.9 → 0.0.11
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/package.json +31 -7
- package/src/firebase/auth/setCustomClaims.ts +26 -4
- package/src/firebase/baseFunction.ts +43 -20
- package/src/firebase/billing/cancelSubscription.ts +9 -1
- package/src/firebase/billing/changePlan.ts +8 -2
- package/src/firebase/billing/createCustomerPortal.ts +16 -2
- package/src/firebase/billing/refreshSubscriptionStatus.ts +3 -1
- package/src/firebase/billing/webhookHandler.ts +13 -1
- package/src/firebase/config/constants.ts +0 -3
- package/src/firebase/crud/aggregate.ts +20 -5
- package/src/firebase/crud/create.ts +31 -7
- package/src/firebase/crud/get.ts +16 -8
- package/src/firebase/crud/list.ts +70 -29
- package/src/firebase/crud/update.ts +29 -7
- package/src/firebase/oauth/exchangeToken.ts +30 -4
- package/src/firebase/oauth/githubAccess.ts +8 -3
- package/src/firebase/registerCrudFunctions.ts +15 -4
- package/src/firebase/scheduled/checkExpiredSubscriptions.ts +20 -3
- package/src/shared/__tests__/detectFirestore.test.ts +52 -0
- package/src/shared/__tests__/errorHandling.test.ts +144 -0
- package/src/shared/__tests__/idempotency.test.ts +95 -0
- package/src/shared/__tests__/rateLimiter.test.ts +142 -0
- package/src/shared/__tests__/validation.test.ts +172 -0
- package/src/shared/billing/__tests__/createCheckout.test.ts +393 -0
- package/src/shared/billing/__tests__/webhookHandler.test.ts +1091 -0
- package/src/shared/billing/webhookHandler.ts +16 -7
- package/src/shared/errorHandling.ts +16 -54
- package/src/shared/firebase.ts +1 -25
- package/src/shared/oauth/__tests__/exchangeToken.test.ts +116 -0
- package/src/shared/oauth/__tests__/grantAccess.test.ts +237 -0
- package/src/shared/schema.ts +7 -1
- package/src/shared/utils/__tests__/functionWrapper.test.ts +122 -0
- package/src/shared/utils/external/subscription.ts +10 -0
- package/src/shared/utils/internal/auth.ts +140 -16
- package/src/shared/utils/internal/rateLimiter.ts +101 -90
- package/src/shared/utils/internal/validation.ts +47 -3
- package/src/shared/utils.ts +154 -39
- package/src/supabase/auth/deleteAccount.ts +59 -0
- package/src/supabase/auth/getCustomClaims.ts +56 -0
- package/src/supabase/auth/getUserAuthStatus.ts +64 -0
- package/src/supabase/auth/removeCustomClaims.ts +75 -0
- package/src/supabase/auth/setCustomClaims.ts +73 -0
- package/src/supabase/baseFunction.ts +302 -0
- package/src/supabase/billing/cancelSubscription.ts +57 -0
- package/src/supabase/billing/changePlan.ts +62 -0
- package/src/supabase/billing/createCheckoutSession.ts +82 -0
- package/src/supabase/billing/createCustomerPortal.ts +58 -0
- package/src/supabase/billing/refreshSubscriptionStatus.ts +89 -0
- package/src/supabase/crud/aggregate.ts +169 -0
- package/src/supabase/crud/create.ts +225 -0
- package/src/supabase/crud/delete.ts +154 -0
- package/src/supabase/crud/get.ts +89 -0
- package/src/supabase/crud/index.ts +24 -0
- package/src/supabase/crud/list.ts +357 -0
- package/src/supabase/crud/update.ts +199 -0
- package/src/supabase/helpers/authProvider.ts +45 -0
- package/src/supabase/index.ts +73 -0
- package/src/supabase/registerCrudFunctions.ts +180 -0
- package/src/supabase/utils/idempotency.ts +141 -0
- package/src/supabase/utils/monitoring.ts +187 -0
- package/src/supabase/utils/rateLimiter.ts +216 -0
- package/src/vercel/api/auth/get-custom-claims.ts +3 -2
- package/src/vercel/api/auth/get-user-auth-status.ts +3 -2
- package/src/vercel/api/auth/remove-custom-claims.ts +3 -2
- package/src/vercel/api/auth/set-custom-claims.ts +5 -2
- package/src/vercel/api/billing/cancel.ts +2 -1
- package/src/vercel/api/billing/change-plan.ts +3 -1
- package/src/vercel/api/billing/customer-portal.ts +4 -1
- package/src/vercel/api/billing/refresh-subscription-status.ts +3 -1
- package/src/vercel/api/billing/webhook-handler.ts +24 -4
- package/src/vercel/api/crud/create.ts +14 -8
- package/src/vercel/api/crud/delete.ts +15 -6
- package/src/vercel/api/crud/get.ts +16 -8
- package/src/vercel/api/crud/list.ts +22 -10
- package/src/vercel/api/crud/update.ts +16 -10
- package/src/vercel/api/oauth/check-github-access.ts +2 -5
- package/src/vercel/api/oauth/grant-github-access.ts +1 -5
- package/src/vercel/api/oauth/revoke-github-access.ts +7 -8
- package/src/vercel/api/utils/cors.ts +13 -2
- package/src/vercel/baseFunction.ts +40 -25
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
// packages/functions/src/shared/billing/__tests__/createCheckout.test.ts
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Tests for createCheckoutAlgorithm
|
|
5
|
+
* @description Unit tests using dependency injection — no real Stripe or Firebase calls.
|
|
6
|
+
*
|
|
7
|
+
* @version 0.0.1
|
|
8
|
+
* @since 0.0.1
|
|
9
|
+
* @author AMBROISE PARK Consulting
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
StripeCheckoutProvider,
|
|
16
|
+
AuthCheckoutProvider,
|
|
17
|
+
} from '../createCheckout.js';
|
|
18
|
+
import { createCheckoutAlgorithm } from '../createCheckout.js';
|
|
19
|
+
import type {
|
|
20
|
+
CreateCheckoutSessionRequest,
|
|
21
|
+
StripeBackConfig,
|
|
22
|
+
} from '@donotdev/core/server';
|
|
23
|
+
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
// Fixtures
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const VALID_PRICE_ID = 'price_test_abc123';
|
|
29
|
+
|
|
30
|
+
const BILLING_CONFIG: StripeBackConfig = {
|
|
31
|
+
pro_monthly: {
|
|
32
|
+
type: 'StripeSubscription',
|
|
33
|
+
name: 'Pro Monthly',
|
|
34
|
+
price: 1999,
|
|
35
|
+
currency: 'EUR',
|
|
36
|
+
priceId: VALID_PRICE_ID,
|
|
37
|
+
tier: 'pro',
|
|
38
|
+
duration: '1month',
|
|
39
|
+
},
|
|
40
|
+
starter_once: {
|
|
41
|
+
type: 'StripePayment',
|
|
42
|
+
name: 'Starter Pack',
|
|
43
|
+
price: 4900,
|
|
44
|
+
currency: 'EUR',
|
|
45
|
+
priceId: 'price_starter_xyz',
|
|
46
|
+
tier: 'starter',
|
|
47
|
+
duration: 'lifetime',
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const BASE_REQUEST: CreateCheckoutSessionRequest = {
|
|
52
|
+
userId: 'user_001',
|
|
53
|
+
priceId: VALID_PRICE_ID,
|
|
54
|
+
successUrl: 'https://app.example.com/billing/success',
|
|
55
|
+
cancelUrl: 'https://app.example.com/billing/cancel',
|
|
56
|
+
metadata: { billingConfigKey: 'pro_monthly' },
|
|
57
|
+
mode: 'subscription',
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// ---------------------------------------------------------------------------
|
|
61
|
+
// Mock factories
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
|
|
64
|
+
function makeStripeProvider(overrides?: Partial<StripeCheckoutProvider>): StripeCheckoutProvider {
|
|
65
|
+
return {
|
|
66
|
+
createCheckoutSession: vi.fn().mockResolvedValue({
|
|
67
|
+
id: 'cs_test_session123',
|
|
68
|
+
url: 'https://checkout.stripe.com/pay/cs_test_session123',
|
|
69
|
+
}),
|
|
70
|
+
...overrides,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function makeAuthProvider(overrides?: Partial<AuthCheckoutProvider>): AuthCheckoutProvider {
|
|
75
|
+
return {
|
|
76
|
+
getUser: vi.fn().mockResolvedValue({ customClaims: {} }),
|
|
77
|
+
...overrides,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// Tests
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
describe('createCheckoutAlgorithm', () => {
|
|
86
|
+
let stripeProvider: StripeCheckoutProvider;
|
|
87
|
+
let authProvider: AuthCheckoutProvider;
|
|
88
|
+
|
|
89
|
+
beforeEach(() => {
|
|
90
|
+
stripeProvider = makeStripeProvider();
|
|
91
|
+
authProvider = makeAuthProvider();
|
|
92
|
+
vi.clearAllMocks();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// -------------------------------------------------------------------------
|
|
96
|
+
// Happy path
|
|
97
|
+
// -------------------------------------------------------------------------
|
|
98
|
+
|
|
99
|
+
describe('valid checkout creation', () => {
|
|
100
|
+
it('returns sessionId and sessionUrl on success', async () => {
|
|
101
|
+
const result = await createCheckoutAlgorithm(
|
|
102
|
+
BASE_REQUEST,
|
|
103
|
+
stripeProvider,
|
|
104
|
+
authProvider,
|
|
105
|
+
BILLING_CONFIG
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual({
|
|
109
|
+
sessionId: 'cs_test_session123',
|
|
110
|
+
sessionUrl: 'https://checkout.stripe.com/pay/cs_test_session123',
|
|
111
|
+
});
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('verifies the user before creating the session', async () => {
|
|
115
|
+
await createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG);
|
|
116
|
+
|
|
117
|
+
expect(authProvider.getUser).toHaveBeenCalledOnce();
|
|
118
|
+
expect(authProvider.getUser).toHaveBeenCalledWith('user_001');
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
it('forwards priceId, mode, successUrl, cancelUrl to Stripe', async () => {
|
|
122
|
+
await createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG);
|
|
123
|
+
|
|
124
|
+
expect(stripeProvider.createCheckoutSession).toHaveBeenCalledWith(
|
|
125
|
+
expect.objectContaining({
|
|
126
|
+
priceId: VALID_PRICE_ID,
|
|
127
|
+
mode: 'subscription',
|
|
128
|
+
successUrl: 'https://app.example.com/billing/success',
|
|
129
|
+
cancelUrl: 'https://app.example.com/billing/cancel',
|
|
130
|
+
})
|
|
131
|
+
);
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('ensures userId and billingConfigKey are set in session metadata', async () => {
|
|
135
|
+
await createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG);
|
|
136
|
+
|
|
137
|
+
expect(stripeProvider.createCheckoutSession).toHaveBeenCalledWith(
|
|
138
|
+
expect.objectContaining({
|
|
139
|
+
metadata: expect.objectContaining({
|
|
140
|
+
userId: 'user_001',
|
|
141
|
+
billingConfigKey: 'pro_monthly',
|
|
142
|
+
}),
|
|
143
|
+
})
|
|
144
|
+
);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
it('passes customerEmail through when provided', async () => {
|
|
148
|
+
const request: CreateCheckoutSessionRequest = {
|
|
149
|
+
...BASE_REQUEST,
|
|
150
|
+
customerEmail: 'user@example.com',
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
await createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG);
|
|
154
|
+
|
|
155
|
+
expect(stripeProvider.createCheckoutSession).toHaveBeenCalledWith(
|
|
156
|
+
expect.objectContaining({ customerEmail: 'user@example.com' })
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('defaults allowPromotionCodes to true when not provided', async () => {
|
|
161
|
+
const request: CreateCheckoutSessionRequest = {
|
|
162
|
+
...BASE_REQUEST,
|
|
163
|
+
allowPromotionCodes: undefined,
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
await createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG);
|
|
167
|
+
|
|
168
|
+
expect(stripeProvider.createCheckoutSession).toHaveBeenCalledWith(
|
|
169
|
+
expect.objectContaining({ allowPromotionCodes: true })
|
|
170
|
+
);
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it('defaults mode to "payment" when not provided', async () => {
|
|
174
|
+
const request: CreateCheckoutSessionRequest = {
|
|
175
|
+
...BASE_REQUEST,
|
|
176
|
+
mode: undefined,
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
await createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG);
|
|
180
|
+
|
|
181
|
+
expect(stripeProvider.createCheckoutSession).toHaveBeenCalledWith(
|
|
182
|
+
expect.objectContaining({ mode: 'payment' })
|
|
183
|
+
);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
it('handles null sessionUrl from Stripe', async () => {
|
|
187
|
+
stripeProvider = makeStripeProvider({
|
|
188
|
+
createCheckoutSession: vi.fn().mockResolvedValue({
|
|
189
|
+
id: 'cs_test_no_url',
|
|
190
|
+
url: null,
|
|
191
|
+
}),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
const result = await createCheckoutAlgorithm(
|
|
195
|
+
BASE_REQUEST,
|
|
196
|
+
stripeProvider,
|
|
197
|
+
authProvider,
|
|
198
|
+
BILLING_CONFIG
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
expect(result.sessionUrl).toBeNull();
|
|
202
|
+
expect(result.sessionId).toBe('cs_test_no_url');
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it('works with a different valid billing config key', async () => {
|
|
206
|
+
const request: CreateCheckoutSessionRequest = {
|
|
207
|
+
...BASE_REQUEST,
|
|
208
|
+
priceId: 'price_starter_xyz',
|
|
209
|
+
metadata: { billingConfigKey: 'starter_once' },
|
|
210
|
+
mode: 'payment',
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const result = await createCheckoutAlgorithm(
|
|
214
|
+
request,
|
|
215
|
+
stripeProvider,
|
|
216
|
+
authProvider,
|
|
217
|
+
BILLING_CONFIG
|
|
218
|
+
);
|
|
219
|
+
|
|
220
|
+
expect(result.sessionId).toBe('cs_test_session123');
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
// -------------------------------------------------------------------------
|
|
225
|
+
// Missing / invalid params
|
|
226
|
+
// -------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
describe('missing required params', () => {
|
|
229
|
+
it('throws when billingConfigKey is absent from metadata', async () => {
|
|
230
|
+
const request: CreateCheckoutSessionRequest = {
|
|
231
|
+
...BASE_REQUEST,
|
|
232
|
+
metadata: {},
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
await expect(
|
|
236
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
237
|
+
).rejects.toThrow('Missing billingConfigKey in metadata');
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it('throws when metadata itself is undefined', async () => {
|
|
241
|
+
const request: CreateCheckoutSessionRequest = {
|
|
242
|
+
...BASE_REQUEST,
|
|
243
|
+
metadata: undefined,
|
|
244
|
+
};
|
|
245
|
+
|
|
246
|
+
await expect(
|
|
247
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
248
|
+
).rejects.toThrow('Missing billingConfigKey in metadata');
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('does not call Stripe when billingConfigKey is missing', async () => {
|
|
252
|
+
const request: CreateCheckoutSessionRequest = {
|
|
253
|
+
...BASE_REQUEST,
|
|
254
|
+
metadata: {},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
await expect(
|
|
258
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
259
|
+
).rejects.toThrow();
|
|
260
|
+
|
|
261
|
+
expect(stripeProvider.createCheckoutSession).not.toHaveBeenCalled();
|
|
262
|
+
});
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
// -------------------------------------------------------------------------
|
|
266
|
+
// Invalid plan / price
|
|
267
|
+
// -------------------------------------------------------------------------
|
|
268
|
+
|
|
269
|
+
describe('invalid plan / price', () => {
|
|
270
|
+
it('throws when billingConfigKey does not exist in config', async () => {
|
|
271
|
+
const request: CreateCheckoutSessionRequest = {
|
|
272
|
+
...BASE_REQUEST,
|
|
273
|
+
metadata: { billingConfigKey: 'nonexistent_plan' },
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
await expect(
|
|
277
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
278
|
+
).rejects.toThrow('Invalid billing config key: nonexistent_plan');
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
it('throws when priceId does not match the config entry', async () => {
|
|
282
|
+
const request: CreateCheckoutSessionRequest = {
|
|
283
|
+
...BASE_REQUEST,
|
|
284
|
+
priceId: 'price_wrong_one',
|
|
285
|
+
metadata: { billingConfigKey: 'pro_monthly' },
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
await expect(
|
|
289
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
290
|
+
).rejects.toThrow('Price ID mismatch with configuration');
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
it('does not call Stripe when priceId mismatches config', async () => {
|
|
294
|
+
const request: CreateCheckoutSessionRequest = {
|
|
295
|
+
...BASE_REQUEST,
|
|
296
|
+
priceId: 'price_totally_different',
|
|
297
|
+
metadata: { billingConfigKey: 'pro_monthly' },
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
await expect(
|
|
301
|
+
createCheckoutAlgorithm(request, stripeProvider, authProvider, BILLING_CONFIG)
|
|
302
|
+
).rejects.toThrow();
|
|
303
|
+
|
|
304
|
+
expect(stripeProvider.createCheckoutSession).not.toHaveBeenCalled();
|
|
305
|
+
});
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
// -------------------------------------------------------------------------
|
|
309
|
+
// Auth validation
|
|
310
|
+
// -------------------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
describe('auth validation', () => {
|
|
313
|
+
it('calls getUser with the request userId', async () => {
|
|
314
|
+
await createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG);
|
|
315
|
+
|
|
316
|
+
expect(authProvider.getUser).toHaveBeenCalledWith('user_001');
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('propagates error when getUser rejects', async () => {
|
|
320
|
+
authProvider = makeAuthProvider({
|
|
321
|
+
getUser: vi.fn().mockRejectedValue(new Error('User not found')),
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
await expect(
|
|
325
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
326
|
+
).rejects.toThrow('User not found');
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
it('does not call Stripe when auth fails', async () => {
|
|
330
|
+
authProvider = makeAuthProvider({
|
|
331
|
+
getUser: vi.fn().mockRejectedValue(new Error('Unauthenticated')),
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
await expect(
|
|
335
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
336
|
+
).rejects.toThrow();
|
|
337
|
+
|
|
338
|
+
expect(stripeProvider.createCheckoutSession).not.toHaveBeenCalled();
|
|
339
|
+
});
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
// -------------------------------------------------------------------------
|
|
343
|
+
// Stripe error handling
|
|
344
|
+
// -------------------------------------------------------------------------
|
|
345
|
+
|
|
346
|
+
describe('Stripe error handling', () => {
|
|
347
|
+
it('propagates Stripe API errors', async () => {
|
|
348
|
+
stripeProvider = makeStripeProvider({
|
|
349
|
+
createCheckoutSession: vi.fn().mockRejectedValue(new Error('Stripe network error')),
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
await expect(
|
|
353
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
354
|
+
).rejects.toThrow('Stripe network error');
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
it('propagates Stripe card declined errors', async () => {
|
|
358
|
+
stripeProvider = makeStripeProvider({
|
|
359
|
+
createCheckoutSession: vi
|
|
360
|
+
.fn()
|
|
361
|
+
.mockRejectedValue(new Error('Your card was declined')),
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
await expect(
|
|
365
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
366
|
+
).rejects.toThrow('Your card was declined');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
it('propagates Stripe rate limit errors', async () => {
|
|
370
|
+
stripeProvider = makeStripeProvider({
|
|
371
|
+
createCheckoutSession: vi
|
|
372
|
+
.fn()
|
|
373
|
+
.mockRejectedValue(new Error('Too many requests to the Stripe API')),
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
await expect(
|
|
377
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
378
|
+
).rejects.toThrow('Too many requests');
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
it('does not swallow unknown errors from Stripe', async () => {
|
|
382
|
+
const unexpected = new Error('Unexpected internal Stripe error');
|
|
383
|
+
|
|
384
|
+
stripeProvider = makeStripeProvider({
|
|
385
|
+
createCheckoutSession: vi.fn().mockRejectedValue(unexpected),
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
await expect(
|
|
389
|
+
createCheckoutAlgorithm(BASE_REQUEST, stripeProvider, authProvider, BILLING_CONFIG)
|
|
390
|
+
).rejects.toBe(unexpected);
|
|
391
|
+
});
|
|
392
|
+
});
|
|
393
|
+
});
|