diff --git a/apps/viewer/src/test/assets/typebots/payment.json b/apps/viewer/src/test/assets/typebots/payment.json new file mode 100644 index 000000000..0fea84e97 --- /dev/null +++ b/apps/viewer/src/test/assets/typebots/payment.json @@ -0,0 +1,94 @@ +{ + "version": "6", + "id": "clxvjgawa0003lu4yp38homts", + "name": "Lead Generation", + "events": [ + { + "id": "k6kY6gwRE6noPoYQNGzgUq", + "outgoingEdgeId": "oNvqaqNExdSH2kKEhKZHuE", + "graphCoordinates": { "x": 0, "y": 0 }, + "type": "start" + } + ], + "groups": [ + { + "id": "kinRXxYop2X4d7F9qt8WNB", + "title": "Welcome", + "graphCoordinates": { "x": 1, "y": 148 }, + "blocks": [ + { + "id": "sc1y8VwDabNJgiVTBi4qtif", + "type": "text", + "content": { + "richText": [ + { + "type": "p", + "children": [ + { "text": "Welcome to " }, + { "bold": true, "text": "AA" }, + { "text": " (Awesome Agency)" } + ] + } + ] + } + }, + { + "id": "xccq75m3fe7l6ads4wcvian0", + "type": "payment input", + "options": { + "credentialsId": "stripe", + "amount": "10" + } + }, + { + "id": "wo8xv8kqc8dpwadvoepv3bua", + "type": "text", + "content": { + "richText": [ + { "type": "p", "children": [{ "text": "Thank you!" }] } + ] + } + } + ] + } + ], + "edges": [ + { + "id": "oNvqaqNExdSH2kKEhKZHuE", + "from": { "eventId": "k6kY6gwRE6noPoYQNGzgUq" }, + "to": { "groupId": "kinRXxYop2X4d7F9qt8WNB" } + } + ], + "variables": [ + { + "id": "vml1jtton0xpmnk2vkwog488g", + "name": "Attachments", + "isSessionVariable": true + }, + { + "id": "giiLFGw5xXBCHzvp1qAbdX", + "name": "Name", + "isSessionVariable": true + }, + { + "id": "v3VFChNVSCXQ2rXv4DrJ8Ah", + "name": "Email", + "isSessionVariable": true + } + ], + "theme": {}, + "selectedThemeTemplateId": null, + "settings": {}, + "createdAt": "2024-06-26T07:53:07.882Z", + "updatedAt": "2024-06-26T08:12:50.867Z", + "icon": "🤝", + "folderId": null, + "publicId": "lead-generation-38homts", + "customDomain": null, + "workspaceId": "proWorkspace", + "resultsTablePreferences": null, + "isArchived": false, + "isClosed": false, + "whatsAppCredentialsId": null, + "riskLevel": null +} diff --git a/apps/viewer/src/test/payment.spec.ts b/apps/viewer/src/test/payment.spec.ts new file mode 100644 index 000000000..90a249689 --- /dev/null +++ b/apps/viewer/src/test/payment.spec.ts @@ -0,0 +1,22 @@ +import { createId } from '@paralleldrive/cuid2' +import test, { expect } from '@playwright/test' +import { getTestAsset } from './utils/playwright' +import { importTypebotInDatabase } from '@typebot.io/playwright/databaseActions' + +test('Payment redirection should work', async ({ page }) => { + const typebotId = createId() + await importTypebotInDatabase(getTestAsset('typebots/payment.json'), { + id: typebotId, + publicId: `${typebotId}-public`, + }) + await page.goto(`/${typebotId}-public`) + const paypalButton = page + .frameLocator('iframe[title="Secure payment input frame"]') + .getByTestId('paypal') + await expect(paypalButton).toBeVisible() + await page.waitForTimeout(1000) + await paypalButton.click() + await page.getByRole('button', { name: 'Pay $' }).click() + await page.getByRole('link', { name: 'Authorize Test Payment' }).click() + await expect(page.getByText('Thank you!')).toBeVisible() +}) diff --git a/packages/bot-engine/continueBotFlow.ts b/packages/bot-engine/continueBotFlow.ts index be2b3e8e9..d7d5b8d55 100644 --- a/packages/bot-engine/continueBotFlow.ts +++ b/packages/bot-engine/continueBotFlow.ts @@ -226,7 +226,7 @@ export const continueBotFlow = async ( return { ...chatReply, lastMessageNewFormat: - formattedReply !== reply ? formattedReply : undefined, + formattedReply !== reply?.text ? formattedReply : undefined, } } @@ -235,7 +235,7 @@ export const continueBotFlow = async ( messages: [], newSessionState, lastMessageNewFormat: - formattedReply !== reply ? formattedReply : undefined, + formattedReply !== reply?.text ? formattedReply : undefined, visitedEdges, setVariableHistory, } @@ -272,7 +272,8 @@ export const continueBotFlow = async ( return { ...chatReply, - lastMessageNewFormat: formattedReply !== reply ? formattedReply : undefined, + lastMessageNewFormat: + formattedReply !== reply?.text ? formattedReply : undefined, } } diff --git a/packages/embeds/js/package.json b/packages/embeds/js/package.json index 8e8f66fbb..43895ad60 100644 --- a/packages/embeds/js/package.json +++ b/packages/embeds/js/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/js", - "version": "0.3.0", + "version": "0.3.1", "description": "Javascript library to display typebots on your website", "type": "module", "main": "dist/index.js", diff --git a/packages/embeds/js/src/components/Bot.tsx b/packages/embeds/js/src/components/Bot.tsx index 65e03e05d..36c98dee6 100644 --- a/packages/embeds/js/src/components/Bot.tsx +++ b/packages/embeds/js/src/components/Bot.tsx @@ -4,7 +4,7 @@ import { isDefined, isNotDefined, isNotEmpty } from '@typebot.io/lib' import { startChatQuery } from '@/queries/startChatQuery' import { ConversationContainer } from './ConversationContainer' import { setIsMobile } from '@/utils/isMobileSignal' -import { BotContext, InitialChatReply, OutgoingLog } from '@/types' +import { BotContext, OutgoingLog } from '@/types' import { ErrorMessage } from './ErrorMessage' import { getExistingResultIdFromStorage, @@ -15,7 +15,12 @@ import { } from '@/utils/storage' import { setCssVariablesValue } from '@/utils/setCssVariablesValue' import immutableCss from '../assets/immutable.css' -import { Font, InputBlock, StartFrom } from '@typebot.io/schemas' +import { + Font, + InputBlock, + StartChatResponse, + StartFrom, +} from '@typebot.io/schemas' import { clsx } from 'clsx' import { HTTPError } from 'ky' import { injectFont } from '@/utils/injectFont' @@ -55,7 +60,7 @@ export type BotProps = { export const Bot = (props: BotProps & { class?: string }) => { const [initialChatReply, setInitialChatReply] = createSignal< - InitialChatReply | undefined + StartChatResponse | undefined >() const [customCss, setCustomCss] = createSignal('') const [isInitialized, setIsInitialized] = createSignal(false) @@ -245,7 +250,7 @@ export const Bot = (props: BotProps & { class?: string }) => { } type BotContentProps = { - initialChatReply: InitialChatReply + initialChatReply: StartChatResponse context: BotContext class?: string progressBarRef?: HTMLDivElement diff --git a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx index 2a50bd1f1..7d93de292 100644 --- a/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx +++ b/packages/embeds/js/src/components/ConversationContainer/ConversationContainer.tsx @@ -3,6 +3,7 @@ import { InputBlock, Theme, ChatLog, + StartChatResponse, } from '@typebot.io/schemas' import { createEffect, @@ -14,12 +15,7 @@ import { } from 'solid-js' import { continueChatQuery } from '@/queries/continueChatQuery' import { ChatChunk } from './ChatChunk' -import { - BotContext, - ChatChunk as ChatChunkType, - InitialChatReply, - OutgoingLog, -} from '@/types' +import { BotContext, ChatChunk as ChatChunkType, OutgoingLog } from '@/types' import { isNotDefined } from '@typebot.io/lib' import { executeClientSideAction } from '@/utils/executeClientSideActions' import { LoadingChunk } from './LoadingChunk' @@ -61,7 +57,7 @@ const parseDynamicTheme = ( }) type Props = { - initialChatReply: InitialChatReply + initialChatReply: StartChatResponse context: BotContext onNewInputBlock?: (inputBlock: InputBlock) => void onAnswer?: (answer: { message: string; blockId: string }) => void diff --git a/packages/embeds/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx b/packages/embeds/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx index f45371926..3446e6768 100644 --- a/packages/embeds/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx +++ b/packages/embeds/js/src/features/blocks/inputs/payment/components/StripePaymentForm.tsx @@ -64,6 +64,7 @@ export const StripePaymentForm = (props: Props) => { setPaymentInProgressInStorage({ sessionId: props.context.sessionId, + resultId: props.context.resultId, typebot: props.context.typebot, }) const { postalCode, ...address } = diff --git a/packages/embeds/js/src/features/blocks/inputs/payment/helpers/paymentInProgressStorage.ts b/packages/embeds/js/src/features/blocks/inputs/payment/helpers/paymentInProgressStorage.ts index e13a0cf56..f7aee3190 100644 --- a/packages/embeds/js/src/features/blocks/inputs/payment/helpers/paymentInProgressStorage.ts +++ b/packages/embeds/js/src/features/blocks/inputs/payment/helpers/paymentInProgressStorage.ts @@ -1,9 +1,8 @@ -import { BotContext } from '@/types' +import { StartChatResponse } from '@typebot.io/schemas' -export const setPaymentInProgressInStorage = (state: { - sessionId: string - typebot: BotContext['typebot'] -}) => { +export const setPaymentInProgressInStorage = ( + state: Pick +) => { sessionStorage.setItem('typebotPaymentInProgress', JSON.stringify(state)) } diff --git a/packages/embeds/js/src/queries/startChatQuery.ts b/packages/embeds/js/src/queries/startChatQuery.ts index 401d8cf21..5da29b8d3 100644 --- a/packages/embeds/js/src/queries/startChatQuery.ts +++ b/packages/embeds/js/src/queries/startChatQuery.ts @@ -1,4 +1,4 @@ -import { BotContext, InitialChatReply } from '@/types' +import { BotContext } from '@/types' import { guessApiHost } from '@/utils/guessApiHost' import { isNotDefined, isNotEmpty } from '@typebot.io/lib' import { @@ -6,7 +6,9 @@ import { removePaymentInProgressFromStorage, } from '@/features/blocks/inputs/payment/helpers/paymentInProgressStorage' import { + ContinueChatResponse, StartChatInput, + StartChatResponse, StartFrom, StartPreviewChatInput, } from '@typebot.io/schemas' @@ -65,9 +67,14 @@ export async function startChatQuery({ timeout: false, } ) - .json() + .json() - return { data } + return { + data: { + ...data, + ...paymentInProgressState, + } satisfies StartChatResponse, + } } catch (error) { return { error } } @@ -94,7 +101,7 @@ export async function startChatQuery({ timeout: false, } ) - .json() + .json() return { data } } catch (error) { @@ -138,7 +145,7 @@ export async function startChatQuery({ ) throw new CorsError(corsAllowOrigin) - return { data: await response.json() } + return { data: await response.json() } } catch (error) { return { error } } diff --git a/packages/embeds/js/src/types.ts b/packages/embeds/js/src/types.ts index 869864fff..ab74dceb0 100644 --- a/packages/embeds/js/src/types.ts +++ b/packages/embeds/js/src/types.ts @@ -7,7 +7,7 @@ export type InputSubmitContent = { } export type BotContext = { - typebot: InitialChatReply['typebot'] + typebot: StartChatResponse['typebot'] resultId?: string isPreview: boolean apiHost?: string @@ -15,11 +15,6 @@ export type BotContext = { storage: 'local' | 'session' | undefined } -export type InitialChatReply = StartChatResponse & { - typebot: NonNullable - sessionId: NonNullable -} - export type OutgoingLog = { status: string description: string diff --git a/packages/embeds/js/src/utils/storage.ts b/packages/embeds/js/src/utils/storage.ts index 3c18dd25b..026533df0 100644 --- a/packages/embeds/js/src/utils/storage.ts +++ b/packages/embeds/js/src/utils/storage.ts @@ -1,4 +1,4 @@ -import { InitialChatReply } from '@/types' +import { StartChatResponse } from '@typebot.io/schemas/features/chat/schema' import { defaultSettings } from '@typebot.io/schemas/features/typebot/settings/constants' const storageResultIdKey = 'resultId' @@ -38,13 +38,13 @@ export const getInitialChatReplyFromStorage = ( sessionStorage.getItem(`typebot-${typebotId}-initialChatReply`) ?? localStorage.getItem(`typebot-${typebotId}-initialChatReply`) if (!rawInitialChatReply) return - return JSON.parse(rawInitialChatReply) as InitialChatReply + return JSON.parse(rawInitialChatReply) as StartChatResponse } catch { /* empty */ } } export const setInitialChatReplyInStorage = ( - initialChatReply: InitialChatReply, + initialChatReply: StartChatResponse, { typebotId, storage, diff --git a/packages/embeds/nextjs/package.json b/packages/embeds/nextjs/package.json index a7c5e1fa6..1cfcfcc7a 100644 --- a/packages/embeds/nextjs/package.json +++ b/packages/embeds/nextjs/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/nextjs", - "version": "0.3.0", + "version": "0.3.1", "description": "Convenient library to display typebots on your Next.js website", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/embeds/react/package.json b/packages/embeds/react/package.json index 1f0943b53..437e97b29 100644 --- a/packages/embeds/react/package.json +++ b/packages/embeds/react/package.json @@ -1,6 +1,6 @@ { "name": "@typebot.io/react", - "version": "0.3.0", + "version": "0.3.1", "description": "Convenient library to display typebots on your React app", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/playwright/databaseSetup.ts b/packages/playwright/databaseSetup.ts index 8c12f4a35..d126aeb52 100644 --- a/packages/playwright/databaseSetup.ts +++ b/packages/playwright/databaseSetup.ts @@ -5,6 +5,8 @@ import { WorkspaceRole, } from '@typebot.io/prisma' import { encrypt } from '@typebot.io/lib/api/encryption/encrypt' +import { env } from '@typebot.io/env' +import { StripeCredentials } from '@typebot.io/schemas' const prisma = new PrismaClient() @@ -134,6 +136,16 @@ const setupCredentials = async () => { refresh_token: '1//039xWRt8YaYa3CgYIARAAGAMSNwF-L9Iru9FyuTrDSa7lkSceggPho83kJt2J29G69iEhT1C6XV1vmo6bQS9puL_R2t8FIwR3gek', }) + const { encryptedData: stripeEncryptedData, iv: stripeIv } = await encrypt({ + test: { + publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY, + secretKey: env.STRIPE_SECRET_KEY, + }, + live: { + publicKey: env.NEXT_PUBLIC_STRIPE_PUBLIC_KEY ?? '', + secretKey: env.STRIPE_SECRET_KEY ?? '', + }, + } satisfies StripeCredentials['data']) return prisma.credentials.createMany({ data: [ { @@ -143,6 +155,14 @@ const setupCredentials = async () => { workspaceId: proWorkspaceId, iv, }, + { + id: 'stripe', + name: 'Test', + type: 'stripe', + data: stripeEncryptedData, + workspaceId: proWorkspaceId, + iv: stripeIv, + }, ], }) } diff --git a/packages/playwright/package.json b/packages/playwright/package.json index 6047fef5b..7e00ec640 100644 --- a/packages/playwright/package.json +++ b/packages/playwright/package.json @@ -10,10 +10,11 @@ "@playwright/test": "1.43.1", "@typebot.io/lib": "workspace:*", "@typebot.io/prisma": "workspace:*", - "@typebot.io/schemas": "workspace:*" + "@typebot.io/schemas": "workspace:*", + "@typebot.io/env": "workspace:*" }, "devDependencies": { "@types/node": "20.4.2", "@typebot.io/tsconfig": "workspace:*" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c7eb93f34..12b9b986a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1820,6 +1820,9 @@ importers: '@playwright/test': specifier: 1.43.1 version: 1.43.1 + '@typebot.io/env': + specifier: workspace:* + version: link:../env '@typebot.io/lib': specifier: workspace:* version: link:../lib