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:
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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}`);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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;
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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.' };
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -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];
|
||||
},
|
||||
});
|
||||
@@ -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];
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user