Add Activepieces integration for workflow automation

- Add Activepieces fork with SmoothSchedule custom piece
- Create integrations app with Activepieces service layer
- Add embed token endpoint for iframe integration
- Create Automations page with embedded workflow builder
- Add sidebar visibility fix for embed mode
- Add list inactive customers endpoint to Public API
- Include SmoothSchedule triggers: event created/updated/cancelled
- Include SmoothSchedule actions: create/update/cancel events, list resources/services/customers

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-18 22:59:37 -05:00
parent 9848268d34
commit 3aa7199503
16292 changed files with 1284892 additions and 4708 deletions

View File

@@ -0,0 +1,113 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const createCustomerAction = createAction({
name: 'create_customer',
auth: checkoutComAuth,
displayName: 'Create Customer',
description: 'Store a customer\'s details in a customer object to reuse in future payments. You can link payment instruments and set a default instrument.',
props: {
email: Property.ShortText({
displayName: 'Email',
description: 'The customer\'s email address',
required: true,
}),
name: Property.ShortText({
displayName: 'Name',
description: 'The customer\'s name',
required: false,
}),
phone_country_code: Property.ShortText({
displayName: 'Phone Country Code',
description: 'The international country calling code (e.g., +1, +44). Required if phone number is provided.',
required: false,
}),
phone_number: Property.ShortText({
displayName: 'Phone Number',
description: 'The phone number (6-25 characters). Required if country code is provided.',
required: false,
}),
metadata: Property.Object({
displayName: 'Metadata',
description: 'Additional information about the customer. Maximum 10 key-value pairs, each up to 100 characters.',
required: false,
}),
default: Property.ShortText({
displayName: 'Default Instrument ID',
description: 'The ID of the payment instrument to set as this customer\'s default',
required: false,
}),
},
async run(context) {
const { email, name, phone_country_code, phone_number, metadata, default: defaultInstrument } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
const body: Record<string, any> = { email };
if (name) {
body['name'] = name;
}
if (phone_country_code || phone_number) {
if (!phone_country_code || !phone_number) {
throw new Error('Both phone country code and phone number are required when providing phone information');
}
if (phone_country_code.length < 1 || phone_country_code.length > 7) {
throw new Error('Country code must be between 1 and 7 characters');
}
if (phone_number.length < 6 || phone_number.length > 25) {
throw new Error('Phone number must be between 6 and 25 characters');
}
body['phone'] = {
country_code: phone_country_code,
number: phone_number,
};
}
if (metadata) {
const metadataKeys = Object.keys(metadata);
if (metadataKeys.length > 10) {
throw new Error('Metadata can have a maximum of 10 key-value pairs');
}
for (const [key, value] of Object.entries(metadata)) {
if (key.length > 100) {
throw new Error(`Metadata key "${key}" exceeds 100 characters`);
}
if (typeof value === 'string' && value.length > 100) {
throw new Error(`Metadata value for key "${key}" exceeds 100 characters`);
}
}
body['metadata'] = metadata;
}
if (defaultInstrument) {
body['default'] = defaultInstrument;
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/customers`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
if (error.response?.status === 422) {
throw new Error(`Invalid data: ${error.response.body?.error_codes?.join(', ') || 'Please check your input data'}`);
}
throw error;
}
},
});

View File

@@ -0,0 +1,499 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const createPaymentLinkAction = createAction({
name: 'create_payment_link',
auth: checkoutComAuth,
displayName: 'Create Payment Link',
description: 'Create a Payment Link to accept and process payment details.',
props: {
amount: Property.Number({
displayName: 'Amount',
description: 'The payment amount in minor units (e.g., cents for USD)',
required: true,
}),
currency: Property.ShortText({
displayName: 'Currency',
description: 'The three-letter ISO currency code (e.g., USD, EUR)',
required: true,
}),
reference: Property.ShortText({
displayName: 'Reference',
description: 'A reference to identify the payment (e.g., order number)',
required: false,
}),
description: Property.LongText({
displayName: 'Description',
description: 'A description of the payment',
required: false,
}),
billing_country: Property.ShortText({
displayName: 'Billing Country',
description: 'The two-letter ISO country code (e.g., US, GB)',
required: true,
}),
billing_address_line1: Property.ShortText({
displayName: 'Billing Address Line 1',
description: 'The first line of the billing address',
required: false,
}),
billing_address_line2: Property.ShortText({
displayName: 'Billing Address Line 2',
description: 'The second line of the billing address',
required: false,
}),
billing_city: Property.ShortText({
displayName: 'Billing City',
description: 'The billing address city',
required: false,
}),
billing_state: Property.ShortText({
displayName: 'Billing State',
description: 'The state or province (ISO 3166-2 code)',
required: false,
}),
billing_zip: Property.ShortText({
displayName: 'Billing ZIP',
description: 'The billing address zip or postal code',
required: false,
}),
billing_phone_country_code: Property.ShortText({
displayName: 'Billing Phone Country Code',
description: 'The international country calling code (e.g., +1)',
required: false,
}),
billing_phone_number: Property.ShortText({
displayName: 'Billing Phone Number',
description: 'The phone number (6-25 characters)',
required: false,
}),
// Customer Information
customer_email: Property.ShortText({
displayName: 'Customer Email',
description: 'The customer\'s email address',
required: false,
}),
customer_name: Property.ShortText({
displayName: 'Customer Name',
description: 'The customer\'s name',
required: false,
}),
customer_phone_country_code: Property.ShortText({
displayName: 'Customer Phone Country Code',
description: 'The international country calling code (e.g., +1)',
required: false,
}),
customer_phone_number: Property.ShortText({
displayName: 'Customer Phone Number',
description: 'The customer\'s phone number',
required: false,
}),
// Payment Configuration
expires_in: Property.Number({
displayName: 'Expires In (seconds)',
description: 'Time for which the link remains valid (default: 86400)',
required: false,
}),
display_name: Property.ShortText({
displayName: 'Display Name',
description: 'The merchant name to display to customers on the checkout page',
required: false,
}),
return_url: Property.ShortText({
displayName: 'Return URL',
description: 'URL to redirect customer after successful payment',
required: false,
}),
locale: Property.StaticDropdown({
displayName: 'Locale',
description: 'Language and region for the payment page',
required: false,
options: {
options: [
{ label: 'English (GB)', value: 'en-GB' },
{ label: 'Arabic', value: 'ar' },
{ label: 'Danish (Denmark)', value: 'da-DK' },
{ label: 'German (Germany)', value: 'de-DE' },
{ label: 'Greek', value: 'el' },
{ label: 'Spanish (Spain)', value: 'es-ES' },
{ label: 'Finnish (Finland)', value: 'fi-FI' },
{ label: 'Filipino (Philippines)', value: 'fil-PH' },
{ label: 'French (France)', value: 'fr-FR' },
{ label: 'Hindi (India)', value: 'hi-IN' },
{ label: 'Indonesian (Indonesia)', value: 'id-ID' },
{ label: 'Italian (Italy)', value: 'it-IT' },
{ label: 'Japanese (Japan)', value: 'ja-JP' },
{ label: 'Malay (Malaysia)', value: 'ms-MY' },
{ label: 'Norwegian (Norway)', value: 'nb-NO' },
{ label: 'Dutch (Netherlands)', value: 'nl-NL' },
{ label: 'Portuguese (Portugal)', value: 'pt-PT' },
{ label: 'Swedish (Sweden)', value: 'sv-SE' },
{ label: 'Thai (Thailand)', value: 'th-TH' },
{ label: 'Vietnamese (Vietnam)', value: 'vi-VN' },
{ label: 'Chinese (China)', value: 'zh-CN' },
{ label: 'Chinese (Hong Kong)', value: 'zh-HK' },
{ label: 'Chinese (Taiwan)', value: 'zh-TW' },
],
},
}),
// Payment Methods Control
allow_payment_methods: Property.StaticMultiSelectDropdown({
displayName: 'Allow Payment Methods',
description: 'Specific payment methods to present to customers',
required: false,
options: {
options: [
{ label: 'Card', value: 'card' },
{ label: 'PayPal', value: 'paypal' },
{ label: 'Apple Pay', value: 'applepay' },
{ label: 'Google Pay', value: 'googlepay' },
{ label: 'Klarna', value: 'klarna' },
{ label: 'Alipay CN', value: 'alipay_cn' },
{ label: 'Bancontact', value: 'bancontact' },
{ label: 'iDEAL', value: 'ideal' },
{ label: 'SEPA', value: 'sepa' },
{ label: 'Benefit', value: 'benefit' },
{ label: 'KNet', value: 'knet' },
{ label: 'STC Pay', value: 'stcpay' },
{ label: 'Tabby', value: 'tabby' },
{ label: 'Tamara', value: 'tamara' },
],
},
}),
disabled_payment_methods: Property.StaticMultiSelectDropdown({
displayName: 'Disabled Payment Methods',
description: 'Payment methods to NOT present to customers',
required: false,
options: {
options: [
{ label: 'Card', value: 'card' },
{ label: 'PayPal', value: 'paypal' },
{ label: 'Apple Pay', value: 'applepay' },
{ label: 'Google Pay', value: 'googlepay' },
{ label: 'Klarna', value: 'klarna' },
{ label: 'Alipay CN', value: 'alipay_cn' },
{ label: 'Bancontact', value: 'bancontact' },
{ label: 'iDEAL', value: 'ideal' },
{ label: 'SEPA', value: 'sepa' },
{ label: 'Benefit', value: 'benefit' },
{ label: 'KNet', value: 'knet' },
{ label: 'STC Pay', value: 'stcpay' },
{ label: 'Tabby', value: 'tabby' },
{ label: 'Tamara', value: 'tamara' },
],
},
}),
// Products/Line Items
products: Property.Array({
displayName: 'Products',
description: 'Details about the products in the order',
required: false,
properties: {
name: Property.ShortText({
displayName: 'Product Name',
description: 'The descriptive name of the product',
required: true,
}),
quantity: Property.Number({
displayName: 'Quantity',
description: 'The number of items',
required: true,
}),
price: Property.Number({
displayName: 'Price',
description: 'The price per item in minor units',
required: true,
}),
reference: Property.ShortText({
displayName: 'Product Reference',
description: 'Product SKU or reference',
required: false,
}),
},
}),
// Shipping Information
shipping_country: Property.ShortText({
displayName: 'Shipping Country',
description: 'The two-letter ISO country code for shipping',
required: false,
}),
shipping_address_line1: Property.ShortText({
displayName: 'Shipping Address Line 1',
description: 'The first line of the shipping address',
required: false,
}),
shipping_address_line2: Property.ShortText({
displayName: 'Shipping Address Line 2',
description: 'The second line of the shipping address',
required: false,
}),
shipping_city: Property.ShortText({
displayName: 'Shipping City',
description: 'The shipping address city',
required: false,
}),
shipping_state: Property.ShortText({
displayName: 'Shipping State',
description: 'The state or province for shipping',
required: false,
}),
shipping_zip: Property.ShortText({
displayName: 'Shipping ZIP',
description: 'The shipping address zip or postal code',
required: false,
}),
// Advanced Settings
payment_type: Property.StaticDropdown({
displayName: 'Payment Type',
description: 'Type of payment for card transactions',
required: false,
options: {
options: [
{ label: 'Regular', value: 'Regular' },
{ label: 'Recurring', value: 'Recurring' },
],
},
}),
processing_channel_id: Property.ShortText({
displayName: 'Processing Channel ID',
description: 'The processing channel to be used for the payment. Find this in your Checkout.com dashboard under Settings > Processing Channels. Format: pc_xxxxxxxxxxxxxxxxxxxxxxxxxx',
required: true,
}),
capture: Property.Checkbox({
displayName: 'Capture Payment',
description: 'Whether to capture the payment immediately',
required: false,
}),
enable_3ds: Property.Checkbox({
displayName: 'Enable 3D Secure',
description: 'Whether to process as 3D Secure payment',
required: false,
}),
challenge_3ds: Property.StaticDropdown({
displayName: '3DS Challenge Preference',
description: 'Preference for 3DS challenge',
required: false,
options: {
options: [
{ label: 'No Preference', value: 'no_preference' },
{ label: 'No Challenge Requested', value: 'no_challenge_requested' },
{ label: 'Challenge Requested', value: 'challenge_requested' },
],
},
}),
// Metadata
metadata: Property.Object({
displayName: 'Metadata',
description: 'Additional key-value pairs for transaction information',
required: false,
}),
},
async run(context) {
const {
amount,
currency,
reference,
description,
billing_country,
billing_address_line1,
billing_address_line2,
billing_city,
billing_state,
billing_zip,
billing_phone_country_code,
billing_phone_number,
customer_email,
customer_name,
customer_phone_country_code,
customer_phone_number,
expires_in,
display_name,
return_url,
locale,
allow_payment_methods,
disabled_payment_methods,
products,
shipping_country,
shipping_address_line1,
shipping_address_line2,
shipping_city,
shipping_state,
shipping_zip,
payment_type,
processing_channel_id,
capture,
enable_3ds,
challenge_3ds,
metadata
} = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
const body: Record<string, any> = {
amount,
currency,
billing: {
address: {
country: billing_country,
},
},
};
// Add optional billing address fields
if (billing_address_line1) {
body['billing']['address']['address_line1'] = billing_address_line1;
}
if (billing_address_line2) {
body['billing']['address']['address_line2'] = billing_address_line2;
}
if (billing_city) {
body['billing']['address']['city'] = billing_city;
}
if (billing_state) {
body['billing']['address']['state'] = billing_state;
}
if (billing_zip) {
body['billing']['address']['zip'] = billing_zip;
}
// Add billing phone if provided
if (billing_phone_country_code && billing_phone_number) {
body['billing']['phone'] = {
country_code: billing_phone_country_code,
number: billing_phone_number,
};
}
// Add optional core fields
if (reference) {
body['reference'] = reference;
}
if (description) {
body['description'] = description;
}
if (display_name) {
body['display_name'] = display_name;
}
if (return_url) {
body['return_url'] = return_url;
}
if (locale) {
body['locale'] = locale;
}
if (expires_in) {
body['expires_in'] = expires_in;
}
if (payment_type) {
body['payment_type'] = payment_type;
}
if (processing_channel_id) {
body['processing_channel_id'] = processing_channel_id;
}
if (typeof capture === 'boolean') {
body['capture'] = capture;
}
// Add customer information if provided
if (customer_email || customer_name || (customer_phone_country_code && customer_phone_number)) {
body['customer'] = {};
if (customer_email) {
body['customer']['email'] = customer_email;
}
if (customer_name) {
body['customer']['name'] = customer_name;
}
if (customer_phone_country_code && customer_phone_number) {
body['customer']['phone'] = {
country_code: customer_phone_country_code,
number: customer_phone_number,
};
}
}
// Add shipping information if provided
if (shipping_country || shipping_address_line1 || shipping_city || shipping_state || shipping_zip) {
body['shipping'] = {
address: {},
};
if (shipping_country) {
body['shipping']['address']['country'] = shipping_country;
}
if (shipping_address_line1) {
body['shipping']['address']['address_line1'] = shipping_address_line1;
}
if (shipping_address_line2) {
body['shipping']['address']['address_line2'] = shipping_address_line2;
}
if (shipping_city) {
body['shipping']['address']['city'] = shipping_city;
}
if (shipping_state) {
body['shipping']['address']['state'] = shipping_state;
}
if (shipping_zip) {
body['shipping']['address']['zip'] = shipping_zip;
}
}
// Add payment method restrictions
if (allow_payment_methods && allow_payment_methods.length > 0) {
body['allow_payment_methods'] = allow_payment_methods;
}
if (disabled_payment_methods && disabled_payment_methods.length > 0) {
body['disabled_payment_methods'] = disabled_payment_methods;
}
// Add products if provided
if (products && products.length > 0) {
body['products'] = products.map((product: any) => ({
name: product.name,
quantity: product.quantity,
price: product.price,
...(product.reference && { reference: product.reference }),
}));
}
// Add 3DS configuration if enabled
if (enable_3ds) {
body['3ds'] = {
enabled: true,
...(challenge_3ds && { challenge_indicator: challenge_3ds }),
};
}
// Add metadata if provided
if (metadata && Object.keys(metadata).length > 0) {
body['metadata'] = metadata;
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/payment-links`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
if (error.response?.status === 422) {
throw new Error(`Invalid data: ${error.response.body?.error_codes?.join(', ') || 'Please check your input data'}`);
}
throw error;
}
},
});

