@farm.js/stripe 0.1.0-beta.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.
@@ -0,0 +1,645 @@
1
+ function requireOrmModel(options) {
2
+ const modelClient = options.orm?.[options.model];
3
+ if (!modelClient || typeof modelClient !== "object") {
4
+ throw new Error(`Stripe ORM storage adapter could not find orm.${options.model}.`);
5
+ }
6
+ const candidate = modelClient;
7
+ if (typeof candidate.findFirst !== "function" ||
8
+ typeof candidate.create !== "function" ||
9
+ typeof candidate.update !== "function") {
10
+ throw new Error(`Stripe ORM storage adapter expected orm.${options.model} to expose findFirst, create, and update.`);
11
+ }
12
+ return candidate;
13
+ }
14
+ function createBillingSnapshotData(snapshot) {
15
+ return {
16
+ ownerId: snapshot.owner.id,
17
+ ownerKind: snapshot.owner.kind,
18
+ stripeCustomerId: snapshot.stripeCustomerId,
19
+ stripeSubscriptionId: snapshot.stripeSubscriptionId,
20
+ planId: snapshot.planId,
21
+ productId: snapshot.productId,
22
+ status: snapshot.status,
23
+ currentPeriodEnd: snapshot.currentPeriodEnd,
24
+ cancelAtPeriodEnd: snapshot.cancelAtPeriodEnd,
25
+ trialEndsAt: snapshot.trialEndsAt,
26
+ trialUsedAt: snapshot.trialUsedAt,
27
+ seatQuantity: snapshot.seatQuantity,
28
+ seatAllowanceOverride: snapshot.seatAllowanceOverride,
29
+ updatedAt: new Date(),
30
+ };
31
+ }
32
+ function isUnknownPrismaFieldError(error, field) {
33
+ if (!(error instanceof Error)) {
34
+ return false;
35
+ }
36
+ const normalizedMessage = error.message.replace(/\s+/g, " ");
37
+ return normalizedMessage.includes(`Unknown argument \`${field}\``);
38
+ }
39
+ async function withPrismaDataFieldsFallback(operation, data, fallbackFields) {
40
+ let nextData = { ...data };
41
+ for (const field of fallbackFields) {
42
+ try {
43
+ return await operation(nextData);
44
+ }
45
+ catch (error) {
46
+ if (!isUnknownPrismaFieldError(error, field)) {
47
+ throw error;
48
+ }
49
+ delete nextData[field];
50
+ }
51
+ }
52
+ return await operation(nextData);
53
+ }
54
+ function createBillingRecordId() {
55
+ const randomUuid = globalThis.crypto?.randomUUID?.();
56
+ if (randomUuid) {
57
+ return randomUuid;
58
+ }
59
+ return `farm-billing-${Date.now()}-${Math.random().toString(16).slice(2)}`;
60
+ }
61
+ function getRecordValue(record, ...keys) {
62
+ for (const key of keys) {
63
+ const value = record[key];
64
+ if (value !== undefined) {
65
+ return value;
66
+ }
67
+ }
68
+ return undefined;
69
+ }
70
+ function getNullableString(record, ...keys) {
71
+ const value = getRecordValue(record, ...keys);
72
+ return typeof value === "string" && value.length > 0 ? value : null;
73
+ }
74
+ function getNullableInteger(record, ...keys) {
75
+ const value = getRecordValue(record, ...keys);
76
+ if (typeof value === "number" && Number.isInteger(value)) {
77
+ return value;
78
+ }
79
+ if (typeof value === "string" && value.trim().length > 0) {
80
+ const parsed = Number(value);
81
+ if (Number.isInteger(parsed)) {
82
+ return parsed;
83
+ }
84
+ }
85
+ return null;
86
+ }
87
+ function getBooleanValue(record, ...keys) {
88
+ const value = getRecordValue(record, ...keys);
89
+ if (typeof value === "number") {
90
+ return value !== 0;
91
+ }
92
+ return Boolean(value);
93
+ }
94
+ function toSnapshot(record) {
95
+ return {
96
+ owner: {
97
+ kind: getRecordValue(record, "ownerKind", "owner_kind") === "organization"
98
+ ? "organization"
99
+ : "user",
100
+ id: String(getRecordValue(record, "ownerId", "owner_id") ?? ""),
101
+ },
102
+ planId: String(getRecordValue(record, "planId", "plan_id") ?? "free"),
103
+ productId: getNullableString(record, "productId", "product_id"),
104
+ status: normalizeStatus(getRecordValue(record, "status")),
105
+ stripeCustomerId: getNullableString(record, "stripeCustomerId", "stripe_customer_id"),
106
+ stripeSubscriptionId: getNullableString(record, "stripeSubscriptionId", "stripe_subscription_id"),
107
+ currentPeriodEnd: getRecordValue(record, "currentPeriodEnd", "current_period_end") instanceof Date
108
+ ? getRecordValue(record, "currentPeriodEnd", "current_period_end")
109
+ : typeof getRecordValue(record, "currentPeriodEnd", "current_period_end") === "string"
110
+ ? new Date(String(getRecordValue(record, "currentPeriodEnd", "current_period_end")))
111
+ : null,
112
+ cancelAtPeriodEnd: getBooleanValue(record, "cancelAtPeriodEnd", "cancel_at_period_end"),
113
+ trialEndsAt: getRecordValue(record, "trialEndsAt", "trial_ends_at") instanceof Date
114
+ ? getRecordValue(record, "trialEndsAt", "trial_ends_at")
115
+ : typeof getRecordValue(record, "trialEndsAt", "trial_ends_at") === "string"
116
+ ? new Date(String(getRecordValue(record, "trialEndsAt", "trial_ends_at")))
117
+ : null,
118
+ trialUsedAt: getRecordValue(record, "trialUsedAt", "trial_used_at") instanceof Date
119
+ ? getRecordValue(record, "trialUsedAt", "trial_used_at")
120
+ : typeof getRecordValue(record, "trialUsedAt", "trial_used_at") === "string"
121
+ ? new Date(String(getRecordValue(record, "trialUsedAt", "trial_used_at")))
122
+ : null,
123
+ seatQuantity: getNullableInteger(record, "seatQuantity", "seat_quantity"),
124
+ seatAllowanceOverride: getNullableInteger(record, "seatAllowanceOverride", "seat_allowance_override"),
125
+ };
126
+ }
127
+ function normalizeStatus(value) {
128
+ switch (value) {
129
+ case "trialing":
130
+ case "active":
131
+ case "past_due":
132
+ case "canceled":
133
+ case "unpaid":
134
+ case "incomplete":
135
+ return value;
136
+ default:
137
+ return "free";
138
+ }
139
+ }
140
+ function requirePrismaDelegate(options) {
141
+ const modelName = options.model ?? "billingBillingAccount";
142
+ const delegate = options.prisma?.[modelName];
143
+ if (!delegate || typeof delegate !== "object") {
144
+ throw new Error(`Stripe Prisma storage adapter could not find prisma.${modelName}.`);
145
+ }
146
+ return delegate;
147
+ }
148
+ export function prismaStorageAdapter(options) {
149
+ const delegate = requirePrismaDelegate(options);
150
+ async function findByOwner(owner) {
151
+ return await delegate.findFirst({
152
+ where: {
153
+ ownerKind: owner.kind,
154
+ ownerId: owner.id,
155
+ },
156
+ });
157
+ }
158
+ async function findByCustomerId(customerId) {
159
+ return await delegate.findFirst({
160
+ where: {
161
+ stripeCustomerId: customerId,
162
+ },
163
+ });
164
+ }
165
+ return {
166
+ async getBillingAccount(owner) {
167
+ const record = await findByOwner(owner);
168
+ return record ? toSnapshot(record) : null;
169
+ },
170
+ async getBillingAccountByStripeCustomerId(customerId) {
171
+ const record = await findByCustomerId(customerId);
172
+ return record ? toSnapshot(record) : null;
173
+ },
174
+ async ensureCustomer({ owner, stripe }) {
175
+ const existing = await findByOwner(owner);
176
+ if (typeof existing?.stripeCustomerId === "string" && existing.stripeCustomerId) {
177
+ return {
178
+ customerId: existing.stripeCustomerId,
179
+ };
180
+ }
181
+ const customer = await stripe.customers.create({
182
+ email: owner.email,
183
+ metadata: {
184
+ ownerId: owner.id,
185
+ ownerKind: owner.kind,
186
+ },
187
+ });
188
+ if (existing?.id) {
189
+ await delegate.update({
190
+ where: { id: existing.id },
191
+ data: {
192
+ stripeCustomerId: customer.id,
193
+ },
194
+ });
195
+ }
196
+ else {
197
+ await withPrismaDataFieldsFallback((data) => delegate.create({
198
+ data,
199
+ }), {
200
+ ownerId: owner.id,
201
+ ownerKind: owner.kind,
202
+ stripeCustomerId: customer.id,
203
+ planId: "free",
204
+ productId: null,
205
+ status: "free",
206
+ cancelAtPeriodEnd: false,
207
+ trialEndsAt: null,
208
+ trialUsedAt: null,
209
+ seatQuantity: null,
210
+ seatAllowanceOverride: null,
211
+ }, ["seatAllowanceOverride", "seatQuantity", "trialUsedAt", "trialEndsAt", "productId"]);
212
+ }
213
+ return {
214
+ customerId: customer.id,
215
+ };
216
+ },
217
+ async saveBillingSnapshot(snapshot) {
218
+ const existing = await findByOwner(snapshot.owner);
219
+ const data = {
220
+ ownerId: snapshot.owner.id,
221
+ ownerKind: snapshot.owner.kind,
222
+ stripeCustomerId: snapshot.stripeCustomerId,
223
+ stripeSubscriptionId: snapshot.stripeSubscriptionId,
224
+ planId: snapshot.planId,
225
+ productId: snapshot.productId,
226
+ status: snapshot.status,
227
+ currentPeriodEnd: snapshot.currentPeriodEnd,
228
+ cancelAtPeriodEnd: snapshot.cancelAtPeriodEnd,
229
+ trialEndsAt: snapshot.trialEndsAt,
230
+ trialUsedAt: snapshot.trialUsedAt,
231
+ seatQuantity: snapshot.seatQuantity,
232
+ seatAllowanceOverride: snapshot.seatAllowanceOverride,
233
+ };
234
+ if (existing?.id) {
235
+ await withPrismaDataFieldsFallback((retryData) => delegate.update({
236
+ where: { id: existing.id },
237
+ data: retryData,
238
+ }), data, ["seatAllowanceOverride", "seatQuantity", "trialUsedAt", "trialEndsAt", "productId"]);
239
+ return;
240
+ }
241
+ await withPrismaDataFieldsFallback((retryData) => delegate.create({
242
+ data: retryData,
243
+ }), data, ["seatAllowanceOverride", "seatQuantity", "trialUsedAt", "trialEndsAt", "productId"]);
244
+ },
245
+ async clearBillingSnapshot(owner) {
246
+ const existing = await findByOwner(owner);
247
+ if (!existing?.id) {
248
+ return;
249
+ }
250
+ await withPrismaDataFieldsFallback((data) => delegate.update({
251
+ where: { id: existing.id },
252
+ data,
253
+ }), {
254
+ planId: "free",
255
+ productId: null,
256
+ status: "free",
257
+ stripeSubscriptionId: null,
258
+ currentPeriodEnd: null,
259
+ cancelAtPeriodEnd: false,
260
+ trialEndsAt: null,
261
+ seatQuantity: null,
262
+ }, ["seatQuantity", "trialEndsAt", "productId"]);
263
+ },
264
+ };
265
+ }
266
+ export function ormStorageAdapter(options) {
267
+ const modelName = options.model ?? "billingAccount";
268
+ let modelPromise;
269
+ async function getModel() {
270
+ modelPromise ??= Promise.resolve(typeof options.orm === "function" ? options.orm() : options.orm).then((orm) => requireOrmModel({
271
+ orm,
272
+ model: modelName,
273
+ }));
274
+ return modelPromise;
275
+ }
276
+ async function findByOwner(owner) {
277
+ const model = await getModel();
278
+ return await model.findFirst({
279
+ where: {
280
+ ownerKind: owner.kind,
281
+ ownerId: owner.id,
282
+ },
283
+ });
284
+ }
285
+ async function findByCustomerId(customerId) {
286
+ const model = await getModel();
287
+ return await model.findFirst({
288
+ where: {
289
+ stripeCustomerId: customerId,
290
+ },
291
+ });
292
+ }
293
+ return {
294
+ async getBillingAccount(owner) {
295
+ const record = await findByOwner(owner);
296
+ return record ? toSnapshot(record) : null;
297
+ },
298
+ async getBillingAccountByStripeCustomerId(customerId) {
299
+ const record = await findByCustomerId(customerId);
300
+ return record ? toSnapshot(record) : null;
301
+ },
302
+ async ensureCustomer({ owner, stripe }) {
303
+ const existing = await findByOwner(owner);
304
+ const existingCustomerId = existing && getNullableString(existing, "stripeCustomerId", "stripe_customer_id");
305
+ if (existingCustomerId) {
306
+ return {
307
+ customerId: existingCustomerId,
308
+ };
309
+ }
310
+ const customer = await stripe.customers.create({
311
+ email: owner.email,
312
+ metadata: {
313
+ ownerId: owner.id,
314
+ ownerKind: owner.kind,
315
+ },
316
+ });
317
+ const model = await getModel();
318
+ if (existing?.id) {
319
+ await model.update({
320
+ where: {
321
+ id: existing.id,
322
+ },
323
+ data: {
324
+ stripeCustomerId: customer.id,
325
+ updatedAt: new Date(),
326
+ },
327
+ });
328
+ }
329
+ else {
330
+ await model.create({
331
+ data: {
332
+ id: createBillingRecordId(),
333
+ ownerId: owner.id,
334
+ ownerKind: owner.kind,
335
+ stripeCustomerId: customer.id,
336
+ stripeSubscriptionId: null,
337
+ planId: "free",
338
+ productId: null,
339
+ status: "free",
340
+ currentPeriodEnd: null,
341
+ cancelAtPeriodEnd: false,
342
+ trialEndsAt: null,
343
+ trialUsedAt: null,
344
+ seatQuantity: null,
345
+ seatAllowanceOverride: null,
346
+ createdAt: new Date(),
347
+ updatedAt: new Date(),
348
+ },
349
+ });
350
+ }
351
+ return {
352
+ customerId: customer.id,
353
+ };
354
+ },
355
+ async saveBillingSnapshot(snapshot) {
356
+ const existing = await findByOwner(snapshot.owner);
357
+ const data = createBillingSnapshotData(snapshot);
358
+ const model = await getModel();
359
+ if (existing?.id) {
360
+ await model.update({
361
+ where: {
362
+ id: existing.id,
363
+ },
364
+ data,
365
+ });
366
+ return;
367
+ }
368
+ await model.create({
369
+ data: {
370
+ id: createBillingRecordId(),
371
+ ...data,
372
+ createdAt: new Date(),
373
+ },
374
+ });
375
+ },
376
+ async clearBillingSnapshot(owner) {
377
+ const existing = await findByOwner(owner);
378
+ if (!existing?.id) {
379
+ return;
380
+ }
381
+ const model = await getModel();
382
+ await model.update({
383
+ where: {
384
+ id: existing.id,
385
+ },
386
+ data: {
387
+ planId: "free",
388
+ productId: null,
389
+ status: "free",
390
+ stripeSubscriptionId: null,
391
+ currentPeriodEnd: null,
392
+ cancelAtPeriodEnd: false,
393
+ trialEndsAt: null,
394
+ seatQuantity: null,
395
+ updatedAt: new Date(),
396
+ },
397
+ });
398
+ },
399
+ };
400
+ }
401
+ export function sqliteStorageAdapter(options) {
402
+ const tableName = options.tableName ?? "billing_account";
403
+ const db = options.db;
404
+ const tableInfoStatement = db.prepare(`PRAGMA table_info("${tableName}")`);
405
+ const supportedColumns = typeof tableInfoStatement.all === "function"
406
+ ? new Set(tableInfoStatement.all().map((row) => String(row.name ?? "")))
407
+ : null;
408
+ const hasColumn = (columnName) => supportedColumns ? supportedColumns.has(columnName) : true;
409
+ const supportsTrialColumns = hasColumn("trial_ends_at") && hasColumn("trial_used_at");
410
+ const supportsSeatQuantityColumn = hasColumn("seat_quantity");
411
+ const supportsSeatAllowanceOverrideColumn = hasColumn("seat_allowance_override");
412
+ const selectByOwner = db.prepare(`SELECT * FROM "${tableName}" WHERE owner_kind = ? AND owner_id = ? LIMIT 1`);
413
+ const selectByCustomerId = db.prepare(`SELECT * FROM "${tableName}" WHERE stripe_customer_id = ? LIMIT 1`);
414
+ const insertRecord = db.prepare(`INSERT INTO "${tableName}" (
415
+ id,
416
+ owner_id,
417
+ owner_kind,
418
+ stripe_customer_id,
419
+ stripe_subscription_id,
420
+ plan_id,
421
+ product_id,
422
+ status,
423
+ current_period_end,
424
+ cancel_at_period_end,
425
+ ${supportsTrialColumns ? "trial_ends_at," : ""}
426
+ ${supportsTrialColumns ? "trial_used_at," : ""}
427
+ ${supportsSeatQuantityColumn ? "seat_quantity," : ""}
428
+ ${supportsSeatAllowanceOverrideColumn ? "seat_allowance_override," : ""}
429
+ created_at,
430
+ updated_at
431
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ${supportsTrialColumns ? "?, ?, " : ""}${supportsSeatQuantityColumn ? "?, " : ""}${supportsSeatAllowanceOverrideColumn ? "?, " : ""}CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`);
432
+ const updateByOwner = db.prepare(`UPDATE "${tableName}" SET
433
+ stripe_customer_id = ?,
434
+ stripe_subscription_id = ?,
435
+ plan_id = ?,
436
+ product_id = ?,
437
+ status = ?,
438
+ current_period_end = ?,
439
+ cancel_at_period_end = ?,
440
+ ${supportsTrialColumns ? "trial_ends_at = ?, trial_used_at = ?," : ""}
441
+ ${supportsSeatQuantityColumn ? "seat_quantity = ?," : ""}
442
+ ${supportsSeatAllowanceOverrideColumn ? "seat_allowance_override = ?," : ""}
443
+ updated_at = CURRENT_TIMESTAMP
444
+ WHERE owner_kind = ? AND owner_id = ?`);
445
+ const clearByOwner = db.prepare(`UPDATE "${tableName}" SET
446
+ plan_id = 'free',
447
+ product_id = NULL,
448
+ status = 'free',
449
+ stripe_subscription_id = NULL,
450
+ current_period_end = NULL,
451
+ cancel_at_period_end = 0,
452
+ ${supportsTrialColumns ? "trial_ends_at = NULL," : ""}
453
+ ${supportsSeatQuantityColumn ? "seat_quantity = NULL," : ""}
454
+ updated_at = CURRENT_TIMESTAMP
455
+ WHERE owner_kind = ? AND owner_id = ?`);
456
+ const updateCustomerByOwner = db.prepare(`UPDATE "${tableName}" SET
457
+ stripe_customer_id = ?,
458
+ updated_at = CURRENT_TIMESTAMP
459
+ WHERE owner_kind = ? AND owner_id = ?`);
460
+ function readByOwner(owner) {
461
+ return selectByOwner.get(owner.kind, owner.id) ?? null;
462
+ }
463
+ return {
464
+ async getBillingAccount(owner) {
465
+ const record = readByOwner(owner);
466
+ return record ? toSnapshot(record) : null;
467
+ },
468
+ async getBillingAccountByStripeCustomerId(customerId) {
469
+ const record = selectByCustomerId.get(customerId) ?? null;
470
+ return record ? toSnapshot(record) : null;
471
+ },
472
+ async ensureCustomer({ owner, stripe }) {
473
+ const existing = readByOwner(owner);
474
+ const existingCustomerId = existing && getNullableString(existing, "stripeCustomerId", "stripe_customer_id");
475
+ if (existingCustomerId) {
476
+ return {
477
+ customerId: existingCustomerId,
478
+ };
479
+ }
480
+ const customer = await stripe.customers.create({
481
+ email: owner.email,
482
+ metadata: {
483
+ ownerId: owner.id,
484
+ ownerKind: owner.kind,
485
+ },
486
+ });
487
+ if (existing) {
488
+ updateCustomerByOwner.run(customer.id, owner.kind, owner.id);
489
+ }
490
+ else {
491
+ insertRecord.run(createBillingRecordId(), owner.id, owner.kind, customer.id, null, "free", null, "free", null, 0, ...(supportsTrialColumns ? [null, null] : []), ...(supportsSeatQuantityColumn ? [null] : []), ...(supportsSeatAllowanceOverrideColumn ? [null] : []));
492
+ }
493
+ return {
494
+ customerId: customer.id,
495
+ };
496
+ },
497
+ async saveBillingSnapshot(snapshot) {
498
+ const existing = readByOwner(snapshot.owner);
499
+ const periodEnd = snapshot.currentPeriodEnd ? snapshot.currentPeriodEnd.toISOString() : null;
500
+ const trialEndsAt = snapshot.trialEndsAt ? snapshot.trialEndsAt.toISOString() : null;
501
+ const trialUsedAt = snapshot.trialUsedAt ? snapshot.trialUsedAt.toISOString() : null;
502
+ const seatQuantity = snapshot.seatQuantity;
503
+ const seatAllowanceOverride = snapshot.seatAllowanceOverride;
504
+ if (existing) {
505
+ updateByOwner.run(snapshot.stripeCustomerId, snapshot.stripeSubscriptionId, snapshot.planId, snapshot.productId, snapshot.status, periodEnd, snapshot.cancelAtPeriodEnd ? 1 : 0, ...(supportsTrialColumns ? [trialEndsAt, trialUsedAt] : []), ...(supportsSeatQuantityColumn ? [seatQuantity] : []), ...(supportsSeatAllowanceOverrideColumn ? [seatAllowanceOverride] : []), snapshot.owner.kind, snapshot.owner.id);
506
+ return;
507
+ }
508
+ insertRecord.run(createBillingRecordId(), snapshot.owner.id, snapshot.owner.kind, snapshot.stripeCustomerId, snapshot.stripeSubscriptionId, snapshot.planId, snapshot.productId, snapshot.status, periodEnd, snapshot.cancelAtPeriodEnd ? 1 : 0, ...(supportsTrialColumns ? [trialEndsAt, trialUsedAt] : []), ...(supportsSeatQuantityColumn ? [seatQuantity] : []), ...(supportsSeatAllowanceOverrideColumn ? [seatAllowanceOverride] : []));
509
+ },
510
+ async clearBillingSnapshot(owner) {
511
+ const existing = readByOwner(owner);
512
+ if (!existing) {
513
+ return;
514
+ }
515
+ clearByOwner.run(owner.kind, owner.id);
516
+ },
517
+ };
518
+ }
519
+ function drizzleHasColumn(table, key) {
520
+ return key in table;
521
+ }
522
+ export function drizzleStorageAdapter(options) {
523
+ const { db, table, eq, and } = options;
524
+ async function firstByOwner(owner) {
525
+ const rows = await db
526
+ .select()
527
+ .from(table)
528
+ .where(and(eq(table.ownerKind, owner.kind), eq(table.ownerId, owner.id)))
529
+ .limit(1);
530
+ return rows[0] ?? null;
531
+ }
532
+ async function firstByCustomerId(customerId) {
533
+ const rows = await db
534
+ .select()
535
+ .from(table)
536
+ .where(eq(table.stripeCustomerId, customerId))
537
+ .limit(1);
538
+ return rows[0] ?? null;
539
+ }
540
+ return {
541
+ async getBillingAccount(owner) {
542
+ const record = await firstByOwner(owner);
543
+ return record ? toSnapshot(record) : null;
544
+ },
545
+ async getBillingAccountByStripeCustomerId(customerId) {
546
+ const record = await firstByCustomerId(customerId);
547
+ return record ? toSnapshot(record) : null;
548
+ },
549
+ async ensureCustomer({ owner, stripe }) {
550
+ const existing = await firstByOwner(owner);
551
+ if (typeof existing?.stripeCustomerId === "string" && existing.stripeCustomerId) {
552
+ return {
553
+ customerId: existing.stripeCustomerId,
554
+ };
555
+ }
556
+ const customer = await stripe.customers.create({
557
+ email: owner.email,
558
+ metadata: {
559
+ ownerId: owner.id,
560
+ ownerKind: owner.kind,
561
+ },
562
+ });
563
+ if (existing?.id != null) {
564
+ await db
565
+ .update(table)
566
+ .set({
567
+ stripeCustomerId: customer.id,
568
+ updatedAt: new Date(),
569
+ })
570
+ .where(eq(table.id, existing.id));
571
+ }
572
+ else {
573
+ await db.insert(table).values({
574
+ ownerId: owner.id,
575
+ ownerKind: owner.kind,
576
+ stripeCustomerId: customer.id,
577
+ planId: "free",
578
+ productId: null,
579
+ status: "free",
580
+ cancelAtPeriodEnd: false,
581
+ ...(drizzleHasColumn(table, "trialEndsAt") ? { trialEndsAt: null } : {}),
582
+ ...(drizzleHasColumn(table, "trialUsedAt") ? { trialUsedAt: null } : {}),
583
+ ...(drizzleHasColumn(table, "seatQuantity") ? { seatQuantity: null } : {}),
584
+ ...(drizzleHasColumn(table, "seatAllowanceOverride")
585
+ ? { seatAllowanceOverride: null }
586
+ : {}),
587
+ createdAt: new Date(),
588
+ updatedAt: new Date(),
589
+ });
590
+ }
591
+ return {
592
+ customerId: customer.id,
593
+ };
594
+ },
595
+ async saveBillingSnapshot(snapshot) {
596
+ const existing = await firstByOwner(snapshot.owner);
597
+ const data = {
598
+ ownerId: snapshot.owner.id,
599
+ ownerKind: snapshot.owner.kind,
600
+ stripeCustomerId: snapshot.stripeCustomerId,
601
+ stripeSubscriptionId: snapshot.stripeSubscriptionId,
602
+ planId: snapshot.planId,
603
+ productId: snapshot.productId,
604
+ status: snapshot.status,
605
+ currentPeriodEnd: snapshot.currentPeriodEnd,
606
+ cancelAtPeriodEnd: snapshot.cancelAtPeriodEnd,
607
+ ...(drizzleHasColumn(table, "trialEndsAt") ? { trialEndsAt: snapshot.trialEndsAt } : {}),
608
+ ...(drizzleHasColumn(table, "trialUsedAt") ? { trialUsedAt: snapshot.trialUsedAt } : {}),
609
+ ...(drizzleHasColumn(table, "seatQuantity") ? { seatQuantity: snapshot.seatQuantity } : {}),
610
+ ...(drizzleHasColumn(table, "seatAllowanceOverride")
611
+ ? { seatAllowanceOverride: snapshot.seatAllowanceOverride }
612
+ : {}),
613
+ updatedAt: new Date(),
614
+ };
615
+ if (existing?.id != null) {
616
+ await db.update(table).set(data).where(eq(table.id, existing.id));
617
+ return;
618
+ }
619
+ await db.insert(table).values({
620
+ ...data,
621
+ createdAt: new Date(),
622
+ });
623
+ },
624
+ async clearBillingSnapshot(owner) {
625
+ const existing = await firstByOwner(owner);
626
+ if (existing?.id == null) {
627
+ return;
628
+ }
629
+ await db
630
+ .update(table)
631
+ .set({
632
+ planId: "free",
633
+ productId: null,
634
+ status: "free",
635
+ stripeSubscriptionId: null,
636
+ currentPeriodEnd: null,
637
+ cancelAtPeriodEnd: false,
638
+ ...(drizzleHasColumn(table, "trialEndsAt") ? { trialEndsAt: null } : {}),
639
+ ...(drizzleHasColumn(table, "seatQuantity") ? { seatQuantity: null } : {}),
640
+ updatedAt: new Date(),
641
+ })
642
+ .where(eq(table.id, existing.id));
643
+ },
644
+ };
645
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@farm.js/stripe",
3
+ "version": "0.1.0-beta.0",
4
+ "description": "Stripe billing integration for Farm.js",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/farming-labs/farm.js",
9
+ "directory": "packages/farm-stripe"
10
+ },
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "type": "module",
15
+ "main": "./dist/index.js",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js"
22
+ },
23
+ "./client": {
24
+ "types": "./dist/client.d.ts",
25
+ "import": "./dist/client.js"
26
+ },
27
+ "./storage": {
28
+ "types": "./dist/storage.d.ts",
29
+ "import": "./dist/storage.js"
30
+ }
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "dependencies": {
36
+ "stripe": "^20.4.1",
37
+ "@farm.js/integration-utils": "0.1.0-beta.0",
38
+ "@farm.js/core": "0.1.0-beta.0"
39
+ },
40
+ "scripts": {
41
+ "build": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\" && tsc",
42
+ "dev": "tsc --watch",
43
+ "type-check": "tsc --noEmit",
44
+ "test": "echo 'No tests in this package'"
45
+ }
46
+ }