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,153 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const cancelPaymentLink = createAction({
auth: cashfreePaymentsAuth,
name: 'cancel-payment-link',
displayName: 'Cancel Payment Link',
description: 'Cancel a payment link in Cashfree Payment Gateway. Only links in ACTIVE status can be cancelled.',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
linkId: Property.ShortText({
displayName: 'Payment Link ID',
description: 'The payment link ID that you want to cancel. Only ACTIVE links can be cancelled.',
required: true,
}),
// Optional Headers
requestId: Property.ShortText({
displayName: 'Request ID',
description: 'Request ID for the API call. Can be used to resolve tech issues',
required: false,
}),
idempotencyKey: Property.ShortText({
displayName: 'Idempotency Key',
description: 'UUID format idempotency key to avoid duplicate actions if request fails or times out',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
linkId,
requestId,
idempotencyKey,
} = context.propsValue;
// Validate link ID format
if (!linkId || linkId.trim().length === 0) {
return {
success: false,
error: 'Invalid link ID',
message: 'Payment Link ID is required and cannot be empty',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? `https://api.cashfree.com/pg/links/${linkId}/cancel`
: `https://sandbox.cashfree.com/pg/links/${linkId}/cancel`;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
// Add optional headers
if (requestId) headers['x-request-id'] = requestId;
if (idempotencyKey) headers['x-idempotency-key'] = idempotencyKey;
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
});
if (response.status === 200 || response.status === 201) {
return {
success: true,
data: response.body,
message: 'Payment link cancelled successfully',
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Link may not be in ACTIVE status or invalid link ID',
status: response.status,
};
} else if (response.status === 404) {
return {
success: false,
error: response.body,
message: 'Payment link not found with the provided link ID',
status: response.status,
};
} else if (response.status === 409) {
return {
success: false,
error: response.body,
message: 'Conflict - Link may already be cancelled or in a non-cancellable state',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to cancel payment link',
status: response.status,
};
}
}
} catch (error) {
console.error('Error cancelling Cashfree payment link:', error);
return {
success: false,
error: error,
message: 'An error occurred while cancelling the payment link',
};
}
},
});

View File