View File

@@ -0,0 +1,577 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const createPayoutAction = createAction({
name: 'create_payment',
auth: checkoutComAuth,
displayName: 'Create Payment',
description: 'Process and manage payments from various sources including card payments and payouts.',
props: {
// Core Payment Information
currency: Property.ShortText({
displayName: 'Currency',
description: 'The three-letter ISO currency code (e.g., USD, EUR)',
required: true,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'The payment amount in minor units (e.g., cents for USD). Use 0 for card verification.',
required: false,
}),
reference: Property.ShortText({
displayName: 'Reference',
description: 'Your reference for the payment (e.g., order number)',
required: false,
}),
description: Property.LongText({
displayName: 'Description',
description: 'A description of the payment',
required: false,
}),
// Payment Source - Card Details
source_type: Property.StaticDropdown({
displayName: 'Payment Source Type',
description: 'The type of payment source',
required: true,
options: {
options: [
{ label: 'Card Payment', value: 'card' },
{ label: 'Customer ID', value: 'customer' },
{ label: 'Token', value: 'token' },
{ label: 'Instrument', value: 'instrument' },
],
},
}),
card_number: Property.ShortText({
displayName: 'Card Number',
description: 'The card number (without separators). Required for card payments.',
required: false,
}),
expiry_month: Property.Number({
displayName: 'Expiry Month',
description: 'The expiry month of the card (1-12)',
required: false,
}),
expiry_year: Property.Number({
displayName: 'Expiry Year',
description: 'The expiry year of the card (4 digits)',
required: false,
}),
cvv: Property.ShortText({
displayName: 'CVV',
description: 'The card verification code (3-4 digits)',
required: false,
}),
cardholder_name: Property.ShortText({
displayName: 'Cardholder Name',
description: 'The name of the cardholder',
required: false,
}),
// Alternative Source IDs
customer_id: Property.ShortText({
displayName: 'Customer ID',
description: 'The customer ID (for customer source type, e.g., cus_y3oqhf46pyzuxjocn2giaqnb44)',
required: false,
}),
token_id: Property.ShortText({
displayName: 'Token ID',
description: 'The token ID (for token source type)',
required: false,
}),
instrument_id: Property.ShortText({
displayName: 'Instrument ID',
description: 'The instrument ID (for instrument source type)',
required: false,
}),
// Billing Address
billing_address_line1: Property.ShortText({
displayName: 'Billing Address Line 1',
description: 'The first line of the billing address',
required: false,
}),
billing_address_line2: Property.ShortText({
displayName: 'Billing Address Line 2',
description: 'The second line of the billing address',
required: false,
}),
billing_city: Property.ShortText({
displayName: 'Billing City',
description: 'The billing city',
required: false,
}),
billing_state: Property.ShortText({
displayName: 'Billing State',
description: 'The billing state or province',
required: false,
}),
billing_zip: Property.ShortText({
displayName: 'Billing ZIP',
description: 'The billing ZIP or postal code',
required: false,
}),
billing_country: Property.ShortText({
displayName: 'Billing Country',
description: 'The two-letter ISO country code',
required: false,
}),
billing_phone_country_code: Property.ShortText({
displayName: 'Billing Phone Country Code',
description: 'The international country calling code (e.g., +1)',
required: false,
}),
billing_phone_number: Property.ShortText({
displayName: 'Billing Phone Number',
description: 'The phone number (6-25 characters)',
required: false,
}),
// Account Holder Information
account_holder_first_name: Property.ShortText({
displayName: 'Account Holder First Name',
description: 'The first name of the account holder',
required: false,
}),
account_holder_last_name: Property.ShortText({
displayName: 'Account Holder Last Name',
description: 'The last name of the account holder',
required: false,
}),
account_holder_middle_name: Property.ShortText({
displayName: 'Account Holder Middle Name',
description: 'The middle name of the account holder',
required: false,
}),
// Customer Information
customer_email: Property.ShortText({
displayName: 'Customer Email',
description: 'The customer\'s email address',
required: false,
}),
customer_name: Property.ShortText({
displayName: 'Customer Name',
description: 'The customer\'s name',
required: false,
}),
customer_phone_country_code: Property.ShortText({
displayName: 'Customer Phone Country Code',
description: 'The international country calling code',
required: false,
}),
customer_phone_number: Property.ShortText({
displayName: 'Customer Phone Number',
description: 'The customer\'s phone number',
required: false,
}),
// Shipping Information
shipping_first_name: Property.ShortText({
displayName: 'Shipping First Name',
description: 'The first name for shipping',
required: false,
}),
shipping_last_name: Property.ShortText({
displayName: 'Shipping Last Name',
description: 'The last name for shipping',
required: false,
}),
shipping_email: Property.ShortText({
displayName: 'Shipping Email',
description: 'The email for shipping notifications',
required: false,
}),
shipping_address_line1: Property.ShortText({
displayName: 'Shipping Address Line 1',
description: 'The first line of the shipping address',
required: false,
}),
shipping_address_line2: Property.ShortText({
displayName: 'Shipping Address Line 2',
description: 'The second line of the shipping address',
required: false,
}),
shipping_city: Property.ShortText({
displayName: 'Shipping City',
description: 'The shipping city',
required: false,
}),
shipping_state: Property.ShortText({
displayName: 'Shipping State',
description: 'The shipping state or province',
required: false,
}),
shipping_zip: Property.ShortText({
displayName: 'Shipping ZIP',
description: 'The shipping ZIP or postal code',
required: false,
}),
shipping_country: Property.ShortText({
displayName: 'Shipping Country',
description: 'The two-letter ISO country code for shipping',
required: false,
}),
// Payment Configuration
payment_type: Property.StaticDropdown({
displayName: 'Payment Type',
description: 'The type of payment',
required: false,
options: {
options: [
{ label: 'Regular', value: 'Regular' },
{ label: 'Recurring', value: 'Recurring' },
{ label: 'MOTO (Mail Order/Telephone Order)', value: 'MOTO' },
{ label: 'Installment', value: 'Installment' },
{ label: 'Pay Later', value: 'PayLater' },
{ label: 'Unscheduled', value: 'Unscheduled' },
],
},
}),
capture: Property.Checkbox({
displayName: 'Capture Payment',
description: 'Whether to capture the payment immediately',
required: false,
}),
authorization_type: Property.StaticDropdown({
displayName: 'Authorization Type',
description: 'The type of authorization',
required: false,
options: {
options: [
{ label: 'Final', value: 'Final' },
{ label: 'Estimated', value: 'Estimated' },
],
},
}),
// 3D Secure Configuration
enable_3ds: Property.Checkbox({
displayName: 'Enable 3D Secure',
description: 'Whether to process as 3D Secure payment',
required: false,
}),
challenge_3ds: Property.StaticDropdown({
displayName: '3DS Challenge Preference',
description: 'Preference for 3DS challenge',
required: false,
options: {
options: [
{ label: 'No Preference', value: 'no_preference' },
{ label: 'No Challenge Requested', value: 'no_challenge_requested' },
{ label: 'Challenge Requested', value: 'challenge_requested' },
{ label: 'Challenge Requested Mandate', value: 'challenge_requested_mandate' },
],
},
}),
allow_3ds_upgrade: Property.Checkbox({
displayName: 'Allow 3DS Upgrade',
description: 'Process as 3DS if soft declined due to 3DS authentication required',
required: false,
}),
// Risk Management
enable_risk_assessment: Property.Checkbox({
displayName: 'Enable Risk Assessment',
description: 'Whether to perform risk assessment',
required: false,
}),
customer_ip: Property.ShortText({
displayName: 'Customer IP Address',
description: 'The customer\'s IP address (IPv4 or IPv6)',
required: false,
}),
// Processing Options
processing_channel_id: Property.ShortText({
displayName: 'Processing Channel ID',
description: 'The processing channel to use for the payment',
required: false,
}),
previous_payment_id: Property.ShortText({
displayName: 'Previous Payment ID',
description: 'Link to existing payment series (for recurring payments)',
required: false,
}),
// Success/Failure URLs
success_url: Property.ShortText({
displayName: 'Success URL',
description: 'Success redirect URL for redirect payment methods',
required: false,
}),
failure_url: Property.ShortText({
displayName: 'Failure URL',
description: 'Failure redirect URL for redirect payment methods',
required: false,
}),
// Metadata
metadata: Property.Object({
displayName: 'Metadata',
description: 'Additional key-value pairs for transaction information',
required: false,
}),
},
async run(context) {
const {
currency,
amount,
reference,
description,
source_type,
card_number,
expiry_month,
expiry_year,
cvv,
cardholder_name,
customer_id,
token_id,
instrument_id,
billing_address_line1,
billing_address_line2,
billing_city,
billing_state,
billing_zip,
billing_country,
billing_phone_country_code,
billing_phone_number,
account_holder_first_name,
account_holder_last_name,
account_holder_middle_name,
customer_email,
customer_name,
customer_phone_country_code,
customer_phone_number,
shipping_first_name,
shipping_last_name,
shipping_email,
shipping_address_line1,
shipping_address_line2,
shipping_city,
shipping_state,
shipping_zip,
shipping_country,
payment_type,
capture,
authorization_type,
enable_3ds,
challenge_3ds,
allow_3ds_upgrade,
enable_risk_assessment,
customer_ip,
processing_channel_id,
previous_payment_id,
success_url,
failure_url,
metadata,
} = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
// Build the request body
const body: Record<string, any> = {
currency,
};
// Add amount if provided
if (typeof amount === 'number') {
body['amount'] = amount;
}
// Add core fields
if (reference) {
body['reference'] = reference;
}
if (description) {
body['description'] = description;
}
if (payment_type) {
body['payment_type'] = payment_type;
}
if (typeof capture === 'boolean') {
body['capture'] = capture;
}
if (authorization_type) {
body['authorization_type'] = authorization_type;
}
if (processing_channel_id) {
body['processing_channel_id'] = processing_channel_id;
}
if (previous_payment_id) {
body['previous_payment_id'] = previous_payment_id;
}
if (success_url) {
body['success_url'] = success_url;
}
if (failure_url) {
body['failure_url'] = failure_url;
}
// Build payment source
if (source_type) {
body['source'] = { type: source_type };
if (source_type === 'card') {
if (!card_number || !expiry_month || !expiry_year) {
throw new Error('Card number, expiry month, and expiry year are required for card payments');
}
body['source']['number'] = card_number;
body['source']['expiry_month'] = expiry_month;
body['source']['expiry_year'] = expiry_year;
if (cvv) {
body['source']['cvv'] = cvv;
}
if (cardholder_name) {
body['source']['name'] = cardholder_name;
}
// Add billing address for card
if (billing_address_line1 || billing_city || billing_country) {
body['source']['billing_address'] = {};
if (billing_address_line1) body['source']['billing_address']['address_line1'] = billing_address_line1;
if (billing_address_line2) body['source']['billing_address']['address_line2'] = billing_address_line2;
if (billing_city) body['source']['billing_address']['city'] = billing_city;
if (billing_state) body['source']['billing_address']['state'] = billing_state;
if (billing_zip) body['source']['billing_address']['zip'] = billing_zip;
if (billing_country) body['source']['billing_address']['country'] = billing_country;
}
// Add billing phone for card
if (billing_phone_country_code && billing_phone_number) {
body['source']['phone'] = {
country_code: billing_phone_country_code,
number: billing_phone_number,
};
}
// Add account holder information
if (account_holder_first_name && account_holder_last_name) {
body['source']['account_holder'] = {
type: 'individual',
first_name: account_holder_first_name,
last_name: account_holder_last_name,
};
if (account_holder_middle_name) {
body['source']['account_holder']['middle_name'] = account_holder_middle_name;
}
}
} else if (source_type === 'customer') {
if (!customer_id) {
throw new Error('Customer ID is required for customer source type');
}
body['source']['id'] = customer_id;
} else if (source_type === 'token') {
if (!token_id) {
throw new Error('Token ID is required for token source type');
}
body['source']['token'] = token_id;
} else if (source_type === 'instrument') {
if (!instrument_id) {
throw new Error('Instrument ID is required for instrument source type');
}
body['source']['id'] = instrument_id;
}
}
// Add customer information
if (customer_email || customer_name || (customer_phone_country_code && customer_phone_number)) {
body['customer'] = {};
if (customer_email) {
body['customer']['email'] = customer_email;
}
if (customer_name) {
body['customer']['name'] = customer_name;
}
if (customer_phone_country_code && customer_phone_number) {
body['customer']['phone'] = {
country_code: customer_phone_country_code,
number: customer_phone_number,
};
}
}
// Add shipping information
if (shipping_first_name || shipping_last_name || shipping_email || shipping_address_line1) {
body['shipping'] = {};
if (shipping_first_name) {
body['shipping']['first_name'] = shipping_first_name;
}
if (shipping_last_name) {
body['shipping']['last_name'] = shipping_last_name;
}
if (shipping_email) {
body['shipping']['email'] = shipping_email;
}
if (shipping_address_line1 || shipping_city || shipping_country) {
body['shipping']['address'] = {};
if (shipping_address_line1) body['shipping']['address']['address_line1'] = shipping_address_line1;
if (shipping_address_line2) body['shipping']['address']['address_line2'] = shipping_address_line2;
if (shipping_city) body['shipping']['address']['city'] = shipping_city;
if (shipping_state) body['shipping']['address']['state'] = shipping_state;
if (shipping_zip) body['shipping']['address']['zip'] = shipping_zip;
if (shipping_country) body['shipping']['address']['country'] = shipping_country;
}
}
// Add 3DS configuration
if (enable_3ds) {
body['3ds'] = {
enabled: true,
};
if (challenge_3ds) {
body['3ds']['challenge_indicator'] = challenge_3ds;
}
if (typeof allow_3ds_upgrade === 'boolean') {
body['3ds']['allow_upgrade'] = allow_3ds_upgrade;
}
}
// Add risk configuration
if (typeof enable_risk_assessment === 'boolean') {
body['risk'] = {
enabled: enable_risk_assessment,
};
if (customer_ip) {
body['risk']['device'] = {
network: {
[customer_ip.includes(':') ? 'ipv6' : 'ipv4']: customer_ip,
},
};
}
}
// Add metadata
if (metadata && Object.keys(metadata).length > 0) {
body['metadata'] = metadata;
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/payments`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
if (error.response?.status === 422) {
throw new Error(`Invalid data: ${error.response.body?.error_codes?.join(', ') || 'Please check your input data'}`);
}
if (error.response?.status === 429) {
throw new Error('Too many requests or duplicate request detected');
}
throw error;
}
},
});

View File

@@ -0,0 +1,106 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const getPaymentActionsAction = createAction({
name: 'get_payment_actions',
auth: checkoutComAuth,
displayName: 'Get Payment Actions',
description: 'Build full transaction lifecycles for audit logs.',
props: {
reference: Property.ShortText({
displayName: 'Reference',
description: 'A reference, such as an order ID, that can be used to identify the payment',
required: true,
}),
paymentId: Property.Dropdown({
auth: checkoutComAuth,
displayName: 'Payment ID',
description: 'Select the payment to get actions for',
required: true,
refreshers: ['reference'],
options: async ({ auth, reference }) => {
if (!reference || !auth) {
return {
disabled: true,
options: [],
placeholder: 'Please enter a reference first',
};
}
try {
const { baseUrl } = getEnvironmentFromApiKey(auth.secret_text);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/payments`,
queryParams: {
reference: reference as string,
limit: '100',
},
headers: {
Authorization: `Bearer ${auth.secret_text}`,
'Content-Type': 'application/json',
},
});
const payments = response.body.data || [];
const validPayments = payments.filter((payment: any) => payment.id.startsWith('pay_'));
if (validPayments.length === 0) {
return {
disabled: true,
options: [],
placeholder: 'No payment transactions found for this reference',
};
}
return {
disabled: false,
options: validPayments.map((payment: any) => ({
label: `${payment.id} - ${payment.amount} ${payment.currency} (${payment.status})`,
value: payment.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading payments',
};
}
},
}),
},
async run(context) {
const { paymentId } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
if (!paymentId.match(/^pay_[a-zA-Z0-9]{26}$/)) {
throw new Error('Invalid payment ID format. Must start with "pay_" followed by 26 alphanumeric characters.');
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/payments/${paymentId}/actions`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
});
return response.body;
} catch (error: any) {
if (error.response?.status === 401) {
throw new Error('Unauthorized: Invalid API key or insufficient permissions');
} else if (error.response?.status === 404) {
throw new Error('Payment not found: The specified payment ID does not exist or is not accessible');
} else {
throw new Error(`Failed to get payment actions: ${error.message}`);
}
}
},
});

View File

@@ -0,0 +1,104 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const getPaymentDetailsAction = createAction({
name: 'get_payment_details',
auth: checkoutComAuth,
displayName: 'Get Payment Details',
description: 'Check transaction amount and status before refunding.',
props: {
reference: Property.ShortText({
displayName: 'Reference',
description: 'A reference, such as an order ID, that can be used to identify the payment',
required: true,
}),
paymentId: Property.Dropdown({
auth: checkoutComAuth,
displayName: 'Payment ID',
description: 'Select the payment to get details for',
required: true,
refreshers: ['reference'],
options: async ({ auth, reference }) => {
if (!reference || !auth) {
return {
disabled: true,
options: [],
placeholder: 'Please enter a reference first',
};
}
try {
const { baseUrl } = getEnvironmentFromApiKey(auth.secret_text);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/payments`,
queryParams: {
reference: reference as string,
limit: '100',
},
headers: {
Authorization: `Bearer ${auth.secret_text}`,
'Content-Type': 'application/json',
},
});
const payments = response.body.data || [];
if (payments.length === 0) {
return {
disabled: true,
options: [],
placeholder: 'No payments found for this reference',
};
}
return {
disabled: false,
options: payments.map((payment: any) => ({
label: `${payment.id} - ${payment.amount} ${payment.currency} (${payment.status})`,
value: payment.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading payments',
};
}
},
}),
},
async run(context) {
const { paymentId } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
if (!paymentId.match(/^(pay|sid)_[a-zA-Z0-9]{26}$/)) {
throw new Error('Invalid payment ID format. Must start with "pay_" or "sid_" followed by 26 alphanumeric characters.');
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/payments/${paymentId}`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
});
return response.body;
} catch (error: any) {
if (error.response?.status === 401) {
throw new Error('Unauthorized: Invalid API key or insufficient permissions');
} else if (error.response?.status === 404) {
throw new Error('Payment not found: The specified payment ID does not exist or is not accessible');
} else {
throw new Error(`Failed to get payment details: ${error.message}`);
}
}
},
});

View File

@@ -0,0 +1,519 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const refundPaymentAction = createAction({
name: 'refund_payment',
auth: checkoutComAuth,
displayName: 'Refund a Payment',
description: 'Issue a refund (full or partial) for a captured payment. Supports split refunds, line items, and bank account destinations.',
props: {
reference: Property.ShortText({
displayName: 'Reference',
description: 'A reference, such as an order ID, that can be used to identify the payment',
required: true,
}),
payment_id: Property.Dropdown({
auth: checkoutComAuth,
displayName: 'Payment ID',
description: 'Select the payment to refund',
required: true,
refreshers: ['reference'],
options: async ({ auth, reference }) => {
if (!reference || !auth) {
return {
disabled: true,
options: [],
placeholder: 'Please enter a reference first',
};
}
try {
const { baseUrl } = getEnvironmentFromApiKey(auth.secret_text);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/payments`,
queryParams: {
reference: reference as string,
limit: '100',
},
headers: {
Authorization: `Bearer ${auth.secret_text}`,
'Content-Type': 'application/json',
},
});
const payments = response.body.data || [];
const refundablePayments = payments.filter((payment: any) =>
payment.status === 'Captured' || payment.status === 'Authorized'
);
if (refundablePayments.length === 0) {
return {
disabled: true,
options: [],
placeholder: 'No refundable payments found for this reference',
};
}
return {
disabled: false,
options: refundablePayments.map((payment: any) => ({
label: `${payment.id} - ${payment.amount} ${payment.currency} (${payment.status})`,
value: payment.id,
})),
};
} catch (error) {
return {
disabled: true,
options: [],
placeholder: 'Error loading payments',
};
}
},
}),
amount: Property.Number({
displayName: 'Refund Amount',
description: 'The amount to refund in minor units. If not specified, full payment amount will be refunded.',
required: false,
}),
refund_reference: Property.ShortText({
displayName: 'Refund Reference',
description: 'Your reference for the refund (max 80 chars, 30 for Amex, 50 for TWINT)',
required: false,
}),
capture_action_id: Property.ShortText({
displayName: 'Capture Action ID',
description: 'The action ID of the capture to refund (only for PayPal and Riverty)',
required: false,
}),
amount_allocations: Property.Array({
displayName: 'Amount Allocations',
description: 'Split refund allocations for sub-entities',
required: false,
properties: {
id: Property.ShortText({
displayName: 'Sub-entity ID',
description: 'The ID of the sub-entity',
required: true,
}),
amount: Property.Number({
displayName: 'Split Amount',
description: 'The amount to refund to this sub-entity in minor units',
required: true,
}),
reference: Property.ShortText({
displayName: 'Split Reference',
description: 'Reference for this split (e.g., order number)',
required: false,
}),
commission_amount: Property.Number({
displayName: 'Commission Amount',
description: 'Fixed commission amount in minor units',
required: false,
}),
commission_percentage: Property.Number({
displayName: 'Commission Percentage',
description: 'Commission percentage (0-100, supports up to 8 decimal places)',
required: false,
}),
},
}),
// Line Items for Detailed Refunds
items: Property.Array({
displayName: 'Line Items',
description: 'Order line items for the refund',
required: false,
properties: {
type: Property.ShortText({
displayName: 'Item Type',
description: 'The item type (e.g., physical, digital)',
required: false,
}),
name: Property.ShortText({
displayName: 'Item Name',
description: 'The descriptive name of the line item',
required: false,
}),
quantity: Property.Number({
displayName: 'Quantity',
description: 'The number of line items',
required: false,
}),
unit_price: Property.Number({
displayName: 'Unit Price',
description: 'The unit price in minor currency units',
required: false,
}),
reference: Property.ShortText({
displayName: 'Item Reference',
description: 'The item reference or product SKU',
required: false,
}),
total_amount: Property.Number({
displayName: 'Total Amount',
description: 'The total cost including tax and discount in minor units',
required: false,
}),
tax_rate: Property.Number({
displayName: 'Tax Rate',
description: 'Tax rate in minor units (e.g., 2000 = 20%)',
required: false,
}),
tax_amount: Property.Number({
displayName: 'Tax Amount',
description: 'Total tax amount in minor units',
required: false,
}),
discount_amount: Property.Number({
displayName: 'Discount Amount',
description: 'Discount applied to the line item',
required: false,
}),
url: Property.ShortText({
displayName: 'Product URL',
description: 'Link to the product page',
required: false,
}),
image_url: Property.ShortText({
displayName: 'Product Image URL',
description: 'Link to the product image',
required: false,
}),
},
}),
// Bank Account Destination (Required for giropay and EPS)
destination_country: Property.ShortText({
displayName: 'Destination Country',
description: 'Two-letter ISO country code for bank account (required for giropay/EPS)',
required: false,
}),
destination_account_number: Property.ShortText({
displayName: 'Account Number',
description: 'The bank account number',
required: false,
}),
destination_bank_code: Property.ShortText({
displayName: 'Bank Code',
description: 'The code that identifies the bank',
required: false,
}),
destination_iban: Property.ShortText({
displayName: 'IBAN',
description: 'International Bank Account Number',
required: false,
}),
destination_swift_bic: Property.ShortText({
displayName: 'SWIFT BIC',
description: '8 or 11-digit code identifying the bank',
required: false,
}),
// Account Holder Information
account_holder_first_name: Property.ShortText({
displayName: 'Account Holder First Name',
description: 'The account holder\'s first name',
required: false,
}),
account_holder_last_name: Property.ShortText({
displayName: 'Account Holder Last Name',
description: 'The account holder\'s last name',
required: false,
}),
account_holder_type: Property.StaticDropdown({
displayName: 'Account Holder Type',
description: 'The type of account holder',
required: false,
options: {
options: [
{ label: 'Individual', value: 'individual' },
{ label: 'Corporate', value: 'corporate' },
{ label: 'Government', value: 'government' },
],
},
}),
account_holder_company_name: Property.ShortText({
displayName: 'Company Name',
description: 'Company name for corporate account holders',
required: false,
}),
account_holder_email: Property.ShortText({
displayName: 'Account Holder Email',
description: 'The account holder\'s email address',
required: false,
}),
account_holder_phone_country_code: Property.ShortText({
displayName: 'Phone Country Code',
description: 'International country calling code',
required: false,
}),
account_holder_phone_number: Property.ShortText({
displayName: 'Phone Number',
description: 'The account holder\'s phone number',
required: false,
}),
// Account Holder Address
account_holder_address_line1: Property.ShortText({
displayName: 'Address Line 1',
description: 'First line of the account holder\'s address',
required: false,
}),
account_holder_address_line2: Property.ShortText({
displayName: 'Address Line 2',
description: 'Second line of the account holder\'s address',
required: false,
}),
account_holder_city: Property.ShortText({
displayName: 'City',
description: 'Account holder\'s city',
required: false,
}),
account_holder_state: Property.ShortText({
displayName: 'State',
description: 'Account holder\'s state or province',
required: false,
}),
account_holder_zip: Property.ShortText({
displayName: 'ZIP Code',
description: 'Account holder\'s ZIP or postal code',
required: false,
}),
account_holder_country: Property.ShortText({
displayName: 'Country',
description: 'Two-letter ISO country code',
required: false,
}),
// Bank Details
bank_name: Property.ShortText({
displayName: 'Bank Name',
description: 'The name of the bank',
required: false,
}),
bank_branch: Property.ShortText({
displayName: 'Bank Branch',
description: 'The name of the bank branch',
required: false,
}),
bank_address_line1: Property.ShortText({
displayName: 'Bank Address Line 1',
description: 'First line of the bank\'s address',
required: false,
}),
bank_address_line2: Property.ShortText({
displayName: 'Bank Address Line 2',
description: 'Second line of the bank\'s address',
required: false,
}),
bank_city: Property.ShortText({
displayName: 'Bank City',
description: 'The bank\'s city',
required: false,
}),
bank_country: Property.ShortText({
displayName: 'Bank Country',
description: 'Two-letter ISO country code for the bank',
required: false,
}),
// Metadata
metadata: Property.Object({
displayName: 'Metadata',
description: 'Additional key-value pairs for the refund request',
required: false,
}),
},
async run(context) {
const {
reference,
payment_id,
amount,
refund_reference,
capture_action_id,
amount_allocations,
items,
destination_country,
destination_account_number,
destination_bank_code,
destination_iban,
destination_swift_bic,
account_holder_first_name,
account_holder_last_name,
account_holder_type,
account_holder_company_name,
account_holder_email,
account_holder_phone_country_code,
account_holder_phone_number,
account_holder_address_line1,
account_holder_address_line2,
account_holder_city,
account_holder_state,
account_holder_zip,
account_holder_country,
bank_name,
bank_branch,
bank_address_line1,
bank_address_line2,
bank_city,
bank_country,
metadata,
} = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
const body: Record<string, any> = {};
if (typeof amount === 'number') {
body['amount'] = amount;
}
if (refund_reference) {
body['reference'] = refund_reference;
}
if (capture_action_id) {
body['capture_action_id'] = capture_action_id;
}
if (amount_allocations && amount_allocations.length > 0) {
body['amount_allocations'] = amount_allocations.map((allocation: any) => {
const allocationObj: any = {
id: allocation.id,
amount: allocation.amount,
};
if (allocation.reference) {
allocationObj['reference'] = allocation.reference;
}
if (allocation.commission_amount || allocation.commission_percentage) {
allocationObj['commission'] = {};
if (allocation.commission_amount) {
allocationObj['commission']['amount'] = allocation.commission_amount;
}
if (allocation.commission_percentage) {
allocationObj['commission']['percentage'] = allocation.commission_percentage;
}
}
return allocationObj;
});
}
if (items && items.length > 0) {
body['items'] = items.map((item: any) => {
const itemObj: any = {};
if (item.type) itemObj['type'] = item.type;
if (item.name) itemObj['name'] = item.name;
if (typeof item.quantity === 'number') itemObj['quantity'] = item.quantity;
if (typeof item.unit_price === 'number') itemObj['unit_price'] = item.unit_price;
if (item.reference) itemObj['reference'] = item.reference;
if (typeof item.total_amount === 'number') itemObj['total_amount'] = item.total_amount;
if (typeof item.tax_rate === 'number') itemObj['tax_rate'] = item.tax_rate;
if (typeof item.tax_amount === 'number') itemObj['tax_amount'] = item.tax_amount;
if (typeof item.discount_amount === 'number') itemObj['discount_amount'] = item.discount_amount;
if (item.url) itemObj['url'] = item.url;
if (item.image_url) itemObj['image_url'] = item.image_url;
return itemObj;
});
}
if (destination_country && destination_account_number && destination_bank_code) {
body['destination'] = {
country: destination_country,
account_number: destination_account_number,
bank_code: destination_bank_code,
};
if (destination_iban) {
body['destination']['iban'] = destination_iban;
}
if (destination_swift_bic) {
body['destination']['swift_bic'] = destination_swift_bic;
}
if (account_holder_first_name && account_holder_last_name) {
body['destination']['account_holder'] = {
first_name: account_holder_first_name,
last_name: account_holder_last_name,
};
if (account_holder_type) {
body['destination']['account_holder']['type'] = account_holder_type;
}
if (account_holder_company_name) {
body['destination']['account_holder']['company_name'] = account_holder_company_name;
}
if (account_holder_email) {
body['destination']['account_holder']['email'] = account_holder_email;
}
if (account_holder_phone_country_code && account_holder_phone_number) {
body['destination']['account_holder']['phone'] = {
country_code: account_holder_phone_country_code,
number: account_holder_phone_number,
};
}
if (account_holder_address_line1 || account_holder_city || account_holder_country) {
body['destination']['account_holder']['billing_address'] = {};
if (account_holder_address_line1) body['destination']['account_holder']['billing_address']['address_line1'] = account_holder_address_line1;
if (account_holder_address_line2) body['destination']['account_holder']['billing_address']['address_line2'] = account_holder_address_line2;
if (account_holder_city) body['destination']['account_holder']['billing_address']['city'] = account_holder_city;
if (account_holder_state) body['destination']['account_holder']['billing_address']['state'] = account_holder_state;
if (account_holder_zip) body['destination']['account_holder']['billing_address']['zip'] = account_holder_zip;
if (account_holder_country) body['destination']['account_holder']['billing_address']['country'] = account_holder_country;
}
}
if (bank_name || bank_branch || bank_address_line1) {
body['destination']['bank'] = {};
if (bank_name) body['destination']['bank']['name'] = bank_name;
if (bank_branch) body['destination']['bank']['branch'] = bank_branch;
if (bank_address_line1 || bank_city || bank_country) {
body['destination']['bank']['address'] = {};
if (bank_address_line1) body['destination']['bank']['address']['address_line1'] = bank_address_line1;
if (bank_address_line2) body['destination']['bank']['address']['address_line2'] = bank_address_line2;
if (bank_city) body['destination']['bank']['address']['city'] = bank_city;
if (bank_country) body['destination']['bank']['address']['country'] = bank_country;
}
}
}
if (metadata && Object.keys(metadata).length > 0) {
body['metadata'] = metadata;
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/payments/${payment_id}/refunds`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
if (error.response?.status === 403) {
throw new Error('Refund not allowed for this payment');
}
if (error.response?.status === 404) {
throw new Error('Payment not found');
}
if (error.response?.status === 422) {
throw new Error(`Invalid data: ${error.response.body?.error_codes?.join(', ') || 'Please check your input data'}`);
}
throw error;
}
},
});

View File

@@ -0,0 +1,122 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const updateCustomerAction = createAction({
name: 'update_customer',
auth: checkoutComAuth,
displayName: 'Update Customer',
description: 'Update existing customer or their metadata.',
props: {
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'The ID of the customer to update (e.g., cus_y3oqhf46pyzuxjocn2giaqnb44)',
required: true,
}),
email: Property.ShortText({
displayName: 'Email',
description: 'The customer\'s email address',
required: false,
}),
name: Property.ShortText({
displayName: 'Name',
description: 'The customer\'s name',
required: false,
}),
phone_country_code: Property.ShortText({
displayName: 'Phone Country Code',
description: 'The international country calling code (e.g., +1, +44). Required if phone number is provided.',
required: false,
}),
phone_number: Property.ShortText({
displayName: 'Phone Number',
description: 'The phone number (6-25 characters). Required if country code is provided.',
required: false,
}),
metadata: Property.Object({
displayName: 'Metadata',
description: 'Additional information about the customer. Will replace any existing metadata.',
required: false,
}),
default: Property.ShortText({
displayName: 'Default Instrument ID',
description: 'The ID of this customer\'s default instrument',
required: false,
}),
},
async run(context) {
const { customerId, email, name, phone_country_code, phone_number, metadata, default: defaultInstrument } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
const body: Record<string, any> = {};
if (email) {
body['email'] = email;
}
if (name) {
body['name'] = name;
}
if (phone_country_code || phone_number) {
if (!phone_country_code || !phone_number) {
throw new Error('Both phone country code and phone number are required when providing phone information');
}
if (phone_country_code.length < 1 || phone_country_code.length > 7) {
throw new Error('Country code must be between 1 and 7 characters');
}
if (phone_number.length < 6 || phone_number.length > 25) {
throw new Error('Phone number must be between 6 and 25 characters');
}
body['phone'] = {
country_code: phone_country_code,
number: phone_number,
};
}
if (metadata) {
const metadataKeys = Object.keys(metadata);
if (metadataKeys.length > 10) {
throw new Error('Metadata can have a maximum of 10 key-value pairs');
}
for (const [key, value] of Object.entries(metadata)) {
if (key.length > 100) {
throw new Error(`Metadata key "${key}" exceeds 100 characters`);
}
if (typeof value === 'string' && value.length > 100) {
throw new Error(`Metadata value for key "${key}" exceeds 100 characters`);
}
}
body['metadata'] = metadata;
}
if (defaultInstrument) {
body['default'] = defaultInstrument;
}
try {
const response = await httpClient.sendRequest({
method: HttpMethod.PATCH,
url: `${baseUrl}/customers/${customerId}`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body,
});
return response.body;
} catch (error: any) {
if (error.response?.status === 422) {
throw new Error(`Invalid data: ${error.response.body?.error_codes?.join(', ') || 'Please check your input data'}`);
}
throw error;
}
},
});

View File

@@ -0,0 +1,53 @@
import { PieceAuth } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod, AuthenticationType } from '@activepieces/pieces-common';
export function getEnvironmentFromApiKey(apiKey: string): { environment: 'sandbox' | 'production', baseUrl: string } {
if (apiKey.startsWith('sk_sbox_')) {
return { environment: 'sandbox', baseUrl: 'https://api.sandbox.checkout.com' };
}
if (apiKey.startsWith('sk_') && !apiKey.startsWith('sk_sbox_')) {
return { environment: 'production', baseUrl: 'https://api.checkout.com' };
}
return { environment: 'production', baseUrl: 'https://api.checkout.com' };
}
export const checkoutComAuth = PieceAuth.SecretText({
displayName: 'Secret Key',
description: 'Your Checkout.com secret key. Use sandbox key (sk_sbox_...) for testing or production key (sk_...) for live transactions. You can find it in the Checkout.com dashboard under Developers > API keys.',
required: true,
validate: async ({auth}) => {
try {
const secretKey = auth;
if (!secretKey.startsWith('sk_')) {
return { valid: false, error: 'Invalid API key format. Must start with "sk_" for production or "sk_sbox_" for sandbox.' };
}
const { environment, baseUrl } = getEnvironmentFromApiKey(secretKey);
await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/workflows`,
authentication: {
type: AuthenticationType.BEARER_TOKEN,
token: secretKey,
},
});
return {
valid: true,
environment: environment
};
} catch (e: any) {
if (e.response?.status === 401) {
return { valid: false, error: 'Invalid Checkout.com secret key. Please check your API key in the Checkout.com dashboard.' };
} else if (e.response?.status === 403) {
return { valid: false, error: 'Insufficient permissions. Please ensure your API key has the required permissions.' };
} else {
return { valid: false, error: 'Unable to validate API key. Please check your connection and try again.' };
}
}
},
});

