feat(payment): connect payment service flow

This commit is contained in:
2026-06-18 15:40:59 +08:00
parent 35c30ac31e
commit a347b39001
34 changed files with 759 additions and 889 deletions
+2 -6
View File
@@ -5,12 +5,8 @@
*
* 原始 Dart: lib/ui/subscription/...
*
* Stripe 接入新增字段:
* - `stripeProductId` / `stripePriceId`Stripe 后台创建 Product + Price 后拿到的 id
* - `voiceMinutesPerDay`:该套餐每天给用户的语音分钟数(订阅后每天补充)
*
* 注:stripeProductId / stripePriceId 没有默认值 —— 为避免硬编码真实 id,由 env 动态注入(见 `src/lib/stripe/stripe-products.ts`
* 本常量表只是 UI 形状 + 业务字段的单一真相源
* 当前订阅页优先使用后端 `/api/payment/plans` 返回的套餐。
* 本常量仅保留给旧 UI / fallback 代码参考。
*/
export type SubscriptionPlanId = "monthly" | "quarterly" | "annual";
@@ -8,6 +8,8 @@ import {
type PaymentOrderStatusResponseInput,
} from "@/data/schemas/payment/payment_order_status_response";
export type { PaymentOrderStatus };
export class PaymentOrderStatusResponse {
declare readonly orderId: string;
declare readonly status: PaymentOrderStatus;
+2 -6
View File
@@ -24,14 +24,10 @@ export class User {
declare readonly avatarUrl: string;
declare readonly loginProvider: string;
declare readonly isGuest: boolean;
/** 是否为 VIP 会员Stripe webhook 更新) */
/** 是否为 VIP 会员 */
declare readonly isVip: boolean;
/** 语音聊天剩余分钟数Stripe webhook 更新) */
/** 语音聊天剩余分钟数 */
declare readonly voiceMinutesRemaining: number;
/** Stripe Customer ID —— 首次 Checkout 完成后由 webhook 设置 */
declare readonly stripeCustomerId: string | null;
private constructor(input: UserInput) {
const data = UserSchema.parse(input);
-1
View File
@@ -17,7 +17,6 @@ export const UserViewSchema = z.object({
isGuest: z.boolean(),
isVip: z.boolean(),
voiceMinutesRemaining: z.number(),
stripeCustomerId: z.string().nullable(),
});
export type UserView = z.infer<typeof UserViewSchema>;
@@ -10,10 +10,10 @@ export const ChatHistoryResponseSchema = z.object({
total: z.number().default(0),
limit: z.number(),
offset: z.number(),
isVip: z.boolean(),
privateFreeLimit: z.number(),
privateUsedToday: z.number(),
privateCanViewFree: z.boolean(),
isVip: z.boolean().default(false),
privateFreeLimit: z.number().default(0),
privateUsedToday: z.number().default(0),
privateCanViewFree: z.boolean().default(false),
});
export type ChatHistoryResponseInput = z.input<typeof ChatHistoryResponseSchema>;
+2 -2
View File
@@ -26,8 +26,8 @@ export const ChatSendResponseSchema = z.object({
blocked: z.boolean().nullable().default(null),
blockReason: z.string().nullable().default(null),
blockDetail: ChatBlockDetailSchema.nullable().default(null),
paywallTriggered: z.boolean(),
showUpgrade: z.boolean(),
paywallTriggered: z.boolean().default(false),
showUpgrade: z.boolean().default(false),
imageType: z.string().nullable().default(null),
imageUrl: z.string().nullable().default(null),
});
+1 -3
View File
@@ -27,12 +27,10 @@ export const UserSchema = z.object({
avatarUrl: stringOrEmpty,
loginProvider: stringOrEmpty,
isGuest: booleanOrFalse,
/** 是否为 VIP 会员(订阅中或未过期) —— Stripe webhook 更新 */
/** 是否为 VIP 会员(订阅中或未过期) */
isVip: booleanOrFalse,
/** 语音聊天剩余分钟数(订阅生效后每天补充;用完为 0 不变) */
voiceMinutesRemaining: numberOrZero,
/** Stripe Customer ID —— 首次 Checkout 完成后由 webhook 设置,用于 Customer Portal 自助管理 */
stripeCustomerId: z.string().nullable().default(null),
});
export type UserInput = z.input<typeof UserSchema>;