fix(chat): suppress paywall for missing unlock

This commit is contained in:
2026-07-15 14:44:12 +08:00
parent 58ca40a728
commit 684ae4bf5d
2 changed files with 89 additions and 24 deletions
@@ -490,6 +490,68 @@ describe("chat unlock flow", () => {
actor.stop(); actor.stop();
}); });
it("does not request payment when the unlock target is not found", async () => {
const actor = createActor(
createTestChatMachine({
historyMessages: [
{
id: "msg-missing",
content: "",
isFromAI: true,
date: "2026-06-29",
locked: true,
lockReason: "private_message",
privateMessageHint: "A private message is waiting.",
},
],
unlockMessageOutput: {
messageId: "msg-missing",
response: makeUnlockPrivateResponse({
unlocked: false,
content: "",
reason: "not_found",
creditBalance: 3,
creditsCharged: 0,
requiredCredits: 0,
shortfallCredits: 0,
}),
},
}),
).start();
actor.send({ type: "ChatUserLogin", token: "token" });
await waitFor(actor, (snapshot) =>
snapshot.matches({ userSession: "ready" }),
);
actor.send({
type: "ChatUnlockMessageRequested",
messageId: "msg-missing",
kind: "private",
});
await waitFor(
actor,
(snapshot) => snapshot.context.unlockMessageError === "not_found",
);
expect(actor.getSnapshot().context).toMatchObject({
isUnlockingMessage: false,
unlockingMessageId: null,
unlockMessageError: "not_found",
unlockPaywallRequest: null,
});
expect(actor.getSnapshot().context.messages).toMatchObject([
{
id: "msg-missing",
locked: true,
lockReason: "private_message",
},
]);
actor.stop();
});
it("clears the insufficient credits prompt after payment succeeds", async () => { it("clears the insufficient credits prompt after payment succeeds", async () => {
const actor = createActor(createTestChatMachine()).start(); const actor = createActor(createTestChatMachine()).start();
+5 -2
View File
@@ -85,6 +85,7 @@ const applyUnlockMessageSucceededAction = sendMachineSetup.assign(
const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign( const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign(
({ context, event }) => { ({ context, event }) => {
const { output } = event as unknown as DoneActorEvent<UnlockMessageOutput>; const { output } = event as unknown as DoneActorEvent<UnlockMessageOutput>;
const shouldOpenPaywall = output.response.reason !== "not_found";
return { return {
promotion: applyPromotionUnlockOutput(context.promotion, output), promotion: applyPromotionUnlockOutput(context.promotion, output),
isUnlockingMessage: false, isUnlockingMessage: false,
@@ -94,7 +95,8 @@ const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign(
unlockingLockType: null, unlockingLockType: null,
unlockingClientLockId: null, unlockingClientLockId: null,
unlockMessageError: output.response.reason, unlockMessageError: output.response.reason,
unlockPaywallRequest: { unlockPaywallRequest: shouldOpenPaywall
? {
displayMessageId: displayMessageId:
output.response.messageId || output.displayMessageId, output.response.messageId || output.displayMessageId,
...(output.response.messageId || output.request.messageId ...(output.response.messageId || output.request.messageId
@@ -117,7 +119,8 @@ const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign(
creditBalance: output.response.creditBalance, creditBalance: output.response.creditBalance,
requiredCredits: output.response.requiredCredits, requiredCredits: output.response.requiredCredits,
shortfallCredits: output.response.shortfallCredits, shortfallCredits: output.response.shortfallCredits,
}, }
: null,
creditBalance: output.response.creditBalance, creditBalance: output.response.creditBalance,
requiredCredits: output.response.requiredCredits, requiredCredits: output.response.requiredCredits,
shortfallCredits: output.response.shortfallCredits, shortfallCredits: output.response.shortfallCredits,