View File

@@ -0,0 +1,163 @@
import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const disputeEventsTrigger = createTrigger({
name: 'dispute_events',
displayName: 'Dispute Events',
description: 'Notify operations upon dispute opening or resolution.',
auth: checkoutComAuth,
props: {
eventTypes: Property.MultiSelectDropdown({
auth: checkoutComAuth,
displayName: 'Event Types',
description: 'Select the dispute events you want to listen for',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your Checkout.com account first',
};
}
try {
const { baseUrl } = getEnvironmentFromApiKey(auth.secret_text);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/workflows/event-types`,
headers: {
Authorization: `Bearer ${auth.secret_text}`,
'Content-Type': 'application/json',
},
});
const eventSources = response.body || [];
const disputeSource = eventSources.find((source: any) => source.id === 'dispute' || source.id === 'disputes');
if (!disputeSource || !disputeSource.events) {
return {
disabled: false,
options: [
{ label: 'Dispute Canceled', value: 'dispute_canceled' },
{ label: 'Dispute Evidence Required', value: 'dispute_evidence_required' },
{ label: 'Dispute Expired', value: 'dispute_expired' },
{ label: 'Dispute Lost', value: 'dispute_lost' },
{ label: 'Dispute Resolved', value: 'dispute_resolved' },
{ label: 'Dispute Won', value: 'dispute_won' },
],
};
}
return {
disabled: false,
options: disputeSource.events.map((event: any) => ({
label: event.display_name || event.id,
value: event.id,
})),
};
} catch (error) {
return {
disabled: false,
options: [
{ label: 'Dispute Canceled', value: 'dispute_canceled' },
{ label: 'Dispute Evidence Required', value: 'dispute_evidence_required' },
{ label: 'Dispute Expired', value: 'dispute_expired' },
{ label: 'Dispute Lost', value: 'dispute_lost' },
{ label: 'Dispute Resolved', value: 'dispute_resolved' },
{ label: 'Dispute Won', value: 'dispute_won' },
],
};
}
},
}),
},
sampleData: {
id: 'evt_lw4cjxhp4jme3dwpztvyizgcau',
source: 'dispute',
type: 'dispute_evidence_required',
timestamp: '2019-08-24T14:15:22Z',
version: '1.0.0',
data: {
id: 'dsp_rbhwd2qrg13uhr4w2oxna5tav6',
category: 'fraudulent',
amount: 6540,
currency: 'USD',
reason_code: '4855',
status: 'evidence_required',
resolved_reason: 'dispute_not_valid',
payment_id: 'pay_mbabizu24mvu3mela5njyhpit4',
payment_reference: 'ORD-5023-4E89',
payment_arn: '74593210001234567890123',
payment_method: 'VISA',
received_on: '2019-08-24T14:15:22Z',
last_update: '2019-08-24T14:15:22Z'
}
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const { eventTypes } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/workflows`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body: {
name: 'Activepieces Dispute Events Workflow',
active: true,
conditions: [
{
type: 'event',
events: {
dispute: eventTypes,
},
},
],
actions: [
{
type: 'webhook',
url: context.webhookUrl,
},
],
},
});
await context.store.put('checkout_dispute_workflow', {
workflowId: response.body.id,
});
} catch (error: any) {
throw new Error(`Failed to create dispute events workflow: ${error.message}`);
}
},
async onDisable(context) {
try {
const workflowData = await context.store.get<{ workflowId: string }>('checkout_dispute_workflow');
if (workflowData?.workflowId) {
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
await httpClient.sendRequest({
method: HttpMethod.DELETE,
url: `${baseUrl}/workflows/${workflowData.workflowId}`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
},
});
}
} catch (error: any) {
console.error('Failed to delete dispute events workflow:', error.message);
}
},
async run(context) {
return [context.payload.body];
},
});

