Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/run-checks.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ permissions:
jobs:
run-checks:
runs-on: depot-ubuntu-22.04-8
timeout-minutes: 10
timeout-minutes: 15
env:
NODE_OPTIONS: '--max_old_space_size=8192'
steps:
Expand Down
6 changes: 5 additions & 1 deletion integrations/salesforce/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { actionDefinitions } from './definitions'
export default new IntegrationDefinition({
name: 'salesforce',
title: 'Salesforce',
version: '1.0.0',
version: '1.0.1',
readme: 'hub.md',
icon: 'icon.svg',
description: 'Salesforce integration allows you to create, search, update and delete a variety of Salesforce objects',
Expand Down Expand Up @@ -51,4 +51,8 @@ export default new IntegrationDefinition({
},
},
actions: actionDefinitions,
attributes: {
category: 'CRM & Sales',
repo: 'botpress',
},
})
21 changes: 11 additions & 10 deletions integrations/stripe/hub.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,26 @@ Connect your Botpress chatbot with Stripe, a popular online payment platform tha

## Setup and Configuration

To set up the integration, you will need to provide your Stripe `apiKey`. This can be obtained from the Stripe Dashboard. You can use the `Secret Key` or create a `Restricted Key`. Once the integration is set up, you can use the built-in actions to manage your Stripe data.
The Stripe integration supports two authentication modes:

### Prerequisites
- **OAuth (recommended)** — authorize Botpress on your Stripe account through Stripe's hosted consent screen. No keys to copy or rotate.
- **API Key (manual)** — paste a Stripe Secret Key (`sk_live_…` / `sk_test_…`) or a Restricted Key.

Before enabling the Botpress Stripe Integration, please ensure that you have the following:
### Prerequisites

- A Botpress cloud account.
- `apiKey` generated from Stripe.
- A Stripe account.

### Enable Integration

To enable the Stripe integration in Botpress, follow these steps:

1. Access your Botpress admin panel.
2. Navigate to the “Integrations” section.
3. Locate the Stripe integration and click on “Enable” or “Configure.”
4. Provide the required `apiKey`.
1. Open your bot in the Botpress dashboard and navigate to the "Integrations" section.
2. Locate the Stripe integration and click on "Configure".
3. Click the configuration link to open the setup wizard.
4. Choose **Connect with OAuth** to be redirected to Stripe's authorization page, or choose **Use a Stripe API Key** to paste a key from the [Stripe Dashboard](https://dashboard.stripe.com/apikeys).
5. Save the configuration.

The integration will create a webhook endpoint on your Stripe account on first use. The webhook signing secret is captured automatically and used to verify all incoming events.

## Usage

