paykitjs 0.0.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 (70) hide show
  1. package/LICENSE +21 -0
  2. package/dist/_virtual/_rolldown/runtime.js +13 -0
  3. package/dist/api/define-route.d.ts +94 -0
  4. package/dist/api/define-route.js +153 -0
  5. package/dist/api/methods.d.ts +422 -0
  6. package/dist/api/methods.js +67 -0
  7. package/dist/cli/commands/check.js +92 -0
  8. package/dist/cli/commands/init.js +264 -0
  9. package/dist/cli/commands/push.js +73 -0
  10. package/dist/cli/commands/telemetry.js +16 -0
  11. package/dist/cli/index.d.ts +1 -0
  12. package/dist/cli/index.js +21 -0
  13. package/dist/cli/templates/index.js +64 -0
  14. package/dist/cli/utils/detect.js +67 -0
  15. package/dist/cli/utils/format.js +58 -0
  16. package/dist/cli/utils/get-config.js +117 -0
  17. package/dist/cli/utils/telemetry.js +103 -0
  18. package/dist/client/index.d.ts +25 -0
  19. package/dist/client/index.js +27 -0
  20. package/dist/core/context.d.ts +17 -0
  21. package/dist/core/context.js +23 -0
  22. package/dist/core/create-paykit.d.ts +7 -0
  23. package/dist/core/create-paykit.js +52 -0
  24. package/dist/core/error-codes.d.ts +12 -0
  25. package/dist/core/error-codes.js +10 -0
  26. package/dist/core/errors.d.ts +41 -0
  27. package/dist/core/errors.js +47 -0
  28. package/dist/core/logger.d.ts +11 -0
  29. package/dist/core/logger.js +51 -0
  30. package/dist/core/utils.js +21 -0
  31. package/dist/customer/customer.api.js +47 -0
  32. package/dist/customer/customer.service.js +342 -0
  33. package/dist/customer/customer.types.d.ts +31 -0
  34. package/dist/database/index.d.ts +8 -0
  35. package/dist/database/index.js +32 -0
  36. package/dist/database/migrations/0000_init.sql +157 -0
  37. package/dist/database/migrations/meta/0000_snapshot.json +1222 -0
  38. package/dist/database/migrations/meta/_journal.json +13 -0
  39. package/dist/database/schema.d.ts +1767 -0
  40. package/dist/database/schema.js +150 -0
  41. package/dist/entitlement/entitlement.api.js +33 -0
  42. package/dist/entitlement/entitlement.service.d.ts +17 -0
  43. package/dist/entitlement/entitlement.service.js +123 -0
  44. package/dist/handlers/next.d.ts +9 -0
  45. package/dist/handlers/next.js +9 -0
  46. package/dist/index.d.ts +14 -0
  47. package/dist/index.js +6 -0
  48. package/dist/invoice/invoice.service.js +54 -0
  49. package/dist/payment/payment.service.js +49 -0
  50. package/dist/payment-method/payment-method.service.js +78 -0
  51. package/dist/product/product-sync.service.js +111 -0
  52. package/dist/product/product.service.js +127 -0
  53. package/dist/providers/provider.d.ts +159 -0
  54. package/dist/providers/stripe.js +547 -0
  55. package/dist/subscription/subscription.api.js +24 -0
  56. package/dist/subscription/subscription.service.js +896 -0
  57. package/dist/subscription/subscription.types.d.ts +18 -0
  58. package/dist/subscription/subscription.types.js +11 -0
  59. package/dist/testing/testing.api.js +29 -0
  60. package/dist/testing/testing.service.js +49 -0
  61. package/dist/types/events.d.ts +181 -0
  62. package/dist/types/instance.d.ts +88 -0
  63. package/dist/types/models.d.ts +11 -0
  64. package/dist/types/options.d.ts +32 -0
  65. package/dist/types/plugin.d.ts +11 -0
  66. package/dist/types/schema.d.ts +99 -0
  67. package/dist/types/schema.js +192 -0
  68. package/dist/webhook/webhook.api.js +29 -0
  69. package/dist/webhook/webhook.service.js +143 -0
  70. package/package.json +72 -0
