@windrun-huaiin/backend-core 10.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 (198) hide show
  1. package/LICENSE +21 -0
  2. package/dist/app/api/stripe/checkout/route.d.ts +19 -0
  3. package/dist/app/api/stripe/checkout/route.d.ts.map +1 -0
  4. package/dist/app/api/stripe/checkout/route.js +120 -0
  5. package/dist/app/api/stripe/checkout/route.mjs +118 -0
  6. package/dist/app/api/stripe/customer-portal/route.d.ts +11 -0
  7. package/dist/app/api/stripe/customer-portal/route.d.ts.map +1 -0
  8. package/dist/app/api/stripe/customer-portal/route.js +73 -0
  9. package/dist/app/api/stripe/customer-portal/route.mjs +71 -0
  10. package/dist/app/api/user/anonymous/init/route.d.ts +7 -0
  11. package/dist/app/api/user/anonymous/init/route.d.ts.map +1 -0
  12. package/dist/app/api/user/anonymous/init/route.js +210 -0
  13. package/dist/app/api/user/anonymous/init/route.mjs +208 -0
  14. package/dist/app/api/webhook/clerk/user/route.d.ts +7 -0
  15. package/dist/app/api/webhook/clerk/user/route.d.ts.map +1 -0
  16. package/dist/app/api/webhook/clerk/user/route.js +202 -0
  17. package/dist/app/api/webhook/clerk/user/route.mjs +200 -0
  18. package/dist/app/api/webhook/stripe/route.d.ts +8 -0
  19. package/dist/app/api/webhook/stripe/route.d.ts.map +1 -0
  20. package/dist/app/api/webhook/stripe/route.js +70 -0
  21. package/dist/app/api/webhook/stripe/route.mjs +67 -0
  22. package/dist/index.d.ts +7 -0
  23. package/dist/index.d.ts.map +1 -0
  24. package/dist/index.js +83 -0
  25. package/dist/index.mjs +18 -0
  26. package/dist/lib/auth-utils.d.ts +46 -0
  27. package/dist/lib/auth-utils.d.ts.map +1 -0
  28. package/dist/lib/auth-utils.js +107 -0
  29. package/dist/lib/auth-utils.mjs +102 -0
  30. package/dist/lib/credit-init.d.ts +8 -0
  31. package/dist/lib/credit-init.d.ts.map +1 -0
  32. package/dist/lib/credit-init.js +16 -0
  33. package/dist/lib/credit-init.mjs +10 -0
  34. package/dist/lib/index.d.ts +5 -0
  35. package/dist/lib/index.d.ts.map +1 -0
  36. package/dist/lib/index.js +31 -0
  37. package/dist/lib/index.mjs +4 -0
  38. package/dist/lib/money-price-config.d.ts +51 -0
  39. package/dist/lib/money-price-config.d.ts.map +1 -0
  40. package/dist/lib/money-price-config.js +156 -0
  41. package/dist/lib/money-price-config.mjs +151 -0
  42. package/dist/lib/stripe-config.d.ts +31 -0
  43. package/dist/lib/stripe-config.d.ts.map +1 -0
  44. package/dist/lib/stripe-config.js +278 -0
  45. package/dist/lib/stripe-config.mjs +268 -0
  46. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.js +48 -0
  47. package/dist/node_modules/.pnpm/@rollup_plugin-typescript@12.1.4_rollup@4.46.2_tslib@2.8.1_typescript@5.9.3/node_modules/tslib/tslib.es6.mjs +45 -0
  48. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.js +54 -0
  49. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/errors.mjs +51 -0
  50. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.js +44 -0
  51. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/iso.mjs +35 -0
  52. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.js +31 -0
  53. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/parse.mjs +18 -0
  54. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.js +587 -0
  55. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/classic/schemas.mjs +527 -0
  56. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.js +447 -0
  57. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/api.mjs +399 -0
  58. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.js +245 -0
  59. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/checks.mjs +232 -0
  60. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.js +68 -0
  61. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/core.mjs +62 -0
  62. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.js +39 -0
  63. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/doc.mjs +37 -0
  64. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.js +80 -0
  65. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/errors.mjs +75 -0
  66. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.js +101 -0
  67. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/parse.mjs +86 -0
  68. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.js +102 -0
  69. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/regexes.mjs +76 -0
  70. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.js +56 -0
  71. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/registries.mjs +52 -0
  72. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.js +1205 -0
  73. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/schemas.mjs +1157 -0
  74. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.js +407 -0
  75. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/util.mjs +374 -0
  76. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.js +9 -0
  77. package/dist/node_modules/.pnpm/zod@4.1.12/node_modules/zod/v4/core/versions.mjs +7 -0
  78. package/dist/prisma/client.d.ts +2 -0
  79. package/dist/prisma/client.d.ts.map +1 -0
  80. package/dist/prisma/client.js +12 -0
  81. package/dist/prisma/client.mjs +1 -0
  82. package/dist/prisma/index.d.ts +4 -0
  83. package/dist/prisma/index.d.ts.map +1 -0
  84. package/dist/prisma/index.js +10 -0
  85. package/dist/prisma/index.mjs +2 -0
  86. package/dist/prisma/prisma-transaction-util.d.ts +3 -0
  87. package/dist/prisma/prisma-transaction-util.d.ts.map +1 -0
  88. package/dist/prisma/prisma-transaction-util.js +29 -0
  89. package/dist/prisma/prisma-transaction-util.mjs +27 -0
  90. package/dist/prisma/prisma.d.ts +4 -0
  91. package/dist/prisma/prisma.d.ts.map +1 -0
  92. package/dist/prisma/prisma.js +109 -0
  93. package/dist/prisma/prisma.mjs +106 -0
  94. package/dist/services/aggregate/billing.aggregate.service.d.ts +83 -0
  95. package/dist/services/aggregate/billing.aggregate.service.d.ts.map +1 -0
  96. package/dist/services/aggregate/billing.aggregate.service.js +308 -0
  97. package/dist/services/aggregate/billing.aggregate.service.mjs +306 -0
  98. package/dist/services/aggregate/index.d.ts +3 -0
  99. package/dist/services/aggregate/index.d.ts.map +1 -0
  100. package/dist/services/aggregate/index.js +9 -0
  101. package/dist/services/aggregate/index.mjs +2 -0
  102. package/dist/services/aggregate/user.aggregate.service.d.ts +34 -0
  103. package/dist/services/aggregate/user.aggregate.service.d.ts.map +1 -0
  104. package/dist/services/aggregate/user.aggregate.service.js +136 -0
  105. package/dist/services/aggregate/user.aggregate.service.mjs +133 -0
  106. package/dist/services/context/index.d.ts +2 -0
  107. package/dist/services/context/index.d.ts.map +1 -0
  108. package/dist/services/context/index.js +13 -0
  109. package/dist/services/context/index.mjs +1 -0
  110. package/dist/services/context/user-context-service.d.ts +30 -0
  111. package/dist/services/context/user-context-service.d.ts.map +1 -0
  112. package/dist/services/context/user-context-service.js +170 -0
  113. package/dist/services/context/user-context-service.mjs +162 -0
  114. package/dist/services/database/apilog.service.d.ts +39 -0
  115. package/dist/services/database/apilog.service.d.ts.map +1 -0
  116. package/dist/services/database/apilog.service.js +174 -0
  117. package/dist/services/database/apilog.service.mjs +170 -0
  118. package/dist/services/database/constants.d.ts +73 -0
  119. package/dist/services/database/constants.d.ts.map +1 -0
  120. package/dist/services/database/constants.js +135 -0
  121. package/dist/services/database/constants.mjs +117 -0
  122. package/dist/services/database/credit.service.d.ts +107 -0
  123. package/dist/services/database/credit.service.d.ts.map +1 -0
  124. package/dist/services/database/credit.service.js +515 -0
  125. package/dist/services/database/credit.service.mjs +512 -0
  126. package/dist/services/database/creditAuditLog.service.d.ts +73 -0
  127. package/dist/services/database/creditAuditLog.service.d.ts.map +1 -0
  128. package/dist/services/database/creditAuditLog.service.js +305 -0
  129. package/dist/services/database/creditAuditLog.service.mjs +302 -0
  130. package/dist/services/database/index.d.ts +10 -0
  131. package/dist/services/database/index.d.ts.map +1 -0
  132. package/dist/services/database/index.js +38 -0
  133. package/dist/services/database/index.mjs +8 -0
  134. package/dist/services/database/prisma-model-type.d.ts +3 -0
  135. package/dist/services/database/prisma-model-type.d.ts.map +1 -0
  136. package/dist/services/database/subscription.service.d.ts +48 -0
  137. package/dist/services/database/subscription.service.d.ts.map +1 -0
  138. package/dist/services/database/subscription.service.js +267 -0
  139. package/dist/services/database/subscription.service.mjs +264 -0
  140. package/dist/services/database/transaction.service.d.ts +92 -0
  141. package/dist/services/database/transaction.service.d.ts.map +1 -0
  142. package/dist/services/database/transaction.service.js +326 -0
  143. package/dist/services/database/transaction.service.mjs +323 -0
  144. package/dist/services/database/user.service.d.ts +45 -0
  145. package/dist/services/database/user.service.d.ts.map +1 -0
  146. package/dist/services/database/user.service.js +180 -0
  147. package/dist/services/database/user.service.mjs +177 -0
  148. package/dist/services/database/userBackup.service.d.ts +45 -0
  149. package/dist/services/database/userBackup.service.d.ts.map +1 -0
  150. package/dist/services/database/userBackup.service.js +249 -0
  151. package/dist/services/database/userBackup.service.mjs +246 -0
  152. package/dist/services/stripe/index.d.ts +2 -0
  153. package/dist/services/stripe/index.d.ts.map +1 -0
  154. package/dist/services/stripe/index.js +7 -0
  155. package/dist/services/stripe/index.mjs +1 -0
  156. package/dist/services/stripe/webhook-handler.d.ts +6 -0
  157. package/dist/services/stripe/webhook-handler.d.ts.map +1 -0
  158. package/dist/services/stripe/webhook-handler.js +537 -0
  159. package/dist/services/stripe/webhook-handler.mjs +535 -0
  160. package/migrations/create.sql +176 -0
  161. package/migrations/db.init.sql +13 -0
  162. package/migrations/init-schema.sql +19 -0
  163. package/migrations/purge.sql +27 -0
  164. package/migrations/test-check.sql +167 -0
  165. package/package.json +123 -0
  166. package/prisma/schema.prisma +191 -0
  167. package/src/app/api/stripe/checkout/route.ts +145 -0
  168. package/src/app/api/stripe/customer-portal/route.ts +83 -0
  169. package/src/app/api/user/anonymous/init/route.ts +284 -0
  170. package/src/app/api/webhook/clerk/user/route.ts +249 -0
  171. package/src/app/api/webhook/stripe/route.ts +93 -0
  172. package/src/index.ts +6 -0
  173. package/src/lib/auth-utils.ts +101 -0
  174. package/src/lib/credit-init.ts +9 -0
  175. package/src/lib/index.ts +4 -0
  176. package/src/lib/money-price-config.ts +168 -0
  177. package/src/lib/stripe-config.ts +333 -0
  178. package/src/prisma/client.ts +2 -0
  179. package/src/prisma/index.ts +3 -0
  180. package/src/prisma/prisma-transaction-util.ts +24 -0
  181. package/src/prisma/prisma.ts +122 -0
  182. package/src/services/aggregate/billing.aggregate.service.ts +498 -0
  183. package/src/services/aggregate/index.ts +2 -0
  184. package/src/services/aggregate/user.aggregate.service.ts +168 -0
  185. package/src/services/context/index.ts +1 -0
  186. package/src/services/context/user-context-service.ts +200 -0
  187. package/src/services/database/apilog.service.ts +185 -0
  188. package/src/services/database/constants.ts +148 -0
  189. package/src/services/database/credit.service.ts +747 -0
  190. package/src/services/database/creditAuditLog.service.ts +402 -0
  191. package/src/services/database/index.ts +41 -0
  192. package/src/services/database/prisma-model-type.ts +13 -0
  193. package/src/services/database/subscription.service.ts +319 -0
  194. package/src/services/database/transaction.service.ts +447 -0
  195. package/src/services/database/user.service.ts +218 -0
  196. package/src/services/database/userBackup.service.ts +290 -0
  197. package/src/services/stripe/index.ts +1 -0
  198. package/src/services/stripe/webhook-handler.ts +648 -0
