feat(chat): render role-bound payment guidance
Docker Image / Build and Push Docker Image (push) Successful in 5m2s

This commit is contained in:
Codex
2026-07-27 18:14:04 +08:00
parent 019caae598
commit 8c0d1ad3ce
49 changed files with 882 additions and 81 deletions
@@ -0,0 +1,80 @@
import { describe, expect, it } from "vitest";
import {
ChatLockDetailSchema,
ChatSendResponseSchema,
PaymentGuidanceSchema,
UnlockHistoryResponseSchema,
UnlockPrivateResponseSchema,
} from "@/data/schemas/chat";
const guidance = {
guidanceId: "guidance-1",
characterId: "maya-tan",
characterName: "Maya Tan",
scene: "voiceLocked",
mode: "guide",
title: "Keep talking with Maya",
copy: "Hey love, I want to keep talking. This voice message needs 20 credits.",
ctaLabel: "View VIP & credit options",
purchaseOptions: ["vip", "topup"],
target: "subscription",
ruleId: "payment_guidance_voiceLocked",
} as const;
describe("payment guidance wire contract", () => {
it("parses the public decision without changing role identity", () => {
expect(PaymentGuidanceSchema.parse(guidance)).toEqual(guidance);
});
it("keeps all backend lock facts instead of dropping hint and CTA", () => {
expect(
ChatLockDetailSchema.parse({
locked: true,
showContent: false,
showUpgrade: true,
reason: "voice_message",
hint: "This voice message needs 20 credits.",
actionLabel: "Unlock",
detail: { requiredCredits: 20 },
}),
).toEqual({
locked: true,
showContent: false,
showUpgrade: true,
reason: "voice_message",
hint: "This voice message needs 20 credits.",
actionLabel: "Unlock",
detail: { requiredCredits: 20 },
});
});
it("keeps backward compatibility when guidance is absent", () => {
const parsed = ChatSendResponseSchema.parse({ reply: "Hello" });
expect(parsed.paymentGuidance).toBeNull();
});
it("parses guidance on chat and both unlock responses", () => {
expect(
ChatSendResponseSchema.parse({
reply: "",
canSendMessage: false,
paymentGuidance: guidance,
}).paymentGuidance,
).toEqual(guidance);
expect(
UnlockPrivateResponseSchema.parse({
unlocked: false,
reason: "insufficient_credits",
paymentGuidance: guidance,
}).paymentGuidance,
).toEqual(guidance);
expect(
UnlockHistoryResponseSchema.parse({
unlocked: false,
reason: "insufficient_balance",
paymentGuidance: { ...guidance, scene: "historyLocked" },
}).paymentGuidance?.scene,
).toBe("historyLocked");
});
});