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