@@ -0,0 +1,308 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth, generateCashgramToken, validateAuthCredentials } from '../auth/cashgram-auth';
export const createCashgram = createAction({
auth: cashfreePaymentsAuth,
name: 'create-cashgram',
displayName: 'Create Cashgram',
description: 'Create a Cashgram for instant money transfers using Cashfree',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
cashgramId: Property.ShortText({
displayName: 'Cashgram ID',
description: 'Unique ID of the Cashgram. Alphanumeric, underscore (_), and hyphen (-) allowed (35 character limit)',
required: true,
}),
amount: Property.Number({
displayName: 'Amount',
description: 'Amount to be transferred (minimum 1.00)',
required: true,
}),
name: Property.ShortText({
displayName: 'Contact Name',
description: 'Name of the contact',
required: true,
}),
phone: Property.ShortText({
displayName: 'Phone Number',
description: 'Phone number of the contact',
required: true,
}),
linkExpiry: Property.ShortText({
displayName: 'Link Expiry Date',
description: 'Date to expire the cashgram link. Format: YYYY/MM/DD (maximum 30 days from creation)',
required: true,
}),
// Optional Fields
email: Property.ShortText({
displayName: 'Email',
description: 'Email of the contact',
required: false,
}),
remarks: Property.ShortText({
displayName: 'Remarks',
description: 'Specify remarks, if any',
required: false,
}),
notifyCustomer: Property.Checkbox({
displayName: 'Notify Customer',
description: 'If enabled, a link is sent to customer phone and email',
required: false,
defaultValue: true,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const {
clientId,
clientSecret,
} = context.auth.props
// const finalBearerToken: string;
// Validate credentials based on auth type
const validation = validateAuthCredentials("client_credentials", {
clientId,
clientSecret,
});
if (!validation.isValid) {
return {
success: false,
error: 'Invalid authentication credentials',
message: validation.error,
};
}
const { environment } = context.propsValue;
const tokenResponse = await generateCashgramToken(
{
clientId: clientId!,
clientSecret: clientSecret!
},
environment as 'sandbox' | 'production'
);
if (!tokenResponse.success || !tokenResponse.token) {
return {
success: false,
error: tokenResponse.error,
message: tokenResponse.message || 'Failed to generate bearer token for Cashgram authentication',
};
}
const finalBearerToken = tokenResponse.token;
const {
cashgramId,
amount,
name,
phone,
linkExpiry,
email,
remarks,
notifyCustomer,
} = context.propsValue;
// Validate cashgram ID format
if (!cashgramId || cashgramId.trim().length === 0) {
return {
success: false,
error: 'Invalid Cashgram ID',
message: 'Cashgram ID is required and cannot be empty',
};
}
if (cashgramId.length > 35) {
return {
success: false,
error: 'Invalid Cashgram ID length',
message: 'Cashgram ID must be 35 characters or less',
};
}
// Validate cashgram ID format (alphanumeric, underscore, hyphen only)
const cashgramIdRegex = /^[a-zA-Z0-9_-]+$/;
if (!cashgramIdRegex.test(cashgramId)) {
return {
success: false,
error: 'Invalid Cashgram ID format',
message: 'Cashgram ID can only contain alphanumeric characters, underscore (_), and hyphen (-)',
};
}
// Validate amount
if (amount < 1.00) {
return {
success: false,
error: 'Invalid amount',
message: 'Amount must be greater than or equal to 1.00',
};
}
// Validate link expiry date format
const dateRegex = /^\d{4}\/\d{2}\/\d{2}$/;
if (!dateRegex.test(linkExpiry)) {
return {
success: false,
error: 'Invalid link expiry date format',
message: 'Link expiry date must be in YYYY/MM/DD format',
};
}
// Validate that expiry date is not more than 30 days from now
const expiryDate = new Date(linkExpiry.replace(/\//g, '-'));
const currentDate = new Date();
const thirtyDaysFromNow = new Date();
thirtyDaysFromNow.setDate(currentDate.getDate() + 30);
if (expiryDate > thirtyDaysFromNow) {
return {
success: false,
error: 'Invalid link expiry date',
message: 'Link expiry date cannot be more than 30 days from today',
};
}
if (expiryDate <= currentDate) {
return {
success: false,
error: 'Invalid link expiry date',
message: 'Link expiry date must be in the future',
};
}
// Validate phone number format (basic validation)
if (!phone || phone.trim().length === 0) {
return {
success: false,
error: 'Invalid phone number',
message: 'Phone number is required',
};
}
// Validate name
if (!name || name.trim().length === 0) {
return {
success: false,
error: 'Invalid name',
message: 'Contact name is required',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? 'https://payout-api.cashfree.com/payout/v1/createCashgram'
: 'https://payout-gamma.cashfree.com/payout/v1/createCashgram';
// Prepare the request body
const requestBody: any = {
cashgramId: cashgramId,
amount: amount,
name: name,
phone: phone,
linkExpiry: linkExpiry,
};
// Add optional fields
if (email) requestBody.email = email;
if (remarks) requestBody.remarks = remarks;
if (notifyCustomer !== undefined) requestBody.notifyCustomer = notifyCustomer ? 1 : 0;
// Build headers
const headers: any = {
'Authorization': `Bearer ${finalBearerToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: requestBody,
});
if (response.status === 200) {
const responseData = response.body;
return {
success: true,
data: responseData,
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Please check your input parameters',
status: response.status,
};
} else if (response.status === 401) {
return {
success: false,
error: response.body,
message: 'Unauthorized - Please check your Bearer Token',
status: response.status,
};
} else if (response.status === 403) {
return {
success: false,
error: response.body,
message: 'Forbidden - You do not have permission to create Cashgrams',
status: response.status,
};
} else if (response.status === 409) {
return {
success: false,
error: response.body,
message: 'Conflict - Cashgram ID already exists',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to create Cashgram',
status: response.status,
};
}
}
} catch (error) {
console.error('Error creating Cashfree Cashgram:', error);
return {
success: false,
error: error,
message: 'An error occurred while creating the Cashgram',
};
}
},
});

View File

@@ -0,0 +1,471 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const createOrder = createAction({
name: 'create-order',
displayName: 'Create Order',
description: 'Creates an order in Cashfree Payment Gateway',
auth: cashfreePaymentsAuth,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
orderAmount: Property.Number({
displayName: 'Order Amount',
description: 'Bill amount for the order. Provide up to two decimals (e.g., 10.15 means Rs 10 and 15 paisa)',
required: true,
}),
orderCurrency: Property.StaticDropdown({
displayName: 'Order Currency',
description: 'Currency for the order',
required: true,
defaultValue: 'INR',
options: {
disabled: false,
options: [
{ label: 'Indian Rupee (INR)', value: 'INR' },
{ label: 'US Dollar (USD)', value: 'USD' },
{ label: 'Euro (EUR)', value: 'EUR' },
{ label: 'British Pound (GBP)', value: 'GBP' },
],
},
}),
customerId: Property.ShortText({
displayName: 'Customer ID',
description: 'A unique identifier for the customer. Use alphanumeric values only (3-50 characters)',
required: true,
}),
customerPhone: Property.ShortText({
displayName: 'Customer Phone',
description: 'Customer phone number (minimum 10 digits). For international numbers, prefix with +',
required: true,
}),
// Optional Customer Details
customerEmail: Property.ShortText({
displayName: 'Customer Email',
description: 'Customer email address (3-100 characters)',
required: false,
}),
customerName: Property.ShortText({
displayName: 'Customer Name',
description: 'Name of the customer (3-100 characters)',
required: false,
}),
customerBankAccountNumber: Property.ShortText({
displayName: 'Customer Bank Account Number',
description: 'Customer bank account. Required for TPV (Third Party Verification) (3-20 characters)',
required: false,
}),
customerBankIfsc: Property.ShortText({
displayName: 'Customer Bank IFSC',
description: 'Customer bank IFSC. Required for TPV (Third Party Verification)',
required: false,
}),
customerBankCode: Property.Number({
displayName: 'Customer Bank Code',
description: 'Customer bank code. Required for net banking payments with TPV',
required: false,
}),
// Order Details
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'Order identifier in your system. Alphanumeric, "_" and "-" only (3-45 characters). Will be auto-generated if not provided',
required: false,
}),
orderNote: Property.ShortText({
displayName: 'Order Note',
description: 'Order note for reference (3-200 characters)',
required: false,
}),
orderExpiryTime: Property.ShortText({
displayName: 'Order Expiry Time',
description: 'ISO 8601 format. Example: 2021-07-02T10:20:12+05:30',
required: false,
}),
// Order Meta
returnUrl: Property.ShortText({
displayName: 'Return URL',
description: 'URL to redirect customer after payment completion (max 250 characters)',
required: false,
}),
notifyUrl: Property.ShortText({
displayName: 'Notify URL',
description: 'HTTPS URL for server-to-server notifications (max 250 characters)',
required: false,
}),
paymentMethods: Property.ShortText({
displayName: 'Payment Methods',
description: 'Comma-separated values: cc,dc,ccc,ppc,nb,upi,paypal,app,paylater,cardlessemi,dcemi,ccemi,banktransfer',
required: false,
}),
// Cart Details
cartName: Property.ShortText({
displayName: 'Cart Name',
description: 'Name of the cart',
required: false,
}),
customerNote: Property.ShortText({
displayName: 'Customer Note',
description: 'Note from customer',
required: false,
}),
shippingCharge: Property.Number({
displayName: 'Shipping Charge',
description: 'Shipping charges for the order',
required: false,
}),
// Shipping Address
shippingFullName: Property.ShortText({
displayName: 'Shipping - Full Name',
description: 'Full name for shipping address',
required: false,
}),
shippingCountry: Property.ShortText({
displayName: 'Shipping - Country',
description: 'Country for shipping address',
required: false,
}),
shippingCity: Property.ShortText({
displayName: 'Shipping - City',
description: 'City for shipping address',
required: false,
}),
shippingState: Property.ShortText({
displayName: 'Shipping - State',
description: 'State for shipping address',
required: false,
}),
shippingPincode: Property.ShortText({
displayName: 'Shipping - Pincode',
description: 'Pincode for shipping address',
required: false,
}),
shippingAddress1: Property.ShortText({
displayName: 'Shipping - Address Line 1',
description: 'Primary address line for shipping',
required: false,
}),
shippingAddress2: Property.ShortText({
displayName: 'Shipping - Address Line 2',
description: 'Secondary address line for shipping',
required: false,
}),
// Billing Address
billingFullName: Property.ShortText({
displayName: 'Billing - Full Name',
description: 'Full name for billing address',
required: false,
}),
billingCountry: Property.ShortText({
displayName: 'Billing - Country',
description: 'Country for billing address',
required: false,
}),
billingCity: Property.ShortText({
displayName: 'Billing - City',
description: 'City for billing address',
required: false,
}),
billingState: Property.ShortText({
displayName: 'Billing - State',
description: 'State for billing address',
required: false,
}),
billingPincode: Property.ShortText({
displayName: 'Billing - Pincode',
description: 'Pincode for billing address',
required: false,
}),
billingAddress1: Property.ShortText({
displayName: 'Billing - Address Line 1',
description: 'Primary address line for billing',
required: false,
}),
billingAddress2: Property.ShortText({
displayName: 'Billing - Address Line 2',
description: 'Secondary address line for billing',
required: false,
}),
// Terminal (for SoftPOS)
terminalType: Property.ShortText({
displayName: 'Terminal Type',
description: 'Type of terminal (e.g., SPOS) for SoftPOS orders (4-10 characters)',
required: false,
}),
terminalId: Property.ShortText({
displayName: 'Terminal ID',
description: 'Terminal ID for merchant reference (3-100 characters)',
required: false,
}),
terminalPhoneNo: Property.ShortText({
displayName: 'Terminal Phone Number',
description: 'Mobile number of the terminal/agent/storefront',
required: false,
}),
terminalName: Property.ShortText({
displayName: 'Terminal Name',
description: 'Name of terminal/agent/storefront',
required: false,
}),
terminalAddress: Property.ShortText({
displayName: 'Terminal Address',
description: 'Location of terminal',
required: false,
}),
terminalNote: Property.ShortText({
displayName: 'Terminal Note',
description: 'Note given by merchant while creating the terminal',
required: false,
}),
// Products Configuration
oneClickCheckoutEnabled: Property.Checkbox({
displayName: 'Enable One Click Checkout',
description: 'Enable One Click Checkout feature',
required: false,
}),
verifyPayEnabled: Property.Checkbox({
displayName: 'Enable Verify and Pay',
description: 'Enable Verify and Pay feature',
required: false,
}),
// Order Tags (JSON string)
orderTags: Property.LongText({
displayName: 'Order Tags',
description: 'Custom tags as JSON object. Example: {"product":"Laptop","city":"Bangalore"}. Maximum 10 tags',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props (including environment)
const {
environment,
orderAmount,
orderCurrency,
customerId,
customerPhone,
customerEmail,
customerName,
customerBankAccountNumber,
customerBankIfsc,
customerBankCode,
orderId,
orderNote,
orderExpiryTime,
returnUrl,
notifyUrl,
paymentMethods,
cartName,
customerNote,
shippingCharge,
shippingFullName,
shippingCountry,
shippingCity,
shippingState,
shippingPincode,
shippingAddress1,
shippingAddress2,
billingFullName,
billingCountry,
billingCity,
billingState,
billingPincode,
billingAddress1,
billingAddress2,
terminalType,
terminalId,
terminalPhoneNo,
terminalName,
terminalAddress,
terminalNote,
oneClickCheckoutEnabled,
verifyPayEnabled,
orderTags,
} = context.propsValue;
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? 'https://api.cashfree.com/pg/orders'
: 'https://sandbox.cashfree.com/pg/orders';
// Generate order ID if not provided
const generatedOrderId = orderId || `order_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Build customer details object
const customerDetails: any = {
customer_id: customerId,
customer_phone: customerPhone,
};
if (customerEmail) customerDetails.customer_email = customerEmail;
if (customerName) customerDetails.customer_name = customerName;
if (customerBankAccountNumber) customerDetails.customer_bank_account_number = customerBankAccountNumber;
if (customerBankIfsc) customerDetails.customer_bank_ifsc = customerBankIfsc;
if (customerBankCode) customerDetails.customer_bank_code = customerBankCode;
// Build order meta object
const orderMeta: any = {};
if (returnUrl) orderMeta.return_url = returnUrl;
if (notifyUrl) orderMeta.notify_url = notifyUrl;
if (paymentMethods) orderMeta.payment_methods = paymentMethods;
// Build cart details object
const cartDetails: any = {};
if (cartName) cartDetails.cart_name = cartName;
if (customerNote) cartDetails.customer_note = customerNote;
if (shippingCharge) cartDetails.shipping_charge = shippingCharge;
// Shipping address
if (shippingFullName || shippingCountry || shippingCity || shippingState || shippingPincode || shippingAddress1 || shippingAddress2) {
cartDetails.customer_shipping_address = {};
if (shippingFullName) cartDetails.customer_shipping_address.full_name = shippingFullName;
if (shippingCountry) cartDetails.customer_shipping_address.country = shippingCountry;
if (shippingCity) cartDetails.customer_shipping_address.city = shippingCity;
if (shippingState) cartDetails.customer_shipping_address.state = shippingState;
if (shippingPincode) cartDetails.customer_shipping_address.pincode = shippingPincode;
if (shippingAddress1) cartDetails.customer_shipping_address.address_1 = shippingAddress1;
if (shippingAddress2) cartDetails.customer_shipping_address.address_2 = shippingAddress2;
}
// Billing address
if (billingFullName || billingCountry || billingCity || billingState || billingPincode || billingAddress1 || billingAddress2) {
cartDetails.customer_billing_address = {};
if (billingFullName) cartDetails.customer_billing_address.full_name = billingFullName;
if (billingCountry) cartDetails.customer_billing_address.country = billingCountry;
if (billingCity) cartDetails.customer_billing_address.city = billingCity;
if (billingState) cartDetails.customer_billing_address.state = billingState;
if (billingPincode) cartDetails.customer_billing_address.pincode = billingPincode;
if (billingAddress1) cartDetails.customer_billing_address.address_1 = billingAddress1;
if (billingAddress2) cartDetails.customer_billing_address.address_2 = billingAddress2;
}
// Build terminal object
const terminal: any = {};
if (terminalType) {
terminal.terminal_type = terminalType;
if (terminalId) terminal.terminal_id = terminalId;
if (terminalPhoneNo) terminal.terminal_phone_no = terminalPhoneNo;
if (terminalName) terminal.terminal_name = terminalName;
if (terminalAddress) terminal.terminal_address = terminalAddress;
if (terminalNote) terminal.terminal_note = terminalNote;
}
// Build products object
const products: any = {};
if (oneClickCheckoutEnabled !== undefined) {
products.one_click_checkout = { enabled: oneClickCheckoutEnabled };
}
if (verifyPayEnabled !== undefined) {
products.verify_pay = { enabled: verifyPayEnabled };
}
// Parse order tags if provided
let parsedOrderTags;
if (orderTags) {
try {
parsedOrderTags = JSON.parse(orderTags);
} catch (error) {
return {
success: false,
error: 'Invalid JSON format for order tags',
message: 'Order tags must be valid JSON format. Example: {"product":"Laptop","city":"Bangalore"}',
};
}
}
// Prepare the request body
const requestBody: any = {
order_amount: orderAmount,
order_currency: orderCurrency,
order_id: generatedOrderId,
customer_details: customerDetails,
};
// Add optional fields
if (Object.keys(orderMeta).length > 0) requestBody.order_meta = orderMeta;
if (Object.keys(cartDetails).length > 0) requestBody.cart_details = cartDetails;
if (Object.keys(terminal).length > 0) requestBody.terminal = terminal;
if (Object.keys(products).length > 0) requestBody.products = products;
if (orderNote) requestBody.order_note = orderNote;
if (orderExpiryTime) requestBody.order_expiry_time = orderExpiryTime;
if (parsedOrderTags) requestBody.order_tags = parsedOrderTags;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-Client-Id': clientId,
'X-Client-Secret': clientSecret,
};
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: requestBody,
});
if (response.status === 200 || response.status === 201) {
return {
success: true,
data: response.body,
message: 'Order created successfully',
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to create order',
status: response.status,
};
}
} catch (error) {
console.error('Error creating Cashfree order:', error);
return {
success: false,
error: error,
message: 'An error occurred while creating the order',
};
}
},
});

View File

@@ -0,0 +1,351 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const createPaymentLink = createAction({
name: 'create-payment-link',
displayName: 'Create Payment Link',
description: 'Creates a payment link in Cashfree Payment Gateway',
auth: cashfreePaymentsAuth,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
linkAmount: Property.Number({
displayName: 'Link Amount',
description: 'Amount to be collected using this link. Provide up to two decimals for paise',
required: true,
}),
linkCurrency: Property.StaticDropdown({
displayName: 'Link Currency',
description: 'Currency for the payment link',
required: true,
defaultValue: 'INR',
options: {
disabled: false,
options: [
{ label: 'Indian Rupee (INR)', value: 'INR' },
{ label: 'US Dollar (USD)', value: 'USD' },
{ label: 'Euro (EUR)', value: 'EUR' },
{ label: 'British Pound (GBP)', value: 'GBP' },
],
},
}),
linkPurpose: Property.ShortText({
displayName: 'Link Purpose',
description: 'A brief description for which payment must be collected (max 500 characters)',
required: true,
}),
customerPhone: Property.ShortText({
displayName: 'Customer Phone',
description: 'Customer phone number (required)',
required: true,
}),
// Optional Customer Details
customerEmail: Property.ShortText({
displayName: 'Customer Email',
description: 'Customer email address',
required: false,
}),
customerName: Property.ShortText({
displayName: 'Customer Name',
description: 'Customer name',
required: false,
}),
customerBankAccountNumber: Property.ShortText({
displayName: 'Customer Bank Account Number',
description: 'Customer bank account number',
required: false,
}),
customerBankIfsc: Property.ShortText({
displayName: 'Customer Bank IFSC',
description: 'Customer bank IFSC code',
required: false,
}),
customerBankCode: Property.StaticDropdown({
displayName: 'Customer Bank Code',
description: 'Customer bank code for net banking',
required: false,
options: {
disabled: false,
options: [
{ label: 'State Bank of India (3003)', value: 3003 },
{ label: 'HDFC Bank (3005)', value: 3005 },
{ label: 'ICICI Bank (3006)', value: 3006 },
{ label: 'Axis Bank (3010)', value: 3010 },
{ label: 'Punjab National Bank (3012)', value: 3012 },
{ label: 'Bank of Baroda (3016)', value: 3016 },
{ label: 'Canara Bank (3019)', value: 3019 },
{ label: 'Union Bank of India (3020)', value: 3020 },
{ label: 'Bank of India (3021)', value: 3021 },
{ label: 'Central Bank of India (3022)', value: 3022 },
{ label: 'Indian Bank (3023)', value: 3023 },
{ label: 'Indian Overseas Bank (3024)', value: 3024 },
{ label: 'UCO Bank (3026)', value: 3026 },
{ label: 'Bank of Maharashtra (3027)', value: 3027 },
{ label: 'Punjab & Sind Bank (3028)', value: 3028 },
{ label: 'IDBI Bank (3029)', value: 3029 },
{ label: 'Federal Bank (3030)', value: 3030 },
{ label: 'South Indian Bank (3031)', value: 3031 },
{ label: 'IndusInd Bank (3032)', value: 3032 },
{ label: 'YES Bank (3033)', value: 3033 },
{ label: 'Kotak Mahindra Bank (7001)', value: 7001 },
],
},
}),
// Link Configuration
linkId: Property.ShortText({
displayName: 'Link ID',
description: 'Unique identifier for the link. Alphanumeric, "-" and "_" only (max 50 characters). Auto-generated if not provided',
required: false,
}),
linkPartialPayments: Property.Checkbox({
displayName: 'Enable Partial Payments',
description: 'Allow customers to make partial payments for the link',
required: false,
}),
linkMinimumPartialAmount: Property.Number({
displayName: 'Minimum Partial Amount',
description: 'Minimum amount in first installment (required if partial payments enabled)',
required: false,
}),
linkExpiryTime: Property.ShortText({
displayName: 'Link Expiry Time',
description: 'ISO 8601 format. Example: 2021-07-02T10:20:12+05:30. Default is 30 days',
required: false,
}),
// Notification Settings
sendSms: Property.Checkbox({
displayName: 'Send SMS Notification',
description: 'Send SMS notification to customer phone',
required: false,
}),
sendEmail: Property.Checkbox({
displayName: 'Send Email Notification',
description: 'Send email notification to customer email',
required: false,
}),
linkAutoReminders: Property.Checkbox({
displayName: 'Auto Reminders',
description: 'Send automatic reminders to customers for payment collection',
required: false,
}),
// Link Meta
notifyUrl: Property.ShortText({
displayName: 'Notify URL',
description: 'HTTPS URL for server-to-server notifications',
required: false,
}),
returnUrl: Property.ShortText({
displayName: 'Return URL',
description: 'URL to redirect user after payment completion (max 250 characters)',
required: false,
}),
upiIntent: Property.Checkbox({
displayName: 'UPI Intent',
description: 'Directly open UPI Intent flow on mobile devices',
required: false,
}),
paymentMethods: Property.ShortText({
displayName: 'Payment Methods',
description: 'Comma-separated values: cc,dc,ccc,ppc,nb,upi,paypal,app. Leave blank for all methods',
required: false,
}),
// Additional Data
linkNotes: Property.LongText({
displayName: 'Link Notes',
description: 'Key-value pairs as JSON. Maximum 5 key-value pairs. Example: {"key_1":"value_1","key_2":"value_2"}',
required: false,
}),
// Order Splits
orderSplits: Property.LongText({
displayName: 'Order Splits',
description: 'JSON array for Easy Split. Example: [{"vendor_id":"vendor1","amount":100}]',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
linkAmount,
linkCurrency,
linkPurpose,
customerPhone,
customerEmail,
customerName,
customerBankAccountNumber,
customerBankIfsc,
customerBankCode,
linkId,
linkPartialPayments,
linkMinimumPartialAmount,
linkExpiryTime,
sendSms,
sendEmail,
linkAutoReminders,
notifyUrl,
returnUrl,
upiIntent,
paymentMethods,
linkNotes,
orderSplits,
} = context.propsValue;
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? 'https://api.cashfree.com/pg/links'
: 'https://sandbox.cashfree.com/pg/links';
// Generate link ID if not provided
const generatedLinkId = linkId || `link_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
// Build customer details object
const customerDetails: any = {
customer_phone: customerPhone,
};
if (customerEmail) customerDetails.customer_email = customerEmail;
if (customerName) customerDetails.customer_name = customerName;
if (customerBankAccountNumber) customerDetails.customer_bank_account_number = customerBankAccountNumber;
if (customerBankIfsc) customerDetails.customer_bank_ifsc = customerBankIfsc;
if (customerBankCode) customerDetails.customer_bank_code = customerBankCode;
// Build link notify object
const linkNotify: any = {};
if (sendSms !== undefined) linkNotify.send_sms = sendSms;
if (sendEmail !== undefined) linkNotify.send_email = sendEmail;
// Build link meta object
const linkMeta: any = {};
if (notifyUrl) linkMeta.notify_url = notifyUrl;
if (returnUrl) linkMeta.return_url = returnUrl;
if (upiIntent !== undefined) linkMeta.upi_intent = upiIntent;
if (paymentMethods) linkMeta.payment_methods = paymentMethods;
// Parse link notes if provided
let parsedLinkNotes;
if (linkNotes) {
try {
parsedLinkNotes = JSON.parse(linkNotes);
} catch (error) {
return {
success: false,
error: 'Invalid JSON format for link notes',
message: 'Link notes must be valid JSON format. Example: {"key_1":"value_1","key_2":"value_2"}',
};
}
}
// Parse order splits if provided
let parsedOrderSplits;
if (orderSplits) {
try {
parsedOrderSplits = JSON.parse(orderSplits);
} catch (error) {
return {
success: false,
error: 'Invalid JSON format for order splits',
message: 'Order splits must be valid JSON array. Example: [{"vendor_id":"vendor1","amount":100}]',
};
}
}
// Prepare the request body
const requestBody: any = {
link_amount: linkAmount,
link_currency: linkCurrency,
link_purpose: linkPurpose,
customer_details: customerDetails,
link_id: generatedLinkId,
};
// Add optional fields
if (linkPartialPayments !== undefined) requestBody.link_partial_payments = linkPartialPayments;
if (linkMinimumPartialAmount) requestBody.link_minimum_partial_amount = linkMinimumPartialAmount;
if (linkExpiryTime) requestBody.link_expiry_time = linkExpiryTime;
if (Object.keys(linkNotify).length > 0) requestBody.link_notify = linkNotify;
if (linkAutoReminders !== undefined) requestBody.link_auto_reminders = linkAutoReminders;
if (Object.keys(linkMeta).length > 0) requestBody.link_meta = linkMeta;
if (parsedLinkNotes) requestBody.link_notes = parsedLinkNotes;
if (parsedOrderSplits) requestBody.order_splits = parsedOrderSplits;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: requestBody,
});
if (response.status === 200 || response.status === 201) {
return {
success: true,
data: response.body,
message: 'Payment link created successfully',
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to create payment link',
status: response.status,
};
}
} catch (error) {
console.error('Error creating Cashfree payment link:', error);
return {
success: false,
error: error,
message: 'An error occurred while creating the payment link',
};
}
},
});

View File

@@ -0,0 +1,291 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const createRefund = createAction({
auth: cashfreePaymentsAuth,
name: 'create-refund',
displayName: 'Create Refund',
description: 'Initiate a refund for a Cashfree order. Refunds can only be initiated within six months of the original transaction.',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID which uniquely identifies your order',
required: true,
}),
refundAmount: Property.Number({
displayName: 'Refund Amount',
description: 'Amount to be refunded. Should be lesser than or equal to the transaction amount. (Decimals allowed)',
required: true,
}),
refundId: Property.ShortText({
displayName: 'Refund ID',
description: 'An unique ID to associate the refund with. Provide alphanumeric values (3-40 characters)',
required: true,
}),
// Optional Fields
refundNote: Property.ShortText({
displayName: 'Refund Note',
description: 'A refund note for your reference (3-100 characters)',
required: false,
}),
refundSpeed: Property.StaticDropdown({
displayName: 'Refund Speed',
description: 'Speed at which the refund is processed',
required: false,
defaultValue: 'STANDARD',
options: {
disabled: false,
options: [
{
label: 'Standard',
value: 'STANDARD',
},
{
label: 'Instant',
value: 'INSTANT',
},
],
},
}),
// Refund Splits
refundSplits: Property.LongText({
displayName: 'Refund Splits',
description: 'JSON array for vendor splits. Example: [{"vendor_id":"vendor1","amount":100},{"vendor_id":"vendor2","percentage":25}]',
required: false,
}),
// Custom Tags
tags: Property.LongText({
displayName: 'Custom Tags',
description: 'Custom Tags in JSON format {"key":"value"}. Maximum 10 tags allowed. Example: {"refund_reason":"customer_request","processed_by":"admin"}',
required: false,
}),
// Request Headers
requestId: Property.ShortText({
displayName: 'Request ID',
description: 'Request ID for the API call. Can be used to resolve tech issues',
required: false,
}),
idempotencyKey: Property.ShortText({
displayName: 'Idempotency Key',
description: 'UUID format idempotency key to avoid duplicate actions if request fails or times out',
required: false,
}),
},
async run(context) {
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
orderId,
refundAmount,
refundId,
refundNote,
refundSpeed,
refundSplits,
tags,
requestId,
idempotencyKey,
} = context.propsValue;
// Validate refund ID format and length
if (refundId.length < 3 || refundId.length > 40) {
return {
success: false,
error: 'Invalid refund ID length',
message: 'Refund ID must be between 3 and 40 characters',
};
}
// Validate refund note length if provided
if (refundNote && (refundNote.length < 3 || refundNote.length > 100)) {
return {
success: false,
error: 'Invalid refund note length',
message: 'Refund note must be between 3 and 100 characters',
};
}
// Validate refund amount
if (refundAmount <= 0) {
return {
success: false,
error: 'Invalid refund amount',
message: 'Refund amount must be greater than 0',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? `https://api.cashfree.com/pg/orders/${orderId}/refunds`
: `https://sandbox.cashfree.com/pg/orders/${orderId}/refunds`;
// Parse refund splits if provided
let parsedRefundSplits;
if (refundSplits) {
try {
parsedRefundSplits = JSON.parse(refundSplits);
// Validate refund splits structure
if (!Array.isArray(parsedRefundSplits)) {
return {
success: false,
error: 'Invalid refund splits format',
message: 'Refund splits must be a JSON array',
};
}
// Validate each split entry
for (const split of parsedRefundSplits) {
if (!split.vendor_id) {
return {
success: false,
error: 'Invalid refund split entry',
message: 'Each refund split must have a vendor_id',
};
}
if (!split.amount && !split.percentage) {
return {
success: false,
error: 'Invalid refund split entry',
message: 'Each refund split must have either amount or percentage',
};
}
}
} catch (error) {
return {
success: false,
error: 'Invalid JSON format for refund splits',
message: 'Refund splits must be valid JSON array. Example: [{"vendor_id":"vendor1","amount":100}]',
};
}
}
// Parse custom tags if provided
let parsedTags;
if (tags) {
try {
parsedTags = JSON.parse(tags);
// Validate tags structure
if (typeof parsedTags !== 'object' || Array.isArray(parsedTags)) {
return {
success: false,
error: 'Invalid tags format',
message: 'Tags must be a JSON object',
};
}
// Validate maximum 10 tags
if (Object.keys(parsedTags).length > 10) {
return {
success: false,
error: 'Too many tags',
message: 'Maximum 10 tags are allowed',
};
}
} catch (error) {
return {
success: false,
error: 'Invalid JSON format for tags',
message: 'Tags must be valid JSON format. Example: {"refund_reason":"customer_request","processed_by":"admin"}',
};
}
}
// Prepare the request body
const requestBody: any = {
refund_amount: refundAmount,
refund_id: refundId,
};
// Add optional fields
if (refundNote) requestBody.refund_note = refundNote;
if (refundSpeed) requestBody.refund_speed = refundSpeed;
if (parsedRefundSplits) requestBody.refund_splits = parsedRefundSplits;
if (parsedTags) requestBody.tags = parsedTags;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Content-Type': 'application/json',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
// Add optional headers
if (requestId) headers['x-request-id'] = requestId;
if (idempotencyKey) headers['x-idempotency-key'] = idempotencyKey;
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: requestBody,
});
if (response.status === 200 || response.status === 201) {
return {
success: true,
data: response.body,
message: 'Refund created successfully',
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to create refund',
status: response.status,
};
}
} catch (error) {
console.error('Error creating Cashfree refund:', error);
return {
success: false,
error: error,
message: 'An error occurred while creating the refund',
};
}
},
});

