feat(payment): connect payment service flow
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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>;
|
||||
|
||||
@@ -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),
|
||||
});
|
||||
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user