diff --git a/packages/nodes-base/credentials/StripeApi.credentials.ts b/packages/nodes-base/credentials/StripeApi.credentials.ts index b6aef0d814..991af27fab 100644 --- a/packages/nodes-base/credentials/StripeApi.credentials.ts +++ b/packages/nodes-base/credentials/StripeApi.credentials.ts @@ -6,7 +6,7 @@ import { export class StripeApi implements ICredentialType { name = 'stripeApi'; - displayName = 'Stripe Api'; + displayName = 'Stripe API'; documentationUrl = 'stripe'; properties: INodeProperties[] = [ // The credentials to get from user and save encrypted. diff --git a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts index 4b9eff2928..503263ad64 100644 --- a/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts +++ b/packages/nodes-base/nodes/Google/Calendar/GenericFunctions.ts @@ -30,6 +30,7 @@ export async function googleApiRequest(this: IExecuteFunctions | IExecuteSingleF if (Object.keys(body).length === 0) { delete options.body; } + console.log(options); //@ts-ignore return await this.helpers.requestOAuth2.call(this, 'googleCalendarOAuth2Api', options); } catch (error) { diff --git a/packages/nodes-base/nodes/Stripe/Stripe.node.ts b/packages/nodes-base/nodes/Stripe/Stripe.node.ts new file mode 100644 index 0000000000..06eb3577ab --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/Stripe.node.ts @@ -0,0 +1,506 @@ +import { + IExecuteFunctions, +} from 'n8n-core'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodeExecutionData, + INodePropertyOptions, + INodeType, + INodeTypeDescription, +} from 'n8n-workflow'; + +import { + isEmpty, +} from 'lodash'; + +import { + adjustChargeFields, + adjustCustomerFields, + adjustMetadata, + handleListing, + loadResource, + stripeApiRequest, +} from './helpers'; + +import { + balanceOperations, + chargeFields, + chargeOperations, + couponFields, + couponOperations, + customerCardFields, + customerCardOperations, + customerFields, + customerOperations, + sourceFields, + sourceOperations, + tokenFields, + tokenOperations, +} from './descriptions'; + +export class Stripe implements INodeType { + description: INodeTypeDescription = { + displayName: 'Stripe', + name: 'stripe', + icon: 'file:stripe.svg', + group: ['transform'], + version: 1, + subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', + description: 'Consume the Stripe API', + defaults: { + name: 'Stripe', + color: '#6772e5', + }, + inputs: ['main'], + outputs: ['main'], + credentials: [ + { + name: 'stripeApi', + required: true, + }, + ], + properties: [ + { + displayName: 'Resource', + name: 'resource', + type: 'options', + options: [ + { + name: 'Balance', + value: 'balance', + }, + { + name: 'Charge', + value: 'charge', + }, + { + name: 'Coupon', + value: 'coupon', + }, + { + name: 'Customer', + value: 'customer', + }, + { + name: 'Customer Card', + value: 'customerCard', + }, + { + name: 'Source', + value: 'source', + }, + { + name: 'Token', + value: 'token', + }, + ], + default: 'balance', + description: 'Resource to consume', + }, + ...balanceOperations, + ...customerCardOperations, + ...customerCardFields, + ...chargeOperations, + ...chargeFields, + ...couponOperations, + ...couponFields, + ...customerOperations, + ...customerFields, + ...sourceOperations, + ...sourceFields, + ...tokenOperations, + ...tokenFields, + ], + }; + + methods = { + loadOptions: { + async getCustomers(this: ILoadOptionsFunctions) { + return await loadResource.call(this, 'customer'); + }, + async getCurrencies(this: ILoadOptionsFunctions): Promise { + const returnData: INodePropertyOptions[] = []; + const { data } = await stripeApiRequest.call(this, 'GET', '/country_specs', {}); + for (const currency of data[0].supported_payment_currencies) { + returnData.push({ + name: currency.toUpperCase(), + value: currency, + }); + } + return returnData; + }, + }, + }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + + const resource = this.getNodeParameter('resource', 0) as string; + const operation = this.getNodeParameter('operation', 0) as string; + + let responseData; + const returnData: IDataObject[] = []; + + for (let i = 0; i < items.length; i++) { + + try { + + if (resource === 'balance') { + + // ********************************************************************* + // balance + // ********************************************************************* + + // https://stripe.com/docs/api/balance + + if (operation === 'get') { + + // ---------------------------------- + // balance: get + // ---------------------------------- + + responseData = await stripeApiRequest.call(this, 'GET', '/balance', {}, {}); + + } + + } else if (resource === 'customerCard') { + + // ********************************************************************* + // customer card + // ********************************************************************* + + // https://stripe.com/docs/api/cards + + if (operation === 'add') { + + // ---------------------------------- + // customerCard: add + // ---------------------------------- + + const body = { + source: this.getNodeParameter('token', i), + } as IDataObject; + + const customerId = this.getNodeParameter('customerId', i); + const endpoint = `/customers/${customerId}/sources`; + responseData = await stripeApiRequest.call(this, 'POST', endpoint, body, {}); + + } else if (operation === 'remove') { + + // ---------------------------------- + // customerCard: remove + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + const cardId = this.getNodeParameter('cardId', i); + const endpoint = `/customers/${customerId}/sources/${cardId}`; + responseData = await stripeApiRequest.call(this, 'DELETE', endpoint, {}, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // customerCard: get + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + const sourceId = this.getNodeParameter('sourceId', i); + const endpoint = `/customers/${customerId}/sources/${sourceId}`; + responseData = await stripeApiRequest.call(this, 'GET', endpoint, {}, {}); + + } + + } else if (resource === 'charge') { + + // ********************************************************************* + // charge + // ********************************************************************* + + // https://stripe.com/docs/api/charges + + if (operation === 'create') { + + // ---------------------------------- + // charge: create + // ---------------------------------- + + const body = { + customer: this.getNodeParameter('customerId', i), + currency: (this.getNodeParameter('currency', i) as string).toLowerCase(), + amount: this.getNodeParameter('amount', i), + source: this.getNodeParameter('source', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (!isEmpty(additionalFields)) { + Object.assign(body, adjustChargeFields(additionalFields)); + } + + responseData = await stripeApiRequest.call(this, 'POST', '/charges', body, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // charge: get + // ---------------------------------- + + const chargeId = this.getNodeParameter('chargeId', i); + responseData = await stripeApiRequest.call(this, 'GET', `/charges/${chargeId}`, {}, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // charge: getAll + // ---------------------------------- + + responseData = await handleListing.call(this, resource); + + } else if (operation === 'update') { + + // ---------------------------------- + // charge: update + // ---------------------------------- + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + Object.assign(body, adjustChargeFields(updateFields)); + + const chargeId = this.getNodeParameter('chargeId', i); + responseData = await stripeApiRequest.call(this, 'POST', `/charges/${chargeId}`, body, {}); + + } + + } else if (resource === 'coupon') { + + // ********************************************************************* + // coupon + // ********************************************************************* + + // https://stripe.com/docs/api/coupons + + if (operation === 'create') { + + // ---------------------------------- + // coupon: create + // ---------------------------------- + + const body = { + duration: this.getNodeParameter('duration', i), + } as IDataObject; + + const type = this.getNodeParameter('type', i); + + if (type === 'fixedAmount') { + body.amount_off = this.getNodeParameter('amountOff', i); + body.currency = this.getNodeParameter('currency', i); + } else { + body.percent_off = this.getNodeParameter('percentOff', i); + } + + responseData = await stripeApiRequest.call(this, 'POST', '/coupons', body, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // coupon: getAll + // ---------------------------------- + + responseData = await handleListing.call(this, resource); + + } + + } else if (resource === 'customer') { + + // ********************************************************************* + // customer + // ********************************************************************* + + // https://stripe.com/docs/api/customers + + if (operation === 'create') { + + // ---------------------------------- + // customer: create + // ---------------------------------- + + const body = { + name: this.getNodeParameter('name', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (!isEmpty(additionalFields)) { + Object.assign(body, adjustCustomerFields(additionalFields)); + } + + responseData = await stripeApiRequest.call(this, 'POST', '/customers', body, {}); + + } else if (operation === 'delete') { + + // ---------------------------------- + // customer: delete + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + responseData = await stripeApiRequest.call(this, 'DELETE', `/customers/${customerId}`, {}, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // customer: get + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + responseData = await stripeApiRequest.call(this, 'GET', `/customers/${customerId}`, {}, {}); + + } else if (operation === 'getAll') { + + // ---------------------------------- + // customer: getAll + // ---------------------------------- + + const qs = {} as IDataObject; + const filters = this.getNodeParameter('filters', i) as IDataObject; + + if (!isEmpty(filters)) { + qs.email = filters.email; + } + + responseData = await handleListing.call(this, resource, qs); + + } else if (operation === 'update') { + + // ---------------------------------- + // customer: update + // ---------------------------------- + + const body = {} as IDataObject; + + const updateFields = this.getNodeParameter('updateFields', i) as IDataObject; + + if (isEmpty(updateFields)) { + throw new Error(`Please enter at least one field to update for the ${resource}.`); + } + + Object.assign(body, adjustCustomerFields(updateFields)); + + const customerId = this.getNodeParameter('customerId', i); + responseData = await stripeApiRequest.call(this, 'POST', `/customers/${customerId}`, body, {}); + + } + + } else if (resource === 'source') { + + // ********************************************************************* + // source + // ********************************************************************* + + // https://stripe.com/docs/api/sources + + if (operation === 'create') { + + // ---------------------------------- + // source: create + // ---------------------------------- + + const customerId = this.getNodeParameter('customerId', i); + + const body = { + type: this.getNodeParameter('type', i), + amount: this.getNodeParameter('amount', i), + currency: this.getNodeParameter('currency', i), + } as IDataObject; + + const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject; + + if (!isEmpty(additionalFields)) { + Object.assign(body, adjustMetadata(additionalFields)); + } + + responseData = await stripeApiRequest.call(this, 'POST', '/sources', body, {}); + + // attach source to customer + const endpoint = `/customers/${customerId}/sources`; + await stripeApiRequest.call(this, 'POST', endpoint, { source: responseData.id }, {}); + + } else if (operation === 'delete') { + + // ---------------------------------- + // source: delete + // ---------------------------------- + + const sourceId = this.getNodeParameter('sourceId', i); + const customerId = this.getNodeParameter('customerId', i); + const endpoint = `/customers/${customerId}/sources/${sourceId}`; + responseData = await stripeApiRequest.call(this, 'DELETE', endpoint, {}, {}); + + } else if (operation === 'get') { + + // ---------------------------------- + // source: get + // ---------------------------------- + + const sourceId = this.getNodeParameter('sourceId', i); + responseData = await stripeApiRequest.call(this, 'GET', `/sources/${sourceId}`, {}, {}); + + } + + } else if (resource === 'token') { + + // ********************************************************************* + // token + // ********************************************************************* + + // https://stripe.com/docs/api/tokens + + if (operation === 'create') { + + // ---------------------------------- + // token: create + // ---------------------------------- + + const type = this.getNodeParameter('type', i); + const body = {} as IDataObject; + + if (type !== 'cardToken') { + throw new Error('Only card token creation implemented.'); + } + + body.card = { + number: this.getNodeParameter('number', i), + exp_month: this.getNodeParameter('expirationMonth', i), + exp_year: this.getNodeParameter('expirationYear', i), + cvc: this.getNodeParameter('cvc', i), + }; + + responseData = await stripeApiRequest.call(this, 'POST', '/tokens', body, {}); + } + + } + + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ error: error.message }); + continue; + } + + throw error; + } + + Array.isArray(responseData) + ? returnData.push(...responseData) + : returnData.push(responseData); + } + + return [this.helpers.returnJsonArray(returnData)]; + + } +} diff --git a/packages/nodes-base/nodes/Stripe/descriptions/BalanceDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/BalanceDescription.ts new file mode 100644 index 0000000000..34dc2d55cb --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/BalanceDescription.ts @@ -0,0 +1,27 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const balanceOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Get', + value: 'get', + description: 'Get a balance', + }, + ], + displayOptions: { + show: { + resource: [ + 'balance', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/ChargeDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/ChargeDescription.ts new file mode 100644 index 0000000000..d26f367839 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/ChargeDescription.ts @@ -0,0 +1,509 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const chargeOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a charge', + }, + { + name: 'Get', + value: 'get', + description: 'Get a charge', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all charges', + }, + { + name: 'Update', + value: 'update', + description: 'Update a charge', + }, + ], + displayOptions: { + show: { + resource: [ + 'charge', + ], + }, + }, + }, +] as INodeProperties[]; + +export const chargeFields = [ + // ---------------------------------- + // charge: create + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to be associated with this charge', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Amount', + name: 'amount', + type: 'number', + required: true, + default: 0, + description: 'Amount in cents to be collected for this charge, e.g. enter 100 for $1.00', + typeOptions: { + minValue: 0, + maxValue: 99999999, + }, + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + required: true, + default: '', + description: 'Three-letter ISO currency code, e.g. USD or EUR. It must be a Stripe-supported currency', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Source ID', + name: 'source', + type: 'string', + required: true, + default: '', + description: 'ID of the customer\'s payment source to be charged', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Arbitrary text to describe the charge to create', + }, + { + displayName: 'Metadata', + name: 'metadata', + type: 'fixedCollection', + default: [], + placeholder: 'Add Metadata Item', + description: 'Set of key-value pairs to attach to the charge to create', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Metadata Properties', + name: 'metadataProperties', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Receipt Email', + name: 'receipt_email', + type: 'string', + default: '', + description: 'Email address to which the receipt for this charge will be sent', + }, + { + displayName: 'Shipping', + name: 'shipping', + type: 'fixedCollection', + description: 'Shipping information for the charge', + placeholder: 'Add Field', + typeOptions: { + multipleValues: true, + }, + default: [], + options: [ + { + displayName: 'Shipping Properties', + name: 'shippingProperties', + values: [ + { + displayName: 'Recipient Name', + name: 'name', + type: 'string', + description: 'Name of the person who will receive the shipment', + default: '', + }, + { + displayName: 'Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Field', + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + + // ---------------------------------- + // charge: get + // ---------------------------------- + { + displayName: 'Charge ID', + name: 'chargeId', + type: 'string', + required: true, + default: '', + description: 'ID of the charge to retrieve.', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // charge: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + + // ---------------------------------- + // charge: update + // ---------------------------------- + { + displayName: 'Charge ID', + name: 'chargeId', + type: 'string', + required: true, + default: '', + description: 'ID of the charge to update', + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'charge', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Arbitrary text to describe the charge to update', + }, + { + displayName: 'Metadata', + name: 'metadata', + type: 'fixedCollection', + placeholder: 'Add Metadata Item', + description: 'Set of key-value pairs to attach to the charge to update', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Metadata Properties', + name: 'metadataProperties', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Receipt Email', + name: 'receipt_email', + type: 'string', + default: '', + description: 'The email address to which the receipt for this charge will be sent', + }, + { + displayName: 'Shipping', + name: 'shipping', + type: 'fixedCollection', + description: 'Shipping information for the charge', + placeholder: 'Add Field', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Shipping Properties', + name: 'shippingProperties', + default: {}, + values: [ + { + displayName: 'Recipient Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Recipient Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address Details', + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/CouponDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/CouponDescription.ts new file mode 100644 index 0000000000..efc041fc73 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/CouponDescription.ts @@ -0,0 +1,213 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const couponOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a coupon', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all coupons', + }, + ], + displayOptions: { + show: { + resource: [ + 'coupon', + ], + }, + }, + }, +] as INodeProperties[]; + +export const couponFields = [ + // ---------------------------------- + // coupon: create + // ---------------------------------- + { + displayName: 'Apply', + name: 'duration', + type: 'options', + required: true, + default: 'once', + description: 'How long the discount will be in effect', + options: [ + { + name: 'Forever', + value: 'forever', + }, + { + name: 'Once', + value: 'once', + }, + ], + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Discount Type', + name: 'type', + type: 'options', + required: true, + default: 'percent', + description: 'Whether the coupon discount is a percentage or a fixed amount', + options: [ + { + name: 'Fixed Amount (in Cents)', + value: 'fixedAmount', + }, + { + name: 'Percent', + value: 'percent', + }, + ], + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Amount Off', + name: 'amountOff', + type: 'number', + required: true, + default: 0, + description: 'Amount in cents to subtract from an invoice total, e.g. enter 100 for $1.00', + typeOptions: { + minValue: 0, + maxValue: 99999999, + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + type: [ + 'fixedAmount', + ], + }, + }, + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + required: true, + default: '', + description: 'Three-letter ISO currency code, e.g. USD or EUR. It must be a Stripe-supported currency', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + type: [ + 'fixedAmount', + ], + }, + }, + }, + { + displayName: 'Percent Off', + name: 'percentOff', + type: 'number', + required: true, + default: 1, + description: 'Percentage to apply with the coupon', + typeOptions: { + minValue: 1, + maxValue: 100, + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'create', + ], + type: [ + 'percent', + ], + }, + }, + }, + + // ---------------------------------- + // coupon: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'coupon', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/CustomerCardDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/CustomerCardDescription.ts new file mode 100644 index 0000000000..68d4a44980 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/CustomerCardDescription.ts @@ -0,0 +1,160 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const customerCardOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Add', + value: 'add', + description: 'Add a customer card', + }, + { + name: 'Get', + value: 'get', + description: 'Get a customer card', + }, + { + name: 'Remove', + value: 'remove', + description: 'Remove a customer card', + }, + ], + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + }, + }, + }, +] as INodeProperties[]; + +export const customerCardFields = [ + // ---------------------------------- + // customerCard: add + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to be associated with this card', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'add', + ], + }, + }, + }, + { + displayName: 'Card Token', + name: 'token', + type: 'string', + required: true, + default: '', + placeholder: 'tok_1IMfKdJhRTnqS5TKQVG1LI9o', + description: 'Token representing sensitive card information', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'add', + ], + }, + }, + }, + + // ---------------------------------- + // customerCard: remove + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer whose card to remove', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'remove', + ], + }, + }, + }, + { + displayName: 'Card ID', + name: 'cardId', + type: 'string', + required: true, + default: '', + description: 'ID of the card to remove', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'remove', + ], + }, + }, + }, + + // ---------------------------------- + // customerCard: get + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer whose card to retrieve', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'get', + ], + }, + }, + }, + { + displayName: 'Source ID', + name: 'sourceId', + type: 'string', + required: true, + default: '', + description: 'ID of the source to retrieve', + displayOptions: { + show: { + resource: [ + 'customerCard', + ], + operation: [ + 'get', + ], + }, + }, + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/CustomerDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/CustomerDescription.ts new file mode 100644 index 0000000000..26e49fc489 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/CustomerDescription.ts @@ -0,0 +1,649 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const customerOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a customer', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a customer', + }, + { + name: 'Get', + value: 'get', + description: 'Get a customer', + }, + { + name: 'Get All', + value: 'getAll', + description: 'Get all customers', + }, + { + name: 'Update', + value: 'update', + description: 'Update a customer', + }, + ], + displayOptions: { + show: { + resource: [ + 'customer', + ], + }, + }, + }, +] as INodeProperties[]; + +export const customerFields = [ + // ---------------------------------- + // customer: create + // ---------------------------------- + { + displayName: 'Name', + name: 'name', + type: 'string', + required: true, + default: '', + description: 'Full name or business name of the customer to create', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'fixedCollection', + description: 'Address of the customer to create', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Arbitrary text to describe the customer to create', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Email of the customer to create', + }, + { + displayName: 'Metadata', + name: 'metadata', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Metadata Item', + description: 'Set of key-value pairs to attach to the customer to create', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Metadata Properties', + name: 'metadataProperties', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Telephone number of the customer to create', + }, + { + displayName: 'Shipping', + name: 'shipping', + type: 'fixedCollection', + description: 'Shipping information for the customer', + typeOptions: { + multipleValues: true, + }, + placeholder: 'Add Field', + options: [ + { + displayName: 'Shipping Properties', + name: 'shippingProperties', + values: [ + { + displayName: 'Recipient Name', + name: 'name', + type: 'string', + default: '', + }, + { + displayName: 'Recipient Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address Details', + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Recipient Phone', + name: 'phone', + type: 'string', + default: '', + }, + ], + }, + ], + }, + ], + }, + + // ---------------------------------- + // customer: delete + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to delete', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // customer: get + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to retrieve', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'get', + ], + }, + }, + }, + + // ---------------------------------- + // customer: getAll + // ---------------------------------- + { + displayName: 'Return All', + name: 'returnAll', + type: 'boolean', + default: false, + description: 'Whether to return all results or only up to a given limit', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + }, + { + displayName: 'Limit', + name: 'limit', + type: 'number', + default: 50, + description: 'How many results to return', + typeOptions: { + minValue: 1, + maxValue: 1000, + }, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + returnAll: [ + false, + ], + }, + }, + }, + { + displayName: 'Filters', + name: 'filters', + type: 'collection', + placeholder: 'Add Filter', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'getAll', + ], + }, + }, + options: [ + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Customer\'s email to filter by', + }, + ], + }, + + // ---------------------------------- + // customer: update + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to update', + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + }, + { + displayName: 'Update Fields', + name: 'updateFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'customer', + ], + operation: [ + 'update', + ], + }, + }, + options: [ + { + displayName: 'Address', + name: 'address', + type: 'fixedCollection', + description: 'Address of the customer to update', + placeholder: 'Add Field', + default: {}, + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Description', + name: 'description', + type: 'string', + default: '', + description: 'Arbitrary text to describe the customer to create', + }, + { + displayName: 'Email', + name: 'email', + type: 'string', + default: '', + description: 'Email of the customer to create', + }, + { + displayName: 'Metadata', + name: 'metadata', + type: 'fixedCollection', + placeholder: 'Add Metadata Item', + description: 'Set of key-value pairs to attach to the customer to create', + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Metadata Properties', + name: 'metadataProperties', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Name', + name: 'name', + type: 'string', + default: '', + description: 'Full name or business name of the customer to create', + }, + { + displayName: 'Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Telephone number of this customer', + }, + { + displayName: 'Shipping', + name: 'shipping', + type: 'fixedCollection', + description: 'Shipping information for the customer', + placeholder: 'Add Field', + typeOptions: { + multipleValues: true, + }, + default: {}, + options: [ + { + displayName: 'Shipping Properties', + name: 'shippingProperties', + values: [ + { + displayName: 'Recipient Name', + name: 'name', + type: 'string', + default: '', + description: 'Name of the person who will receive the shipment', + }, + { + displayName: 'Recipient Address', + name: 'address', + type: 'fixedCollection', + default: {}, + placeholder: 'Add Address Details', + options: [ + { + displayName: 'Details', + name: 'details', + values: [ + { + displayName: 'Line 1', + name: 'line1', + description: 'Address line 1 (e.g. street, PO Box, or company name)', + type: 'string', + default: '', + }, + { + displayName: 'Line 2', + name: 'line2', + description: 'Address line 2 (e.g. apartment, suite, unit, or building)', + type: 'string', + default: '', + }, + { + displayName: 'City', + name: 'city', + description: 'City, district, suburb, town, or village', + type: 'string', + default: '', + }, + { + displayName: 'State', + name: 'state', + description: 'State, county, province, or region', + type: 'string', + default: '', + }, + { + displayName: 'Country', + name: 'country', + description: 'Two-letter country code (ISO 3166-1 alpha-2)', + type: 'string', + default: '', + }, + { + displayName: 'Postal Code', + name: 'postal_code', + description: 'ZIP or postal code', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Recipient Phone', + name: 'phone', + type: 'string', + default: '', + description: 'Phone number of the person who will receive the shipment', + }, + ], + }, + ], + }, + ], + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/SourceDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/SourceDescription.ts new file mode 100644 index 0000000000..9cd3a27cf5 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/SourceDescription.ts @@ -0,0 +1,246 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const sourceOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'get', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a source', + }, + { + name: 'Delete', + value: 'delete', + description: 'Delete a source', + }, + { + name: 'Get', + value: 'get', + description: 'Get a source', + }, + ], + displayOptions: { + show: { + resource: [ + 'source', + ], + }, + }, + }, +] as INodeProperties[]; + +export const sourceFields = [ + // ---------------------------------- + // source: create + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer to attach the source to', + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Type', + name: 'type', + type: 'options', + required: true, + default: 'wechat', + description: 'Type of source (payment instrument) to create', + options: [ + { + name: 'WeChat', + value: 'wechat', + }, + ], + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Amount', + name: 'amount', + type: 'number', + default: 0, + description: 'Amount in cents to be collected for this charge, e.g. enter 100 for $1.00', + typeOptions: { + minValue: 0, + maxValue: 99999999, + }, + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Currency', + name: 'currency', + type: 'options', + typeOptions: { + loadOptionsMethod: 'getCurrencies', + }, + default: '', + description: 'Three-letter ISO currency code, e.g. USD or EUR. It must be a Stripe-supported currency', + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'create', + ], + }, + }, + }, + { + displayName: 'Additional Fields', + name: 'additionalFields', + type: 'collection', + placeholder: 'Add Field', + default: {}, + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'create', + ], + }, + }, + options: [ + { + displayName: 'Metadata', + name: 'metadata', + type: 'fixedCollection', + placeholder: 'Add Metadata Item', + description: 'Set of key-value pairs to attach to the source to create', + default: {}, + typeOptions: { + multipleValues: true, + }, + options: [ + { + displayName: 'Metadata Properties', + name: 'metadataProperties', + values: [ + { + displayName: 'Key', + name: 'key', + type: 'string', + default: '', + }, + { + displayName: 'Value', + name: 'value', + type: 'string', + default: '', + }, + ], + }, + ], + }, + { + displayName: 'Statement Descriptor', + name: 'statement_descriptor', + type: 'string', + default: '', + description: 'Arbitrary text to display on the customer\'s statement', + }, + ], + }, + + // ---------------------------------- + // source: delete + // ---------------------------------- + { + displayName: 'Customer ID', + name: 'customerId', + type: 'string', + required: true, + default: '', + description: 'ID of the customer whose source to delete', + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'delete', + ], + }, + }, + }, + { + displayName: 'Source ID', + name: 'sourceId', + type: 'string', + required: true, + default: '', + description: 'ID of the source to delete', + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'delete', + ], + }, + }, + }, + + // ---------------------------------- + // source: get + // ---------------------------------- + { + displayName: 'Source ID', + name: 'sourceId', + type: 'string', + required: true, + default: '', + description: 'ID of the source to retrieve', + displayOptions: { + show: { + resource: [ + 'source', + ], + operation: [ + 'get', + ], + }, + }, + }, + +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts b/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts new file mode 100644 index 0000000000..83bbea55d4 --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/TokenDescription.ts @@ -0,0 +1,130 @@ +import { + INodeProperties, +} from 'n8n-workflow'; + +export const tokenOperations = [ + { + displayName: 'Operation', + name: 'operation', + type: 'options', + default: 'create', + description: 'Operation to perform', + options: [ + { + name: 'Create', + value: 'create', + description: 'Create a token', + }, + ], + displayOptions: { + show: { + resource: [ + 'token', + ], + }, + }, + }, +] as INodeProperties[]; + +export const tokenFields = [ + // ---------------------------------- + // token: create + // ---------------------------------- + { + displayName: 'Type', + name: 'type', + type: 'options', + required: true, + default: 'cardToken', + description: 'Type of token to create', + options: [ + { + name: 'Card Token', + value: 'cardToken', + }, + ], + }, + { + displayName: 'Card Number', + name: 'number', + type: 'string', + displayOptions: { + show: { + resource: [ + 'token', + ], + operation: [ + 'create', + ], + type: [ + 'cardToken' + ], + }, + }, + placeholder: '4242424242424242', + default: '', + }, + { + displayName: 'CVC', + name: 'cvc', + type: 'string', + displayOptions: { + show: { + resource: [ + 'token', + ], + operation: [ + 'create', + ], + type: [ + 'cardToken' + ], + }, + }, + default: '', + placeholder: '314', + description: 'Security code printed on the back of the card', + }, + { + displayName: 'Expiration Month', + description: 'Number of the month when the card will expire', + name: 'expirationMonth', + type: 'string', + displayOptions: { + show: { + resource: [ + 'token', + ], + operation: [ + 'create', + ], + type: [ + 'cardToken' + ], + }, + }, + default: '', + placeholder: '10', + }, + { + displayName: 'Expiration Year', + description: 'Year when the card will expire', + name: 'expirationYear', + type: 'string', + displayOptions: { + show: { + resource: [ + 'token', + ], + operation: [ + 'create', + ], + type: [ + 'cardToken' + ], + }, + }, + default: '', + placeholder: '2022', + }, +] as INodeProperties[]; diff --git a/packages/nodes-base/nodes/Stripe/descriptions/index.ts b/packages/nodes-base/nodes/Stripe/descriptions/index.ts new file mode 100644 index 0000000000..cadbadefbd --- /dev/null +++ b/packages/nodes-base/nodes/Stripe/descriptions/index.ts @@ -0,0 +1,7 @@ +export * from './BalanceDescription'; +export * from './CustomerCardDescription'; +export * from './ChargeDescription'; +export * from './CouponDescription'; +export * from './CustomerDescription'; +export * from './SourceDescription'; +export * from './TokenDescription'; diff --git a/packages/nodes-base/nodes/Stripe/helpers.ts b/packages/nodes-base/nodes/Stripe/helpers.ts index 0fb8ebdb97..c4d5e06c65 100644 --- a/packages/nodes-base/nodes/Stripe/helpers.ts +++ b/packages/nodes-base/nodes/Stripe/helpers.ts @@ -2,7 +2,23 @@ import { IExecuteFunctions, IHookFunctions, } from 'n8n-core'; -import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; + +import { + NodeApiError, + NodeOperationError, +} from 'n8n-workflow'; + +import { + flow, + isEmpty, + omit, +} from 'lodash'; + +import { + IDataObject, + ILoadOptionsFunctions, + INodePropertyOptions, +} from 'n8n-workflow'; /** * Make an API request to Stripe @@ -13,7 +29,13 @@ import { NodeApiError, NodeOperationError, } from 'n8n-workflow'; * @param {object} body * @returns {Promise} */ -export async function stripeApiRequest(this: IHookFunctions | IExecuteFunctions, method: string, endpoint: string, body: object, query?: object): Promise { // tslint:disable-line:no-any +export async function stripeApiRequest( + this: IHookFunctions | IExecuteFunctions | ILoadOptionsFunctions, + method: string, + endpoint: string, + body: object, + query?: object, +) { const credentials = this.getCredentials('stripeApi'); if (credentials === undefined) { throw new NodeOperationError(this.getNode(), 'No credentials got returned!'); @@ -30,9 +52,121 @@ export async function stripeApiRequest(this: IHookFunctions | IExecuteFunctions, json: true, }; + if (options.qs && Object.keys(options.qs).length === 0) { + delete options.qs; + } + try { - return await this.helpers.request(options); + return await this.helpers.request!.call(this, options); } catch (error) { throw new NodeApiError(this.getNode(), error); } } + +/** + * Make n8n's charge fields compliant with the Stripe API request object. + */ +export const adjustChargeFields = flow([ + adjustShipping, + adjustMetadata, +]); + +/** + * Make n8n's customer fields compliant with the Stripe API request object. + */ +export const adjustCustomerFields = flow([ + adjustShipping, + adjustAddress, + adjustMetadata, +]); + +/** + * Convert n8n's address object into a Stripe API request shipping object. + */ +function adjustAddress( + addressFields: { address: { details: IDataObject } }, +) { + if (!addressFields.address) return addressFields; + + return { + ...omit(addressFields, ['address']), + address: addressFields.address.details, + }; +} + +/** + * Convert n8n's `fixedCollection` metadata object into a Stripe API request metadata object. + */ +export function adjustMetadata( + fields: { metadata?: { metadataProperties: Array<{ key: string; value: string }> } }, +) { + if (!fields.metadata || isEmpty(fields.metadata)) return fields; + + let adjustedMetadata = {}; + + fields.metadata.metadataProperties.forEach(pair => { + adjustedMetadata = { ...adjustedMetadata, ...pair }; + }); + + return { + ...omit(fields, ['metadata']), + metadata: adjustedMetadata, + }; +} + +/** + * Convert n8n's shipping object into a Stripe API request shipping object. + */ +function adjustShipping( + shippingFields: { shipping?: { shippingProperties: Array<{ address: { details: IDataObject }; name: string }> } }, +) { + const shippingProperties = shippingFields.shipping?.shippingProperties[0]; + + if (!shippingProperties?.address || isEmpty(shippingProperties.address)) return shippingFields; + + return { + ...omit(shippingFields, ['shipping']), + shipping: { + ...omit(shippingProperties, ['address']), + address: shippingProperties.address.details, + }, + }; +} + +/** + * Load a resource so it can be selected by name from a dropdown. + */ +export async function loadResource( + this: ILoadOptionsFunctions, + resource: 'charge' | 'customer' | 'source', +): Promise { + const responseData = await stripeApiRequest.call(this, 'GET', `/${resource}s`, {}, {}); + + return responseData.data.map(({ name, id }: { name: string, id: string }) => ({ + name, + value: id, + })); +} + +/** + * Handles a Stripe listing by returning all items or up to a limit. + */ +export async function handleListing( + this: IExecuteFunctions, + resource: string, + qs: IDataObject = {}, +) { + let responseData; + + responseData = await stripeApiRequest.call(this, 'GET', `/${resource}s`, qs, {}); + responseData = responseData.data; + + const returnAll = this.getNodeParameter('returnAll', 0) as boolean; + + if (!returnAll) { + const limit = this.getNodeParameter('limit', 0) as number; + responseData = responseData.slice(0, limit); + } + + return responseData; +} diff --git a/packages/nodes-base/nodes/Stripe/stripe.svg b/packages/nodes-base/nodes/Stripe/stripe.svg index 063b3994a4..e5419a6d1d 100644 --- a/packages/nodes-base/nodes/Stripe/stripe.svg +++ b/packages/nodes-base/nodes/Stripe/stripe.svg @@ -1 +1,22 @@ - \ No newline at end of file + + + + + + + + + + + + + diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index 3c42c04fd1..4d7363c412 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -530,6 +530,7 @@ "dist/nodes/Strapi/Strapi.node.js", "dist/nodes/Strava/Strava.node.js", "dist/nodes/Strava/StravaTrigger.node.js", + "dist/nodes/Stripe/Stripe.node.js", "dist/nodes/Stripe/StripeTrigger.node.js", "dist/nodes/Switch.node.js", "dist/nodes/Salesmate/Salesmate.node.js",