diff --git a/packages/backend/src/apps/stripe/assets/favicon.svg b/packages/backend/src/apps/stripe/assets/favicon.svg new file mode 100644 index 00000000..25d00aaa --- /dev/null +++ b/packages/backend/src/apps/stripe/assets/favicon.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/auth/index.ts b/packages/backend/src/apps/stripe/auth/index.ts new file mode 100644 index 00000000..f25205f1 --- /dev/null +++ b/packages/backend/src/apps/stripe/auth/index.ts @@ -0,0 +1,31 @@ +import verifyCredentials from "./verify-credentials"; +import isStillVerified from "./is-still-verified"; + +export default { + fields: [ + { + key: 'secretKey', + label: 'Secret Key', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: null, + clickToCopy: false, + }, + { + key: 'displayName', + label: 'Account Name', + type: 'string' as const, + required: true, + readOnly: false, + value: null, + placeholder: null, + description: 'The display name that identifies this stripe connection - most likely the associated account name', + clickToCopy: false, + }, + ], + verifyCredentials, + isStillVerified +}; \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/auth/is-still-verified.ts b/packages/backend/src/apps/stripe/auth/is-still-verified.ts new file mode 100644 index 00000000..4470a643 --- /dev/null +++ b/packages/backend/src/apps/stripe/auth/is-still-verified.ts @@ -0,0 +1,9 @@ +import { IGlobalVariable } from '@automatisch/types'; +import verifyCredentials from "./verify-credentials"; + +const isStillVerified = async ($: IGlobalVariable) => { + await verifyCredentials($); + return true; +}; + +export default isStillVerified; diff --git a/packages/backend/src/apps/stripe/auth/verify-credentials.ts b/packages/backend/src/apps/stripe/auth/verify-credentials.ts new file mode 100644 index 00000000..1297d9df --- /dev/null +++ b/packages/backend/src/apps/stripe/auth/verify-credentials.ts @@ -0,0 +1,12 @@ +import { IGlobalVariable } from '@automatisch/types'; + +const verifyCredentials = async ($: IGlobalVariable) => { + await $.http.get( + `/v1/events`, + ); + await $.auth.set({ + screenName: $.auth.data?.displayName, + }); +}; + +export default verifyCredentials; diff --git a/packages/backend/src/apps/stripe/common/add-auth-header.ts b/packages/backend/src/apps/stripe/common/add-auth-header.ts new file mode 100644 index 00000000..2e393e9c --- /dev/null +++ b/packages/backend/src/apps/stripe/common/add-auth-header.ts @@ -0,0 +1,8 @@ +import {TBeforeRequest} from "@automatisch/types"; + +const addAuthHeader: TBeforeRequest = ($, requestConfig) => { + requestConfig.headers['Authorization'] = `Bearer ${$.auth.data?.secretKey}` + return requestConfig +} + +export default addAuthHeader; \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/index.ts b/packages/backend/src/apps/stripe/index.ts new file mode 100644 index 00000000..505f13bf --- /dev/null +++ b/packages/backend/src/apps/stripe/index.ts @@ -0,0 +1,19 @@ +import defineApp from "../../helpers/define-app"; +import addAuthHeader from "./common/add-auth-header"; +import auth from "./auth" +import triggers from "./triggers" + +export default defineApp({ + name: 'Stripe', + key: 'stripe', + iconUrl: '{BASE_URL}/apps/stripe/assets/favicon.svg', + authDocUrl: 'https://automatisch.io/docs/apps/stripe/connection', + supportsConnections: true, + baseUrl: 'https://stripe.com', + apiBaseUrl: 'https://api.stripe.com', + primaryColor: '635bff', + beforeRequest: [addAuthHeader], + auth, + triggers, + actions: [], +}) \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/triggers/balance-transaction/get-balance-transactions.ts b/packages/backend/src/apps/stripe/triggers/balance-transaction/get-balance-transactions.ts new file mode 100644 index 00000000..d70f1bfe --- /dev/null +++ b/packages/backend/src/apps/stripe/triggers/balance-transaction/get-balance-transactions.ts @@ -0,0 +1,32 @@ +import {IGlobalVariable, IJSONObject} from "@automatisch/types"; +import {URLSearchParams} from "url"; +import {isEmpty, omitBy} from "lodash"; + +const getBalanceTransactions = async ($: IGlobalVariable) => { + let response; + let lastId = undefined; + + do { + const params: IJSONObject = { + starting_after: lastId, + ending_before: $.flow.lastInternalId + } + const queryParams = new URLSearchParams(omitBy(params, isEmpty)) + const requestPath = `/v1/balance_transactions${ + queryParams.toString() ? `?${queryParams.toString()}` : '' + }`; + + response = (await $.http.get(requestPath)).data + for (const entry of response.data) { + $.pushTriggerItem({ + raw: entry, + meta: { + internalId: entry.id as string + } + }) + lastId = entry.id + } + } while (response.has_more) +}; + +export default getBalanceTransactions; \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/triggers/balance-transaction/index.ts b/packages/backend/src/apps/stripe/triggers/balance-transaction/index.ts new file mode 100644 index 00000000..27f410ea --- /dev/null +++ b/packages/backend/src/apps/stripe/triggers/balance-transaction/index.ts @@ -0,0 +1,12 @@ +import defineTrigger from "../../../../helpers/define-trigger"; +import getBalanceTransactions from "./get-balance-transactions"; + +export default defineTrigger({ + name: 'New Balance Transactions', + key: 'newBalanceTransactions', + description: 'Triggers when a new transaction is processed (refund, payout, adjustment, ...)', + pollInterval: 15, + async run($) { + await getBalanceTransactions($) + } +}) \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/triggers/index.ts b/packages/backend/src/apps/stripe/triggers/index.ts new file mode 100644 index 00000000..f1a9d0c8 --- /dev/null +++ b/packages/backend/src/apps/stripe/triggers/index.ts @@ -0,0 +1,4 @@ +import balanceTransaction from "./balance-transaction"; +import payouts from "./payouts"; + +export default [balanceTransaction, payouts]; \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/triggers/payouts/get-payouts.ts b/packages/backend/src/apps/stripe/triggers/payouts/get-payouts.ts new file mode 100644 index 00000000..8791f89a --- /dev/null +++ b/packages/backend/src/apps/stripe/triggers/payouts/get-payouts.ts @@ -0,0 +1,32 @@ +import {IGlobalVariable, IJSONObject} from "@automatisch/types"; +import {URLSearchParams} from "url"; +import {isEmpty, omitBy} from "lodash"; + +const getPayouts = async ($: IGlobalVariable) => { + let response; + let lastId = undefined; + + do { + const params: IJSONObject = { + starting_after: lastId, + ending_before: $.flow.lastInternalId + } + const queryParams = new URLSearchParams(omitBy(params, isEmpty)) + const requestPath = `/v1/payouts${ + queryParams.toString() ? `?${queryParams.toString()}` : '' + }`; + + response = (await $.http.get(requestPath)).data + for (const entry of response.data) { + $.pushTriggerItem({ + raw: entry, + meta: { + internalId: entry.id as string + } + }) + lastId = entry.id + } + } while (response.has_more) +}; + +export default getPayouts; \ No newline at end of file diff --git a/packages/backend/src/apps/stripe/triggers/payouts/index.ts b/packages/backend/src/apps/stripe/triggers/payouts/index.ts new file mode 100644 index 00000000..324b09dd --- /dev/null +++ b/packages/backend/src/apps/stripe/triggers/payouts/index.ts @@ -0,0 +1,12 @@ +import defineTrigger from "../../../../helpers/define-trigger"; +import getPayouts from "./get-payouts"; + +export default defineTrigger({ + name: 'New Payouts', + key: 'newPayouts', + description: 'Triggers when a payout (Stripe <-> Bank account) has been updated', + pollInterval: 15, + async run($) { + await getPayouts($) + } +}) \ No newline at end of file diff --git a/packages/docs/pages/.vitepress/config.js b/packages/docs/pages/.vitepress/config.js index 118ebfd1..1ea35237 100644 --- a/packages/docs/pages/.vitepress/config.js +++ b/packages/docs/pages/.vitepress/config.js @@ -105,6 +105,14 @@ export default defineConfig({ { text: 'Connection', link: '/apps/smtp/connection' }, ], }, + { + text: 'Stripe', + collapsible: true, + items: [ + { text: 'Triggers', link: '/apps/stripe/triggers' }, + { text: 'Connection', link: '/apps/stripe/connection' }, + ], + }, { text: 'Twilio', collapsible: true, diff --git a/packages/docs/pages/apps/stripe/connection.md b/packages/docs/pages/apps/stripe/connection.md new file mode 100644 index 00000000..0cd174eb --- /dev/null +++ b/packages/docs/pages/apps/stripe/connection.md @@ -0,0 +1,14 @@ +# Stripe + +:::info +This page explains the steps you need to follow to set up the Stripe connection in Automatisch. If any of the steps are outdated, please let us know! +::: + +:::info +You are free to use the **Testing secret key** instead of the productive secret key as well. +::: + +1. Go to the [Stripe Dashboard > Developer > API keys](https://dashboard.stripe.com/apikeys) +2. Click on **Reveal live key** in the table row **Secret key** and copy the now shown secret key +3. Paste the **Secret key** in the named field in Automatisch and assign a display name for the connection. +4. Congrats! You can start using the new Stripe connection! diff --git a/packages/docs/pages/apps/stripe/triggers.md b/packages/docs/pages/apps/stripe/triggers.md new file mode 100644 index 00000000..dea401bf --- /dev/null +++ b/packages/docs/pages/apps/stripe/triggers.md @@ -0,0 +1,18 @@ +--- +favicon: /favicons/stripe.svg +items: + - name: New Payouts + desc: Triggers when stripe sent a payout to a third-party bank account or vice versa. + org: Stripe Documentation + orgLink: https://stripe.com/docs/api/payouts/object + - name: New Balance Transactions + desc: Triggers when a fund has been moved through your stripe account. + org: Stripe Documentation + orgLink: https://stripe.com/docs/api/balance_transactions/object +--- + + + + diff --git a/packages/docs/pages/guide/available-apps.md b/packages/docs/pages/guide/available-apps.md index 854d92e7..5697771f 100644 --- a/packages/docs/pages/guide/available-apps.md +++ b/packages/docs/pages/guide/available-apps.md @@ -15,5 +15,6 @@ Following integrations are currently supported by Automatisch. - [Scheduler](/apps/scheduler/triggers) - [Slack](/apps/slack/actions) - [SMTP](/apps/smtp/actions) +- [Stripe](/apps/stripe/triggers) - [Twilio](/apps/twilio/triggers) - [Twitter](/apps/twitter/triggers) diff --git a/packages/docs/pages/public/favicons/stripe.svg b/packages/docs/pages/public/favicons/stripe.svg new file mode 100644 index 00000000..25d00aaa --- /dev/null +++ b/packages/docs/pages/public/favicons/stripe.svg @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file