diff --git a/src/stores/chat/__tests__/chat-unlock-flow.test.ts b/src/stores/chat/__tests__/chat-unlock-flow.test.ts index ac92b2c9..a5280b3c 100644 --- a/src/stores/chat/__tests__/chat-unlock-flow.test.ts +++ b/src/stores/chat/__tests__/chat-unlock-flow.test.ts @@ -490,6 +490,68 @@ describe("chat unlock flow", () => { 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 () => { const actor = createActor(createTestChatMachine()).start(); diff --git a/src/stores/chat/machine/unlock-flow.ts b/src/stores/chat/machine/unlock-flow.ts index 1fdc6617..ffaed215 100644 --- a/src/stores/chat/machine/unlock-flow.ts +++ b/src/stores/chat/machine/unlock-flow.ts @@ -85,6 +85,7 @@ const applyUnlockMessageSucceededAction = sendMachineSetup.assign( const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign( ({ context, event }) => { const { output } = event as unknown as DoneActorEvent; + const shouldOpenPaywall = output.response.reason !== "not_found"; return { promotion: applyPromotionUnlockOutput(context.promotion, output), isUnlockingMessage: false, @@ -94,30 +95,32 @@ const requestUnlockPaymentFromOutputAction = sendMachineSetup.assign( unlockingLockType: null, unlockingClientLockId: null, unlockMessageError: output.response.reason, - unlockPaywallRequest: { - displayMessageId: - output.response.messageId || output.displayMessageId, - ...(output.response.messageId || output.request.messageId - ? { - messageId: - output.response.messageId || output.request.messageId, - } - : {}), - kind: context.unlockingMessageKind ?? "private", - ...(output.request.lockType - ? { lockType: output.request.lockType } - : {}), - ...(output.request.clientLockId - ? { clientLockId: output.request.clientLockId } - : {}), - ...(context.promotion?.message.id === output.displayMessageId - ? { promotion: context.promotion.session } - : {}), - reason: output.response.reason, - creditBalance: output.response.creditBalance, - requiredCredits: output.response.requiredCredits, - shortfallCredits: output.response.shortfallCredits, - }, + unlockPaywallRequest: shouldOpenPaywall + ? { + displayMessageId: + output.response.messageId || output.displayMessageId, + ...(output.response.messageId || output.request.messageId + ? { + messageId: + output.response.messageId || output.request.messageId, + } + : {}), + kind: context.unlockingMessageKind ?? "private", + ...(output.request.lockType + ? { lockType: output.request.lockType } + : {}), + ...(output.request.clientLockId + ? { clientLockId: output.request.clientLockId } + : {}), + ...(context.promotion?.message.id === output.displayMessageId + ? { promotion: context.promotion.session } + : {}), + reason: output.response.reason, + creditBalance: output.response.creditBalance, + requiredCredits: output.response.requiredCredits, + shortfallCredits: output.response.shortfallCredits, + } + : null, creditBalance: output.response.creditBalance, requiredCredits: output.response.requiredCredits, shortfallCredits: output.response.shortfallCredits,