@chargebee/chargebee-apps-libs 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +24 -0
- package/README.md +97 -0
- package/SECURITY.md +8 -0
- package/config/README.md +83 -0
- package/config/allowed-modules.json +24 -0
- package/dist/config/config-loader.d.ts +24 -0
- package/dist/config/config-loader.js +95 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +21 -0
- package/dist/libs/port-service.d.ts +18 -0
- package/dist/libs/port-service.js +27 -0
- package/dist/logger/cb-public-logger.d.ts +59 -0
- package/dist/logger/cb-public-logger.js +158 -0
- package/dist/sandbox/public-sandbox-wrapper.d.ts +41 -0
- package/dist/sandbox/public-sandbox-wrapper.js +208 -0
- package/package.json +44 -0
- package/templates/serverless-node-starter-app/.env +4 -0
- package/templates/serverless-node-starter-app/README.md +290 -0
- package/templates/serverless-node-starter-app/handler/handler.js +34 -0
- package/templates/serverless-node-starter-app/jsconfig.json +35 -0
- package/templates/serverless-node-starter-app/manifest.json +15 -0
- package/templates/serverless-node-starter-app/test_data/customer_created.json +51 -0
- package/templates/serverless-node-starter-app/test_data/invoice_generated.json +112 -0
- package/templates/serverless-node-starter-app/test_data/subscription_created.json +173 -0
- package/templates/serverless-node-starter-app/types/types.d.ts +45 -0
- package/templates/serverless-node-starter-app-with-iparams/.env +4 -0
- package/templates/serverless-node-starter-app-with-iparams/README.md +717 -0
- package/templates/serverless-node-starter-app-with-iparams/handler/handler.js +39 -0
- package/templates/serverless-node-starter-app-with-iparams/iparams.json +41 -0
- package/templates/serverless-node-starter-app-with-iparams/iparams.local.json +9 -0
- package/templates/serverless-node-starter-app-with-iparams/jsconfig.json +35 -0
- package/templates/serverless-node-starter-app-with-iparams/manifest.json +15 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/customer_created.json +51 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/invoice_generated.json +112 -0
- package/templates/serverless-node-starter-app-with-iparams/test_data/subscription_created.json +173 -0
- package/templates/serverless-node-starter-app-with-iparams/types/types.d.ts +63 -0
- package/ui/README.md +118 -0
- package/ui/web/assets/css/main.css +1121 -0
- package/ui/web/assets/images/Chargebee-logo.png +0 -0
- package/ui/web/assets/js/main.js +61 -0
- package/ui/web/assets/js/modules/api.js +97 -0
- package/ui/web/assets/js/modules/constants.js +33 -0
- package/ui/web/assets/js/modules/editors.js +41 -0
- package/ui/web/assets/js/modules/events.js +195 -0
- package/ui/web/assets/js/modules/form-utils.js +70 -0
- package/ui/web/assets/js/modules/iparams-inputs.js +495 -0
- package/ui/web/assets/js/modules/iparams.js +82 -0
- package/ui/web/assets/js/modules/ui-utils.js +87 -0
- package/ui/web/index.html +164 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { AllowedModule, CBFileSystem, DependencyManager, ExecutionResult, HandlerPayload, ICBLogger, ISandboxWrapper, ManifestValidator, PackageValidator, SandboxEnvironment } from '@chargebee/chargebee-apps-shared';
|
|
2
|
+
/**
|
|
3
|
+
* PublicSandboxWrapper provides a secure environment for executing user-provided NodeJS code
|
|
4
|
+
* Public implementation for local development environment
|
|
5
|
+
*/
|
|
6
|
+
export declare class PublicSandboxWrapper implements ISandboxWrapper {
|
|
7
|
+
private allowedModules;
|
|
8
|
+
private manifestValidator;
|
|
9
|
+
private packageValidator;
|
|
10
|
+
private fileSystem;
|
|
11
|
+
private dependencyManager;
|
|
12
|
+
constructor(allowedModules: AllowedModule[], fileSystem: CBFileSystem, packageValidator: PackageValidator, dependencyManager: DependencyManager, manifestValidator: ManifestValidator);
|
|
13
|
+
/**
|
|
14
|
+
* Creates a safe require function that uses isolated node_modules
|
|
15
|
+
* @param {string} projectPath - Path to the user project
|
|
16
|
+
* @returns {Function} Safe require function
|
|
17
|
+
*/
|
|
18
|
+
createSafeRequire(projectPath: string): (moduleName: string) => any;
|
|
19
|
+
/**
|
|
20
|
+
* Loads environment variables from .env file in the project directory
|
|
21
|
+
* @param {string} projectPath - Path to the user project
|
|
22
|
+
* @returns {Record<string, string>} Environment variables object
|
|
23
|
+
*/
|
|
24
|
+
private loadEnvironmentVariables;
|
|
25
|
+
/**
|
|
26
|
+
* Creates a secure sandbox environment for code execution
|
|
27
|
+
* @param {string} projectPath - Path to the user project
|
|
28
|
+
* @param {HandlerPayload} payload - Handler payload (event + iparams) to expose to user code
|
|
29
|
+
* @param {ICBLogger} sessionLogger - Logger instance for this server session
|
|
30
|
+
* @returns {SandboxEnvironment} The sandbox environment object
|
|
31
|
+
*/
|
|
32
|
+
createSandbox(projectPath: string, payload: HandlerPayload, sessionLogger: ICBLogger): SandboxEnvironment;
|
|
33
|
+
/**
|
|
34
|
+
* Executes user-provided code in a secure sandbox environment.
|
|
35
|
+
* @param userCodePath - Path to the directory containing user code
|
|
36
|
+
* @param payload - Handler payload (event + iparams) to pass to the user handler
|
|
37
|
+
* @param sessionLogger - The logger instance for this server session
|
|
38
|
+
* @returns {Promise<ExecutionResult>} Execution result with success status and data/error
|
|
39
|
+
*/
|
|
40
|
+
execute(userCodePath: string, payload: HandlerPayload, sessionLogger: ICBLogger): Promise<ExecutionResult>;
|
|
41
|
+
}
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.PublicSandboxWrapper = void 0;
|
|
40
|
+
const chargebee_apps_shared_1 = require("@chargebee/chargebee-apps-shared");
|
|
41
|
+
const path_1 = __importDefault(require("path"));
|
|
42
|
+
const vm_1 = __importDefault(require("vm"));
|
|
43
|
+
const dotenv = __importStar(require("dotenv"));
|
|
44
|
+
const cb_public_logger_1 = require("../logger/cb-public-logger");
|
|
45
|
+
/**
|
|
46
|
+
* PublicSandboxWrapper provides a secure environment for executing user-provided NodeJS code
|
|
47
|
+
* Public implementation for local development environment
|
|
48
|
+
*/
|
|
49
|
+
class PublicSandboxWrapper {
|
|
50
|
+
constructor(allowedModules, fileSystem, packageValidator, dependencyManager, manifestValidator) {
|
|
51
|
+
this.fileSystem = fileSystem;
|
|
52
|
+
this.packageValidator = packageValidator;
|
|
53
|
+
this.dependencyManager = dependencyManager;
|
|
54
|
+
this.allowedModules = allowedModules;
|
|
55
|
+
this.manifestValidator = manifestValidator;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Creates a safe require function that uses isolated node_modules
|
|
59
|
+
* @param {string} projectPath - Path to the user project
|
|
60
|
+
* @returns {Function} Safe require function
|
|
61
|
+
*/
|
|
62
|
+
createSafeRequire(projectPath) {
|
|
63
|
+
return this.dependencyManager.createIsolatedRequire(projectPath, this.allowedModules);
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Loads environment variables from .env file in the project directory
|
|
67
|
+
* @param {string} projectPath - Path to the user project
|
|
68
|
+
* @returns {Record<string, string>} Environment variables object
|
|
69
|
+
*/
|
|
70
|
+
loadEnvironmentVariables(projectPath) {
|
|
71
|
+
const envPath = path_1.default.join(projectPath, '.env');
|
|
72
|
+
const envVars = {};
|
|
73
|
+
try {
|
|
74
|
+
// Load .env file if it exists
|
|
75
|
+
const result = dotenv.config({ path: envPath });
|
|
76
|
+
if (result.parsed) {
|
|
77
|
+
// Only include MKPLC_CB_READ_ONLY_API and MKPLC_CB_READ_WRITE_API keys
|
|
78
|
+
if (result.parsed['MKPLC_CB_READ_ONLY_API']) {
|
|
79
|
+
envVars['MKPLC_CB_READ_ONLY_API'] = result.parsed['MKPLC_CB_READ_ONLY_API'];
|
|
80
|
+
}
|
|
81
|
+
if (result.parsed['MKPLC_CB_READ_WRITE_API']) {
|
|
82
|
+
envVars['MKPLC_CB_READ_WRITE_API'] = result.parsed['MKPLC_CB_READ_WRITE_API'];
|
|
83
|
+
}
|
|
84
|
+
if (result.parsed['MKPLC_SITE_DOMAIN']) {
|
|
85
|
+
envVars['MKPLC_SITE_DOMAIN'] = result.parsed['MKPLC_SITE_DOMAIN'];
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
envVars['MKPLC_SITE_DOMAIN'] = '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
chargebee_apps_shared_1.__logger.debug(`Failed to load .env file from ${envPath}: ${error}`);
|
|
94
|
+
}
|
|
95
|
+
return envVars;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Creates a secure sandbox environment for code execution
|
|
99
|
+
* @param {string} projectPath - Path to the user project
|
|
100
|
+
* @param {HandlerPayload} payload - Handler payload (event + iparams) to expose to user code
|
|
101
|
+
* @param {ICBLogger} sessionLogger - Logger instance for this server session
|
|
102
|
+
* @returns {SandboxEnvironment} The sandbox environment object
|
|
103
|
+
*/
|
|
104
|
+
createSandbox(projectPath, payload, sessionLogger) {
|
|
105
|
+
const moduleObj = { exports: {} };
|
|
106
|
+
const exportsObj = moduleObj.exports;
|
|
107
|
+
// Load environment variables from .env file
|
|
108
|
+
const envVars = this.loadEnvironmentVariables(projectPath);
|
|
109
|
+
const hasIparams = Object.prototype.hasOwnProperty.call(payload, 'iparams');
|
|
110
|
+
const safePayload = Object.freeze(hasIparams
|
|
111
|
+
? { event: payload.event, iparams: payload.iparams ?? {} }
|
|
112
|
+
: { event: payload.event });
|
|
113
|
+
return {
|
|
114
|
+
// Controlled logging
|
|
115
|
+
console: (0, cb_public_logger_1.createSafeConsole)(sessionLogger),
|
|
116
|
+
// Read-only payload (event + iparams) for handler(payload)
|
|
117
|
+
payload: safePayload,
|
|
118
|
+
// Safe async primitives
|
|
119
|
+
setTimeout,
|
|
120
|
+
clearTimeout,
|
|
121
|
+
setImmediate,
|
|
122
|
+
clearImmediate,
|
|
123
|
+
// CommonJS emulation
|
|
124
|
+
module: moduleObj,
|
|
125
|
+
exports: exportsObj,
|
|
126
|
+
// Controlled module access
|
|
127
|
+
require: this.createSafeRequire(projectPath),
|
|
128
|
+
// Limited process access
|
|
129
|
+
process: {
|
|
130
|
+
cwd: () => process.cwd(),
|
|
131
|
+
env: envVars,
|
|
132
|
+
nextTick: process.nextTick
|
|
133
|
+
},
|
|
134
|
+
// Prevent escape hatches
|
|
135
|
+
global: undefined,
|
|
136
|
+
Buffer: undefined,
|
|
137
|
+
__dirname: undefined,
|
|
138
|
+
__filename: undefined
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Executes user-provided code in a secure sandbox environment.
|
|
143
|
+
* @param userCodePath - Path to the directory containing user code
|
|
144
|
+
* @param payload - Handler payload (event + iparams) to pass to the user handler
|
|
145
|
+
* @param sessionLogger - The logger instance for this server session
|
|
146
|
+
* @returns {Promise<ExecutionResult>} Execution result with success status and data/error
|
|
147
|
+
*/
|
|
148
|
+
async execute(userCodePath, payload, sessionLogger) {
|
|
149
|
+
try {
|
|
150
|
+
chargebee_apps_shared_1.__logger.debug(`Executing user code from: ${userCodePath}`);
|
|
151
|
+
// Check if required files exist.
|
|
152
|
+
this.packageValidator.validateHandlerExists(userCodePath);
|
|
153
|
+
this.packageValidator.validateManifestExists(userCodePath);
|
|
154
|
+
// Validate manifest file.
|
|
155
|
+
const manifestPath = path_1.default.join(userCodePath, 'manifest.json');
|
|
156
|
+
const manifest = this.manifestValidator.validateAndGetManifestFile(manifestPath);
|
|
157
|
+
// Load user code
|
|
158
|
+
const eventType = payload.event.event_type;
|
|
159
|
+
const eventConfig = manifest.events[eventType];
|
|
160
|
+
if (!eventConfig) {
|
|
161
|
+
throw new Error(`No handler configured for event type: ${eventType}`);
|
|
162
|
+
}
|
|
163
|
+
const handler = eventConfig.handler;
|
|
164
|
+
const handlerPath = path_1.default.join(userCodePath, 'handler', 'handler.js');
|
|
165
|
+
const userCode = this.fileSystem.readFileSync(handlerPath, 'utf8');
|
|
166
|
+
// Create execution context
|
|
167
|
+
const contextData = {
|
|
168
|
+
eventId: payload.event.id || 'unknown',
|
|
169
|
+
eventType: payload.event.event_type || 'unknown',
|
|
170
|
+
};
|
|
171
|
+
return await chargebee_apps_shared_1.sandboxContext.run(contextData, async () => {
|
|
172
|
+
const sandbox = this.createSandbox(userCodePath, payload, sessionLogger);
|
|
173
|
+
const vmContext = vm_1.default.createContext(sandbox);
|
|
174
|
+
// Execute user code and run handler with single payload argument
|
|
175
|
+
const executeCode = `
|
|
176
|
+
(async function() {
|
|
177
|
+
"use strict";
|
|
178
|
+
try {
|
|
179
|
+
${userCode}
|
|
180
|
+
const handler = module.exports["${handler}"];
|
|
181
|
+
if (typeof handler !== 'function') {
|
|
182
|
+
throw new Error('Handler function "${handler}" not found in handler.js');
|
|
183
|
+
}
|
|
184
|
+
return await handler(payload);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
console.error(error.stack ? error.stack : error);
|
|
187
|
+
throw error;
|
|
188
|
+
}
|
|
189
|
+
})()
|
|
190
|
+
`;
|
|
191
|
+
await vm_1.default.runInContext(executeCode, vmContext, {
|
|
192
|
+
timeout: 15000, // 15 second timeout
|
|
193
|
+
displayErrors: true
|
|
194
|
+
});
|
|
195
|
+
return { success: true, statusCode: 200 };
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
catch (error) {
|
|
199
|
+
const err = error;
|
|
200
|
+
return {
|
|
201
|
+
success: false,
|
|
202
|
+
error: `${err.message}`,
|
|
203
|
+
stack: err.stack || undefined
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
exports.PublicSandboxWrapper = PublicSandboxWrapper;
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@chargebee/chargebee-apps-libs",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Public library implementations for Chargebee Apps CLI",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"templates",
|
|
10
|
+
"config",
|
|
11
|
+
"ui",
|
|
12
|
+
"LICENSE",
|
|
13
|
+
"SECURITY.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"clean": "rm -rf dist && rm -rf coverage",
|
|
18
|
+
"test": "jest",
|
|
19
|
+
"test:watch": "jest --watch",
|
|
20
|
+
"test:coverage": "jest --coverage",
|
|
21
|
+
"test:unit": "jest --testPathPattern=tests/unit",
|
|
22
|
+
"minify": "esbuild 'dist/**/*.js' --minify --platform=node --outdir=dist --allow-overwrite"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"@chargebee/chargebee-apps-shared": "0.0.1",
|
|
26
|
+
"dotenv": "^16.3.1"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/jest": "^29.5.0",
|
|
30
|
+
"@types/node": "^24.3.0",
|
|
31
|
+
"@types/dotenv": "^8.2.0",
|
|
32
|
+
"esbuild": "^0.28.1",
|
|
33
|
+
"jest": "^29.5.0",
|
|
34
|
+
"ts-jest": "^29.1.0",
|
|
35
|
+
"typescript": "^5.0.0"
|
|
36
|
+
},
|
|
37
|
+
"publishConfig": {
|
|
38
|
+
"access": "public"
|
|
39
|
+
},
|
|
40
|
+
"license": "MIT",
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=22.0.0"
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
# Serverless Node.js App Template
|
|
2
|
+
|
|
3
|
+
This is a serverless Node.js application template that demonstrates how to build and test serverless functions with event-driven architecture. This template provides a foundation for creating scalable serverless applications with built-in testing capabilities.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Event-Driven Architecture**: Configure multiple event handlers in `manifest.json`
|
|
8
|
+
- **Handler Functions**: Implement your business logic in `handler.js`
|
|
9
|
+
- **Test Data**: Sample event data for local development and testing
|
|
10
|
+
- **Local Testing UI**: Interactive web interface to test your handlers
|
|
11
|
+
- **Logging**: Built-in logging system for debugging and monitoring
|
|
12
|
+
|
|
13
|
+
## How It Works
|
|
14
|
+
|
|
15
|
+
1. **Event Configuration**: The `manifest.json` file maps event types to their corresponding handler functions
|
|
16
|
+
2. **Handler Implementation**: Each event type has its handler function in `handler.js`
|
|
17
|
+
3. **Test Data**: Sample JSON files in `test_data/` folder match your event names (e.g., `onCustomerCreate.json`)
|
|
18
|
+
4. **Local Testing**: Use the CLI tool to start a local testing server with a web UI
|
|
19
|
+
5. **Interactive Testing**: Select event types from the dropdown and click "Test Data" to execute handlers
|
|
20
|
+
6. **Real-time Logs**: View execution logs directly in your terminal
|
|
21
|
+
|
|
22
|
+
## Quick Start
|
|
23
|
+
|
|
24
|
+
1. **Create your app**: `chargebee-apps create <app_directory>`
|
|
25
|
+
2. **Navigate to app**: `cd <app_directory>`
|
|
26
|
+
3. **Configure API keys** (optional): Edit `.env` file and replace placeholder values with your Chargebee API keys
|
|
27
|
+
4. **Start local server**: `chargebee-apps run <app_directory>`
|
|
28
|
+
5. **Open browser**: Visit `http://localhost:15000`
|
|
29
|
+
6. **Test handlers**: Select an event type and click "Test Data"
|
|
30
|
+
7. **View logs**: Check your terminal for execution output
|
|
31
|
+
8. **Package your app**: `chargebee-apps package <app_directory>`
|
|
32
|
+
|
|
33
|
+
## Project Structure
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
├── handler/handler.js # Main serverless function handler
|
|
37
|
+
├── test_data/ # Test data directory
|
|
38
|
+
├── types/types.d.ts # Recognised types that can be used as JSDoc inside handler function
|
|
39
|
+
├── .env # Environment variables (API keys)
|
|
40
|
+
├── jsconfig.json # JS config for dev
|
|
41
|
+
├── manifest.json # Project configuration
|
|
42
|
+
└── README.md # Read me file
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Configuration
|
|
46
|
+
|
|
47
|
+
### Manifest.json
|
|
48
|
+
|
|
49
|
+
The `manifest.json` file is the core configuration file for your serverless application. It defines:
|
|
50
|
+
|
|
51
|
+
1. **Event Handlers**: Maps Chargebee webhook events to your handler functions
|
|
52
|
+
2. **Application Metadata**: Basic information about your application
|
|
53
|
+
3. **Dependencies**: Custom npm packages your application requires
|
|
54
|
+
|
|
55
|
+
#### Basic Structure
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"name": "my-app",
|
|
60
|
+
"version": "1.0.0",
|
|
61
|
+
"description": "A custom app",
|
|
62
|
+
"events": {
|
|
63
|
+
"customer_created": {
|
|
64
|
+
"handler": "customerCreateHandler"
|
|
65
|
+
},
|
|
66
|
+
"subscription_created": {
|
|
67
|
+
"handler": "subscriptionCreatedHandler"
|
|
68
|
+
},
|
|
69
|
+
"invoice_generated": {
|
|
70
|
+
"handler": "invoiceGeneratedHandler"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"dependencies": {
|
|
74
|
+
"lodash": "^4.17.21",
|
|
75
|
+
"axios": "^1.6.0"
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
#### Event Handler Configuration
|
|
81
|
+
|
|
82
|
+
Each event in the `events` object maps a Chargebee webhook event to a handler function:
|
|
83
|
+
|
|
84
|
+
```json
|
|
85
|
+
{
|
|
86
|
+
"events": {
|
|
87
|
+
"customer_created": {
|
|
88
|
+
"handler": "customerCreateHandler"
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The handler function must be exported from `handler/handler.js`. Each handler receives a single `payload` argument; use `payload.event` to access the event data:
|
|
95
|
+
|
|
96
|
+
```javascript
|
|
97
|
+
// handler/handler.js
|
|
98
|
+
module.exports = {
|
|
99
|
+
customerCreateHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
100
|
+
// Your business logic here
|
|
101
|
+
console.log('Customer created:', payload.event.content.customer.email);
|
|
102
|
+
|
|
103
|
+
// Example: Send welcome email
|
|
104
|
+
const customerEmail = payload.event.content.customer.email;
|
|
105
|
+
const customerName = payload.event.content.customer.first_name;
|
|
106
|
+
|
|
107
|
+
// You can use custom dependencies here
|
|
108
|
+
const moment = require('moment');
|
|
109
|
+
const welcomeMessage = `Welcome ${customerName}! You joined on ${moment().format('MMMM Do YYYY')}`;
|
|
110
|
+
|
|
111
|
+
console.log(welcomeMessage);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
#### Custom Dependencies
|
|
117
|
+
|
|
118
|
+
You can add custom npm packages to your application by including them in the `dependencies` section:
|
|
119
|
+
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"dependencies": {
|
|
123
|
+
"lodash": "^4.17.21",
|
|
124
|
+
"axios": "^1.6.0",
|
|
125
|
+
"uuid": "^9.0.0"
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
Then use them in your handler functions:
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
// handler/handler.js
|
|
134
|
+
const _ = require('lodash');
|
|
135
|
+
const axios = require('axios');
|
|
136
|
+
const moment = require('moment');
|
|
137
|
+
const { v4: uuidv4 } = require('uuid');
|
|
138
|
+
|
|
139
|
+
module.exports = {
|
|
140
|
+
customerCreateHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
141
|
+
// Use lodash for data manipulation
|
|
142
|
+
const customerData = _.pick(payload.event.content.customer, ['email', 'first_name', 'last_name']);
|
|
143
|
+
|
|
144
|
+
// Generate unique ID
|
|
145
|
+
const customerId = uuidv4();
|
|
146
|
+
|
|
147
|
+
// Make HTTP requests
|
|
148
|
+
axios.post('https://api.example.com/customers', {
|
|
149
|
+
id: customerId,
|
|
150
|
+
...customerData,
|
|
151
|
+
created_at: moment().toISOString()
|
|
152
|
+
}).then(response => {
|
|
153
|
+
console.log('Customer synced to external system:', response.data);
|
|
154
|
+
}).catch(error => {
|
|
155
|
+
console.error('Failed to sync customer:', error.message);
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
subscriptionCreatedHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
160
|
+
// Use moment for date formatting
|
|
161
|
+
const subscriptionDate = moment(payload.event.content.subscription.created_at * 1000);
|
|
162
|
+
const formattedDate = subscriptionDate.format('MMMM Do YYYY, h:mm:ss a');
|
|
163
|
+
|
|
164
|
+
console.log(`New subscription created on ${formattedDate}`);
|
|
165
|
+
|
|
166
|
+
// Use lodash for data validation
|
|
167
|
+
const requiredFields = ['id', 'customer_id', 'plan_id'];
|
|
168
|
+
const missingFields = _.difference(requiredFields, Object.keys(payload.event.content.subscription));
|
|
169
|
+
|
|
170
|
+
if (missingFields.length > 0) {
|
|
171
|
+
console.warn('Missing subscription fields:', missingFields);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
#### Supported Event Types
|
|
178
|
+
Chargebee Apps supports all the event types that are provided in the Chargebee's [documentation](https://apidocs.chargebee.com/docs/api/events#event_types).
|
|
179
|
+
|
|
180
|
+
### Environment Variables
|
|
181
|
+
|
|
182
|
+
The CLI exposes **only these three** variables via `process.env`; no other environment variables are available to your handlers:
|
|
183
|
+
|
|
184
|
+
| Variable | Description |
|
|
185
|
+
|----------|-------------|
|
|
186
|
+
| `MKPLC_CB_READ_ONLY_API` | Chargebee read-only API key |
|
|
187
|
+
| `MKPLC_CB_READ_WRITE_API` | Chargebee read-write API key |
|
|
188
|
+
| `MKPLC_SITE_DOMAIN` | Chargebee site domain |
|
|
189
|
+
|
|
190
|
+
- **Production:** The system provides these values at runtime. You do not configure them in production.
|
|
191
|
+
- **Local development:** You must provide these values in the `.env` file so your handlers can run locally.
|
|
192
|
+
|
|
193
|
+
#### Setting up .env for local development
|
|
194
|
+
|
|
195
|
+
1. **Open the `.env` file** in your project root
|
|
196
|
+
2. **Replace the placeholder values** with your actual Chargebee API keys:
|
|
197
|
+
|
|
198
|
+
```env
|
|
199
|
+
# Replace these with your actual Chargebee API keys
|
|
200
|
+
MKPLC_CB_READ_ONLY_API=your_read_only_api_key_here
|
|
201
|
+
MKPLC_CB_READ_WRITE_API=your_read_write_api_key_here
|
|
202
|
+
MKPLC_SITE_DOMAIN=yousite123
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
#### Using these variables in your handlers
|
|
206
|
+
|
|
207
|
+
Access them through the `process.env` object in your handler functions:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
// handler/handler.js
|
|
211
|
+
module.exports = {
|
|
212
|
+
customerCreateHandler: async function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
213
|
+
// Access your API keys
|
|
214
|
+
const readOnlyApiKey = process.env.MKPLC_CB_READ_ONLY_API;
|
|
215
|
+
const readWriteApiKey = process.env.MKPLC_CB_READ_WRITE_API;
|
|
216
|
+
|
|
217
|
+
if (!readOnlyApiKey || !readWriteApiKey) {
|
|
218
|
+
console.error('API keys not configured. Please check your .env file.');
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Use the API keys for Chargebee API calls
|
|
223
|
+
console.log('Using API keys for Chargebee integration');
|
|
224
|
+
|
|
225
|
+
const Chargebee = require('chargebee');
|
|
226
|
+
const chargebee = new Chargebee({
|
|
227
|
+
site: process.env.MKPLC_SITE_DOMAIN,
|
|
228
|
+
apiKey: process.env.MKPLC_CB_READ_ONLY_API
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
const result = await chargebee.customer.retrieve(payload.event.content.customer.id);
|
|
232
|
+
console.log(result.customer);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
#### Security Notes
|
|
238
|
+
|
|
239
|
+
- **Never commit real API keys** to version control
|
|
240
|
+
- The `.env` file in this template contains placeholder values
|
|
241
|
+
- Replace the placeholder values with your actual API keys before testing
|
|
242
|
+
- Keep your API keys secure and don't share them publicly
|
|
243
|
+
|
|
244
|
+
## Local Testing
|
|
245
|
+
|
|
246
|
+
Use the CLI tool to test your functions locally with an interactive web interface:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
# Start the testing server on a specific port
|
|
250
|
+
chargebee-apps run <app_directory> --port <port>
|
|
251
|
+
# Example:
|
|
252
|
+
chargebee-apps run ./sample_app_v0 --port 16000
|
|
253
|
+
|
|
254
|
+
# Open http://localhost:16000 in your browser
|
|
255
|
+
# Select event types from the dropdown and click "Test Data" to execute handlers
|
|
256
|
+
# View real-time logs in your terminal
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
## Testing
|
|
260
|
+
|
|
261
|
+
Test data files are located in the `test_data/` directory. Each file is named after its corresponding event type (e.g., `customer_created.json`, `customer_updated.json`). These JSON files contain sample event data that you can use to test your handlers during local development.
|
|
262
|
+
|
|
263
|
+
The test data structure should match the expected input format for your handler functions. You can modify these files to test different scenarios or add new test data files for additional event types.
|
|
264
|
+
|
|
265
|
+
## Packaging
|
|
266
|
+
|
|
267
|
+
The package command bundles your serverless application into a zip file for deployment.
|
|
268
|
+
|
|
269
|
+
### Basic Usage
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
# Package with default name (app directory name)
|
|
273
|
+
chargebee-apps package <app_directory>
|
|
274
|
+
|
|
275
|
+
# Package with custom name
|
|
276
|
+
chargebee-apps package <app_directory> --name my-app-v1.0.0
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Examples
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
# Package current directory
|
|
283
|
+
chargebee-apps package .
|
|
284
|
+
|
|
285
|
+
# Package specific directory
|
|
286
|
+
chargebee-apps package ./my-app
|
|
287
|
+
|
|
288
|
+
# Package with versioned name
|
|
289
|
+
chargebee-apps package ./my-app --name my-app-v1.0.0
|
|
290
|
+
```
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Serverless function handlers
|
|
3
|
+
* Each handler receives a single payload argument with payload.event
|
|
4
|
+
*/
|
|
5
|
+
module.exports = {
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Handles customer_created events
|
|
9
|
+
* @param {import('../types/types.d.ts').HandlerPayload} payload
|
|
10
|
+
*/
|
|
11
|
+
customerCreateHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
12
|
+
console.log('Processing customer_created event');
|
|
13
|
+
console.log('Customer created for email: ', payload.event.content.customer.email);
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Handles subscription_created events
|
|
18
|
+
* @param {import('../types/types.d.ts').HandlerPayload} payload
|
|
19
|
+
*/
|
|
20
|
+
subscriptionCreatedHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
21
|
+
console.log('Processing subscription_created event');
|
|
22
|
+
console.log('Subscription created for email: ', payload.event.content.customer.email);
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Handles invoice_generated events
|
|
27
|
+
* @param {import('../types/types.d.ts').HandlerPayload} payload
|
|
28
|
+
*/
|
|
29
|
+
invoiceGeneratedHandler: function(/** @type {import('../types/types.d.ts').HandlerPayload} */payload) {
|
|
30
|
+
console.log('Processing invoice_generated event');
|
|
31
|
+
console.log('Invoice status: ', payload.event.content.invoice.status);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"module": "commonjs",
|
|
5
|
+
"allowSyntheticDefaultImports": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"allowJs": true,
|
|
8
|
+
"checkJs": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
10
|
+
"strict": false,
|
|
11
|
+
"noImplicitAny": false,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noImplicitThis": false,
|
|
14
|
+
"noUnusedLocals": true,
|
|
15
|
+
"noUnusedParameters": false,
|
|
16
|
+
"baseUrl": ".",
|
|
17
|
+
"paths": {
|
|
18
|
+
"@/*": ["./*"],
|
|
19
|
+
"@handler/*": ["./handler/*"],
|
|
20
|
+
"@types/*": ["./types/*"],
|
|
21
|
+
"@test_data/*": ["./test_data/*"]
|
|
22
|
+
},
|
|
23
|
+
"typeRoots": ["./types", "./node_modules/@types"]
|
|
24
|
+
},
|
|
25
|
+
"include": [
|
|
26
|
+
"**/*.js",
|
|
27
|
+
"**/*.d.ts",
|
|
28
|
+
"**/*.json"
|
|
29
|
+
],
|
|
30
|
+
"exclude": [
|
|
31
|
+
"node_modules",
|
|
32
|
+
"dist",
|
|
33
|
+
"*.min.js"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "<your-app-name>",
|
|
3
|
+
"description": "<your-app-description>",
|
|
4
|
+
"events": {
|
|
5
|
+
"customer_created": {
|
|
6
|
+
"handler": "customerCreateHandler"
|
|
7
|
+
},
|
|
8
|
+
"subscription_created": {
|
|
9
|
+
"handler": "subscriptionCreatedHandler"
|
|
10
|
+
},
|
|
11
|
+
"invoice_generated": {
|
|
12
|
+
"handler": "invoiceGeneratedHandler"
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
}
|