View File

@@ -0,0 +1,199 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth, generateCashgramToken, validateAuthCredentials } from '../auth/cashgram-auth';
export const deactivateCashgram = createAction({
auth: cashfreePaymentsAuth,
name: 'deactivate-cashgram',
displayName: 'Deactivate Cashgram',
description: 'Deactivate a Cashgram to prevent further redemptions using Cashfree',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
cashgramId: Property.ShortText({
displayName: 'Cashgram ID',
description: 'ID of the Cashgram to be deactivated. Alphanumeric and underscore (_) allowed (35 character limit)',
required: true,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const {
clientId,
clientSecret,
} = context.auth.props
// Validate credentials based on auth type
const validation = validateAuthCredentials('client_credentials', {
clientId,
clientSecret,
});
if (!validation.isValid) {
return {
success: false,
error: 'Invalid authentication credentials',
message: validation.error,
};
}
const { environment, cashgramId } = context.propsValue;
const tokenResponse = await generateCashgramToken(
{
clientId: clientId!,
clientSecret: clientSecret!,
},
environment as 'sandbox' | 'production'
);
if (!tokenResponse.success || !tokenResponse.token) {
return {
success: false,
error: tokenResponse.error,
message: tokenResponse.message || 'Failed to generate bearer token for Cashgram authentication',
};
}
const finalBearerToken = tokenResponse.token;
// Validate cashgram ID format
if (!cashgramId || cashgramId.trim().length === 0) {
return {
success: false,
error: 'Invalid Cashgram ID',
message: 'Cashgram ID is required and cannot be empty',
};
}
if (cashgramId.length > 35) {
return {
success: false,
error: 'Invalid Cashgram ID length',
message: 'Cashgram ID must be 35 characters or less',
};
}
// Validate cashgram ID format (alphanumeric and underscore only)
const cashgramIdRegex = /^[a-zA-Z0-9_]+$/;
if (!cashgramIdRegex.test(cashgramId)) {
return {
success: false,
error: 'Invalid Cashgram ID format',
message: 'Cashgram ID can only contain alphanumeric characters and underscore (_)',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? 'https://payout-api.cashfree.com/payout/v1/deactivateCashgram'
: 'https://payout-gamma.cashfree.com/payout/v1/deactivateCashgram';
// Prepare the request body
const requestBody = {
cashgramId: cashgramId,
};
// Build headers
const headers: any = {
'Authorization': `Bearer ${finalBearerToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
try {
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: requestBody,
});
if (response.status === 200) {
const responseData = response.body;
return {
success: true,
data: responseData,
message: responseData?.message || 'Cashgram deactivated successfully',
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Please check your Cashgram ID format',
status: response.status,
};
} else if (response.status === 401) {
return {
success: false,
error: response.body,
message: 'Unauthorized - Please check your Bearer Token',
status: response.status,
};
} else if (response.status === 403) {
return {
success: false,
error: response.body,
message: 'Forbidden - You do not have permission to deactivate Cashgrams',
status: response.status,
};
} else if (response.status === 404) {
return {
success: false,
error: response.body,
message: 'Cashgram not found with the provided ID',
status: response.status,
};
} else if (response.status === 409) {
return {
success: false,
error: response.body,
message: 'Conflict - Cashgram may already be deactivated or cannot be deactivated',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to deactivate Cashgram',
status: response.status,
};
}
}
} catch (error) {
console.error('Error deactivating Cashfree Cashgram:', error);
return {
success: false,
error: error,
message: 'An error occurred while deactivating the Cashgram',
};
}
},
});

View File

@@ -0,0 +1,163 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const fetchPaymentLinkDetails = createAction({
name: 'fetch-payment-link-details',
displayName: 'Fetch Payment Link Details',
description: 'View all details and status of a payment link in Cashfree Payment Gateway',
requireAuth: true,
auth: cashfreePaymentsAuth,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
linkId: Property.ShortText({
displayName: 'Payment Link ID',
description: 'The payment link ID for which you want to view the details',
required: true,
}),
// Optional Headers
requestId: Property.ShortText({
displayName: 'Request ID',
description: 'Request ID for the API call. Can be used to resolve tech issues',
required: false,
}),
idempotencyKey: Property.ShortText({
displayName: 'Idempotency Key',
description: 'UUID format idempotency key for request deduplication',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
linkId,
requestId,
idempotencyKey,
} = context.propsValue;
// Validate link ID format
if (!linkId || linkId.trim().length === 0) {
return {
success: false,
error: 'Invalid link ID',
message: 'Payment Link ID is required and cannot be empty',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? `https://api.cashfree.com/pg/links/${linkId}`
: `https://sandbox.cashfree.com/pg/links/${linkId}`;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
// Add optional headers
if (requestId) headers['x-request-id'] = requestId;
if (idempotencyKey) headers['x-idempotency-key'] = idempotencyKey;
try {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: baseUrl,
headers: headers,
});
if (response.status === 200) {
const linkData = response.body;
return {
success: true,
data: linkData,
message: 'Payment link details fetched successfully',
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Invalid link ID format or parameters',
status: response.status,
};
} else if (response.status === 404) {
return {
success: false,
error: response.body,
message: 'Payment link not found with the provided link ID',
status: response.status,
};
} else if (response.status === 401) {
return {
success: false,
error: response.body,
message: 'Unauthorized - Please check your authentication credentials',
status: response.status,
};
} else if (response.status === 403) {
return {
success: false,
error: response.body,
message: 'Forbidden - You do not have permission to access this link',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to fetch payment link details',
status: response.status,
};
}
}
} catch (error) {
console.error('Error fetching Cashfree payment link details:', error);
return {
success: false,
error: error,
message: 'An error occurred while fetching the payment link details',
};
}
},
});

View File

@@ -0,0 +1,241 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const getAllRefundsForOrder = createAction({
auth: cashfreePaymentsAuth,
name: 'get-all-refunds-for-order',
displayName: 'Get All Refunds for Order',
description: 'Fetch all refunds processed against an order in Cashfree Payment Gateway',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
orderId: Property.ShortText({
displayName: 'Order ID',
description: 'The ID which uniquely identifies your order',
required: true,
}),
// Optional Headers
requestId: Property.ShortText({
displayName: 'Request ID',
description: 'Request ID for the API call. Can be used to resolve tech issues',
required: false,
}),
idempotencyKey: Property.ShortText({
displayName: 'Idempotency Key',
description: 'UUID format idempotency key for request deduplication',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
orderId,
requestId,
idempotencyKey,
} = context.propsValue;
// Validate order ID format
if (!orderId || orderId.trim().length === 0) {
return {
success: false,
error: 'Invalid order ID',
message: 'Order ID is required and cannot be empty',
};
}
// Determine the base URL based on environment
const baseUrl = environment === 'production'
? `https://api.cashfree.com/pg/orders/${orderId}/refunds`
: `https://sandbox.cashfree.com/pg/orders/${orderId}/refunds`;
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
// Add optional headers
if (requestId) headers['x-request-id'] = requestId;
if (idempotencyKey) headers['x-idempotency-key'] = idempotencyKey;
try {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: baseUrl,
headers: headers,
});
if (response.status === 200) {
const refundsData = response.body;
// Extract refunds array and metadata
const refunds = Array.isArray(refundsData) ? refundsData : refundsData?.refunds || [];
const totalRefunds = refunds.length;
// Calculate summary statistics
let totalRefundAmount = 0;
let successfulRefunds = 0;
let pendingRefunds = 0;
let failedRefunds = 0;
const refundStatuses = new Set();
const refundModes = new Set();
const refundSpeeds = new Set();
refunds.forEach((refund: any) => {
// Calculate amounts
if (refund.refund_amount) {
totalRefundAmount += parseFloat(refund.refund_amount);
}
// Track statuses
if (refund.refund_status) {
refundStatuses.add(refund.refund_status);
// Count by status
const status = refund.refund_status.toUpperCase();
if (status === 'SUCCESS' || status === 'PROCESSED') {
successfulRefunds++;
} else if (status === 'PENDING' || status === 'PROCESSING') {
pendingRefunds++;
} else if (status === 'FAILED' || status === 'CANCELLED') {
failedRefunds++;
}
}
// Track refund modes and speeds
if (refund.refund_mode) refundModes.add(refund.refund_mode);
if (refund.refund_speed) refundSpeeds.add(refund.refund_speed);
});
// Extract unique refund IDs and other metadata
const refundIds = refunds.map((refund: any) => refund.refund_id || refund.cf_refund_id).filter(Boolean);
const refundCharges = refunds.reduce((sum: number, refund: any) => {
return sum + (refund.refund_charge ? parseFloat(refund.refund_charge) : 0);
}, 0);
return {
success: true,
data: refundsData,
message: `Found ${totalRefunds} refund(s) for order ${orderId}`,
// Summary information
summary: {
totalRefunds: totalRefunds,
successfulRefunds: successfulRefunds,
pendingRefunds: pendingRefunds,
failedRefunds: failedRefunds,
totalRefundAmount: totalRefundAmount,
totalRefundCharges: refundCharges,
refundStatuses: Array.from(refundStatuses),
refundModes: Array.from(refundModes),
refundSpeeds: Array.from(refundSpeeds),
},
// Individual refunds
refunds: refunds,
// Request parameters for reference
orderId: orderId,
// Quick access to key refund information
refundIds: refundIds,
latestRefund: refunds.length > 0 ? refunds[refunds.length - 1] : null,
oldestRefund: refunds.length > 0 ? refunds[0] : null,
// Financial summary
financialSummary: {
totalRefunded: totalRefundAmount,
averageRefundAmount: totalRefunds > 0 ? (totalRefundAmount / totalRefunds) : 0,
totalCharges: refundCharges,
netRefundAmount: totalRefundAmount - refundCharges,
},
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Invalid order ID format',
status: response.status,
};
} else if (response.status === 404) {
return {
success: false,
error: response.body,
message: 'Order not found with the provided order ID',
status: response.status,
};
} else if (response.status === 401) {
return {
success: false,
error: response.body,
message: 'Unauthorized - Please check your authentication credentials',
status: response.status,
};
} else if (response.status === 403) {
return {
success: false,
error: response.body,
message: 'Forbidden - You do not have permission to access this order\'s refunds',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to fetch refunds for order',
status: response.status,
};
}
}
} catch (error) {
console.error('Error fetching refunds for Cashfree order:', error);
return {
success: false,
error: error,
message: 'An error occurred while fetching refunds for the order',
};
}
},
});