View File

@@ -0,0 +1,169 @@
import { createTrigger, Property, TriggerStrategy } from '@activepieces/pieces-framework';
import { checkoutComAuth, getEnvironmentFromApiKey } from '../common/auth';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
export const paymentEventsTrigger = createTrigger({
name: 'payment_events',
displayName: 'Payment Events',
description: 'Trigger order fulfillment when payment is approved.',
auth: checkoutComAuth,
props: {
eventTypes: Property.MultiSelectDropdown({
auth: checkoutComAuth,
displayName: 'Event Types',
description: 'Select the payment events you want to listen for',
required: true,
refreshers: [],
options: async ({ auth }) => {
if (!auth) {
return {
disabled: true,
options: [],
placeholder: 'Please connect your Checkout.com account first',
};
}
try {
const { baseUrl } = getEnvironmentFromApiKey(auth.secret_text);
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: `${baseUrl}/workflows/event-types`,
headers: {
Authorization: `Bearer ${auth.secret_text}`,
'Content-Type': 'application/json',
},
});
const eventSources = response.body || [];
const gatewaySource = eventSources.find((source: any) => source.id === 'gateway');
if (!gatewaySource || !gatewaySource.events) {
return {
disabled: false,
options: [
{ label: 'Payment Approved', value: 'payment_approved' },
{ label: 'Payment Declined', value: 'payment_declined' },
{ label: 'Payment Captured', value: 'payment_captured' },
{ label: 'Payment Refunded', value: 'payment_refunded' },
{ label: 'Payment Voided', value: 'payment_voided' },
{ label: 'Card Verified', value: 'card_verified' },
{ label: 'Card Verification Declined', value: 'card_verification_declined' },
{ label: 'Payment Authorization Incremented', value: 'payment_authorization_incremented' },
{ label: 'Payment Authorization Increment Declined', value: 'payment_authorization_increment_declined' },
{ label: 'Payment Capture Declined', value: 'payment_capture_declined' },
{ label: 'Payment Refund Declined', value: 'payment_refund_declined' },
{ label: 'Payment Void Declined', value: 'payment_void_declined' },
],
};
}
return {
disabled: false,
options: gatewaySource.events.map((event: any) => ({
label: event.display_name || event.id,
value: event.id,
})),
};
} catch (error) {
return {
disabled: false,
options: [
{ label: 'Payment Approved', value: 'payment_approved' },
{ label: 'Payment Declined', value: 'payment_declined' },
{ label: 'Payment Captured', value: 'payment_captured' },
{ label: 'Payment Refunded', value: 'payment_refunded' },
{ label: 'Payment Voided', value: 'payment_voided' },
{ label: 'Card Verified', value: 'card_verified' },
{ label: 'Card Verification Declined', value: 'card_verification_declined' },
],
};
}
},
}),
},
sampleData: {
id: 'evt_az5sblvku4ge3dwpztvyizgcau',
source: 'gateway',
type: 'payment_approved',
timestamp: '2019-08-24T14:15:22Z',
version: '1.0.0',
data: {
id: 'pay_mbabizu24mvu3mela5njyhpit4',
action_id: 'act_y3oqhf46pyzuxjbcn2giaqnb44',
amount: 6540,
currency: 'USD',
approved: true,
status: 'Authorized',
auth_code: '643381',
payment_type: 'Regular',
response_code: '10000',
response_summary: 'Approved',
reference: 'ORD-5023-4E89',
processed_on: '2020-02-27T11:26:59Z'
}
},
type: TriggerStrategy.WEBHOOK,
async onEnable(context) {
const { eventTypes } = context.propsValue;
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: `${baseUrl}/workflows`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
'Content-Type': 'application/json',
},
body: {
name: 'Activepieces Payment Events Workflow',
active: true,
conditions: [
{
type: 'event',
events: {
gateway: eventTypes,
},
},
],
actions: [
{
type: 'webhook',
url: context.webhookUrl,
},
],
},
});
await context.store.put('checkout_payment_workflow', {
workflowId: response.body.id,
});
} catch (error: any) {
throw new Error(`Failed to create payment events workflow: ${error.message}`);
}
},
async onDisable(context) {
try {
const workflowData = await context.store.get<{ workflowId: string }>('checkout_payment_workflow');
if (workflowData?.workflowId) {
const { baseUrl } = getEnvironmentFromApiKey(context.auth.secret_text);
await httpClient.sendRequest({
method: HttpMethod.DELETE,
url: `${baseUrl}/workflows/${workflowData.workflowId}`,
headers: {
Authorization: `Bearer ${context.auth.secret_text}`,
},
});
}
} catch (error: any) {
console.error('Failed to delete payment events workflow:', error.message);
}
},
async run(context) {
return [context.payload.body];
},
});