Initial @trigger.dev/stripe integration, with support for triggers and tasks (#231)

* Initial Stripe integration, with support for triggers and tasks

* Adding stripe integration to the catalog
This commit is contained in:
Eric Allam
2023-07-27 22:09:07 +01:00
committed by GitHub
parent af722e54d8
commit 9351c05126
26 changed files with 2338 additions and 46 deletions
+6
View File
@@ -0,0 +1,6 @@
---
"@trigger.dev/stripe": minor
"@trigger.dev/sdk": patch
---
Initial Stripe integration
@@ -64,6 +64,7 @@ export function ConnectToIntegrationSheet({
to={docsIntegrationPath(integration.identifier)}
variant="secondary/small"
LeadingIcon="docs"
target="_blank"
>
View docs
</LinkButton>
@@ -28,7 +28,7 @@ export function HelpInstall({ packageName }: { packageName: string }) {
<ClientTabsContent value={"pnpm"}>
<ClipboardField
variant="secondary/medium"
value={`pnpm install ${packageName}`}
value={`pnpm add ${packageName}`}
className="mb-4"
/>
</ClientTabsContent>
@@ -45,6 +45,7 @@ export function IntegrationWithMissingFieldSheet({
to={docsIntegrationPath(integration.identifier)}
variant="secondary/small"
LeadingIcon="docs"
target="_blank"
>
View docs
</LinkButton>
@@ -0,0 +1,28 @@
import { ActionArgs } from "@remix-run/server-runtime";
/*
To use this route, use the stripe CLI to forward events to this route:
stripe listen --forward-to localhost:3030/api/internal/stripe_webhooks
*/
export async function action({ request }: ActionArgs) {
const body: any = await request.json();
const response = await fetch("https://jsonhero.io/api/create.json", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
title: body.type,
content: body,
readOnly: true,
}),
});
const json: any = await response.json();
console.log({ [body.type]: json.location });
return json;
}
@@ -3,6 +3,7 @@ import { openai } from "./integrations/openai";
import { plain } from "./integrations/plain";
import { resend } from "./integrations/resend";
import { slack } from "./integrations/slack";
import { stripe } from "./integrations/stripe";
import { supabaseManagement, supabase } from "./integrations/supabase";
import { typeform } from "./integrations/typeform";
import type { Integration } from "./types";
@@ -28,14 +29,13 @@ export class IntegrationCatalog {
}
export const integrationCatalog = new IntegrationCatalog({
//todo support airtable
// airtable,
github,
openai,
plain,
resend,
slack,
typeform,
stripe,
supabaseManagement,
supabase,
});
@@ -0,0 +1,74 @@
import type { Integration } from "../types";
export const stripe: Integration = {
identifier: "stripe",
name: "Stripe",
packageName: "@trigger.dev/stripe",
authenticationMethods: {
apikey: {
type: "apikey",
help: {
samples: [
{
title: "Creating the integration",
code: `
import { Stripe } from "@trigger.dev/stripe";
export const stripe = new Stripe({
id: "__SLUG__",
apiKey: process.env.PLAIN_API_KEY!,
});
`,
},
{
title: "Using the integration",
code: `
client.defineJob({
id: "stripe-playground",
name: "Stripe Playground",
version: "0.1.1",
integrations: {
stripe,
},
trigger: eventTrigger({
name: "stripe.playground",
}),
run: async (payload, io, ctx) => {
await io.stripe.createCharge("charge-customer", {
amount: 100,
currency: "usd",
source: payload.source,
customer: payload.customerId,
});
},
});
`,
highlight: [
[5, 7],
[12, 17],
],
},
{
title: "Using Stripe triggers",
code: `
client.defineJob({
id: "stripe-on-subscription-created",
name: "Stripe On Subscription Created",
version: "0.1.0",
trigger: stripe.onCustomerSubscriptionCreated({
filter: {
currency: ["usd"],
},
}),
run: async (payload, io, ctx) => {
await io.logger.info("Subscription created in USD!");
},
});
`,
highlight: [[5, 9]],
},
],
},
},
},
};
+1 -1
View File
@@ -343,7 +343,7 @@ export function runCompletedPath(runPath: string) {
// Docs
export function docsRoot() {
return "https://docs.trigger.dev";
return "https://trigger.dev/docs";
}
export function docsPath(path: string) {
+2 -1
View File
@@ -29,7 +29,8 @@
"react-query": "^3.39.3",
"typescript": "5.0.4",
"zod": "3.21.4",
"@trigger.dev/supabase": "workspace:*"
"@trigger.dev/supabase": "workspace:*",
"@trigger.dev/stripe": "workspace:*"
},
"devDependencies": {
"@trigger.dev/cli": "workspace:*",
@@ -14,5 +14,6 @@ import "@/jobs/typeform";
import "@/jobs/edgeCases";
import "@/jobs/hooks";
import "@/jobs/supabase";
import "@/jobs/stripe";
export const { POST, dynamic } = createAppRoute(client);
+146
View File
@@ -0,0 +1,146 @@
import { Stripe } from "@trigger.dev/stripe";
import { client } from "@/trigger";
import { eventTrigger } from "@trigger.dev/sdk";
import { z } from "zod";
const stripe = new Stripe({
id: "stripe",
apiKey: process.env.STRIPE_API_KEY!,
});
client.defineJob({
id: "stripe-example-1",
name: "Stripe Example 1",
version: "0.1.0",
trigger: eventTrigger({
name: "stripe.example",
schema: z.object({
customerId: z.string(),
source: z.string(),
}),
}),
integrations: {
stripe,
},
run: async (payload, io, ctx) => {
await io.stripe.createCharge("create-charge", {
amount: 100,
currency: "usd",
source: payload.source,
customer: payload.customerId,
});
},
});
client.defineJob({
id: "stripe-example-1",
name: "Stripe Example 1",
version: "0.1.0",
trigger: eventTrigger({
name: "stripe.example",
schema: z.object({
customerId: z.string(),
source: z.string(),
}),
}),
integrations: {
stripe,
},
run: async (payload, io, ctx) => {
await io.stripe.createCharge("create-charge", {
amount: 100,
currency: "usd",
source: payload.source,
customer: payload.customerId,
});
},
});
client.defineJob({
id: "stripe-create-customer",
name: "Stripe Create Customer",
version: "0.1.0",
trigger: eventTrigger({
name: "stripe.new.customer",
schema: z.object({
email: z.string(),
name: z.string(),
}),
}),
integrations: {
stripe,
},
run: async (payload, io, ctx) => {
await io.stripe.createCustomer("create-customer", {
email: payload.email,
name: payload.name,
});
},
});
client.defineJob({
id: "stripe-update-customer",
name: "Stripe Update Customer",
version: "0.1.0",
trigger: eventTrigger({
name: "stripe.update.customer",
schema: z.object({
customerId: z.string(),
name: z.string(),
}),
}),
integrations: {
stripe,
},
run: async (payload, io, ctx) => {
await io.stripe.updateCustomer("update-customer", {
id: payload.customerId,
name: payload.name,
});
},
});
client.defineJob({
id: "stripe-retrieve-subscription",
name: "Stripe Retrieve Subscription",
version: "0.1.0",
trigger: eventTrigger({
name: "stripe.retrieve.subscription",
schema: z.object({
id: z.string(),
}),
}),
integrations: {
stripe,
},
run: async (payload, io, ctx) => {
const subscription = await io.stripe.retrieveSubscription("get", {
id: payload.id,
expand: ["customer"],
});
},
});
client.defineJob({
id: "stripe-on-price-created",
name: "Stripe On Price Created",
version: "0.1.0",
trigger: stripe.onPriceCreated(),
run: async (payload, io, ctx) => {
await io.logger.info("ctx", { ctx });
},
});
client.defineJob({
id: "stripe-on-subscription-created",
name: "Stripe On Subscription Created",
version: "0.1.0",
trigger: stripe.onCustomerSubscriptionCreated({
filter: {
currency: ["usd"],
},
}),
run: async (payload, io, ctx) => {
await io.logger.info("ctx", { ctx });
},
});
+3 -3
View File
@@ -18,8 +18,6 @@
"@/*": ["./src/*"],
"@trigger.dev/sdk": ["../../packages/trigger-sdk/src/index"],
"@trigger.dev/sdk/*": ["../../packages/trigger-sdk/src/*"],
"@trigger.dev/react": ["../../packages/react/src/index"],
"@trigger.dev/react/*": ["../../packages/react/src/*"],
"@trigger.dev/nextjs": ["../../packages/nextjs/src/index"],
"@trigger.dev/nextjs/*": ["../../packages/nextjs/src/*"],
"@trigger.dev/internal": ["../../packages/internal/src/index"],
@@ -43,7 +41,9 @@
"@trigger.dev/plain": ["../../integrations/plain/src/index"],
"@trigger.dev/plain/*": ["../../integrations/plain/src/*"],
"@trigger.dev/supabase": ["../../integrations/supabase/src/index"],
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"]
"@trigger.dev/supabase/*": ["../../integrations/supabase/src/*"],
"@trigger.dev/stripe": ["../../integrations/stripe/src/index"],
"@trigger.dev/stripe/*": ["../../integrations/stripe/src/*"]
},
"plugins": [
{
+3
View File
@@ -0,0 +1,3 @@
# @trigger.dev/stripe
+37
View File
@@ -0,0 +1,37 @@
{
"name": "@trigger.dev/stripe",
"version": "0.0.1",
"description": "Trigger.dev integration for stripe",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"publishConfig": {
"access": "public"
},
"files": [
"dist/index.js",
"dist/index.d.ts",
"dist/index.js.map"
],
"devDependencies": {
"@types/node": "16.x",
"rimraf": "^3.0.2",
"stripe-event-types": "^2.4.0",
"tsup": "7.1.x",
"typescript": "4.9.4"
},
"scripts": {
"clean": "rimraf dist",
"build": "npm run clean && npm run build:tsup",
"build:tsup": "tsup",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@trigger.dev/integration-kit": "workspace:^2.0.0-next.5",
"@trigger.dev/sdk": "workspace:^2.0.0-next.19",
"stripe": "^12.14.0",
"zod": "3.21.4"
},
"engines": {
"node": ">=16.8.0"
}
}
+347
View File
@@ -0,0 +1,347 @@
import type { EventSpecification } from "@trigger.dev/sdk";
import {
cancelledSubscriptionExample,
checkoutSessionExample,
customerSubscriptionExample,
pausedSubscriptionExample,
updatedSubscriptionExample,
} from "./examples";
import {
OnCheckoutSession,
OnCustomerSubscription,
OnPriceEvent,
OnProductEvent,
} from "./types";
export const onPriceCreated: EventSpecification<OnPriceEvent> = {
name: "price.created",
title: "On Price Created",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "recurring",
name: "Recurring Price",
icon: "stripe",
payload: {
id: "price_1NYV6vI0XSgju2urKsSmI53v",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690467853,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLBTh0QPxDXkIU",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
},
],
parsePayload: (payload) => payload as OnPriceEvent,
runProperties: (payload) => [{ label: "Price ID", text: payload.id }],
};
export const onPriceUpdated: EventSpecification<OnPriceEvent> = {
name: "price.updated",
title: "On Price Updated",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "recurring",
name: "Recurring Price",
icon: "stripe",
payload: {
id: "price_1NYVmXI0XSgju2urA56rnf3e",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690470433,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {
foo: "bar",
},
nickname: null,
product: "prod_OLCAdNbcBTwgEn",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
},
],
parsePayload: (payload) => payload as OnPriceEvent,
runProperties: (payload) => [{ label: "Price ID", text: payload.id }],
};
export const onPriceDeleted: EventSpecification<OnPriceEvent> = {
name: "price.deleted",
title: "On Price Deleted",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "recurring",
name: "Recurring Price",
icon: "stripe",
payload: {
id: "plan_OLCbCoAUbHPcHT",
object: "price",
active: false,
billing_scheme: "per_unit",
created: 1690472058,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLCbckE3tpR34b",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 2000,
unit_amount_decimal: "2000",
},
},
],
parsePayload: (payload) => payload as OnPriceEvent,
runProperties: (payload) => [{ label: "Price ID", text: payload.id }],
};
export const onProductCreated: EventSpecification<OnProductEvent> = {
name: "product.created",
title: "On Product Created",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "mock_product",
name: "Mock Product",
icon: "stripe",
payload: {
id: "prod_OLBTh0QPxDXkIU",
object: "product",
active: true,
attributes: [],
created: 1690467853,
default_price: null,
description: "(created by Stripe CLI)",
images: [],
livemode: false,
metadata: {},
name: "myproduct",
package_dimensions: null,
shippable: null,
statement_descriptor: null,
tax_code: null,
type: "service",
unit_label: null,
updated: 1690467853,
url: null,
},
},
],
parsePayload: (payload) => payload as OnProductEvent,
runProperties: (payload) => [{ label: "Product ID", text: payload.id }],
};
export const onProductUpdated: EventSpecification<OnProductEvent> = {
name: "product.updated",
title: "On Product Updated",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "mock_product",
name: "Mock Product",
icon: "stripe",
payload: {
id: "prod_OLBTh0QPxDXkIU",
object: "product",
active: true,
attributes: [],
created: 1690467853,
default_price: null,
description: "(created by Stripe CLI)",
images: [],
livemode: false,
metadata: {},
name: "myproduct",
package_dimensions: null,
shippable: null,
statement_descriptor: null,
tax_code: null,
type: "service",
unit_label: null,
updated: 1690467853,
url: null,
},
},
],
parsePayload: (payload) => payload as OnProductEvent,
runProperties: (payload) => [{ label: "Product ID", text: payload.id }],
};
export const onProductDeleted: EventSpecification<OnProductEvent> = {
name: "product.deleted",
title: "On Product Deleted",
source: "stripe.com",
icon: "stripe",
examples: [
{
id: "mock_product",
name: "Mock Product",
icon: "stripe",
payload: {
id: "prod_OLBTh0QPxDXkIU",
object: "product",
active: true,
attributes: [],
created: 1690467853,
default_price: null,
description: "(created by Stripe CLI)",
images: [],
livemode: false,
metadata: {},
name: "myproduct",
package_dimensions: null,
shippable: null,
statement_descriptor: null,
tax_code: null,
type: "service",
unit_label: null,
updated: 1690467853,
url: null,
},
},
],
parsePayload: (payload) => payload as OnProductEvent,
runProperties: (payload) => [{ label: "Product ID", text: payload.id }],
};
export const onCheckoutSessionCompleted: EventSpecification<OnCheckoutSession> =
{
name: "checkout.session.completed",
title: "On Checkout Session Completed",
source: "stripe.com",
icon: "stripe",
examples: [checkoutSessionExample],
parsePayload: (payload) => payload as OnCheckoutSession,
runProperties: (payload) => [{ label: "Session ID", text: payload.id }],
};
export const onCheckoutSessionExpired: EventSpecification<OnCheckoutSession> = {
name: "checkout.session.expired",
title: "On Checkout Session Expired",
source: "stripe.com",
icon: "stripe",
examples: [checkoutSessionExample],
parsePayload: (payload) => payload as OnCheckoutSession,
runProperties: (payload) => [{ label: "Session ID", text: payload.id }],
};
export const onCustomerSubscriptionCreated: EventSpecification<OnCustomerSubscription> =
{
name: "customer.subscription.created",
title: "On Customer Subscription Created",
source: "stripe.com",
icon: "stripe",
examples: [customerSubscriptionExample],
parsePayload: (payload) => payload as OnCustomerSubscription,
runProperties: (payload) => [
{ label: "Subscription ID", text: payload.id },
{ label: "Status", text: payload.status },
],
};
export const onCustomerSubscriptionPaused: EventSpecification<OnCustomerSubscription> =
{
name: "customer.subscription.paused",
title: "On Customer Subscription Paused",
source: "stripe.com",
icon: "stripe",
examples: [pausedSubscriptionExample],
parsePayload: (payload) => payload as OnCustomerSubscription,
runProperties: (payload) => [
{ label: "Subscription ID", text: payload.id },
{ label: "Status", text: payload.status },
],
};
export const onCustomerSubscriptionResumed: EventSpecification<OnCustomerSubscription> =
{
name: "customer.subscription.resumed",
title: "On Customer Subscription Resumed",
source: "stripe.com",
icon: "stripe",
examples: [customerSubscriptionExample],
parsePayload: (payload) => payload as OnCustomerSubscription,
runProperties: (payload) => [
{ label: "Subscription ID", text: payload.id },
{ label: "Status", text: payload.status },
],
};
export const onCustomerSubscriptionDeleted: EventSpecification<OnCustomerSubscription> =
{
name: "customer.subscription.deleted",
title: "On Customer Subscription Deleted",
source: "stripe.com",
icon: "stripe",
examples: [cancelledSubscriptionExample],
parsePayload: (payload) => payload as OnCustomerSubscription,
runProperties: (payload) => [
{ label: "Subscription ID", text: payload.id },
{ label: "Status", text: payload.status },
],
};
export const onCustomerSubscriptionUpdated: EventSpecification<OnCustomerSubscription> =
{
name: "customer.subscription.updated",
title: "On Customer Subscription Deleted",
source: "stripe.com",
icon: "stripe",
examples: [updatedSubscriptionExample],
parsePayload: (payload) => payload as OnCustomerSubscription,
runProperties: (payload) => [
{ label: "Subscription ID", text: payload.id },
{ label: "Status", text: payload.status },
],
};
+700
View File
@@ -0,0 +1,700 @@
export const checkoutSessionExample = {
id: "test_session",
name: "Mock Checkout Session",
icon: "stripe",
payload: {
id: "cs_test_a1LFuyKHsmzBxr6SvxfFu184ISK2sNebnb60DloYuCenBcJtfGttTydm4n",
object: "checkout.session",
after_expiration: null,
allow_promotion_codes: null,
amount_subtotal: 3000,
amount_total: 3000,
automatic_tax: {
enabled: false,
status: null,
},
billing_address_collection: null,
cancel_url: "https://httpbin.org/post",
client_reference_id: null,
consent: null,
consent_collection: null,
created: 1690472483,
currency: "usd",
currency_conversion: null,
custom_fields: [],
custom_text: {
shipping_address: null,
submit: null,
},
customer: null,
customer_creation: "if_required",
customer_details: {
address: {
city: "South San Francisco",
country: "US",
line1: "354 Oyster Point Blvd",
line2: null,
postal_code: "94080",
state: "CA",
},
email: "stripe@example.com",
name: "Jenny Rosen",
phone: null,
tax_exempt: "none",
tax_ids: [],
},
customer_email: null,
expires_at: 1690558883,
invoice: null,
invoice_creation: {
enabled: false,
invoice_data: {
account_tax_ids: null,
custom_fields: null,
description: null,
footer: null,
metadata: {},
rendering_options: null,
},
},
livemode: false,
locale: null,
metadata: {},
mode: "payment",
payment_intent: "pi_3NYWJcI0XSgju2ur1bZTeRhR",
payment_link: null,
payment_method_collection: "always",
payment_method_options: {},
payment_method_types: ["card"],
payment_status: "paid",
phone_number_collection: {
enabled: false,
},
recovered_from: null,
setup_intent: null,
shipping_address_collection: null,
shipping_cost: null,
shipping_details: null,
shipping_options: [],
status: "complete",
submit_type: null,
subscription: null,
success_url: "https://httpbin.org/post",
total_details: {
amount_discount: 0,
amount_shipping: 0,
amount_tax: 0,
},
url: null,
},
};
export const customerSubscriptionExample = {
id: "test_subscription",
name: "Mock Subscription",
icon: "stripe",
payload: {
id: "sub_1NYWgUI0XSgju2urmVcglalG",
object: "subscription",
application: null,
application_fee_percent: null,
automatic_tax: {
enabled: false,
},
billing_cycle_anchor: 1690473902,
billing_thresholds: null,
cancel_at: null,
cancel_at_period_end: false,
canceled_at: null,
cancellation_details: {
comment: null,
feedback: null,
reason: null,
},
collection_method: "charge_automatically",
created: 1690473902,
currency: "usd",
current_period_end: 1693152302,
current_period_start: 1690473902,
customer: "cus_OLD6IR3D8CJasG",
days_until_due: null,
default_payment_method: null,
default_source: null,
default_tax_rates: [],
description: null,
discount: null,
ended_at: null,
items: {
object: "list",
data: [
{
id: "si_OLD6qgIKdp0wZO",
object: "subscription_item",
billing_thresholds: null,
created: 1690473903,
metadata: {},
plan: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
price: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
quantity: 1,
subscription: "sub_1NYWgUI0XSgju2urmVcglalG",
tax_rates: [],
},
],
has_more: false,
total_count: 1,
url: "/v1/subscription_items?subscription=sub_1NYWgUI0XSgju2urmVcglalG",
},
latest_invoice: "in_1NYWgUI0XSgju2urV5ZTEyIn",
livemode: false,
metadata: {},
next_pending_invoice_item_invoice: null,
on_behalf_of: null,
pause_collection: null,
payment_settings: {
payment_method_options: null,
payment_method_types: null,
save_default_payment_method: "off",
},
pending_invoice_item_interval: null,
pending_setup_intent: null,
pending_update: null,
plan: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
quantity: 1,
schedule: null,
start_date: 1690473902,
status: "active",
test_clock: null,
transfer_data: null,
trial_end: null,
trial_settings: {
end_behavior: {
missing_payment_method: "create_invoice",
},
},
trial_start: null,
},
};
export const pausedSubscriptionExample = {
id: "test_subscription",
name: "Mock Subscription",
icon: "stripe",
payload: {
id: "sub_1NYWgUI0XSgju2urmVcglalG",
object: "subscription",
application: null,
application_fee_percent: null,
automatic_tax: {
enabled: false,
},
billing_cycle_anchor: 1690473902,
billing_thresholds: null,
cancel_at: null,
cancel_at_period_end: false,
canceled_at: null,
cancellation_details: {
comment: null,
feedback: null,
reason: null,
},
collection_method: "charge_automatically",
created: 1690473902,
currency: "usd",
current_period_end: 1693152302,
current_period_start: 1690473902,
customer: "cus_OLD6IR3D8CJasG",
days_until_due: null,
default_payment_method: null,
default_source: null,
default_tax_rates: [],
description: null,
discount: null,
ended_at: null,
items: {
object: "list",
data: [
{
id: "si_OLD6qgIKdp0wZO",
object: "subscription_item",
billing_thresholds: null,
created: 1690473903,
metadata: {},
plan: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
price: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
quantity: 1,
subscription: "sub_1NYWgUI0XSgju2urmVcglalG",
tax_rates: [],
},
],
has_more: false,
total_count: 1,
url: "/v1/subscription_items?subscription=sub_1NYWgUI0XSgju2urmVcglalG",
},
latest_invoice: "in_1NYWgUI0XSgju2urV5ZTEyIn",
livemode: false,
metadata: {},
next_pending_invoice_item_invoice: null,
on_behalf_of: null,
pause_collection: null,
payment_settings: {
payment_method_options: null,
payment_method_types: null,
save_default_payment_method: "off",
},
pending_invoice_item_interval: null,
pending_setup_intent: null,
pending_update: null,
plan: {
id: "price_1NYWgUI0XSgju2ur3rwJGw1n",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690473902,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLD6aEjleFDUfA",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
quantity: 1,
schedule: null,
start_date: 1690473902,
status: "paused",
test_clock: null,
transfer_data: null,
trial_end: null,
trial_settings: {
end_behavior: {
missing_payment_method: "create_invoice",
},
},
trial_start: null,
},
};
export const cancelledSubscriptionExample = {
id: "test_subscription",
name: "Cancelled Subscription",
icon: "stripe",
payload: {
id: "sub_1NYWmqI0XSgju2urM6J7U81g",
object: "subscription",
application: null,
application_fee_percent: null,
automatic_tax: {
enabled: false,
},
billing_cycle_anchor: 1690474295,
billing_thresholds: null,
cancel_at: null,
cancel_at_period_end: false,
canceled_at: 1690474298,
cancellation_details: {
comment: null,
feedback: null,
reason: "cancellation_requested",
},
collection_method: "charge_automatically",
created: 1690474295,
currency: "usd",
current_period_end: 1693152695,
current_period_start: 1690474295,
customer: "cus_OLDDzu7ZE2wLZB",
days_until_due: null,
default_payment_method: null,
default_source: null,
default_tax_rates: [],
description: null,
discount: null,
ended_at: 1690474298,
items: {
object: "list",
data: [
{
id: "si_OLDDWvHquPJrro",
object: "subscription_item",
billing_thresholds: null,
created: 1690474296,
metadata: {},
plan: {
id: "price_1NYWmpI0XSgju2ur8YboK1MK",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690474295,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLDDFHiTTQUoPT",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
price: {
id: "price_1NYWmpI0XSgju2ur8YboK1MK",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690474295,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLDDFHiTTQUoPT",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
quantity: 1,
subscription: "sub_1NYWmqI0XSgju2urM6J7U81g",
tax_rates: [],
},
],
has_more: false,
total_count: 1,
url: "/v1/subscription_items?subscription=sub_1NYWmqI0XSgju2urM6J7U81g",
},
latest_invoice: "in_1NYWmqI0XSgju2ur1YkCeiE3",
livemode: false,
metadata: {},
next_pending_invoice_item_invoice: null,
on_behalf_of: null,
pause_collection: null,
payment_settings: {
payment_method_options: null,
payment_method_types: null,
save_default_payment_method: "off",
},
pending_invoice_item_interval: null,
pending_setup_intent: null,
pending_update: null,
plan: {
id: "price_1NYWmpI0XSgju2ur8YboK1MK",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690474295,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLDDFHiTTQUoPT",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
quantity: 1,
schedule: null,
start_date: 1690474295,
status: "canceled",
test_clock: null,
transfer_data: null,
trial_end: null,
trial_settings: {
end_behavior: {
missing_payment_method: "create_invoice",
},
},
trial_start: null,
},
};
export const updatedSubscriptionExample = {
id: "test_subscription",
name: "Updated Subscription",
icon: "stripe",
payload: {
id: "sub_1NYWr9I0XSgju2urREzIjypw",
object: "subscription",
application: null,
application_fee_percent: null,
automatic_tax: {
enabled: false,
},
billing_cycle_anchor: 1690474563,
billing_thresholds: null,
cancel_at: null,
cancel_at_period_end: false,
canceled_at: null,
cancellation_details: {
comment: null,
feedback: null,
reason: null,
},
collection_method: "charge_automatically",
created: 1690474563,
currency: "usd",
current_period_end: 1693152963,
current_period_start: 1690474563,
customer: "cus_OLDHaOoGMJKpbQ",
days_until_due: null,
default_payment_method: null,
default_source: null,
default_tax_rates: [],
description: null,
discount: null,
ended_at: null,
items: {
object: "list",
data: [
{
id: "si_OLDHbwqQpcI7wJ",
object: "subscription_item",
billing_thresholds: null,
created: 1690474564,
metadata: {},
plan: {
id: "price_1NYWr9I0XSgju2urK88vlVrN",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690474563,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLDHCaYW7Sp4Xg",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
price: {
id: "price_1NYWr9I0XSgju2urK88vlVrN",
object: "price",
active: true,
billing_scheme: "per_unit",
created: 1690474563,
currency: "usd",
custom_unit_amount: null,
livemode: false,
lookup_key: null,
metadata: {},
nickname: null,
product: "prod_OLDHCaYW7Sp4Xg",
recurring: {
aggregate_usage: null,
interval: "month",
interval_count: 1,
trial_period_days: null,
usage_type: "licensed",
},
tax_behavior: "unspecified",
tiers_mode: null,
transform_quantity: null,
type: "recurring",
unit_amount: 1500,
unit_amount_decimal: "1500",
},
quantity: 1,
subscription: "sub_1NYWr9I0XSgju2urREzIjypw",
tax_rates: [],
},
],
has_more: false,
total_count: 1,
url: "/v1/subscription_items?subscription=sub_1NYWr9I0XSgju2urREzIjypw",
},
latest_invoice: "in_1NYWr9I0XSgju2uro3uB0RuT",
livemode: false,
metadata: {
foo: "bar",
},
next_pending_invoice_item_invoice: null,
on_behalf_of: null,
pause_collection: null,
payment_settings: {
payment_method_options: null,
payment_method_types: null,
save_default_payment_method: "off",
},
pending_invoice_item_interval: null,
pending_setup_intent: null,
pending_update: null,
plan: {
id: "price_1NYWr9I0XSgju2urK88vlVrN",
object: "plan",
active: true,
aggregate_usage: null,
amount: 1500,
amount_decimal: "1500",
billing_scheme: "per_unit",
created: 1690474563,
currency: "usd",
interval: "month",
interval_count: 1,
livemode: false,
metadata: {},
nickname: null,
product: "prod_OLDHCaYW7Sp4Xg",
tiers_mode: null,
transform_usage: null,
trial_period_days: null,
usage_type: "licensed",
},
quantity: 1,
schedule: null,
start_date: 1690474563,
status: "active",
test_clock: null,
transfer_data: null,
trial_end: null,
trial_settings: {
end_behavior: {
missing_payment_method: "create_invoice",
},
},
trial_start: null,
},
};
+384
View File
@@ -0,0 +1,384 @@
import { Stripe as StripeClient } from "stripe";
import {
EventFilter,
ExternalSource,
ExternalSourceTrigger,
type HandlerEvent,
type IntegrationClient,
type Logger,
type TriggerIntegration,
} from "@trigger.dev/sdk";
import type {
StripeSDK,
StripeIntegrationOptions,
WebhookEvents,
} from "./types";
import * as tasks from "./tasks";
import z from "zod";
import * as events from "./events";
export * from "./types";
type StripeIntegrationClient = IntegrationClient<StripeSDK, typeof tasks>;
type StripeIntegration = TriggerIntegration<StripeIntegrationClient>;
export class Stripe implements StripeIntegration {
client: StripeIntegrationClient;
constructor(private options: StripeIntegrationOptions) {
this.client = {
tasks,
usesLocalAuth: true,
client: new StripeClient(options.apiKey, {
apiVersion: "2022-11-15",
typescript: true,
timeout: 10000,
maxNetworkRetries: 0,
stripeAccount: options.stripeAccount,
appInfo: {
name: "Trigger.dev Stripe Integration",
version: "0.1.0",
url: "https://trigger.dev",
},
}),
auth: {
apiKey: options.apiKey,
},
};
}
get id() {
return this.options.id;
}
get metadata() {
return { id: "stripe", name: "Stripe" };
}
get source() {
return createWebhookEventSource(this);
}
/**
* Occurs whenever a price is created.
*/
onPriceCreated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onPriceCreated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a price is updated.
*/
onPriceUpdated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onPriceUpdated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a price is deleted.
*/
onPriceDeleted(params?: TriggerParams) {
return createTrigger(
this.source,
events.onPriceDeleted,
params ?? { connect: false }
);
}
/**
* Occurs whenever a product is created.
*/
onProductCreated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onProductCreated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a product is updated.
*/
onProductUpdated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onProductUpdated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a product is deleted.
*/
onProductDeleted(params?: TriggerParams) {
return createTrigger(
this.source,
events.onProductDeleted,
params ?? { connect: false }
);
}
/**
* Occurs when a Checkout Session has been successfully completed.
*/
onCheckoutSessionCompleted(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCheckoutSessionCompleted,
params ?? { connect: false }
);
}
/**
* Occurs when a Checkout Session is expired.
*/
onCheckoutSessionExpired(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCheckoutSessionExpired,
params ?? { connect: false }
);
}
/**
* Occurs whenever a customer is signed up for a new plan.
*/
onCustomerSubscriptionCreated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCustomerSubscriptionCreated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a customer's subscription is paused. Only applies when subscriptions enter `status=paused`, not when [payment collection](https://stripe.com/docs/billing/subscriptions/pause) is paused.
*/
onCustomerSubscriptionPaused(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCustomerSubscriptionPaused,
params ?? { connect: false }
);
}
/**
* Occurs whenever a customer's subscription is no longer paused. Only applies when a `status=paused` subscription is [resumed](https://stripe.com/docs/api/subscriptions/resume), not when [payment collection](https://stripe.com/docs/billing/subscriptions/pause) is resumed.
*/
onCustomerSubscriptionResumed(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCustomerSubscriptionResumed,
params ?? { connect: false }
);
}
/**
* Occurs whenever a subscription changes (e.g., switching from one plan to another, or changing the status from trial to active).
*/
onCustomerSubscriptionUpdated(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCustomerSubscriptionUpdated,
params ?? { connect: false }
);
}
/**
* Occurs whenever a customer's subscription ends.
*/
onCustomerSubscriptionDeleted(params?: TriggerParams) {
return createTrigger(
this.source,
events.onCustomerSubscriptionDeleted,
params ?? { connect: false }
);
}
}
export type TriggerParams = {
connect?: boolean;
filter?: EventFilter;
};
type StripeEvents = (typeof events)[keyof typeof events];
type CreateTriggersResult<TEventSpecification extends StripeEvents> =
ExternalSourceTrigger<
TEventSpecification,
ReturnType<typeof createWebhookEventSource>
>;
function createTrigger<TEventSpecification extends StripeEvents>(
source: ReturnType<typeof createWebhookEventSource>,
event: TEventSpecification,
params: TriggerParams
): CreateTriggersResult<TEventSpecification> {
return new ExternalSourceTrigger({
event,
params,
source,
});
}
const WebhookDataSchema = z.object({
id: z.string(),
object: z.literal("webhook_endpoint"),
api_version: z.string().nullable(),
application: z.string().nullable(),
created: z.number(),
description: z.string().nullable(),
enabled_events: z.array(z.string()),
livemode: z.boolean(),
metadata: z.record(z.string()),
status: z.enum(["enabled", "disabled"]),
url: z.string(),
});
function createWebhookEventSource(
integration: StripeIntegration
): ExternalSource<StripeIntegration, { connect?: boolean }, "HTTP"> {
return new ExternalSource("HTTP", {
id: "stripe.webhook",
schema: z.object({ connect: z.boolean().optional() }),
version: "0.1.0",
integration,
key: (params) => `stripe.webhook${params.connect ? ".connect" : ""}`,
handler: webhookHandler,
register: async (event, io, ctx) => {
const { params, source: httpSource, events, missingEvents } = event;
const webhookData = WebhookDataSchema.safeParse(httpSource.data);
const allEvents = Array.from(new Set([...events, ...missingEvents]));
if (httpSource.active && webhookData.success) {
if (missingEvents.length === 0) return;
const updatedWebhook = await io.integration.updateWebhook(
"update-webhook",
{
id: webhookData.data.id,
url: httpSource.url,
enabled_events: allEvents as unknown as WebhookEvents[],
}
);
return {
data: WebhookDataSchema.parse(updatedWebhook),
registeredEvents: allEvents,
};
}
const listResponse = await io.integration.listWebhooks("list-webhooks", {
limit: 100,
});
const existingWebhook = listResponse.data.find(
(w) => w.url === httpSource.url
);
if (existingWebhook) {
const updatedWebhook = await io.integration.updateWebhook(
"update-found-webhook",
{
id: existingWebhook.id,
url: httpSource.url,
enabled_events: allEvents as unknown as WebhookEvents[],
disabled: false,
}
);
return {
data: WebhookDataSchema.parse(updatedWebhook),
registeredEvents: allEvents,
};
}
const webhook = await io.integration.createWebhook("create-webhook", {
url: httpSource.url,
enabled_events: allEvents as unknown as WebhookEvents[],
connect: params.connect,
});
return {
data: WebhookDataSchema.parse(webhook),
secret: webhook.secret,
registeredEvents: allEvents,
};
},
});
}
async function webhookHandler(event: HandlerEvent<"HTTP">, logger: Logger) {
logger.debug("[@trigger.dev/stripe] Handling webhook payload");
const { rawEvent: request, source } = event;
if (!request.body) {
logger.debug("[@trigger.dev/stripe] No body found");
return { events: [] };
}
const rawBody = await request.text();
const signature = request.headers.get("stripe-signature");
if (signature) {
const stripeClient = new StripeClient("", { apiVersion: "2022-11-15" });
try {
const event = stripeClient.webhooks.constructEvent(
rawBody,
signature,
source.secret
);
return {
events: [
{
id: event.id,
payload: event.data.object,
source: "stripe.com",
name: event.type,
timestamp: new Date(event.created * 1000),
context: {
apiVersion: event.api_version,
livemode: event.livemode,
request: event.request,
previousAttributes: event.data.previous_attributes,
},
},
],
};
} catch (error) {
if (error instanceof Error) {
logger.error(
"[@trigger.dev/stripe] Error while validating webhook signature",
{
error: { name: error.name, message: error.message },
}
);
} else {
logger.error(
"[@trigger.dev/stripe] Unknown Error while validating webhook signature"
);
}
return { events: [] };
}
}
return {
events: [],
};
}
+369
View File
@@ -0,0 +1,369 @@
import type {
StripeSDK,
CreateChargeParams,
CreateChargeResponse,
CreateCustomerResponse,
CreateCustomerParams,
UpdateCustomerParams,
UpdateCustomerResponse,
RetrieveSubscriptionParams,
RetrieveSubscriptionResponse,
CreateCheckoutSessionParams,
CreateCheckoutSessionResponse,
CreateWebhookParams,
CreateWebhookResponse,
UpdateWebhookParams,
UpdateWebhookResponse,
ListWebhooksResponse,
ListWebhooksParams,
} from "./types";
import { AuthenticatedTask } from "@trigger.dev/sdk";
import { Stripe } from "stripe";
import { omit } from "./utils";
export const createCharge: AuthenticatedTask<
StripeSDK,
CreateChargeParams,
CreateChargeResponse
> = {
onError: (error) => {
if (error instanceof Stripe.errors.StripeError) {
console.log("Stripe error", error);
}
throw error;
},
run: async (params, client, task) => {
const response = await client.charges.create(params, {
idempotencyKey: task.idempotencyKey,
stripeAccount: params.stripeAccount,
});
task.outputProperties = [
{
label: "Charge ID",
text: response.id,
},
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Create Charge",
params,
icon: "stripe",
properties: [
{
label: "Amount",
text: `${params.amount}`,
},
...(params.currency
? [
{
label: "Currency",
text: params.currency,
},
]
: []),
...(params.stripeAccount
? [
{
label: "Stripe Account",
text: params.stripeAccount,
},
]
: []),
],
};
},
};
export const createCustomer: AuthenticatedTask<
StripeSDK,
CreateCustomerParams,
CreateCustomerResponse
> = {
run: async (params, client, task) => {
const response = await client.customers.create(params, {
idempotencyKey: task.idempotencyKey,
stripeAccount: params.stripeAccount,
});
task.outputProperties = [
{
label: "Customer ID",
text: response.id,
},
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Create Customer",
params,
icon: "stripe",
properties: [
...(params.stripeAccount
? [
{
label: "Stripe Account",
text: params.stripeAccount,
},
]
: []),
],
};
},
};
export const updateCustomer: AuthenticatedTask<
StripeSDK,
UpdateCustomerParams,
UpdateCustomerResponse
> = {
run: async (params, client, task) => {
const response = await client.customers.update(
params.id,
omit(params, "id"),
{
idempotencyKey: task.idempotencyKey,
stripeAccount: params.stripeAccount,
}
);
task.outputProperties = [
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Update Customer",
params,
icon: "stripe",
properties: [
{
label: "Customer ID",
text: params.id,
},
...(params.stripeAccount
? [
{
label: "Stripe Account",
text: params.stripeAccount,
},
]
: []),
],
};
},
};
export const retrieveSubscription: AuthenticatedTask<
StripeSDK,
RetrieveSubscriptionParams,
RetrieveSubscriptionResponse
> = {
run: async (params, client, task) => {
const response = await client.subscriptions.retrieve(
params.id,
omit(params, "id"),
{
stripeAccount: params.stripeAccount,
}
);
return response;
},
init: (params) => {
return {
name: "Retrieve Subscription",
params,
icon: "stripe",
properties: [
{
label: "Subscription ID",
text: params.id,
},
...(params.stripeAccount
? [
{
label: "Stripe Account",
text: params.stripeAccount,
},
]
: []),
],
};
},
};
export const createCheckoutSession: AuthenticatedTask<
StripeSDK,
CreateCheckoutSessionParams,
CreateCheckoutSessionResponse
> = {
run: async (params, client, task) => {
const response = await client.checkout.sessions.create(params, {
idempotencyKey: task.idempotencyKey,
stripeAccount: params.stripeAccount,
});
task.outputProperties = [
{
label: "Session ID",
text: response.id,
},
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Create Checkout Session",
params,
icon: "stripe",
properties: [
...(params.stripeAccount
? [
{
label: "Stripe Account",
text: params.stripeAccount,
},
]
: []),
],
};
},
};
export const createWebhook: AuthenticatedTask<
StripeSDK,
CreateWebhookParams,
CreateWebhookResponse
> = {
run: async (params, client, task) => {
const response = await client.webhookEndpoints.create(params, {
idempotencyKey: task.idempotencyKey,
});
task.outputProperties = [
{
label: "Webhook ID",
text: response.id,
},
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Create Webhook",
params,
icon: "stripe",
};
},
};
export const updateWebhook: AuthenticatedTask<
StripeSDK,
UpdateWebhookParams,
UpdateWebhookResponse
> = {
run: async (params, client, task) => {
const response = await client.webhookEndpoints.update(
params.id,
omit(params, "id"),
{
idempotencyKey: task.idempotencyKey,
}
);
task.outputProperties = [
...(response.lastResponse.requestId
? [
{
label: "Request ID",
text: response.lastResponse.requestId,
},
]
: []),
];
return response;
},
init: (params) => {
return {
name: "Update Webhook",
params,
icon: "stripe",
properties: [
{
label: "Webhook ID",
text: params.id,
},
],
};
},
};
export const listWebhooks: AuthenticatedTask<
StripeSDK,
ListWebhooksParams,
ListWebhooksResponse
> = {
run: async (params, client, task) => {
const response = await client.webhookEndpoints.list(params);
return response;
},
init: (params) => {
return {
name: "List Webhooks",
params,
icon: "stripe",
};
},
};
+89
View File
@@ -0,0 +1,89 @@
/// <reference types="stripe-event-types" />
import { Stripe } from "stripe";
import { Prettify } from "@trigger.dev/integration-kit";
export type StripeSDK = Stripe;
export type StripeIntegrationOptions = {
id: string;
apiKey: string;
/**
* An account id on whose behalf you wish to make every request.
*/
stripeAccount?: string;
};
type WithStripeConnectOptions<T> = T & {
stripeAccount?: string;
};
export type CreateChargeParams = Prettify<
WithStripeConnectOptions<Stripe.ChargeCreateParams>
>;
export type CreateChargeResponse = Prettify<Stripe.Response<Stripe.Charge>>;
export type CreateCustomerParams = Prettify<
WithStripeConnectOptions<Stripe.CustomerCreateParams>
>;
export type CreateCustomerResponse = Prettify<Stripe.Response<Stripe.Customer>>;
export type UpdateCustomerParams = Prettify<
WithStripeConnectOptions<Stripe.CustomerUpdateParams & { id: string }>
>;
export type UpdateCustomerResponse = Prettify<Stripe.Response<Stripe.Customer>>;
export type RetrieveSubscriptionParams = Prettify<
WithStripeConnectOptions<Stripe.SubscriptionRetrieveParams & { id: string }>
>;
export type RetrieveSubscriptionResponse = Prettify<
Stripe.Response<Stripe.Subscription>
>;
export type CreateCheckoutSessionParams = Prettify<
WithStripeConnectOptions<Stripe.Checkout.SessionCreateParams>
>;
export type CreateCheckoutSessionResponse = Prettify<
Stripe.Response<Stripe.Checkout.Session>
>;
export type CreateWebhookParams = Prettify<Stripe.WebhookEndpointCreateParams>;
export type CreateWebhookResponse = Prettify<Stripe.WebhookEndpoint>;
export type UpdateWebhookParams = Prettify<
Stripe.WebhookEndpointUpdateParams & { id: string }
>;
export type UpdateWebhookResponse = Prettify<Stripe.WebhookEndpoint>;
export type WebhookEvents = Exclude<
Stripe.WebhookEndpointUpdateParams.EnabledEvent,
"*"
>;
export type ListWebhooksParams = Prettify<Stripe.WebhookEndpointListParams>;
export type ListWebhooksResponse = Prettify<
Stripe.Response<Stripe.ApiList<Stripe.WebhookEndpoint>>
>;
type ExtractWebhookPayload<T extends Stripe.DiscriminatedEvent> = Prettify<
T["data"]["object"]
>;
export type OnPriceEvent =
ExtractWebhookPayload<Stripe.DiscriminatedEvent.PriceEvent>;
export type OnProductEvent =
ExtractWebhookPayload<Stripe.DiscriminatedEvent.ProductEvent>;
export type OnCheckoutSession =
ExtractWebhookPayload<Stripe.DiscriminatedEvent.CheckoutSessionEvent>;
export type OnCustomerSubscription =
ExtractWebhookPayload<Stripe.DiscriminatedEvent.CustomerSubscriptionEvent>;
+5
View File
@@ -0,0 +1,5 @@
export function omit<T, K extends keyof T>(obj: T, ...keys: K[]): Omit<T, K> {
const result = { ...obj };
keys.forEach((key) => delete result[key]);
return result;
}
+33
View File
@@ -0,0 +1,33 @@
{
"compilerOptions": {
"composite": false,
"declaration": false,
"declarationMap": false,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"inlineSources": false,
"isolatedModules": true,
"moduleResolution": "node16",
"noUnusedLocals": false,
"noUnusedParameters": false,
"preserveWatchOutput": true,
"skipLibCheck": true,
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"sourceMap": true,
"resolveJsonModule": true,
"lib": [
"es2019"
],
"module": "commonjs",
"target": "es2021"
},
"include": [
"./src/**/*.ts",
"tsup.config.ts"
],
"exclude": [
"node_modules"
]
}
+24
View File
@@ -0,0 +1,24 @@
import { defineConfig } from "tsup";
export default defineConfig([
{
name: "main",
entry: ["./src/index.ts"],
outDir: "./dist",
platform: "node",
format: ["cjs"],
legacyOutput: true,
sourcemap: true,
clean: true,
bundle: true,
splitting: false,
dts: true,
treeshake: {
preset: "smallest",
},
esbuildPlugins: [],
external: ["http", "https", "util", "events", "tty", "os", "timers"],
},
]);
+15 -21
View File
@@ -2,35 +2,29 @@
import { EventFilter } from "./schemas";
// This function should take two EventFilters and return a new EventFilter that is the result of merging the two.
export function deepMergeFilters(
filter: EventFilter,
other: EventFilter
): EventFilter {
const result: EventFilter = { ...filter };
// This function should take any number of EventFilters and return a new EventFilter that is the result of merging of them.
export function deepMergeFilters(...filters: EventFilter[]): EventFilter {
const result: EventFilter = {};
for (const key in other) {
if (other.hasOwnProperty(key)) {
const otherValue = other[key];
if (
typeof otherValue === "object" &&
!Array.isArray(otherValue) &&
otherValue !== null
) {
for (const filter of filters) {
for (const key in filter) {
if (filter.hasOwnProperty(key)) {
const filterValue = filter[key];
const existingValue = result[key];
if (
filterValue &&
existingValue &&
typeof existingValue === "object" &&
typeof filterValue === "object" &&
!Array.isArray(filterValue)
!Array.isArray(existingValue) &&
!Array.isArray(filterValue) &&
existingValue !== null &&
filterValue !== null
) {
result[key] = deepMergeFilters(filterValue, otherValue);
result[key] = deepMergeFilters(existingValue, filterValue);
} else {
result[key] = { ...other[key] };
result[key] = filterValue;
}
} else {
result[key] = other[key];
}
}
}
+23 -15
View File
@@ -591,24 +591,32 @@ export class IO {
}
if (onError) {
const onErrorResult = onError(error, task, this);
try {
const onErrorResult = onError(error, task, this);
if (onErrorResult) {
if (onErrorResult instanceof Error) {
error = onErrorResult;
} else {
const parsedError = ErrorWithStackSchema.safeParse(
onErrorResult.error
);
if (onErrorResult) {
if (onErrorResult instanceof Error) {
error = onErrorResult;
} else {
const parsedError = ErrorWithStackSchema.safeParse(
onErrorResult.error
);
throw new RetryWithTaskError(
parsedError.success
? parsedError.data
: { message: "Unknown error" },
task,
onErrorResult.retryAt
);
throw new RetryWithTaskError(
parsedError.success
? parsedError.data
: { message: "Unknown error" },
task,
onErrorResult.retryAt
);
}
}
} catch (innerError) {
if (isTriggerError(innerError)) {
throw innerError;
}
error = innerError;
}
}
@@ -211,7 +211,7 @@ export class ExternalSource<
export type ExternalSourceParams<
TExternalSource extends ExternalSource<any, any, any>
> = TExternalSource extends ExternalSource<any, infer TParams, any>
? TParams
? TParams & { filter?: EventFilter }
: never;
export type ExternalSourceTriggerOptions<
@@ -247,7 +247,8 @@ export class ExternalSourceTrigger<
event: this.event.name,
payload: deepMergeFilters(
this.options.source.filter(this.options.params),
this.event.filter ?? {}
this.event.filter ?? {},
this.options.params.filter ?? {}
),
source: this.event.source,
},
+40
View File
@@ -415,6 +415,7 @@ importers:
'@trigger.dev/resend': workspace:*
'@trigger.dev/sdk': workspace:*
'@trigger.dev/slack': workspace:*
'@trigger.dev/stripe': workspace:*
'@trigger.dev/supabase': workspace:*
'@trigger.dev/typeform': workspace:*
'@types/node': 18.15.13
@@ -441,6 +442,7 @@ importers:
'@trigger.dev/resend': link:../../integrations/resend
'@trigger.dev/sdk': link:../../packages/trigger-sdk
'@trigger.dev/slack': link:../../integrations/slack
'@trigger.dev/stripe': link:../../integrations/stripe
'@trigger.dev/supabase': link:../../integrations/supabase
'@trigger.dev/typeform': link:../../integrations/typeform
'@types/node': 18.15.13
@@ -570,6 +572,29 @@ importers:
rimraf: 3.0.2
tsup: 6.6.3
integrations/stripe:
specifiers:
'@trigger.dev/integration-kit': workspace:^2.0.0-next.5
'@trigger.dev/sdk': workspace:^2.0.0-next.19
'@types/node': 16.x
rimraf: ^3.0.2
stripe: ^12.14.0
stripe-event-types: ^2.4.0
tsup: 7.1.x
typescript: 4.9.4
zod: 3.21.4
dependencies:
'@trigger.dev/integration-kit': link:../../packages/integration-kit
'@trigger.dev/sdk': link:../../packages/trigger-sdk
stripe: 12.14.0
zod: 3.21.4
devDependencies:
'@types/node': 16.18.11
rimraf: 3.0.2
stripe-event-types: 2.4.0_stripe@12.14.0
tsup: 7.1.0_typescript@4.9.4
typescript: 4.9.4
integrations/supabase:
specifiers:
'@supabase/supabase-js': ^2.26.0
@@ -22804,6 +22829,21 @@ packages:
acorn: 8.10.0
dev: true
/stripe-event-types/2.4.0_stripe@12.14.0:
resolution: {integrity: sha512-5ORZMW/WKjc31nsGMCUECOS+xVjtHhAFJJ8833ft3471hIjQrMbLMPBEb5AcJM8mKRRULpKmCVMDU0lLYrxERQ==}
peerDependencies:
stripe: '>=10.0.0'
dependencies:
stripe: 12.14.0
dev: true
/stripe/12.14.0:
resolution: {integrity: sha512-WrDlYH1p5jliY7uzSU5nLDY7OCIeRe6FkC0hhScpTGwMthP/Muk38WXGeggjDHKeXAGCs43jUheZ7Ud/NEAJdg==}
engines: {node: '>=12.*'}
dependencies:
'@types/node': 20.4.2
qs: 6.11.0
/striptags/2.2.1:
resolution: {integrity: sha512-vZTvmFP0IYu/zn8MXV6PrLb6VKbd9WGSEnlm4D5RNXS/+zYYlHrSfJgoBw1w56D6RJCr515er3BittRGQqihLA==}
dev: false