GitHub Action 1 месяц назад
Родитель
Сommit
c128579cfc

+ 4 - 1
packages/console/app/src/routes/stripe/webhook.ts

@@ -421,7 +421,10 @@ export async function POST(input: APIEvent) {
       })
     }
     if (body.type === "invoice.payment_succeeded") {
-      if (body.data.object.billing_reason === "subscription_cycle" || body.data.object.billing_reason === "subscription_create") {
+      if (
+        body.data.object.billing_reason === "subscription_cycle" ||
+        body.data.object.billing_reason === "subscription_create"
+      ) {
         const invoiceID = body.data.object.id as string
         const amountInCents = body.data.object.amount_paid
         const customerID = body.data.object.customer as string

+ 30 - 104
packages/console/core/migrations/meta/0055_snapshot.json

@@ -43,9 +43,7 @@
       "compositePrimaryKeys": {
         "account_id_pk": {
           "name": "account_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -109,17 +107,12 @@
       "indexes": {
         "provider": {
           "name": "provider",
-          "columns": [
-            "provider",
-            "subject"
-          ],
+          "columns": ["provider", "subject"],
           "isUnique": true
         },
         "account_id": {
           "name": "account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         }
       },
@@ -127,9 +120,7 @@
       "compositePrimaryKeys": {
         "auth_id_pk": {
           "name": "auth_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -193,9 +184,7 @@
       "indexes": {
         "time_created": {
           "name": "time_created",
-          "columns": [
-            "time_created"
-          ],
+          "columns": ["time_created"],
           "isUnique": false
         }
       },
@@ -203,9 +192,7 @@
       "compositePrimaryKeys": {
         "benchmark_id_pk": {
           "name": "benchmark_id_pk",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -388,16 +375,12 @@
       "indexes": {
         "global_customer_id": {
           "name": "global_customer_id",
-          "columns": [
-            "customer_id"
-          ],
+          "columns": ["customer_id"],
           "isUnique": true
         },
         "global_subscription_id": {
           "name": "global_subscription_id",
-          "columns": [
-            "subscription_id"
-          ],
+          "columns": ["subscription_id"],
           "isUnique": true
         }
       },
@@ -405,10 +388,7 @@
       "compositePrimaryKeys": {
         "billing_workspace_id_id_pk": {
           "name": "billing_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -502,10 +482,7 @@
       "compositePrimaryKeys": {
         "payment_workspace_id_id_pk": {
           "name": "payment_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -590,10 +567,7 @@
       "indexes": {
         "workspace_user_id": {
           "name": "workspace_user_id",
-          "columns": [
-            "workspace_id",
-            "user_id"
-          ],
+          "columns": ["workspace_id", "user_id"],
           "isUnique": true
         }
       },
@@ -601,10 +575,7 @@
       "compositePrimaryKeys": {
         "subscription_workspace_id_id_pk": {
           "name": "subscription_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -731,10 +702,7 @@
       "indexes": {
         "usage_time_created": {
           "name": "usage_time_created",
-          "columns": [
-            "workspace_id",
-            "time_created"
-          ],
+          "columns": ["workspace_id", "time_created"],
           "isUnique": false
         }
       },
@@ -742,10 +710,7 @@
       "compositePrimaryKeys": {
         "usage_workspace_id_id_pk": {
           "name": "usage_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -781,10 +746,7 @@
       "compositePrimaryKeys": {
         "ip_rate_limit_ip_interval_pk": {
           "name": "ip_rate_limit_ip_interval_pk",
-          "columns": [
-            "ip",
-            "interval"
-          ]
+          "columns": ["ip", "interval"]
         }
       },
       "uniqueConstraints": {},
@@ -836,9 +798,7 @@
       "compositePrimaryKeys": {
         "ip_ip_pk": {
           "name": "ip_ip_pk",
-          "columns": [
-            "ip"
-          ]
+          "columns": ["ip"]
         }
       },
       "uniqueConstraints": {},
@@ -916,9 +876,7 @@
       "indexes": {
         "global_key": {
           "name": "global_key",
-          "columns": [
-            "key"
-          ],
+          "columns": ["key"],
           "isUnique": true
         }
       },
@@ -926,10 +884,7 @@
       "compositePrimaryKeys": {
         "key_workspace_id_id_pk": {
           "name": "key_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -986,10 +941,7 @@
       "indexes": {
         "model_workspace_model": {
           "name": "model_workspace_model",
-          "columns": [
-            "workspace_id",
-            "model"
-          ],
+          "columns": ["workspace_id", "model"],
           "isUnique": true
         }
       },
@@ -997,10 +949,7 @@
       "compositePrimaryKeys": {
         "model_workspace_id_id_pk": {
           "name": "model_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -1064,10 +1013,7 @@
       "indexes": {
         "workspace_provider": {
           "name": "workspace_provider",
-          "columns": [
-            "workspace_id",
-            "provider"
-          ],
+          "columns": ["workspace_id", "provider"],
           "isUnique": true
         }
       },
@@ -1075,10 +1021,7 @@
       "compositePrimaryKeys": {
         "provider_workspace_id_id_pk": {
           "name": "provider_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -1191,32 +1134,22 @@
       "indexes": {
         "user_account_id": {
           "name": "user_account_id",
-          "columns": [
-            "workspace_id",
-            "account_id"
-          ],
+          "columns": ["workspace_id", "account_id"],
           "isUnique": true
         },
         "user_email": {
           "name": "user_email",
-          "columns": [
-            "workspace_id",
-            "email"
-          ],
+          "columns": ["workspace_id", "email"],
           "isUnique": true
         },
         "global_account_id": {
           "name": "global_account_id",
-          "columns": [
-            "account_id"
-          ],
+          "columns": ["account_id"],
           "isUnique": false
         },
         "global_email": {
           "name": "global_email",
-          "columns": [
-            "email"
-          ],
+          "columns": ["email"],
           "isUnique": false
         }
       },
@@ -1224,10 +1157,7 @@
       "compositePrimaryKeys": {
         "user_workspace_id_id_pk": {
           "name": "user_workspace_id_id_pk",
-          "columns": [
-            "workspace_id",
-            "id"
-          ]
+          "columns": ["workspace_id", "id"]
         }
       },
       "uniqueConstraints": {},
@@ -1284,9 +1214,7 @@
       "indexes": {
         "slug": {
           "name": "slug",
-          "columns": [
-            "slug"
-          ],
+          "columns": ["slug"],
           "isUnique": true
         }
       },
@@ -1294,9 +1222,7 @@
       "compositePrimaryKeys": {
         "workspace_id": {
           "name": "workspace_id",
-          "columns": [
-            "id"
-          ]
+          "columns": ["id"]
         }
       },
       "uniqueConstraints": {},
@@ -1313,4 +1239,4 @@
     "tables": {},
     "indexes": {}
   }
-}
+}

+ 1 - 1
packages/console/core/migrations/meta/_journal.json

@@ -395,4 +395,4 @@
       "breakpoints": true
     }
   ]
-}
+}

+ 58 - 55
packages/console/core/src/billing.ts

@@ -290,65 +290,68 @@ export namespace Billing {
     },
   )
 
-  export const subscribe = fn(z.object({
-    seats: z.number(),
-    coupon: z.string().optional(),
-  }), async ({ seats, coupon }) => {
-    const user = Actor.assert("user")
-    const billing = await Database.use((tx) =>
-      tx
-        .select({
-          customerID: BillingTable.customerID,
-          paymentMethodID: BillingTable.paymentMethodID,
-          subscriptionID: BillingTable.subscriptionID,
-          subscriptionPlan: BillingTable.subscriptionPlan,
-          timeSubscriptionSelected: BillingTable.timeSubscriptionSelected,
-        })
-        .from(BillingTable)
-        .where(eq(BillingTable.workspaceID, Actor.workspace()))
-        .then((rows) => rows[0]),
-    )
+  export const subscribe = fn(
+    z.object({
+      seats: z.number(),
+      coupon: z.string().optional(),
+    }),
+    async ({ seats, coupon }) => {
+      const user = Actor.assert("user")
+      const billing = await Database.use((tx) =>
+        tx
+          .select({
+            customerID: BillingTable.customerID,
+            paymentMethodID: BillingTable.paymentMethodID,
+            subscriptionID: BillingTable.subscriptionID,
+            subscriptionPlan: BillingTable.subscriptionPlan,
+            timeSubscriptionSelected: BillingTable.timeSubscriptionSelected,
+          })
+          .from(BillingTable)
+          .where(eq(BillingTable.workspaceID, Actor.workspace()))
+          .then((rows) => rows[0]),
+      )
 
-    if (!billing) throw new Error("Billing record not found")
-    if (!billing.timeSubscriptionSelected) throw new Error("Not selected for subscription")
-    if (billing.subscriptionID) throw new Error("Already subscribed")
-    if (!billing.customerID) throw new Error("No customer ID")
-    if (!billing.paymentMethodID) throw new Error("No payment method")
-    if (!billing.subscriptionPlan) throw new Error("No subscription plan")
+      if (!billing) throw new Error("Billing record not found")
+      if (!billing.timeSubscriptionSelected) throw new Error("Not selected for subscription")
+      if (billing.subscriptionID) throw new Error("Already subscribed")
+      if (!billing.customerID) throw new Error("No customer ID")
+      if (!billing.paymentMethodID) throw new Error("No payment method")
+      if (!billing.subscriptionPlan) throw new Error("No subscription plan")
 
-    const subscription = await Billing.stripe().subscriptions.create({
-      customer: billing.customerID,
-      default_payment_method: billing.paymentMethodID,
-      items: [{ price: BlackData.planToPriceID({ plan: billing.subscriptionPlan }) }],
-      metadata: {
-        workspaceID: Actor.workspace(),
-      },
-    })
+      const subscription = await Billing.stripe().subscriptions.create({
+        customer: billing.customerID,
+        default_payment_method: billing.paymentMethodID,
+        items: [{ price: BlackData.planToPriceID({ plan: billing.subscriptionPlan }) }],
+        metadata: {
+          workspaceID: Actor.workspace(),
+        },
+      })
 
-    await Database.transaction(async (tx) => {
-      await tx
-        .update(BillingTable)
-        .set({
-          subscriptionID: subscription.id,
-          subscription: {
-            status: "subscribed",
-            coupon,
-            seats,
-            plan: billing.subscriptionPlan!,
-          },
-          subscriptionPlan: null,
-          timeSubscriptionBooked: null,
-          timeSubscriptionSelected: null,
-        })
-        .where(eq(BillingTable.workspaceID, Actor.workspace()))
+      await Database.transaction(async (tx) => {
+        await tx
+          .update(BillingTable)
+          .set({
+            subscriptionID: subscription.id,
+            subscription: {
+              status: "subscribed",
+              coupon,
+              seats,
+              plan: billing.subscriptionPlan!,
+            },
+            subscriptionPlan: null,
+            timeSubscriptionBooked: null,
+            timeSubscriptionSelected: null,
+          })
+          .where(eq(BillingTable.workspaceID, Actor.workspace()))
 
-      await tx.insert(SubscriptionTable).values({
-        workspaceID: Actor.workspace(),
-        id: Identifier.create("subscription"),
-        userID: user.properties.userID,
+        await tx.insert(SubscriptionTable).values({
+          workspaceID: Actor.workspace(),
+          id: Identifier.create("subscription"),
+          userID: user.properties.userID,
+        })
       })
-    })
 
-    return subscription.id
-  })
+      return subscription.id
+    },
+  )
 }

+ 27 - 18
packages/console/core/src/black.ts

@@ -28,28 +28,37 @@ export namespace BlackData {
     return input
   })
 
-  export const getLimits = fn(z.object({
+  export const getLimits = fn(
+    z.object({
       plan: z.enum(SubscriptionPlan),
-    }), ({ plan }) => {
-    const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value)
-    return Schema.parse(json)[plan]
-  })
+    }),
+    ({ plan }) => {
+      const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value)
+      return Schema.parse(json)[plan]
+    },
+  )
 
-  export const planToPriceID = fn(z.object({
+  export const planToPriceID = fn(
+    z.object({
       plan: z.enum(SubscriptionPlan),
-    }), ({ plan }) => {
-    if (plan === "200") return Resource.ZEN_BLACK_PRICE.plan200
-    if (plan === "100") return Resource.ZEN_BLACK_PRICE.plan100
-    return Resource.ZEN_BLACK_PRICE.plan20
-  })
+    }),
+    ({ plan }) => {
+      if (plan === "200") return Resource.ZEN_BLACK_PRICE.plan200
+      if (plan === "100") return Resource.ZEN_BLACK_PRICE.plan100
+      return Resource.ZEN_BLACK_PRICE.plan20
+    },
+  )
 
-  export const priceIDToPlan = fn(z.object({
-    priceID: z.string(),
-  }), ({ priceID }) => {
-    if (priceID === Resource.ZEN_BLACK_PRICE.plan200) return "200"
-    if (priceID === Resource.ZEN_BLACK_PRICE.plan100) return "100"
-    return "20"
-  })
+  export const priceIDToPlan = fn(
+    z.object({
+      priceID: z.string(),
+    }),
+    ({ priceID }) => {
+      if (priceID === Resource.ZEN_BLACK_PRICE.plan200) return "200"
+      if (priceID === Resource.ZEN_BLACK_PRICE.plan100) return "100"
+      return "20"
+    },
+  )
 }
 
 export namespace Black {