Buckets:
| import { Billing } from "../src/billing.js" | |
| import { and, Database, eq, isNull } from "../src/drizzle/index.js" | |
| import { UserTable } from "../src/schema/user.sql.js" | |
| import { BillingTable, SubscriptionTable } from "../src/schema/billing.sql.js" | |
| import { Identifier } from "../src/identifier.js" | |
| import { AuthTable } from "../src/schema/auth.sql.js" | |
| import { BlackData } from "../src/black.js" | |
| const plan = "200" | |
| const couponID = "JAIr0Pe1" | |
| const workspaceID = process.argv[2] | |
| const seats = parseInt(process.argv[3]) | |
| console.log(`Gifting ${seats} seats of Black to workspace ${workspaceID}`) | |
| if (!workspaceID || !seats) throw new Error("Usage: bun foo.ts <workspaceID> <seats>") | |
| // Get workspace user | |
| const users = await Database.use((tx) => | |
| tx | |
| .select({ | |
| id: UserTable.id, | |
| role: UserTable.role, | |
| email: AuthTable.subject, | |
| }) | |
| .from(UserTable) | |
| .leftJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email"))) | |
| .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))), | |
| ) | |
| if (users.length === 0) throw new Error(`Error: No users found in workspace ${workspaceID}`) | |
| if (users.length !== seats) | |
| throw new Error(`Error: Workspace ${workspaceID} has ${users.length} users, expected ${seats}`) | |
| const adminUser = users.find((user) => user.role === "admin") | |
| if (!adminUser) throw new Error(`Error: No admin user found in workspace ${workspaceID}`) | |
| if (!adminUser.email) throw new Error(`Error: Admin user ${adminUser.id} has no email`) | |
| // Get Billing | |
| const billing = await Database.use((tx) => | |
| tx | |
| .select({ | |
| customerID: BillingTable.customerID, | |
| subscriptionID: BillingTable.subscriptionID, | |
| }) | |
| .from(BillingTable) | |
| .where(eq(BillingTable.workspaceID, workspaceID)) | |
| .then((rows) => rows[0]), | |
| ) | |
| if (!billing) throw new Error(`Error: Workspace ${workspaceID} has no billing record`) | |
| if (billing.subscriptionID) throw new Error(`Error: Workspace ${workspaceID} already has a subscription`) | |
| // Look up the Stripe customer by email | |
| const customerID = | |
| billing.customerID ?? | |
| (await (() => | |
| Billing.stripe() | |
| .customers.create({ | |
| email: adminUser.email, | |
| metadata: { | |
| workspaceID, | |
| }, | |
| }) | |
| .then((customer) => customer.id))()) | |
| console.log(`Customer ID: ${customerID}`) | |
| const subscription = await Billing.stripe().subscriptions.create({ | |
| customer: customerID!, | |
| items: [ | |
| { | |
| price: BlackData.planToPriceID({ plan }), | |
| discounts: [{ coupon: couponID }], | |
| quantity: seats, | |
| }, | |
| ], | |
| metadata: { | |
| workspaceID, | |
| }, | |
| }) | |
| console.log(`Subscription ID: ${subscription.id}`) | |
| await Database.transaction(async (tx) => { | |
| // Set customer id, subscription id, and payment method on workspace billing | |
| await tx | |
| .update(BillingTable) | |
| .set({ | |
| customerID, | |
| subscriptionID: subscription.id, | |
| subscription: { status: "subscribed", coupon: couponID, seats, plan }, | |
| }) | |
| .where(eq(BillingTable.workspaceID, workspaceID)) | |
| // Create a row in subscription table | |
| for (const user of users) { | |
| await tx.insert(SubscriptionTable).values({ | |
| workspaceID, | |
| id: Identifier.create("subscription"), | |
| userID: user.id, | |
| }) | |
| } | |
| // | |
| // // Create a row in payments table | |
| // await tx.insert(PaymentTable).values({ | |
| // workspaceID, | |
| // id: Identifier.create("payment"), | |
| // amount: centsToMicroCents(amountInCents), | |
| // customerID, | |
| // invoiceID, | |
| // paymentID, | |
| // enrichment: { | |
| // type: "subscription", | |
| // couponID, | |
| // }, | |
| // }) | |
| }) | |
| console.log(`done`) | |
Xet Storage Details
- Size:
- 3.63 kB
- Xet hash:
- 50a75a026d4f1c84f6a0206c75bec06bcf0b850f97a6c3d1908194ed04b3c3db
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.