View File

@@ -0,0 +1,234 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { cashfreePaymentsAuth } from '../auth/cashgram-auth';
export const getOrdersForPaymentLink = createAction({
auth: cashfreePaymentsAuth,
name: 'get-orders-for-payment-link',
displayName: 'Get Orders for Payment Link',
description: 'View all order details for a payment link in Cashfree Payment Gateway',
requireAuth: true,
props: {
environment: Property.StaticDropdown({
displayName: 'Environment',
description: 'Choose the environment for API calls',
required: true,
defaultValue: 'sandbox',
options: {
disabled: false,
options: [
{
label: 'Sandbox',
value: 'sandbox',
},
{
label: 'Production',
value: 'production',
},
],
},
}),
// Required Fields
linkId: Property.ShortText({
displayName: 'Payment Link ID',
description: 'The payment link ID for which you want to view the order details',
required: true,
}),
// Optional Query Parameters
status: Property.StaticDropdown({
displayName: 'Order Status Filter',
description: 'Filter orders by status (default is PAID)',
required: false,
defaultValue: 'PAID',
options: {
disabled: false,
options: [
{
label: 'All Orders',
value: 'ALL',
},
{
label: 'Paid Orders Only',
value: 'PAID',
},
],
},
}),
// Optional Headers
requestId: Property.ShortText({
displayName: 'Request ID',
description: 'Request ID for the API call. Can be used to resolve tech issues',
required: false,
}),
idempotencyKey: Property.ShortText({
displayName: 'Idempotency Key',
description: 'UUID format idempotency key for request deduplication',
required: false,
}),
},
async run(context) {
// Get authentication values from piece-level auth
const { clientId, clientSecret } = context.auth.props
if (!clientId || !clientSecret) {
return {
success: false,
error: 'Client ID and Client Secret are required',
message: 'Please provide both Client ID and Client Secret for authentication',
};
}
// Get action-specific values from props
const {
environment,
linkId,
status,
requestId,
idempotencyKey,
} = context.propsValue;
// Validate link ID format
if (!linkId || linkId.trim().length === 0) {
return {
success: false,
error: 'Invalid link ID',
message: 'Payment Link ID is required and cannot be empty',
};
}
// Determine the base URL based on environment
let baseUrl = environment === 'production'
? `https://api.cashfree.com/pg/links/${linkId}/orders`
: `https://sandbox.cashfree.com/pg/links/${linkId}/orders`;
// Add query parameters if status is provided
const queryParams = new URLSearchParams();
if (status && status !== 'PAID') {
queryParams.append('status', status);
}
if (queryParams.toString()) {
baseUrl += `?${queryParams.toString()}`;
}
// Build headers - only client credentials supported
const headers: any = {
'x-api-version': '2025-01-01',
'Accept': 'application/json',
'x-client-id': clientId,
'x-client-secret': clientSecret,
};
// Add optional headers
if (requestId) headers['x-request-id'] = requestId;
if (idempotencyKey) headers['x-idempotency-key'] = idempotencyKey;
try {
const response = await httpClient.sendRequest({
method: HttpMethod.GET,
url: baseUrl,
headers: headers,
});
if (response.status === 200) {
const ordersData = response.body;
// Extract orders array and metadata
const orders = Array.isArray(ordersData) ? ordersData : ordersData?.orders || [];
const totalOrders = orders.length;
// Calculate summary statistics
let totalAmount = 0;
let paidAmount = 0;
let paidOrdersCount = 0;
const orderStatuses = new Set();
orders.forEach((order: any) => {
if (order.order_amount) totalAmount += parseFloat(order.order_amount);
if (order.order_status) orderStatuses.add(order.order_status);
if (order.order_status === 'PAID') {
paidOrdersCount++;
if (order.order_amount) paidAmount += parseFloat(order.order_amount);
}
});
return {
success: true,
data: ordersData,
message: `Found ${totalOrders} order(s) for payment link`,
// Summary information
summary: {
totalOrders: totalOrders,
paidOrders: paidOrdersCount,
totalAmount: totalAmount,
paidAmount: paidAmount,
orderStatuses: Array.from(orderStatuses),
},
// Individual orders
orders: orders,
// Request parameters for reference
linkId: linkId,
statusFilter: status || 'PAID',
// Quick access to key order information
orderIds: orders.map((order: any) => order.order_id).filter(Boolean),
customerIds: [...new Set(orders.map((order: any) => order.customer_details?.customer_id).filter(Boolean))],
paymentMethods: [...new Set(orders.map((order: any) => order.payment_method).filter(Boolean))],
};
} else {
// Handle specific error cases
if (response.status === 400) {
return {
success: false,
error: response.body,
message: 'Bad request - Invalid link ID or query parameters',
status: response.status,
};
} else if (response.status === 404) {
return {
success: false,
error: response.body,
message: 'Payment link not found with the provided link ID',
status: response.status,
};
} else if (response.status === 401) {
return {
success: false,
error: response.body,
message: 'Unauthorized - Please check your authentication credentials',
status: response.status,
};
} else if (response.status === 403) {
return {
success: false,
error: response.body,
message: 'Forbidden - You do not have permission to access this link\'s orders',
status: response.status,
};
} else {
return {
success: false,
error: response.body,
message: 'Failed to fetch orders for payment link',
status: response.status,
};
}
}
} catch (error) {
console.error('Error fetching orders for Cashfree payment link:', error);
return {
success: false,
error: error,
message: 'An error occurred while fetching orders for the payment link',
};
}
},
});

