Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Publish v3 to NPM (latest)
name: Publish v3 to NPM (beta)

on:
push:
Expand Down Expand Up @@ -30,7 +30,7 @@ jobs:
*) echo "ERROR: package.json version must be 3.x.x for v3 tags" && exit 1 ;;
esac

- name: Publish to NPM with dist-tag "latest"
run: npm run prepack && npm publish --tag latest --//registry.npmjs.org/:_authToken="$NPM_TOKEN"
- name: Publish to NPM with dist-tag "beta"
run: npm run prepack && npm publish --tag beta --//registry.npmjs.org/:_authToken="$NPM_TOKEN"
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
### v3.25.0-beta.1 (2026-05-14)
* * *

### Experimental
- **Zod-backed request validation (beta)** — Enable `enableValidation` on the client to validate outgoing request payloads against generated Zod schemas before each API call; invalid payloads raise `ChargebeeZodValidationError` with the original `ZodError` for inspection.
- **Runtime dependency** — Added [`zod`](https://www.npmjs.com/package/zod) (v4) as a dependency to support the above.


### v3.24.0 (2026-05-04)
* * *
### New Resources:
Expand Down
39 changes: 39 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,45 @@ try {
}
```

### Request parameter validation (Zod)

When `enableValidation` is set to `true`, the SDK validates parameters for **every** API request against Zod schemas **before** the HTTP call is made. If you omit the params object on a call, it is validated as `{}`. This is **off by default**. Schemas are included for API actions that support them; actions without a bundled schema behave as usual.

```typescript
import Chargebee, { ChargebeeZodValidationError } from 'chargebee';

const chargebee = new Chargebee({
site: '{{site}}',
apiKey: '{{api-key}}',
enableValidation: true,
});

try {
await chargebee.customer.create({
id: 'a'.repeat(100),
auto_collection: 'invalid',
});
} catch (err) {
if (err instanceof ChargebeeZodValidationError) {
console.error(err.message);
console.error(err.actionName);
console.error(err.zodError.issues);
} else {
throw err;
}
}
```

Invalid parameters produce a `ChargebeeZodValidationError`. The error message lists every problem (field path and message). You can also inspect `actionName` (the API action, for example `create`) and `zodError` (Zod’s `ZodError`, including `issues`) for structured handling.

**Example message:**

```text
ChargebeeZodValidationError: [Chargebee] Validation failed for 'create': id: Too big: expected string to have <=50 characters; auto_collection: Invalid option: expected one of "on"|"off"
```

The same `ChargebeeZodValidationError` shape applies to any action with a schema when parameters are invalid (for example bad filters or limits on `list`).

### Using filters in the List API

For pagination, `offset` is the parameter that is being used. The value used for this parameter must be the value returned for `next_offset` parameter in the previous API call.
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.24.0
3.25.0-beta.1
16 changes: 14 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "chargebee",
"version": "3.24.0",
"version": "3.25.0-beta.1",
"description": "A library for integrating with Chargebee.",
"scripts": {
"prepack": "npm install && npm run build",
Expand Down Expand Up @@ -75,5 +75,8 @@
"semi": true,
"singleQuote": true,
"parser": "typescript"
},
"dependencies": {
"zod": "^4.3.6"
}
}
46 changes: 46 additions & 0 deletions src/RequestWrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ import {
} from './types.js';
import { handleResponse } from './coreCommon.js';
import { Buffer } from 'node:buffer';
import type { ZodObject, ZodRawShape } from 'zod';
import { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';
import { getSchema } from './validationLoader.js';

export class RequestWrapper {
private readonly args: IArguments;
Expand All @@ -42,6 +45,22 @@ export class RequestWrapper {
return idParam;
}

/**
* Validate parameters against the action's Zod schema when enableValidation is true.
* Query params are validated as `params ?? {}`; body params are validated when `params` is non-null.
* Throws a descriptive error listing every validation violation.
*/
private static _validateParams(
params: JSONValue,
schema: ZodObject<ZodRawShape>,
actionName: string,
): void {
const result = schema.safeParse(params);
if (!result.success) {
throw new ChargebeeZodValidationError(actionName, result.error);
}
}

private static parseRetryAfter(retryAfter?: string): number | null {
if (!retryAfter) return null;
const seconds = parseInt(retryAfter, 10);
Expand Down Expand Up @@ -71,6 +90,33 @@ export class RequestWrapper {
: this.args[0];
let headers = this.apiCall.hasIdInUrl ? this.args[2] : this.args[1];

// Lazy-load Zod schema when enableValidation is true
if (
env.enableValidation &&
this.apiCall.resourceKey &&
this.apiCall.actionName
) {
const schema = await getSchema(
this.apiCall.resourceKey,
this.apiCall.actionName,
);
if (schema) {
if (this.apiCall.httpMethod === 'GET') {
RequestWrapper._validateParams(
params ?? {},
schema,
this.apiCall.methodName,
);
} else if (params != null) {
RequestWrapper._validateParams(
params,
schema,
this.apiCall.methodName,
);
}
}
}

Object.assign(this.httpHeaders, headers);

if (
Expand Down
4 changes: 4 additions & 0 deletions src/chargebee.cjs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
WebhookPayloadParseError,
} from './resources/webhook/handler.js';
import { basicAuthValidator } from './resources/webhook/auth.js';
import { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';

const httpClient = new FetchHttpClient();
const Chargebee = CreateChargebee(httpClient);
Expand All @@ -27,6 +28,9 @@ module.exports.WebhookAuthenticationError = WebhookAuthenticationError;
module.exports.WebhookPayloadValidationError = WebhookPayloadValidationError;
module.exports.WebhookPayloadParseError = WebhookPayloadParseError;

// Export validation error class
module.exports.ChargebeeZodValidationError = ChargebeeZodValidationError;

// Export webhook types
export type {
WebhookEvent,
Expand Down
2 changes: 2 additions & 0 deletions src/chargebee.cjs.worker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CreateChargebee } from './createChargebee.js';
import { FetchHttpClient } from './net/FetchClient.js';
import { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';

const httpClient = new FetchHttpClient();
const Chargebee = CreateChargebee(httpClient);
Expand Down Expand Up @@ -29,3 +30,4 @@ export type {
RequestValidator,
} from './resources/webhook/handler.js';
export type { CredentialValidator } from './resources/webhook/auth.js';
module.exports.ChargebeeZodValidationError = ChargebeeZodValidationError;
3 changes: 3 additions & 0 deletions src/chargebee.esm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export {
WebhookPayloadParseError,
} from './resources/webhook/handler.js';

// Export validation error class
export { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';

// Export webhook types
export type {
WebhookEvent,
Expand Down
1 change: 1 addition & 0 deletions src/chargebee.esm.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,4 @@ export type {
RequestValidator,
} from './resources/webhook/handler.js';
export type { CredentialValidator } from './resources/webhook/auth.js';
export { ChargebeeZodValidationError } from './chargebeeZodValidationError.js';
18 changes: 18 additions & 0 deletions src/chargebeeZodValidationError.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { ZodError } from 'zod';

export class ChargebeeZodValidationError extends Error {
readonly actionName: string;
readonly zodError: ZodError;

constructor(actionName: string, zodError: ZodError) {
const messages = zodError.issues
.map((e) => `${e.path.join('.')}: ${e.message}`)
.join('; ');
super(`[Chargebee] Validation failed for '${actionName}': ${messages}`);
Object.setPrototypeOf(this, new.target.prototype);
this.name = 'ChargebeeZodValidationError';
this.actionName = actionName;
this.zodError = zodError;
Comment thread
cb-alish marked this conversation as resolved.
Error.captureStackTrace?.(this, this.constructor);
}
}
5 changes: 5 additions & 0 deletions src/createChargebee.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,11 @@ export const CreateChargebee = (httpClient: HttpClientInterface) => {
jsonKeys: metaArr[7],
options: metaArr[8],
};
if (this._env.enableValidation) {
Comment thread
cb-karthikp marked this conversation as resolved.
// Store resource and action for lazy schema loading in RequestWrapper
apiCall.resourceKey = res;
apiCall.actionName = metaArr[0] as string;
}
this[res][apiCall.methodName] = this._createApiFunc(
apiCall,
this._env,
Expand Down
2 changes: 1 addition & 1 deletion src/environment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const Environment = {
hostSuffix: '.chargebee.com',
apiPath: '/api/v2',
timeout: DEFAULT_TIME_OUT,
clientVersion: 'v3.24.0',
clientVersion: 'v3.25.0-beta.1',
port: DEFAULT_PORT,
timemachineWaitInMillis: DEFAULT_TIME_MACHINE_WAIT,
exportWaitInMillis: DEFAULT_EXPORT_WAIT,
Expand Down
Loading
Loading