@@ -0,0 +1,547 @@
1
+ import { PAYKIT_ERROR_CODES, PayKitError } from "../core/errors.js";
2
+ import StripeSdk from "stripe";
3
+ //#region src/providers/stripe.ts
4
+ function toDate(value) {
5
+ return typeof value === "number" ? /* @__PURE__ */ new Date(value * 1e3) : null;
6
+ }
7
+ function getLatestPeriodEnd(subscription) {
8
+ const firstItem = subscription.items.data[0];
9
+ if (!firstItem) return subscription.current_period_end ?? null;
10
+ return subscription.items.data.reduce((latest, item) => {
11
+ return Math.max(latest, item.current_period_end);
12
+ }, firstItem.current_period_end);
13
+ }
14
+ function getEarliestPeriodStart(subscription) {
15
+ const firstItem = subscription.items.data[0];
16
+ if (!firstItem) return subscription.current_period_start ?? null;
17
+ return subscription.items.data.reduce((earliest, item) => {
18
+ return Math.min(earliest, item.current_period_start);
19
+ }, firstItem.current_period_start);
20
+ }
21
+ function getStripeCustomerId(customer) {
22
+ if (!customer) return null;
23
+ return typeof customer === "string" ? customer : customer.id;
24
+ }
25
+ function normalizeStripePaymentMethod(paymentMethod) {
26
+ return {
27
+ expiryMonth: paymentMethod.card?.exp_month ?? void 0,
28
+ expiryYear: paymentMethod.card?.exp_year ?? void 0,
29
+ last4: paymentMethod.card?.last4 ?? void 0,
30
+ providerMethodId: paymentMethod.id,
31
+ type: paymentMethod.type
32
+ };
33
+ }
34
+ function normalizeStripePaymentIntent(paymentIntent) {
35
+ const providerMethodId = typeof paymentIntent.payment_method === "string" ? paymentIntent.payment_method : paymentIntent.payment_method?.id;
36
+ return {
37
+ amount: paymentIntent.amount_received || paymentIntent.amount,
38
+ createdAt: /* @__PURE__ */ new Date(paymentIntent.created * 1e3),
39
+ currency: paymentIntent.currency,
40
+ description: paymentIntent.description,
41
+ metadata: Object.keys(paymentIntent.metadata).length > 0 ? paymentIntent.metadata : void 0,
42
+ providerMethodId,
43
+ providerPaymentId: paymentIntent.id,
44
+ status: paymentIntent.status
45
+ };
46
+ }
47
+ function normalizeStripeInvoice(invoice) {
48
+ return {
49
+ currency: invoice.currency,
50
+ hostedUrl: invoice.hosted_invoice_url,
51
+ periodEndAt: toDate(invoice.period_end),
52
+ periodStartAt: toDate(invoice.period_start),
53
+ providerInvoiceId: invoice.id,
54
+ status: invoice.status,
55
+ totalAmount: invoice.total ?? 0
56
+ };
57
+ }
58
+ function normalizeStripeSubscription(subscription) {
59
+ const firstItem = subscription.items.data[0];
60
+ const providerPriceId = typeof firstItem?.price === "string" ? firstItem.price : firstItem?.price.id;
61
+ const periodStart = getEarliestPeriodStart(subscription);
62
+ const periodEnd = getLatestPeriodEnd(subscription);
63
+ const cancelAt = subscription.cancel_at;
64
+ return {
65
+ cancelAtPeriodEnd: subscription.cancel_at_period_end || cancelAt != null && cancelAt > 0,
66
+ canceledAt: toDate(subscription.canceled_at),
67
+ currentPeriodEndAt: toDate(periodEnd),
68
+ currentPeriodStartAt: toDate(periodStart),
69
+ endedAt: toDate(subscription.ended_at),
70
+ providerPriceId: providerPriceId ?? null,
71
+ providerSubscriptionId: subscription.id,
72
+ providerSubscriptionScheduleId: (typeof subscription.schedule === "string" ? subscription.schedule : subscription.schedule?.id) ?? null,
73
+ status: subscription.status
74
+ };
75
+ }
76
+ function normalizeStripeTestClock(clock) {
77
+ return {
78
+ frozenTime: /* @__PURE__ */ new Date(clock.frozen_time * 1e3),
79
+ id: clock.id,
80
+ name: clock.name ?? null,
81
+ status: clock.status
82
+ };
83
+ }
84
+ function assertStripeTestKey(options) {
85
+ if (!options.secretKey.startsWith("sk_test_")) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_TEST_KEY_REQUIRED);
86
+ }
87
+ async function retrieveExpandedSubscription(client, providerSubscriptionId) {
88
+ return await client.subscriptions.retrieve(providerSubscriptionId, { expand: [
89
+ "items.data.price",
90
+ "latest_invoice.payment_intent",
91
+ "schedule"
92
+ ] });
93
+ }
94
+ function normalizeRequiredAction(paymentIntent) {
95
+ const nextActionType = paymentIntent?.next_action?.type;
96
+ if (!nextActionType) return null;
97
+ return {
98
+ clientSecret: paymentIntent.client_secret ?? void 0,
99
+ paymentIntentId: paymentIntent.id,
100
+ type: nextActionType
101
+ };
102
+ }
103
+ function isPaymentMethodAttachedToCustomer(paymentMethod, stripeCustomerId) {
104
+ if (!stripeCustomerId) return false;
105
+ return getStripeCustomerId(paymentMethod.customer) === stripeCustomerId;
106
+ }
107
+ async function getCheckoutPaymentDetails(client, session) {
108
+ const stripeCustomerId = getStripeCustomerId(session.customer);
109
+ if (!stripeCustomerId) return {
110
+ paymentIntent: null,
111
+ paymentMethod: null
112
+ };
113
+ if (session.mode === "payment" || session.mode === "subscription") {
114
+ const paymentIntentId = typeof session.payment_intent === "string" ? session.payment_intent : session.payment_intent?.id;
115
+ if (paymentIntentId) {
116
+ const paymentIntent = await client.paymentIntents.retrieve(paymentIntentId, { expand: ["payment_method"] });
117
+ const paymentMethod = paymentIntent.payment_method;
118
+ if (paymentMethod && typeof paymentMethod !== "string") return {
119
+ paymentIntent,
120
+ paymentMethod: isPaymentMethodAttachedToCustomer(paymentMethod, stripeCustomerId) ? paymentMethod : null
121
+ };
122
+ }
123
+ if (session.mode === "subscription") {
124
+ const subscriptionId = typeof session.subscription === "string" ? session.subscription : session.subscription?.id;
125
+ if (subscriptionId) {
126
+ const paymentMethod = (await client.subscriptions.retrieve(subscriptionId, { expand: ["default_payment_method"] })).default_payment_method;
127
+ if (paymentMethod && typeof paymentMethod !== "string") return {
128
+ paymentIntent: null,
129
+ paymentMethod: isPaymentMethodAttachedToCustomer(paymentMethod, stripeCustomerId) ? paymentMethod : null
130
+ };
131
+ }
132
+ }
133
+ return {
134
+ paymentIntent: null,
135
+ paymentMethod: null
136
+ };
137
+ }
138
+ if (session.mode === "setup") {
139
+ const setupIntentId = typeof session.setup_intent === "string" ? session.setup_intent : session.setup_intent?.id;
140
+ if (!setupIntentId) return {
141
+ paymentIntent: null,
142
+ paymentMethod: null
143
+ };
144
+ const paymentMethod = (await client.setupIntents.retrieve(setupIntentId, { expand: ["payment_method"] })).payment_method;
145
+ if (!paymentMethod || typeof paymentMethod === "string") return {
146
+ paymentIntent: null,
147
+ paymentMethod: null
148
+ };
149
+ return {
150
+ paymentIntent: null,
151
+ paymentMethod: isPaymentMethodAttachedToCustomer(paymentMethod, stripeCustomerId) ? paymentMethod : null
152
+ };
153
+ }
154
+ return {
155
+ paymentIntent: null,
156
+ paymentMethod: null
157
+ };
158
+ }
159
+ async function createCheckoutCompletedEvents(client, event) {
160
+ if (event.type !== "checkout.session.completed") return [];
161
+ const session = event.data.object;
162
+ const stripeCustomerId = getStripeCustomerId(session.customer);
163
+ const providerCustomerId = session.client_reference_id ?? stripeCustomerId;
164
+ if (!providerCustomerId) return [];
165
+ const events = [];
166
+ const { paymentIntent, paymentMethod } = await getCheckoutPaymentDetails(client, session);
167
+ const providerSubscriptionId = typeof session.subscription === "string" ? session.subscription : session.subscription?.id ?? null;
168
+ const providerInvoiceId = typeof session.invoice === "string" ? session.invoice : session.invoice?.id ?? null;
169
+ const expandedSubscription = session.mode === "subscription" && providerSubscriptionId ? await retrieveExpandedSubscription(client, providerSubscriptionId) : null;
170
+ const expandedInvoice = providerInvoiceId != null ? await client.invoices.retrieve(providerInvoiceId, { expand: ["payment_intent"] }) : null;
171
+ if (paymentMethod) {
172
+ const normalizedPaymentMethod = {
173
+ ...normalizeStripePaymentMethod(paymentMethod),
174
+ isDefault: session.mode === "subscription"
175
+ };
176
+ events.push({
177
+ actions: [{
178
+ data: {
179
+ paymentMethod: normalizedPaymentMethod,
180
+ providerCustomerId
181
+ },
182
+ type: "payment_method.upsert"
183
+ }],
184
+ name: "payment_method.attached",
185
+ payload: {
186
+ paymentMethod: normalizedPaymentMethod,
187
+ providerCustomerId
188
+ }
189
+ });
190
+ }
191
+ if (session.mode === "payment" && paymentIntent?.status === "succeeded") {
192
+ const normalizedPayment = normalizeStripePaymentIntent(paymentIntent);
193
+ events.push({
194
+ actions: [{
195
+ data: {
196
+ payment: normalizedPayment,
197
+ providerCustomerId
198
+ },
199
+ type: "payment.upsert"
200
+ }],
201
+ name: "payment.succeeded",
202
+ payload: {
203
+ payment: normalizedPayment,
204
+ providerCustomerId
205
+ }
206
+ });
207
+ }
208
+ const sessionMetadata = session.metadata ?? {};
209
+ events.push({
210
+ name: "checkout.completed",
211
+ payload: {
212
+ checkoutSessionId: session.id,
213
+ invoice: expandedInvoice ? normalizeStripeInvoice(expandedInvoice) : void 0,
214
+ metadata: Object.keys(sessionMetadata).length > 0 ? sessionMetadata : void 0,
215
+ mode: session.mode ?? void 0,
216
+ paymentStatus: session.payment_status,
217
+ providerCustomerId,
218
+ providerEventId: event.id,
219
+ providerInvoiceId: providerInvoiceId ?? void 0,
220
+ providerSubscriptionId: providerSubscriptionId ?? void 0,
221
+ status: session.status,
222
+ subscription: expandedSubscription ? normalizeStripeSubscription(expandedSubscription) : void 0
223
+ }
224
+ });
225
+ return events;
226
+ }
227
+ async function createSubscriptionEvents(event) {
228
+ if (event.type !== "customer.subscription.created" && event.type !== "customer.subscription.updated" && event.type !== "customer.subscription.deleted") return [];
229
+ const subscription = event.data.object;
230
+ const providerCustomerId = getStripeCustomerId(subscription.customer);
231
+ if (!providerCustomerId) return [];
232
+ if (event.type === "customer.subscription.deleted") return [{
233
+ actions: [{
234
+ data: {
235
+ providerCustomerId,
236
+ providerSubscriptionId: subscription.id
237
+ },
238
+ type: "subscription.delete"
239
+ }],
240
+ name: "subscription.deleted",
241
+ payload: {
242
+ providerCustomerId,
243
+ providerEventId: event.id,
244
+ providerSubscriptionId: subscription.id
245
+ }
246
+ }];
247
+ const normalizedSubscription = normalizeStripeSubscription(subscription);
248
+ return [{
249
+ actions: [{
250
+ data: {
251
+ providerCustomerId,
252
+ subscription: normalizedSubscription
253
+ },
254
+ type: "subscription.upsert"
255
+ }],
256
+ name: "subscription.updated",
257
+ payload: {
258
+ providerCustomerId,
259
+ providerEventId: event.id,
260
+ subscription: normalizedSubscription
261
+ }
262
+ }];
263
+ }
264
+ function createInvoiceEvents(event) {
265
+ if (event.type !== "invoice.created" && event.type !== "invoice.finalized" && event.type !== "invoice.paid" && event.type !== "invoice.payment_failed" && event.type !== "invoice.updated") return [];
266
+ const invoice = event.data.object;
267
+ const providerCustomerId = getStripeCustomerId(invoice.customer);
268
+ if (!providerCustomerId) return [];
269
+ const providerSubscriptionId = typeof invoice.subscription === "string" ? invoice.subscription : invoice.subscription?.id ?? null;
270
+ const normalizedInvoice = normalizeStripeInvoice(invoice);
271
+ return [{
272
+ actions: [{
273
+ data: {
274
+ invoice: normalizedInvoice,
275
+ providerCustomerId,
276
+ providerSubscriptionId
277
+ },
278
+ type: "invoice.upsert"
279
+ }],
280
+ name: "invoice.updated",
281
+ payload: {
282
+ invoice: normalizedInvoice,
283
+ providerCustomerId,
284
+ providerEventId: event.id,
285
+ providerSubscriptionId
286
+ }
287
+ }];
288
+ }
289
+ function createDetachedPaymentMethodEvents(event) {
290
+ if (event.type !== "payment_method.detached") return [];
291
+ const paymentMethod = event.data.object;
292
+ return [{
293
+ actions: [{
294
+ data: { providerMethodId: paymentMethod.id },
295
+ type: "payment_method.delete"
296
+ }],
297
+ name: "payment_method.detached",
298
+ payload: {
299
+ providerEventId: event.id,
300
+ providerMethodId: paymentMethod.id
301
+ }
302
+ }];
303
+ }
304
+ function createStripeProvider(client, options) {
305
+ const currency = options.currency ?? "usd";
306
+ return {
307
+ async upsertCustomer(data) {
308
+ let testClock;
309
+ if (data.createTestClock) {
310
+ assertStripeTestKey(options);
311
+ testClock = normalizeStripeTestClock(await client.testHelpers.testClocks.create({
312
+ frozen_time: Math.floor(Date.now() / 1e3),
313
+ name: data.id
314
+ }));
315
+ }
316
+ return { providerCustomer: {
317
+ id: (await client.customers.create({
318
+ email: data.email,
319
+ metadata: {
320
+ customerId: data.id,
321
+ ...data.metadata
322
+ },
323
+ name: data.name,
324
+ test_clock: testClock?.id
325
+ })).id,
326
+ frozenTime: testClock?.frozenTime.toISOString(),
327
+ testClockId: testClock?.id
328
+ } };
329
+ },
330
+ async deleteCustomer(data) {
331
+ await client.customers.del(data.providerCustomerId);
332
+ },
333
+ async getTestClock(data) {
334
+ return normalizeStripeTestClock(await client.testHelpers.testClocks.retrieve(data.testClockId));
335
+ },
336
+ async advanceTestClock(data) {
337
+ assertStripeTestKey(options);
338
+ await client.testHelpers.testClocks.advance(data.testClockId, { frozen_time: Math.floor(data.frozenTime.getTime() / 1e3) });
339
+ for (let i = 0; i < 60; i++) {
340
+ const clock = await client.testHelpers.testClocks.retrieve(data.testClockId);
341
+ if (clock.status === "ready") return normalizeStripeTestClock(clock);
342
+ await new Promise((resolve) => setTimeout(resolve, 2e3));
343
+ }
344
+ throw new Error(`Test clock ${data.testClockId} did not reach 'ready' status`);
345
+ },
346
+ async attachPaymentMethod(data) {
347
+ const session = await client.checkout.sessions.create({
348
+ cancel_url: data.returnURL,
349
+ client_reference_id: data.providerCustomerId,
350
+ customer: data.providerCustomerId,
351
+ mode: "setup",
352
+ success_url: data.returnURL
353
+ });
354
+ if (!session.url) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SESSION_INVALID);
355
+ return { url: session.url };
356
+ },
357
+ async createSubscriptionCheckout(data) {
358
+ const sessionParams = {
359
+ cancel_url: data.cancelUrl ?? data.successUrl,
360
+ client_reference_id: data.providerCustomerId,
361
+ customer: data.providerCustomerId,
362
+ line_items: [{
363
+ price: data.providerPriceId,
364
+ quantity: 1
365
+ }],
366
+ metadata: data.metadata,
367
+ mode: "subscription",
368
+ success_url: data.successUrl
369
+ };
370
+ const session = await client.checkout.sessions.create(sessionParams);
371
+ if (!session.url) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SESSION_INVALID);
372
+ return {
373
+ paymentUrl: session.url,
374
+ providerCheckoutSessionId: session.id
375
+ };
376
+ },
377
+ async createSubscription(data) {
378
+ const createParams = {
379
+ customer: data.providerCustomerId,
380
+ items: [{ price: data.providerPriceId }],
381
+ payment_behavior: "default_incomplete",
382
+ expand: ["latest_invoice.payment_intent"]
383
+ };
384
+ const createdSubscription = await client.subscriptions.create(createParams);
385
+ const latestInvoice = createdSubscription.latest_invoice;
386
+ return {
387
+ invoice: latestInvoice && typeof latestInvoice !== "string" ? normalizeStripeInvoice(latestInvoice) : null,
388
+ paymentUrl: null,
389
+ requiredAction: normalizeRequiredAction((latestInvoice && typeof latestInvoice !== "string" ? latestInvoice.payment_intent : null) ?? null),
390
+ subscription: normalizeStripeSubscription(createdSubscription)
391
+ };
392
+ },
393
+ async updateSubscription(data) {
394
+ const currentItem = (await retrieveExpandedSubscription(client, data.providerSubscriptionId)).items.data[0];
395
+ if (!currentItem) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SUBSCRIPTION_MISSING_ITEMS);
396
+ const updatedSubscription = await client.subscriptions.update(data.providerSubscriptionId, {
397
+ items: [{
398
+ id: currentItem.id,
399
+ price: data.providerPriceId
400
+ }],
401
+ payment_behavior: "pending_if_incomplete",
402
+ proration_behavior: "always_invoice",
403
+ expand: ["latest_invoice.payment_intent"]
404
+ });
405
+ const latestInvoice = updatedSubscription.latest_invoice;
406
+ return {
407
+ invoice: latestInvoice && typeof latestInvoice !== "string" ? normalizeStripeInvoice(latestInvoice) : null,
408
+ paymentUrl: null,
409
+ requiredAction: normalizeRequiredAction((latestInvoice && typeof latestInvoice !== "string" ? latestInvoice.payment_intent : null) ?? null),
410
+ subscription: normalizeStripeSubscription(updatedSubscription)
411
+ };
412
+ },
413
+ async scheduleSubscriptionChange(data) {
414
+ if (!data.providerPriceId) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_PRICE_REQUIRED);
415
+ const currentSub = await client.subscriptions.retrieve(data.providerSubscriptionId, { expand: ["items"] });
416
+ const periodEndSeconds = getLatestPeriodEnd(currentSub);
417
+ if (typeof periodEndSeconds !== "number") throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SUBSCRIPTION_MISSING_PERIOD);
418
+ const currentItems = currentSub.items.data.map((item) => ({
419
+ price: item.price.id,
420
+ quantity: 1
421
+ }));
422
+ let schedule;
423
+ if (data.providerSubscriptionScheduleId) schedule = await client.subscriptionSchedules.retrieve(data.providerSubscriptionScheduleId);
424
+ else {
425
+ const existingScheduleId = typeof currentSub.schedule === "string" ? currentSub.schedule : currentSub.schedule?.id ?? null;
426
+ schedule = existingScheduleId ? await client.subscriptionSchedules.retrieve(existingScheduleId) : await client.subscriptionSchedules.create({ from_subscription: data.providerSubscriptionId });
427
+ }
428
+ const scheduleId = schedule.id;
429
+ const currentPhaseStart = schedule.phases[0]?.start_date ?? Math.floor(Date.now() / 1e3);
430
+ await client.subscriptionSchedules.update(scheduleId, {
431
+ end_behavior: "release",
432
+ phases: [{
433
+ items: currentItems,
434
+ start_date: currentPhaseStart,
435
+ end_date: periodEndSeconds
436
+ }, {
437
+ items: [{
438
+ price: data.providerPriceId,
439
+ quantity: 1
440
+ }],
441
+ start_date: periodEndSeconds
442
+ }]
443
+ });
444
+ return {
445
+ paymentUrl: null,
446
+ requiredAction: null,
447
+ subscription: normalizeStripeSubscription(await retrieveExpandedSubscription(client, data.providerSubscriptionId))
448
+ };
449
+ },
450
+ async cancelSubscription(data) {
451
+ const currentSubscription = await client.subscriptions.retrieve(data.providerSubscriptionId);
452
+ let scheduleId = data.providerSubscriptionScheduleId ?? null;
453
+ if (!scheduleId) scheduleId = typeof currentSubscription.schedule === "string" ? currentSubscription.schedule : currentSubscription.schedule?.id ?? null;
454
+ if (scheduleId) {
455
+ const schedule = await client.subscriptionSchedules.retrieve(scheduleId);
456
+ if (schedule.status !== "released" && schedule.status !== "canceled") await client.subscriptionSchedules.release(scheduleId);
457
+ }
458
+ return {
459
+ paymentUrl: null,
460
+ requiredAction: null,
461
+ subscription: normalizeStripeSubscription(await client.subscriptions.update(data.providerSubscriptionId, { cancel_at_period_end: true }))
462
+ };
463
+ },
464
+ async listActiveSubscriptions(data) {
465
+ return (await client.subscriptions.list({
466
+ customer: data.providerCustomerId,
467
+ status: "active"
468
+ })).data.map((sub) => ({ providerSubscriptionId: sub.id }));
469
+ },
470
+ async resumeSubscription(data) {
471
+ let scheduleId = data.providerSubscriptionScheduleId ?? null;
472
+ if (!scheduleId) {
473
+ const sub = await client.subscriptions.retrieve(data.providerSubscriptionId);
474
+ scheduleId = typeof sub.schedule === "string" ? sub.schedule : sub.schedule?.id ?? null;
475
+ }
476
+ if (scheduleId) {
477
+ const schedule = await client.subscriptionSchedules.retrieve(scheduleId);
478
+ if (schedule.status !== "released" && schedule.status !== "canceled") await client.subscriptionSchedules.release(scheduleId);
479
+ }
480
+ return {
481
+ paymentUrl: null,
482
+ requiredAction: null,
483
+ subscription: normalizeStripeSubscription(await client.subscriptions.update(data.providerSubscriptionId, { cancel_at_period_end: false }))
484
+ };
485
+ },
486
+ async detachPaymentMethod(data) {
487
+ await client.paymentMethods.detach(data.providerMethodId);
488
+ },
489
+ async syncProduct(data) {
490
+ let providerProductId = data.existingProviderProductId;
491
+ if (!providerProductId) providerProductId = (await client.products.create({
492
+ metadata: { paykit_product_id: data.id },
493
+ name: data.name
494
+ })).id;
495
+ else await client.products.update(providerProductId, { name: data.name });
496
+ if (data.existingProviderPriceId) return {
497
+ providerPriceId: data.existingProviderPriceId,
498
+ providerProductId
499
+ };
500
+ const priceParams = {
501
+ currency,
502
+ product: providerProductId,
503
+ unit_amount: data.priceAmount
504
+ };
505
+ if (data.priceInterval) priceParams.recurring = { interval: data.priceInterval };
506
+ return {
507
+ providerPriceId: (await client.prices.create(priceParams)).id,
508
+ providerProductId
509
+ };
510
+ },
511
+ async createInvoice(data) {
512
+ const stripeInvoice = await client.invoices.create({
513
+ auto_advance: data.autoAdvance ?? true,
514
+ collection_method: "charge_automatically",
515
+ customer: data.providerCustomerId,
516
+ currency
517
+ });
518
+ if (data.lines.length > 0) await client.invoices.addLines(stripeInvoice.id, { lines: data.lines.map((line) => ({
519
+ amount: line.amount,
520
+ description: line.description
521
+ })) });
522
+ return normalizeStripeInvoice(await client.invoices.finalizeInvoice(stripeInvoice.id));
523
+ },
524
+ async handleWebhook(data) {
525
+ const signature = data.headers["stripe-signature"];
526
+ if (!signature) throw PayKitError.from("BAD_REQUEST", PAYKIT_ERROR_CODES.PROVIDER_SIGNATURE_MISSING);
527
+ const event = client.webhooks.constructEvent(data.body, signature, options.webhookSecret);
528
+ return [
529
+ ...await createCheckoutCompletedEvents(client, event),
530
+ ...await createSubscriptionEvents(event),
531
+ ...createInvoiceEvents(event),
532
+ ...createDetachedPaymentMethodEvents(event)
533
+ ];
534
+ },
535
+ async createPortalSession(data) {
536
+ return { url: (await client.billingPortal.sessions.create({
537
+ customer: data.providerCustomerId,
538
+ return_url: data.returnUrl
539
+ })).url };
540
+ }
541
+ };
542
+ }
543
+ function createStripeRuntime(options) {
544
+ return createStripeProvider(new StripeSdk(options.secretKey), options);
545
+ }
546
+ //#endregion
547
+ export { createStripeRuntime };
@@ -0,0 +1,24 @@
1
+ import { subscribeToPlan } from "./subscription.service.js";
2
+ import { definePayKitMethod } from "../api/define-route.js";
3
+ import { subscribeBodySchema } from "./subscription.types.js";
4
+ //#region src/subscription/subscription.api.ts
5
+ /** Applies a subscription change for the resolved customer. */
6
+ const subscribe = definePayKitMethod({
7
+ input: subscribeBodySchema,
8
+ requireCustomer: true,
9
+ route: {
10
+ client: true,
11
+ method: "POST",
12
+ path: "/subscribe"
13
+ }
14
+ }, async (ctx) => {
15
+ return subscribeToPlan(ctx.paykit, {
16
+ customerId: ctx.customer.id,
17
+ forceCheckout: ctx.input.forceCheckout,
18
+ planId: ctx.input.planId,
19
+ successUrl: ctx.input.successUrl,
20
+ cancelUrl: ctx.input.cancelUrl
21
+ });
22
+ });
23
+ //#endregion
24
+ export { subscribe };