@@ -0,0 +1,191 @@
1
+ // NOTE:
2
+ // - generator/datasource 配置仅为示例,合并到宿主时请以宿主的 schema 为准,必要时删除/覆盖本段。
3
+ // - models 定义可直接复用,数据库连接信息应由宿主自行配置。
4
+ generator client {
5
+ provider = "prisma-client-js"
6
+ }
7
+
8
+ datasource db {
9
+ provider = "postgresql"
10
+ url = env("DATABASE_URL")
11
+ schemas = ["nextai", "public"]
12
+ }
13
+
14
+ model User {
15
+ id BigInt @id @default(autoincrement())
16
+ userId String @unique @default(dbgenerated("gen_random_uuid()")) @map("user_id") @db.Uuid
17
+ status String @default("anonymous") @db.VarChar(50)
18
+ fingerprintId String? @map("fingerprint_id") @db.VarChar(255)
19
+ clerkUserId String? @unique @map("clerk_user_id") @db.VarChar(255)
20
+ stripeCusId String? @unique @map("stripe_cus_id") @db.VarChar(255)
21
+ email String? @db.VarChar(255)
22
+ userName String? @map("user_name") @db.VarChar(255)
23
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
24
+ updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
25
+ sourceRef Json? @map("source_ref")
26
+ creditAuditLogs CreditAuditLog[]
27
+ credit Credit?
28
+ subscription Subscription?
29
+ transactions Transaction[]
30
+
31
+ @@index([fingerprintId], map: "idx_users_fingerprint_id")
32
+ @@map("users")
33
+
34
+ @@schema("nextai")
35
+ }
36
+
37
+ model Subscription {
38
+ id BigInt @id @default(autoincrement())
39
+ userId String @unique @map("user_id") @db.Uuid
40
+ status String @default("incomplete") @db.VarChar(50)
41
+ paySubscriptionId String? @map("pay_subscription_id") @db.VarChar(255)
42
+ orderId String? @map("order_id") @db.VarChar(255)
43
+ priceId String? @map("price_id") @db.VarChar(255)
44
+ priceName String? @map("price_name") @db.VarChar(255)
45
+ creditsAllocated Int @default(0) @map("credits_allocated")
46
+ subPeriodStart DateTime? @map("sub_period_start") @db.Timestamptz(6)
47
+ subPeriodEnd DateTime? @map("sub_period_end") @db.Timestamptz(6)
48
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
49
+ updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
50
+ deleted Int @default(0)
51
+ user User @relation(fields: [userId], references: [userId], onDelete: NoAction, onUpdate: NoAction)
52
+
53
+ @@index([paySubscriptionId], map: "idx_subscriptions_pay_subscription_id")
54
+ @@index([orderId], map: "idx_subscriptions_order_id")
55
+ @@map("subscriptions")
56
+
57
+ @@schema("nextai")
58
+ }
59
+
60
+ model Credit {
61
+ id BigInt @id @default(autoincrement())
62
+ userId String @unique @map("user_id") @db.Uuid
63
+
64
+ // ===== 免费积分 =====
65
+ balanceFree Int @default(0) @map("balance_free")
66
+ totalFreeLimit Int @default(0) @map("total_free_limit")
67
+ freeStart DateTime? @map("free_start") @db.Timestamptz(6)
68
+ freeEnd DateTime? @map("free_end") @db.Timestamptz(6)
69
+
70
+ // ===== 订阅付费积分 =====
71
+ balancePaid Int @default(0) @map("balance_paid")
72
+ totalPaidLimit Int @default(0) @map("total_paid_limit")
73
+ paidStart DateTime? @map("paid_start") @db.Timestamptz(6)
74
+ paidEnd DateTime? @map("paid_end") @db.Timestamptz(6)
75
+
76
+ // ===== 一次性支付积分 =====
77
+ balanceOneTimePaid Int @default(0) @map("balance_onetime_paid")
78
+ totalOneTimePaidLimit Int @default(0) @map("total_onetime_paid_limit")
79
+ oneTimePaidStart DateTime? @map("onetime_paid_start") @db.Timestamptz(6)
80
+ oneTimePaidEnd DateTime? @map("onetime_paid_end") @db.Timestamptz(6)
81
+
82
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
83
+ updatedAt DateTime? @default(now()) @updatedAt @map("updated_at") @db.Timestamptz(6)
84
+ user User @relation(fields: [userId], references: [userId], onDelete: NoAction, onUpdate: NoAction)
85
+
86
+ @@index([userId], map: "idx_credits_user_id")
87
+ @@map("credits")
88
+
89
+ @@schema("nextai")
90
+ }
91
+
92
+ model Transaction {
93
+ id BigInt @id @default(autoincrement())
94
+ userId String @map("user_id") @db.Uuid
95
+ orderId String @unique @map("order_id") @db.VarChar(255)
96
+ orderStatus String @default("created") @map("order_status") @db.VarChar(50)
97
+ orderCreatedAt DateTime? @default(now()) @map("order_created_at") @db.Timestamptz(6)
98
+ orderExpiredAt DateTime? @map("order_expired_at") @db.Timestamptz(6)
99
+ orderUpdatedAt DateTime? @default(now()) @updatedAt @map("order_updated_at") @db.Timestamptz(6)
100
+ type String? @db.VarChar(50)
101
+ paySupplier String? @map("pay_supplier") @db.VarChar(255)
102
+ paySessionId String? @map("pay_session_id") @db.VarChar(255)
103
+ payTransactionId String? @unique @map("pay_transaction_id") @db.VarChar(255)
104
+ paySubscriptionId String? @map("pay_subscription_id") @db.VarChar(255)
105
+ subPeriodStart DateTime? @map("sub_period_start") @db.Timestamptz(6)
106
+ subPeriodEnd DateTime? @map("sub_period_end") @db.Timestamptz(6)
107
+ subLastTryCancelAt DateTime? @map("sub_last_try_cancel_at") @db.Timestamptz(6)
108
+ subPeriodCanceledAt DateTime? @map("sub_period_canceled_at") @db.Timestamptz(6)
109
+ subCancellationDetail String? @map("sub_cancellation_detail") @db.Text
110
+ priceId String? @map("price_id") @db.VarChar(255)
111
+ priceName String? @map("price_name") @db.VarChar(255)
112
+ amount Decimal? @db.Decimal(10, 2)
113
+ currency String? @db.VarChar(50)
114
+ creditsGranted Int? @default(0) @map("credits_granted")
115
+ payInvoiceId String? @map("pay_invoice_id") @db.VarChar(255)
116
+ paymentStatus String @default("un_paid") @map("payment_status") @db.VarChar(50)
117
+ billingReason String? @map("billing_reason") @db.VarChar(100)
118
+ hostedInvoiceUrl String? @map("hosted_invoice_url") @db.Text
119
+ invoicePdf String? @map("invoice_pdf") @db.Text
120
+ orderDetail String? @map("order_detail")
121
+ paidEmail String? @map("paid_email") @db.VarChar(255)
122
+ paidAt DateTime? @map("paid_at") @db.Timestamptz(6)
123
+ paidDetail String? @map("paid_detail")
124
+ payUpdatedAt DateTime? @map("pay_updated_at") @db.Timestamptz(6)
125
+ deleted Int @default(0)
126
+ user User @relation(fields: [userId], references: [userId], onDelete: NoAction, onUpdate: NoAction)
127
+
128
+ @@index([orderId], map: "idx_transactions_order_id")
129
+ @@index([paySubscriptionId], map: "idx_transactions_pay_subscription_id")
130
+ @@index([userId], map: "idx_transactions_user_id")
131
+ @@map("transactions")
132
+
133
+ @@schema("nextai")
134
+ }
135
+
136
+ model CreditAuditLog {
137
+ id BigInt @id @default(autoincrement())
138
+ userId String @map("user_id") @db.Uuid
139
+ creditsChange Int @map("credits_change")
140
+ feature String? @db.VarChar(255)
141
+ creditType String @map("credit_type") @db.VarChar(50)
142
+ operationType String @map("operation_type") @db.VarChar(100)
143
+ operationReferId String? @map("operation_refer_id") @db.VarChar(255)
144
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
145
+ deleted Int @default(0)
146
+ user User @relation(fields: [userId], references: [userId], onDelete: NoAction, onUpdate: NoAction)
147
+
148
+ @@index([creditType], map: "idx_credit_audit_log_credit_type")
149
+ @@index([operationType], map: "idx_credit_audit_log_operation_type")
150
+ @@index([userId], map: "idx_credit_audit_log_user_id")
151
+ @@map("credit_audit_log")
152
+
153
+ @@schema("nextai")
154
+ }
155
+
156
+ model UserBackup {
157
+ id BigInt @id @default(autoincrement())
158
+ originalUserId String @map("original_user_id") @db.Uuid
159
+ status String? @db.VarChar(50)
160
+ fingerprintId String? @map("fingerprint_id") @db.VarChar(255)
161
+ clerkUserId String? @map("clerk_user_id") @db.VarChar(255)
162
+ stripeCusId String? @map("stripe_cus_id") @db.VarChar(255)
163
+ email String? @db.VarChar(255)
164
+ userName String? @map("user_name") @db.VarChar(255)
165
+ backupData Json? @map("backup_data")
166
+ deletedAt DateTime? @default(now()) @map("deleted_at") @db.Timestamptz(6)
167
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
168
+ deleted Int @default(0)
169
+
170
+ @@index([clerkUserId], map: "idx_user_backup_clerk_user_id")
171
+ @@index([email], map: "idx_user_backup_email")
172
+ @@index([fingerprintId], map: "idx_user_backup_fingerprint_id")
173
+ @@index([originalUserId], map: "idx_user_backup_original_user_id")
174
+ @@map("user_backup")
175
+
176
+ @@schema("nextai")
177
+ }
178
+
179
+ model Apilog {
180
+ id BigInt @id @default(autoincrement())
181
+ apiType String @map("api_type") @db.VarChar(100)
182
+ methodName String @map("method_name") @db.VarChar(255)
183
+ summary String?
184
+ request String?
185
+ response String?
186
+ createdAt DateTime? @default(now()) @map("created_at") @db.Timestamptz(6)
187
+
188
+ @@map("apilog")
189
+
190
+ @@schema("nextai")
191
+ }
@@ -0,0 +1,145 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { z } from 'zod';
3
+ import {
4
+ createCheckoutSession,
5
+ createOrGetCustomer,
6
+ ActiveSubscriptionExistsError,
7
+ } from '../../../../lib/stripe-config';
8
+ import {
9
+ transactionService,
10
+ TransactionType,
11
+ OrderStatus,
12
+ PaySupplier,
13
+ PaymentStatus
14
+ } from '../../../../services/database/index';
15
+ import { ApiAuthUtils } from '../../../../lib/auth-utils';
16
+ import { getPriceConfig } from '../../../../lib/money-price-config';
17
+
18
+ // Request validation schema
19
+ const createCheckoutSchema = z.object({
20
+ priceId: z.string().min(1, 'PriceID is required'),
21
+ plan: z.string().min(1, 'Plan is required'),
22
+ billingType: z.string().min(1, 'BillingType is required'),
23
+ provider: z.string().min(1, 'Provider is required'),
24
+ });
25
+
26
+ export async function POST(request: NextRequest) {
27
+ try {
28
+ // Parse request body
29
+ const body = await request.json();
30
+ const { priceId, plan, billingType, provider } = createCheckoutSchema.parse(body);
31
+
32
+ console.log(`Create Checkout: ${priceId} | ${plan} | ${billingType} | ${provider}`);
33
+
34
+ // Use unified authentication to get user info
35
+ const authUtils = new ApiAuthUtils(request);
36
+ const { user } = await authUtils.requireAuthWithUser();
37
+
38
+ // Validate price configuration
39
+ const priceConfig = getPriceConfig(priceId, plan, billingType, provider);
40
+ if (!priceConfig) {
41
+ return NextResponse.json(
42
+ { error: 'Invalid price configuration' },
43
+ { status: 400 }
44
+ );
45
+ }
46
+
47
+ // Create or get Stripe customer
48
+ const customerId = await createOrGetCustomer({
49
+ userId: user.userId,
50
+ });
51
+
52
+ // Generate order ID
53
+ const orderId = `order_${Date.now()}_${Math.random().toString(36).substring(2)}`;
54
+
55
+ // Default URLs if not provided
56
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
57
+ const defaultSuccessUrl = `${baseUrl}`;
58
+ const defaultCancelUrl = `${baseUrl}`;
59
+
60
+ // Create Stripe checkout session with interval for dynamic mode
61
+
62
+ const basciParams = {
63
+ priceId,
64
+ customerId,
65
+ clientReferenceId: user.userId,
66
+ successUrl: defaultSuccessUrl,
67
+ cancelUrl: defaultCancelUrl,
68
+ interval: priceConfig.interval, // ✅ Pass interval to auto-determine mode
69
+ metadata: {
70
+ order_id: orderId,
71
+ user_id: user.userId,
72
+ price_name: priceConfig.priceName,
73
+ credits_granted: priceConfig.credits?.toString() || '',
74
+ }
75
+ }
76
+
77
+ const subscriptionData = {
78
+ metadata: {
79
+ order_id: orderId,
80
+ user_id: user.userId,
81
+ },
82
+ };
83
+ const session = await createCheckoutSession(basciParams,subscriptionData);
84
+
85
+ // Create transaction record with session info
86
+ const orderType = priceConfig.interval && priceConfig.interval !== 'onetime' ? TransactionType.SUBSCRIPTION : TransactionType.ONE_TIME;
87
+ await transactionService.createTransaction({
88
+ userId: user.userId,
89
+ orderId,
90
+ orderStatus: OrderStatus.CREATED,
91
+ paymentStatus: PaymentStatus.UN_PAID,
92
+ paySupplier: PaySupplier.STRIPE,
93
+ paySessionId: session.id,
94
+ priceId,
95
+ priceName: priceConfig.priceName,
96
+ amount: priceConfig.amount,
97
+ currency: priceConfig.currency,
98
+ type: orderType,
99
+ creditsGranted: priceConfig.credits,
100
+ orderDetail: priceConfig.description,
101
+ paidEmail: null
102
+ });
103
+
104
+ return NextResponse.json({
105
+ success: true,
106
+ data: {
107
+ sessionId: session.id,
108
+ sessionUrl: session.url,
109
+ orderId,
110
+ priceConfig: {
111
+ priceName: priceConfig.priceName,
112
+ amount: priceConfig.amount,
113
+ currency: priceConfig.currency,
114
+ credits: priceConfig.credits,
115
+ description: priceConfig.description,
116
+ },
117
+ },
118
+ });
119
+
120
+ } catch (error) {
121
+ console.error('Create checkout error:', error);
122
+
123
+ if (error instanceof ActiveSubscriptionExistsError) {
124
+ return NextResponse.json(
125
+ {
126
+ error: 'Active subscription exists',
127
+ detail: 'Please use the customer portal to manage your existing subscription.',
128
+ },
129
+ { status: 409 }
130
+ );
131
+ }
132
+
133
+ if (error instanceof z.ZodError) {
134
+ return NextResponse.json(
135
+ { error: 'Validation error', details: error.issues },
136
+ { status: 400 }
137
+ );
138
+ }
139
+
140
+ return NextResponse.json(
141
+ { error: 'Failed to create checkout session' },
142
+ { status: 500 }
143
+ );
144
+ }
145
+ }
@@ -0,0 +1,83 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { z } from 'zod';
3
+ import {
4
+ createCustomerPortalSession,
5
+ createOrGetCustomer,
6
+ } from '../../../../lib/stripe-config';
7
+ import { ApiAuthUtils } from '../../../../lib/auth-utils';
8
+ import { subscriptionService } from '../../../../services/database/index';
9
+
10
+ const createCustomerPortalSchema = z
11
+ .object({
12
+ returnUrl: z.string().min(1).optional(),
13
+ })
14
+ .optional();
15
+
16
+ export async function POST(request: NextRequest) {
17
+ try {
18
+ const body = await request.json().catch(() => ({}));
19
+ const parsed = createCustomerPortalSchema.parse(body);
20
+ const returnUrlInput = parsed?.returnUrl;
21
+
22
+ const authUtils = new ApiAuthUtils(request);
23
+ const { user } = await authUtils.requireAuthWithUser();
24
+
25
+ const activeSubscription = await subscriptionService.getActiveSubscription(user.userId);
26
+ if (!activeSubscription) {
27
+ return NextResponse.json(
28
+ { error: 'No active subscription' },
29
+ { status: 403 }
30
+ );
31
+ }
32
+
33
+ const customerId = await createOrGetCustomer({
34
+ userId: user.userId,
35
+ });
36
+
37
+ const baseUrl = process.env.NEXT_PUBLIC_BASE_URL || 'http://localhost:3000';
38
+ const resolvedReturnUrl = resolveReturnUrl(returnUrlInput, baseUrl);
39
+
40
+ const session = await createCustomerPortalSession({
41
+ customerId,
42
+ returnUrl: resolvedReturnUrl,
43
+ });
44
+
45
+ return NextResponse.json({
46
+ success: true,
47
+ data: {
48
+ sessionId: session.id,
49
+ sessionUrl: session.url,
50
+ },
51
+ });
52
+ } catch (error) {
53
+ console.error('Create customer portal error:', error);
54
+
55
+ if (error instanceof z.ZodError) {
56
+ return NextResponse.json(
57
+ { error: 'Validation error', details: error.issues },
58
+ { status: 400 }
59
+ );
60
+ }
61
+
62
+ return NextResponse.json(
63
+ { error: 'Failed to create customer portal session' },
64
+ { status: 500 }
65
+ );
66
+ }
67
+ }
68
+
69
+ function resolveReturnUrl(returnUrl: string | undefined, baseUrl: string): string {
70
+ if (!returnUrl) {
71
+ return baseUrl;
72
+ }
73
+
74
+ try {
75
+ return new URL(returnUrl).toString();
76
+ } catch {
77
+ try {
78
+ return new URL(returnUrl, baseUrl).toString();
79
+ } catch {
80
+ return baseUrl;
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,284 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+
3
+ // Fix BigInt serialization issue
4
+ (BigInt.prototype as any).toJSON = function () {
5
+ return this.toString();
6
+ };
7
+
8
+ import { userAggregateService } from '../../../../../services/aggregate/index';
9
+ import { XCredit, XSubscription, XUser } from '@windrun-huaiin/third-ui/fingerprint';
10
+ import { extractFingerprintFromNextRequest } from '@windrun-huaiin/third-ui/fingerprint/server';
11
+ import { auth } from '@clerk/nextjs/server';
12
+ import { NextRequest, NextResponse } from 'next/server';
13
+ import {
14
+ applyUserMockContext,
15
+ fetchLatestUserContextByFingerprintId,
16
+ fetchUserContextByClerkUserId,
17
+ mapCreditToXCredit,
18
+ mapSubscriptionToXSubscription,
19
+ mapUserToXUser,
20
+ type UserContextEntities,
21
+ } from '../../../../../services/context/user-context-service';
22
+ import type { Prisma } from '../../../../../services/database/prisma-model-type';
23
+
24
+
25
+ // ==================== 类型定义 ====================
26
+
27
+ /** 成功响应数据 */
28
+ interface XUserResponse {
29
+ success: true;
30
+ xUser: XUser;
31
+ xCredit: XCredit | null;
32
+ xSubscription: XSubscription | null;
33
+ isNewUser: boolean;
34
+ totalUsersOnDevice?: number;
35
+ hasAnonymousUser?: boolean;
36
+ }
37
+
38
+ /** 错误响应数据 */
39
+ interface ErrorResponse {
40
+ error: string;
41
+ }
42
+
43
+ // ==================== 工具函数 ====================
44
+
45
+ /** 创建成功响应对象 */
46
+ function createSuccessResponse(params: {
47
+ entities: UserContextEntities;
48
+ isNewUser: boolean;
49
+ options?: {
50
+ totalUsersOnDevice?: number;
51
+ hasAnonymousUser?: boolean;
52
+ };
53
+ }): XUserResponse {
54
+ const response: XUserResponse = {
55
+ success: true,
56
+ xUser: mapUserToXUser(params.entities.user),
57
+ xCredit: params.entities.credit ? mapCreditToXCredit(params.entities.credit) : null,
58
+ xSubscription: mapSubscriptionToXSubscription(params.entities.subscription),
59
+ isNewUser: params.isNewUser,
60
+ ...params.options,
61
+ };
62
+
63
+ return applyUserMockContext(response);
64
+ }
65
+
66
+ /** 创建错误响应 */
67
+ function createErrorResponse(message: string, status = 400): NextResponse {
68
+ const errorResponse: ErrorResponse = { error: message };
69
+ return NextResponse.json(errorResponse, { status });
70
+ }
71
+
72
+ type SourceRefData = Prisma.InputJsonObject & {
73
+ httpRefer?: string;
74
+ utmSource?: string;
75
+ utmMedium?: string;
76
+ utmCampaign?: string;
77
+ utmTerm?: string;
78
+ utmContent?: string;
79
+ ref?: string;
80
+ };
81
+
82
+ type SourceRefKey = 'utmSource' | 'utmMedium' | 'utmCampaign' | 'utmTerm' | 'utmContent' | 'ref';
83
+
84
+ const SOURCE_REF_MAX_LENGTH = 2048;
85
+ const QUERY_PARAM_MAX_LENGTH = 512;
86
+
87
+ function normalizeSourceRef(ref: string | null): string | null {
88
+ if (!ref) {
89
+ return null;
90
+ }
91
+
92
+ const trimmed = ref.trim();
93
+ if (!trimmed) {
94
+ return null;
95
+ }
96
+
97
+ return trimmed.length > SOURCE_REF_MAX_LENGTH
98
+ ? trimmed.slice(0, SOURCE_REF_MAX_LENGTH)
99
+ : trimmed;
100
+ }
101
+
102
+ function normalizeQueryParam(value: string | null): string | null {
103
+ if (!value) {
104
+ return null;
105
+ }
106
+
107
+ const trimmed = value.trim();
108
+ if (!trimmed) {
109
+ return null;
110
+ }
111
+
112
+ return trimmed.length > QUERY_PARAM_MAX_LENGTH
113
+ ? trimmed.slice(0, QUERY_PARAM_MAX_LENGTH)
114
+ : trimmed;
115
+ }
116
+
117
+ function applySearchParams(sourceRef: SourceRefData, params: URLSearchParams) {
118
+ const setIfEmpty = (key: SourceRefKey, value: string | null) => {
119
+ if (sourceRef[key] !== undefined) {
120
+ return;
121
+ }
122
+ const normalized = normalizeQueryParam(value);
123
+ if (normalized) {
124
+ sourceRef[key] = normalized;
125
+ }
126
+ };
127
+
128
+ setIfEmpty('utmSource', params.get('utm_source'));
129
+ setIfEmpty('utmMedium', params.get('utm_medium'));
130
+ setIfEmpty('utmCampaign', params.get('utm_campaign'));
131
+ setIfEmpty('utmTerm', params.get('utm_term'));
132
+ setIfEmpty('utmContent', params.get('utm_content'));
133
+ setIfEmpty('ref', params.get('ref'));
134
+ }
135
+
136
+ // 提取用户首次访问来源
137
+ function extractSourceRef(request: NextRequest): SourceRefData | null {
138
+ const headerRef = request.headers.get('referer') || request.headers.get('referrer');
139
+ const customRef = request.headers.get('x-source-ref');
140
+ const queryRef = request.nextUrl.searchParams.get('ref');
141
+ console.log({
142
+ headerRef,
143
+ customRef,
144
+ queryRef
145
+ })
146
+
147
+ const sourceRef: SourceRefData = {};
148
+
149
+ let normalizedHttpRef: string | null = null;
150
+ const candidates = [headerRef, customRef, queryRef];
151
+ for (const candidate of candidates) {
152
+ const normalized = normalizeSourceRef(candidate);
153
+ if (normalized) {
154
+ normalizedHttpRef = normalized;
155
+ sourceRef.httpRefer = normalized;
156
+ break;
157
+ }
158
+ }
159
+
160
+ const searchParams = request.nextUrl.searchParams;
161
+ applySearchParams(sourceRef, searchParams);
162
+
163
+ if (normalizedHttpRef) {
164
+ try {
165
+ const refererUrl = new URL(normalizedHttpRef);
166
+ applySearchParams(sourceRef, refererUrl.searchParams);
167
+ } catch (error) {
168
+ console.warn('Failed to parse referer url for utm/ref:', error);
169
+ }
170
+ }
171
+
172
+ return Object.keys(sourceRef).length > 0 ? sourceRef : null;
173
+ }
174
+
175
+
176
+ /**
177
+ * 根据fingerprint_id查询用户并返回响应数据
178
+ */
179
+ async function getUserByClerkId(clerkUserId: string): Promise<XUserResponse | null> {
180
+ const entities = await fetchUserContextByClerkUserId(clerkUserId);
181
+ if (!entities) {
182
+ return null;
183
+ }
184
+
185
+ return createSuccessResponse({
186
+ entities,
187
+ isNewUser: false,
188
+ });
189
+ }
190
+
191
+ /**
192
+ * 根据fingerprint_id查询用户并返回响应数据
193
+ */
194
+ async function getUserByFingerprintId(fingerprintId: string): Promise<XUserResponse | null> {
195
+ const result = await fetchLatestUserContextByFingerprintId(fingerprintId);
196
+ if (!result) {
197
+ return null;
198
+ }
199
+
200
+ const { totalUsersOnDevice, hasAnonymousUser, ...entities } = result;
201
+
202
+ return createSuccessResponse({
203
+ entities,
204
+ isNewUser: false,
205
+ options: {
206
+ totalUsersOnDevice,
207
+ hasAnonymousUser,
208
+ },
209
+ });
210
+ }
211
+
212
+ /**
213
+ * 通用的fingerprint处理逻辑
214
+ */
215
+ async function handleFingerprintRequest(request: NextRequest, options: { createIfNotExists?: boolean; } = {}) {
216
+ // 从请求中提取fingerprint ID
217
+ const fingerprintId = extractFingerprintFromNextRequest(request);
218
+ // 验证fingerprint ID
219
+ if (!fingerprintId) {
220
+ return createErrorResponse('Invalid or missing fingerprint ID');
221
+ }
222
+ console.log('Received fingerprintId:', fingerprintId);
223
+
224
+ const { userId: clerkUserId } = await auth();
225
+ try {
226
+ // 优先根据 Clerk ID 查询(如果已登录)
227
+ let existingUserResult: XUserResponse | null = null;
228
+ if (clerkUserId) {
229
+ // 已登录一律按照clerkUserId去查
230
+ existingUserResult = await getUserByClerkId(clerkUserId);
231
+ if (existingUserResult && existingUserResult.xUser.fingerprintId !== fingerprintId) {
232
+ // 说明当前用户的指纹ID发生了改变,为什么呢?因为它使用同一账号去注册Clerk,Clerk判定是同一用户!
233
+ // 这个时候一定以登录用户clerkUserId为准
234
+ // 但是考虑到同一指纹ID本身可以绑定多个账号,所以这里什么都不做
235
+ // 就是以当前登录用户去查他自己的数据就行!
236
+ console.warn(`Current login user used diff fp_ids: ${clerkUserId}, db_fp_id=${existingUserResult.xUser.fingerprintId}, req_fp_id=${fingerprintId}`);
237
+ }
238
+ } else {
239
+ // 其次才是检查是否已存在该fingerprint的用户
240
+ existingUserResult = await getUserByFingerprintId(fingerprintId);
241
+ }
242
+ if (existingUserResult) {
243
+ return NextResponse.json(existingUserResult);
244
+ }
245
+
246
+ // 如果不存在用户且不允许创建,返回404
247
+ if (!options.createIfNotExists) {
248
+ return createErrorResponse('User not found', 404);
249
+ }
250
+
251
+ const sourceRef = extractSourceRef(request);
252
+
253
+ // 创建新的匿名用户
254
+ const { newUser, credit } = await userAggregateService.initAnonymousUser(
255
+ fingerprintId,
256
+ { sourceRef: sourceRef?? undefined}
257
+ );
258
+
259
+ console.log(`Created new anonymous user ${newUser.userId} with fingerprint ${fingerprintId}`);
260
+
261
+ // 返回创建结果
262
+ const response = createSuccessResponse({
263
+ entities: {
264
+ user: newUser,
265
+ credit,
266
+ subscription: null,
267
+ },
268
+ isNewUser: true,
269
+ });
270
+ return NextResponse.json(response);
271
+
272
+ } catch (error) {
273
+ console.error('Fingerprint request error:', error);
274
+ return createErrorResponse('Internal server error', 500);
275
+ }
276
+ }
277
+
278
+ /**
279
+ * 匿名用户初始化API
280
+ * POST /api/user/anonymous/init
281
+ */
282
+ export async function POST(request: NextRequest) {
283
+ return handleFingerprintRequest(request, { createIfNotExists: true });
284
+ }