Once the integration is enabled, you can start using Stripe features from your Botpress chatbot. The integration offers several actions for interacting with Stripe, such as `createPaymentLink` , `createSubsLink` (For generate Subscription Payment Link), `listPaymentLinks` (IDs and URLs), `listProductPrices` (If price has the "recurring" property, the product is of type subscription.), `findPaymentLink` (By URL, return ID), and `deactivatePaymentLink` (By ID). And actions for Customers, `listCustomers` (Optional filter by e-mail), `searchCustomers` (By e-mail, name or/and phone), `createCustomer` and `createOrRetrieveCustomer` (If the user already exists, his email has already been registered, get it. If there are multiple users with the same email, return an array of them. If it does not exist, it creates it).
Expand Down
57 changes: 51 additions & 6 deletions integrations/stripe/integration.definition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,19 +34,17 @@ import {

export default new IntegrationDefinition({
name: 'stripe',
version: '0.5.6',
version: '0.6.0',
title: 'Stripe',
readme: 'hub.md',
icon: 'icon.svg',
description:
'Manage payments, subscriptions, and customers seamlessly. Execute workflows on charge failures and subscription updates.',
configuration: {
identifier: {
linkTemplateScript: 'linkTemplate.vrl',
},
schema: z.object({
apiKey: z
.string()
.min(1)
.describe('The secret key or a restricted key from your Stripe account')
.title('API Key'),
apiVersion: z
.string()
.optional()
Expand Down Expand Up @@ -109,16 +107,63 @@ export default new IntegrationDefinition({
},
channels: {},
states: {
oAuthCredentials: {
type: 'integration',
schema: z.object({
accessToken: z.string().secret().title('Access Token').describe('The OAuth access token'),
refreshToken: z.string().secret().title('Refresh Token').describe('The rotating OAuth refresh token'),
expiresAt: z
.string()
.datetime()
.title('Access Token Expires At')
.describe('The timestamp of when the access token expires'),
refreshExpiresAt: z
.string()
.datetime()
.title('Refresh Token Expires At')
.describe('The timestamp of when the refresh token expires'),
scopes: z.array(z.string()).title('Scopes').describe('The scopes granted to the token'),
stripeUserId: z
.string()
.title('Stripe Account ID')
.describe('The Stripe account ID (acct_xxx) the token was issued for'),
livemode: z.boolean().title('Live Mode').describe('Whether the token operates against Stripe live mode'),
}),
},
manualCredentials: {
type: 'integration',
schema: z.object({
apiKey: z
.string()
.secret()
.title('Stripe API Key')
.describe('The secret key or a restricted key from your Stripe account'),
}),
},
stripeIntegrationInfo: {
type: 'integration',
schema: z.object({
stripeWebhookId: z
.string()
.title('Stripe Webhook ID')
.describe('The unique identifier for the Stripe webhook.'),
stripeWebhookSecret: z
.string()
.secret()
.optional()
.title('Stripe Webhook Signing Secret')
.describe('The signing secret returned by Stripe when the webhook endpoint was created.'),
}),
},
},
secrets: {
CLIENT_ID: {
description: 'The client ID of the Stripe OAuth app.',
},
CLIENT_SECRET: {
description: 'The Stripe secret API key (sk_live_/sk_test_) used to authenticate to the OAuth token endpoint.',
},
},
actions: {
createPaymentLink: {
title: 'Create Payment Link',
Expand Down
4 changes: 4 additions & 0 deletions integrations/stripe/linkTemplate.vrl
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
webhookId = to_string!(.webhookId)
webhookUrl = to_string!(.webhookUrl)

"{{ webhookUrl }}/oauth/wizard/start?state={{ webhookId }}"
8 changes: 4 additions & 4 deletions integrations/stripe/src/actions/create-customer.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { getClient } from '../client'
import { createCustomerInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const createCustomer: IntegrationProps['actions']['createCustomer'] = async ({ ctx, logger, input }) => {
export const createCustomer: IntegrationProps['actions']['createCustomer'] = async ({ ctx, client, logger, input }) => {
const validatedInput = createCustomerInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response
try {
const params = {
Expand All @@ -15,7 +15,7 @@ export const createCustomer: IntegrationProps['actions']['createCustomer'] = asy
payment_method: validatedInput.paymentMethodId,
address: validatedInput.address ? JSON.parse(validatedInput.address) : undefined,
}
const customer = await StripeClient.createCustomer(params)
const customer = await stripeClient.createCustomer(params)

response = {
customer,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { getClient } from '../client'
import { createOrRetrieveCustomerInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const createOrRetrieveCustomer: IntegrationProps['actions']['createOrRetrieveCustomer'] = async ({
ctx,
client,
logger,
input,
}) => {
const validatedInput = createOrRetrieveCustomerInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response
try {
const customers = await StripeClient.searchCustomers(validatedInput.email)
const customers = await stripeClient.searchCustomers(validatedInput.email)
let customer
if (customers.length === 0) {
const params = {
Expand All @@ -22,7 +23,7 @@ export const createOrRetrieveCustomer: IntegrationProps['actions']['createOrRetr
payment_method: validatedInput.paymentMethodId,
address: validatedInput.address ? JSON.parse(validatedInput.address) : undefined,
}
customer = await StripeClient.createCustomer(params)
customer = await stripeClient.createCustomer(params)
response = { customer }
} else {
response = customers.length === 1 ? { customer: customers[0] } : { customers }
Expand Down
33 changes: 19 additions & 14 deletions integrations/stripe/src/actions/create-paymentlink.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
import { getClient } from '../client'
import { createPaymentLinkInputSchema } from '../misc/custom-schemas'
import type { ProductBasic, StripeClient } from '../misc/stripe-client'
import type { ProductBasic } from '../misc/stripe-client'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

const findOrCreateProduct = async (StripeClient: StripeClient, productName: string) => {
const products = await StripeClient.listAllProductsBasic()
const findOrCreateProduct = async (stripeClient: StripeClient, productName: string) => {
const products = await stripeClient.listAllProductsBasic()
let product = products.find((p: ProductBasic) => p.name === productName)

if (!product) {
product = await StripeClient.createProduct(productName)
product = await stripeClient.createProduct(productName)
}

return product
}

const findOrCreatePrice = async (
StripeClient: StripeClient,
stripeClient: StripeClient,
productId: string,
unitAmount: number,
currency: string
) => {
const prices = await StripeClient.listPrices(productId)
const prices = await stripeClient.listPrices(productId)

if (!unitAmount) {
return prices.data[0] || (await StripeClient.createPrice(productId, 0, currency))
return prices.data[0] || (await stripeClient.createPrice(productId, 0, currency))
}

let price = prices.data.find((p) => p.unit_amount === unitAmount && p.currency === currency)

if (!price) {
price = await StripeClient.createPrice(productId, unitAmount, currency)
price = await stripeClient.createPrice(productId, unitAmount, currency)
}

return price
Expand All @@ -53,13 +53,18 @@ const buildLineItem = (
}
}

export const createPaymentLink: IntegrationProps['actions']['createPaymentLink'] = async ({ ctx, logger, input }) => {
export const createPaymentLink: IntegrationProps['actions']['createPaymentLink'] = async ({
ctx,
client,
logger,
input,
}) => {
const validatedInput = createPaymentLinkInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })

try {
const product = await findOrCreateProduct(StripeClient, validatedInput.productName)
const price = await findOrCreatePrice(StripeClient, product.id, validatedInput.unit_amount, validatedInput.currency)
const product = await findOrCreateProduct(stripeClient, validatedInput.productName)
const price = await findOrCreatePrice(stripeClient, product.id, validatedInput.unit_amount, validatedInput.currency)

const lineItem = buildLineItem(
price.id,
Expand All @@ -69,7 +74,7 @@ export const createPaymentLink: IntegrationProps['actions']['createPaymentLink']
validatedInput.adjustableQuantityMinimum
)

const paymentLink = await StripeClient.createPaymentLink(lineItem)
const paymentLink = await stripeClient.createPaymentLink(lineItem)

logger.forBot().info(`Successful - Create Payment Link - ${paymentLink.id}`)

Expand Down
16 changes: 8 additions & 8 deletions integrations/stripe/src/actions/create-subslink.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
import Stripe from 'stripe'
import { getClient } from '../client'
import { createSubsLinkInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = async ({ ctx, logger, input }) => {
export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = async ({ ctx, client, logger, input }) => {
const validatedInput = createSubsLinkInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response
try {
const products = await StripeClient.listAllProductsBasic()
const products = await stripeClient.listAllProductsBasic()
let product = products.find((p) => p.name === validatedInput.productName)

if (!product) {
product = await StripeClient.createProduct(validatedInput.productName)
product = await stripeClient.createProduct(validatedInput.productName)
}

const prices = await StripeClient.listPrices(product.id)
const prices = await stripeClient.listPrices(product.id)
let price
if (!validatedInput.unit_amount) {
price = prices.data.find((p) => p.recurring)
Expand All @@ -33,7 +33,7 @@ export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = asy
: 'month'

if (!price) {
price = await StripeClient.createSubsPrice(product.id, validatedInput.unit_amount, validatedInput.currency, {
price = await stripeClient.createSubsPrice(product.id, validatedInput.unit_amount, validatedInput.currency, {
interval,
})
}
Expand All @@ -55,7 +55,7 @@ export const createSubsLink: IntegrationProps['actions']['createSubsLink'] = asy
}
: undefined

const paymentLink = await StripeClient.createSubsLink(lineItem, subscriptionData)
const paymentLink = await stripeClient.createSubsLink(lineItem, subscriptionData)

response = {
id: paymentLink.id,
Expand Down
7 changes: 4 additions & 3 deletions integrations/stripe/src/actions/deactivate-paymentlink.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { getClient } from '../client'
import { deactivatePaymentLinkInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const deactivatePaymentLink: IntegrationProps['actions']['deactivatePaymentLink'] = async ({
ctx,
client,
logger,
input,
}) => {
const validatedInput = deactivatePaymentLinkInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response
try {
const paymentLink = await StripeClient.deactivatePaymentLink(validatedInput.id)
const paymentLink = await stripeClient.deactivatePaymentLink(validatedInput.id)

response = {
id: paymentLink.id,
Expand Down
13 changes: 9 additions & 4 deletions integrations/stripe/src/actions/find-paymentlink.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { getClient } from '../client'
import { findPaymentLinkInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const findPaymentLink: IntegrationProps['actions']['findPaymentLink'] = async ({ ctx, logger, input }) => {
export const findPaymentLink: IntegrationProps['actions']['findPaymentLink'] = async ({
ctx,
client,
logger,
input,
}) => {
const validatedInput = findPaymentLinkInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response = {}
try {
const paymentLinks = await StripeClient.listAllPaymentLinksBasic()
const paymentLinks = await stripeClient.listAllPaymentLinksBasic()
const paymentLink = paymentLinks.find((link) => link.url === validatedInput.url)

if (paymentLink) {
Expand Down
8 changes: 4 additions & 4 deletions integrations/stripe/src/actions/list-customers.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import type { Customer } from 'src/misc/custom-types'
import { getClient } from '../client'
import { listCustomersInputSchema } from '../misc/custom-schemas'
import type { IntegrationProps } from '../misc/types'
import { StripeClient } from '../stripe-api/stripe-client'

export const listCustomers: IntegrationProps['actions']['listCustomers'] = async ({ ctx, logger, input }) => {
export const listCustomers: IntegrationProps['actions']['listCustomers'] = async ({ ctx, client, logger, input }) => {
const validatedInput = listCustomersInputSchema.parse(input)
const StripeClient = getClient(ctx.configuration)
const stripeClient = await StripeClient.createFromStates({ client, ctx, logger })
let response
try {
const customers = await StripeClient.listAllCustomerBasic(validatedInput.email || undefined)
const customers = await stripeClient.listAllCustomerBasic(validatedInput.email || undefined)

const customerByEmails: Record<string, Customer[]> = {}

Expand Down
Loading
Loading