View File

@@ -0,0 +1,161 @@
import { httpClient, HttpMethod } from '@activepieces/pieces-common';
import { PieceAuth, Property } from '@activepieces/pieces-framework';
export interface CashgramAuthCredentials {
clientId: string;
clientSecret: string;
}
export interface CashgramTokenResponse {
success: boolean;
token?: string;
error?: any;
message?: string;
}
/**
* Generate bearer token for Cashgram operations using client credentials and public key
*
* This implementation uses crypto to generate the proper x-cf-signature.
*/
export async function generateCashgramToken(
credentials: CashgramAuthCredentials,
environment: 'sandbox' | 'production'
): Promise<CashgramTokenResponse> {
try {
const baseUrl = environment === 'production'
? 'https://payout-api.cashfree.com/payout/v1/authorize'
: 'https://payout-gamma.cashfree.com/payout/v1/authorize';
const headers = {
'x-client-id': credentials.clientId,
'x-client-secret': credentials.clientSecret,
'Content-Type': 'application/json',
};
const response = await httpClient.sendRequest({
method: HttpMethod.POST,
url: baseUrl,
headers: headers,
body: {},
});
if (response.status === 200 && response.body?.data?.token) {
return {
success: true,
token: response.body.data.token,
message: 'Bearer token generated successfully',
};
} else {
return {
success: false,
error: response.body,
message: `Failed to generate bearer token. Status: ${response.status}. Response: ${JSON.stringify(response.body)}`,
};
}
} catch (error) {
console.error('Error generating Cashgram token:', error);
return {
success: false,
error: error,
message: 'An error occurred while generating the bearer token',
};
}
}
/**
* Validate authentication credentials based on auth type
*/
export function validateAuthCredentials(authType: string, credentials: {
clientId?: string;
clientSecret?: string;
}): {
isValid: boolean;
error?: string;
} {
if (authType === 'client_credentials') {
if (!credentials.clientId || (credentials.clientId).trim().length === 0) {
return {
isValid: false,
error: 'Client ID is required for Client Credentials authentication',
};
}
if (!credentials.clientSecret || (credentials.clientSecret).trim().length === 0) {
return {
isValid: false,
error: 'Client Secret is required for Client Credentials authentication',
};
}
} else if (authType === 'client_credentials_with_public_key') {
if (!credentials.clientId || (credentials.clientId).trim().length === 0) {
return {
isValid: false,
error: 'Client ID is required for Client Credentials + Public Key authentication',
};
}
if (!credentials.clientSecret || (credentials.clientSecret).trim().length === 0) {
return {
isValid: false,
error: 'Client Secret is required for Client Credentials + Public Key authentication',
};
}
}
return {
isValid: true,
};
}
/**
* Legacy function for backward compatibility
* @deprecated Use validateAuthCredentials instead
*/
export function validateCashgramCredentials(credentials: CashgramAuthCredentials): {
isValid: boolean;
error?: string;
} {
return validateAuthCredentials('client_credentials_with_public_key', credentials);
}
export const cashfreePaymentsAuth = PieceAuth.CustomAuth({
description: `Connect your Cashfree account
This connector requires Cashfree API credentials (Client ID and Client Secret). Important: each Cashfree product is a separate product and requires its own credentials. For example, the Payments API and the Payouts API each need their own Client ID / Client Secret pairs.
Create two connections (recommended)
- For clarity and security we recommend creating two separate Activepieces connections:
1. **Payments connection** — use the Payments API Client ID / Client Secret. Use this connection for payments-related actions (create order, payment links, refunds, etc.).
2. **Payouts connection** — use the Payouts API Client ID / Client Secret. Use this connection for Cashgram and other payouts-related actions.
Which keys to use
- Payments API: use the credentials generated for the Payments product.
- Payouts API (required by Cashgram actions): use credentials generated from the Payouts dashboard.
How to generate API keys:
1. Sign in to your Cashfree account and open the *Payouts* dashboard.
2. In the navigation panel select **Developers**.
3. Click **API Keys**.
4. Click **Generate API Keys** on the API Keys screen.
5. The **New API Keys** popup displays the Client ID and Client Secret.
6. Click **Download API Keys** to save the keys locally. Keep these secret — do not share them.
`,
props: {
clientId: Property.ShortText({
displayName: 'Cashfree Client ID',
description: 'Your Cashfree Client ID',
required: false,
}),
clientSecret: Property.ShortText({
displayName: 'Cashfree Client Secret',
description: 'Your Cashfree Client Secret',
required: false,
})
},
required: true,
});