feat(input): ⚡️ Better payment accessibility
This commit is contained in:
@ -75,7 +75,13 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
const handleButtonLabelChange = (button: string) =>
|
const handleButtonLabelChange = (button: string) =>
|
||||||
onOptionsChange({
|
onOptionsChange({
|
||||||
...options,
|
...options,
|
||||||
labels: { button },
|
labels: { ...options.labels, button },
|
||||||
|
})
|
||||||
|
|
||||||
|
const handleSuccessLabelChange = (success: string) =>
|
||||||
|
onOptionsChange({
|
||||||
|
...options,
|
||||||
|
labels: { ...options.labels, success },
|
||||||
})
|
})
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -130,6 +136,14 @@ export const PaymentSettings = ({ options, onOptionsChange }: Props) => {
|
|||||||
placeholder="Pay"
|
placeholder="Pay"
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
|
<Stack>
|
||||||
|
<Text>Success message:</Text>
|
||||||
|
<Input
|
||||||
|
onChange={handleSuccessLabelChange}
|
||||||
|
defaultValue={options.labels.success ?? 'Success'}
|
||||||
|
placeholder="Success"
|
||||||
|
/>
|
||||||
|
</Stack>
|
||||||
<Accordion allowToggle>
|
<Accordion allowToggle>
|
||||||
<AccordionItem>
|
<AccordionItem>
|
||||||
<AccordionButton justifyContent="space-between">
|
<AccordionButton justifyContent="space-between">
|
||||||
|
@ -61,6 +61,7 @@ test.describe('Free workspace', () => {
|
|||||||
},
|
},
|
||||||
])
|
])
|
||||||
await page.goto(`/typebots/${typebotId}/share`)
|
await page.goto(`/typebots/${typebotId}/share`)
|
||||||
|
await expect(page.locator('text=Pro')).toBeVisible()
|
||||||
await page.click('text=Add my domain')
|
await page.click('text=Add my domain')
|
||||||
await expect(page.locator('text=For solo creator')).toBeVisible()
|
await expect(page.locator('text=For solo creator')).toBeVisible()
|
||||||
})
|
})
|
||||||
|
@ -11,6 +11,7 @@ test.describe.parallel('Templates page', () => {
|
|||||||
|
|
||||||
test('From file should import correctly', async ({ page }) => {
|
test('From file should import correctly', async ({ page }) => {
|
||||||
await page.goto('/typebots/create')
|
await page.goto('/typebots/create')
|
||||||
|
await page.waitForTimeout(1000)
|
||||||
await page.setInputFiles(
|
await page.setInputFiles(
|
||||||
'input[type="file"]',
|
'input[type="file"]',
|
||||||
path.join(__dirname, '../fixtures/typebots/singleChoiceTarget.json')
|
path.join(__dirname, '../fixtures/typebots/singleChoiceTarget.json')
|
||||||
|
@ -61,25 +61,39 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
const receiptEmail = parseVariables(variables)(
|
const receiptEmail = parseVariables(variables)(
|
||||||
inputOptions.additionalInformation?.email
|
inputOptions.additionalInformation?.email
|
||||||
)
|
)
|
||||||
const paymentIntent = await stripe.paymentIntents.create({
|
try {
|
||||||
amount,
|
const paymentIntent = await stripe.paymentIntents.create({
|
||||||
currency: inputOptions.currency,
|
amount,
|
||||||
receipt_email: receiptEmail === '' ? undefined : receiptEmail,
|
currency: inputOptions.currency,
|
||||||
automatic_payment_methods: {
|
receipt_email: receiptEmail === '' ? undefined : receiptEmail,
|
||||||
enabled: true,
|
automatic_payment_methods: {
|
||||||
},
|
enabled: true,
|
||||||
})
|
},
|
||||||
|
})
|
||||||
|
|
||||||
return res.send({
|
return res.send({
|
||||||
clientSecret: paymentIntent.client_secret,
|
clientSecret: paymentIntent.client_secret,
|
||||||
publicKey:
|
publicKey:
|
||||||
isPreview && stripeKeys.test?.publicKey
|
isPreview && stripeKeys.test?.publicKey
|
||||||
? stripeKeys.test.publicKey
|
? stripeKeys.test.publicKey
|
||||||
: stripeKeys.live.publicKey,
|
: stripeKeys.live.publicKey,
|
||||||
amountLabel: `${amount / 100}${
|
amountLabel: `${amount / 100}${
|
||||||
currencySymbols[inputOptions.currency] ?? inputOptions.currency
|
currencySymbols[inputOptions.currency] ?? ` ${inputOptions.currency}`
|
||||||
}`,
|
}`,
|
||||||
})
|
})
|
||||||
|
} catch (err) {
|
||||||
|
const error = err as any
|
||||||
|
return 'raw' in error
|
||||||
|
? res.status(error.raw.statusCode).send({
|
||||||
|
error: {
|
||||||
|
name: `${error.raw.type} ${error.raw.param}`,
|
||||||
|
message: error.raw.message,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: res.status(500).send({
|
||||||
|
error,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return methodNotAllowed(res)
|
return methodNotAllowed(res)
|
||||||
}
|
}
|
||||||
|
@ -112,7 +112,7 @@ const Input = ({
|
|||||||
return (
|
return (
|
||||||
<PaymentForm
|
<PaymentForm
|
||||||
options={step.options}
|
options={step.options}
|
||||||
onSuccess={() => onSubmit('Success')}
|
onSuccess={() => onSubmit(step.options.labels.success ?? 'Success')}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ export const StripePaymentForm = ({ options, onSuccess }: Props) => {
|
|||||||
apiHost,
|
apiHost,
|
||||||
isPreview,
|
isPreview,
|
||||||
typebot: { variables },
|
typebot: { variables },
|
||||||
|
onNewLog,
|
||||||
} = useTypebot()
|
} = useTypebot()
|
||||||
const { window: frameWindow, document: frameDocument } = useFrame()
|
const { window: frameWindow, document: frameDocument } = useFrame()
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
@ -34,7 +35,13 @@ export const StripePaymentForm = ({ options, onSuccess }: Props) => {
|
|||||||
variables,
|
variables,
|
||||||
inputOptions: options,
|
inputOptions: options,
|
||||||
})
|
})
|
||||||
if (error || !data) return console.error(error)
|
if (error)
|
||||||
|
return onNewLog({
|
||||||
|
status: 'error',
|
||||||
|
description: error.name + ' ' + error.message,
|
||||||
|
details: error.message,
|
||||||
|
})
|
||||||
|
if (!data) return
|
||||||
await initStripe(frameDocument)
|
await initStripe(frameDocument)
|
||||||
setStripe(frameWindow.Stripe(data.publicKey))
|
setStripe(frameWindow.Stripe(data.publicKey))
|
||||||
setClientSecret(data.clientSecret)
|
setClientSecret(data.clientSecret)
|
||||||
@ -82,6 +89,8 @@ const CheckoutForm = ({
|
|||||||
const [message, setMessage] = useState<string>()
|
const [message, setMessage] = useState<string>()
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false)
|
||||||
|
|
||||||
|
const [isPayButtonVisible, setIsPayButtonVisible] = useState(false)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!stripe || !clientSecret) return
|
if (!stripe || !clientSecret) return
|
||||||
|
|
||||||
@ -145,20 +154,28 @@ const CheckoutForm = ({
|
|||||||
setMessage('An unexpected error occured.')
|
setMessage('An unexpected error occured.')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const showPayButton = () => setIsPayButtonVisible(true)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
id="payment-form"
|
id="payment-form"
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex flex-col rounded-lg p-4 typebot-input w-full items-center"
|
className="flex flex-col rounded-lg p-4 typebot-input w-full items-center"
|
||||||
>
|
>
|
||||||
<PaymentElement id="payment-element" className="w-full" />
|
<PaymentElement
|
||||||
<SendButton
|
id="payment-element"
|
||||||
label={`${options.labels.button} ${amountLabel}`}
|
className="w-full"
|
||||||
isDisabled={isLoading || !stripe || !elements}
|
onReady={showPayButton}
|
||||||
isLoading={isLoading}
|
|
||||||
className="mt-4 w-full max-w-lg"
|
|
||||||
disableIcon
|
|
||||||
/>
|
/>
|
||||||
|
{isPayButtonVisible && (
|
||||||
|
<SendButton
|
||||||
|
label={`${options.labels.button} ${amountLabel}`}
|
||||||
|
isDisabled={isLoading || !stripe || !elements}
|
||||||
|
isLoading={isLoading}
|
||||||
|
className="mt-4 w-full max-w-lg"
|
||||||
|
disableIcon
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{message && (
|
{message && (
|
||||||
<div
|
<div
|
||||||
|
@ -144,7 +144,7 @@ export type PaymentInputOptions = OptionBase & {
|
|||||||
email?: string
|
email?: string
|
||||||
phoneNumber?: string
|
phoneNumber?: string
|
||||||
}
|
}
|
||||||
labels: { button: string }
|
labels: { button: string; success?: string }
|
||||||
}
|
}
|
||||||
|
|
||||||
const defaultButtonLabel = 'Send'
|
const defaultButtonLabel = 'Send'
|
||||||
@ -198,6 +198,6 @@ export const defaultChoiceInputOptions: ChoiceInputOptions = {
|
|||||||
|
|
||||||
export const defaultPaymentInputOptions: PaymentInputOptions = {
|
export const defaultPaymentInputOptions: PaymentInputOptions = {
|
||||||
provider: PaymentProvider.STRIPE,
|
provider: PaymentProvider.STRIPE,
|
||||||
labels: { button: 'Pay' },
|
labels: { button: 'Pay', success: 'Success' },
|
||||||
currency: 'USD',
|
currency: 'USD',
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ export const sendRequest = async <ResponseData>(
|
|||||||
? JSON.stringify(params.body)
|
? JSON.stringify(params.body)
|
||||||
: undefined,
|
: undefined,
|
||||||
})
|
})
|
||||||
if (!response.ok) throw new Error(response.statusText)
|
|
||||||
const data = await response.json()
|
const data = await response.json()
|
||||||
|
if (!response.ok) throw 'error' in data ? data.error : data
|
||||||
return { data }
|
return { data }
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
Reference in New Issue
Block a user