chargebee 3.7.0 → 3.8.0-beta.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/cjs/util.js CHANGED
@@ -7,53 +7,50 @@ exports.serialize = serialize;
7
7
  exports.encodeListParams = encodeListParams;
8
8
  exports.getHost = getHost;
9
9
  exports.encodeParams = encodeParams;
10
+ exports.log = log;
11
+ exports.generateUUIDv4 = generateUUIDv4;
10
12
  const extend = (deep, target, copy) => {
11
13
  _extendsFn(deep, target, copy);
12
14
  };
13
15
  exports.extend = extend;
14
16
  const _extendsFn = (...args) => {
15
- {
16
- let options, name, src, copy, copyIsArray, clone, target = args[0] || {}, i = 1, length = args.length, deep = false;
17
- if (typeof target === 'boolean') {
18
- deep = target;
19
- target = args[1] || {};
20
- i = 2;
21
- }
22
- if (typeof target !== 'object' && typeof target !== 'function') {
23
- target = {};
24
- }
25
- if (length === i) {
26
- target = this;
27
- --i;
28
- }
29
- for (; i < length; i++) {
30
- if ((options = args[i]) !== null) {
31
- for (name in options) {
32
- src = target[name];
33
- copy = options[name];
34
- if (target === copy) {
35
- continue;
36
- }
37
- if (deep &&
38
- copy &&
39
- (typeof copy === 'object' || (copyIsArray = (0, exports.isArray)(copy)))) {
40
- if (copyIsArray) {
41
- copyIsArray = false;
42
- clone = src && (0, exports.isArray)(src) ? src : [];
43
- }
44
- else {
45
- clone = src && typeof src === 'object' ? src : {};
46
- }
47
- target[name] = (0, exports.extend)(deep, clone, copy);
17
+ let options, name, src, copy, copyIsArray, clone;
18
+ let target = args[0] || {};
19
+ let i = 1;
20
+ const length = args.length;
21
+ let deep = false;
22
+ if (typeof target === 'boolean') {
23
+ deep = target;
24
+ target = args[1] || {};
25
+ i = 2;
26
+ }
27
+ if (typeof target !== 'object' && typeof target !== 'function') {
28
+ target = {};
29
+ }
30
+ for (; i < length; i++) {
31
+ if ((options = args[i]) !== null && options !== undefined) {
32
+ for (name in options) {
33
+ src = target[name];
34
+ copy = options[name];
35
+ if (target === copy) {
36
+ continue;
37
+ }
38
+ if (deep && copy && (typeof copy === 'object' || (0, exports.isArray)(copy))) {
39
+ if ((0, exports.isArray)(copy)) {
40
+ clone = (0, exports.isArray)(src) ? src : [];
48
41
  }
49
- else if (copy !== undefined) {
50
- target[name] = copy;
42
+ else {
43
+ clone = (0, exports.isObject)(src) ? src : {};
51
44
  }
45
+ target[name] = _extendsFn(deep, clone, copy);
46
+ }
47
+ else if (copy !== undefined) {
48
+ target[name] = copy;
52
49
  }
53
50
  }
54
51
  }
55
- return target;
56
52
  }
53
+ return target;
57
54
  };
58
55
  const isArray = (obj) => {
59
56
  return (Array.isArray(obj) ||
@@ -221,3 +218,31 @@ function encodeParams(paramObj, serialized, scope, index, jsonKeys, level = 0) {
221
218
  }
222
219
  return serialized.join('&').replace(/%20/g, '+');
223
220
  }
221
+ function log(env, { level = 'INFO', message = '', context = {}, functionName = '' }) {
222
+ if (!env.enableDebugLogs) {
223
+ return;
224
+ }
225
+ const timestamp = new Date().toISOString();
226
+ const service = 'chargebee-node';
227
+ const metaString = Object.entries(context)
228
+ .map(([key, value]) => `${key}=${value}`)
229
+ .join(', ');
230
+ const logLine = `[${timestamp}] [${level.toUpperCase()}] [${service}] ${functionName} - ${message}${metaString ? ` (${metaString})` : ''}`;
231
+ console.debug(logLine);
232
+ }
233
+ const crypto_1 = require("crypto");
234
+ function generateUUIDv4() {
235
+ const bytes = (0, crypto_1.randomBytes)(16);
236
+ // Set version to 4 (UUIDv4)
237
+ bytes[6] = (bytes[6] & 0x0f) | 0x40;
238
+ // Set variant to 10xxxxxx
239
+ bytes[8] = (bytes[8] & 0x3f) | 0x80;
240
+ const hex = bytes.toString('hex');
241
+ return [
242
+ hex.slice(0, 8),
243
+ hex.slice(8, 12),
244
+ hex.slice(12, 16),
245
+ hex.slice(16, 20),
246
+ hex.slice(20),
247
+ ].join('-');
248
+ }
@@ -1,5 +1,5 @@
1
- import { extend, callbackifyPromise, getApiURL, encodeListParams, encodeParams, serialize, getHost, } from './util.js';
2
- import { handleResponse } from './coreCommon.js';
1
+ import { extend, callbackifyPromise, getApiURL, encodeListParams, encodeParams, serialize, getHost, log, generateUUIDv4 as uuidv4, } from './util';
2
+ import { handleResponse } from './coreCommon';
3
3
  import { Buffer } from 'node:buffer';
4
4
  export class RequestWrapper {
5
5
  constructor(args, apiCall, envArg) {
@@ -20,72 +20,133 @@ export class RequestWrapper {
20
20
  }
21
21
  return idParam;
22
22
  }
23
- request() {
23
+ static parseRetryAfter(retryAfter) {
24
+ if (!retryAfter)
25
+ return null;
26
+ const seconds = parseInt(retryAfter, 10);
27
+ if (!isNaN(seconds)) {
28
+ return seconds * 1000;
29
+ }
30
+ return null;
31
+ }
32
+ async request() {
24
33
  let env = {};
25
34
  extend(true, env, this.envArg);
35
+ const retryConfig = Object.assign({ enabled: false, maxRetries: 3, delayMs: 200, retryOn: [500, 502, 503, 504] }, env.retryConfig);
26
36
  const urlIdParam = this.apiCall.hasIdInUrl ? this.args[0] : null;
27
37
  let params = this.apiCall.hasIdInUrl
28
38
  ? this.args[1]
29
39
  : this.args[0];
30
40
  let headers = this.apiCall.hasIdInUrl ? this.args[2] : this.args[1];
31
41
  Object.assign(this.httpHeaders, headers);
32
- const promise = new Promise(async (resolve, reject) => {
33
- function callBackWrapper(err, response) {
34
- if (err) {
35
- reject(err);
36
- }
37
- else {
38
- resolve(response);
39
- }
42
+ if (this.apiCall.httpMethod === 'POST' &&
43
+ !this.httpHeaders['chargebee-idempotency-key'] &&
44
+ this.apiCall.options &&
45
+ this.apiCall.options.isIdempotent) {
46
+ this.httpHeaders['chargebee-idempotency-key'] = uuidv4();
47
+ }
48
+ const makeRequest = async (attempt = 0) => {
49
+ let path = getApiURL(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
50
+ if (typeof params === 'undefined' || params === null) {
51
+ params = {};
40
52
  }
53
+ if (this.apiCall.httpMethod === 'GET') {
54
+ const queryParam = this.apiCall.isListReq
55
+ ? encodeListParams(serialize(params))
56
+ : encodeParams(serialize(params));
57
+ path += '?' + queryParam;
58
+ params = {};
59
+ }
60
+ const jsonKeys = this.apiCall.jsonKeys;
61
+ const data = this.apiCall.isJsonRequest
62
+ ? JSON.stringify(params)
63
+ : encodeParams(params, undefined, undefined, undefined, jsonKeys);
64
+ const requestHeaders = Object.assign({}, this.httpHeaders);
65
+ if (data.length) {
66
+ extend(true, requestHeaders, {
67
+ 'Content-Length': data.length,
68
+ });
69
+ }
70
+ const contentType = this.apiCall.isJsonRequest
71
+ ? 'application/json;charset=UTF-8'
72
+ : 'application/x-www-form-urlencoded; charset=utf-8';
73
+ extend(true, requestHeaders, {
74
+ Authorization: 'Basic ' + Buffer.from(env.apiKey + ':').toString('base64'),
75
+ Accept: 'application/json',
76
+ 'Content-Type': contentType,
77
+ 'User-Agent': 'Chargebee-NodeJs-Client ' + env.clientVersion,
78
+ 'Lang-Version': typeof process === 'undefined' ? '' : process.version,
79
+ });
80
+ if (attempt > 0) {
81
+ requestHeaders['X-CB-Retry-Attempt'] = attempt.toString();
82
+ }
83
+ const resp = await this.envArg.httpClient.makeApiRequest({
84
+ host: getHost(env, this.apiCall.subDomain),
85
+ port: env.port,
86
+ path,
87
+ method: this.apiCall.httpMethod,
88
+ protocol: env.protocol,
89
+ headers: requestHeaders,
90
+ data,
91
+ timeout: env.timeout,
92
+ });
93
+ return new Promise((resolve, reject) => {
94
+ handleResponse((err, response) => {
95
+ if (err)
96
+ return reject(err);
97
+ return resolve(response);
98
+ }, resp);
99
+ });
100
+ };
101
+ const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
102
+ const withRetry = async (retryCount, startTime) => {
103
+ var _a, _b, _c, _d, _e, _f;
41
104
  try {
42
- let path = getApiURL(env, this.apiCall.urlPrefix, this.apiCall.urlSuffix, urlIdParam);
43
- if (typeof params === 'undefined' || params === null) {
44
- params = {};
45
- }
46
- if (this.apiCall.httpMethod === 'GET') {
47
- params = serialize(params);
48
- let queryParam = this.apiCall.isListReq
49
- ? encodeListParams(params)
50
- : encodeParams(params);
51
- path += '?' + queryParam;
52
- params = {};
105
+ return await makeRequest(retryCount);
106
+ }
107
+ catch (err) {
108
+ const statusCode = (_d = (_c = (_a = err.statusCode) !== null && _a !== void 0 ? _a : (_b = err.response) === null || _b === void 0 ? void 0 : _b.statusCode) !== null && _c !== void 0 ? _c : err.http_code) !== null && _d !== void 0 ? _d : err.http_status_code;
109
+ const isRateLimitError = statusCode === 429 && retryConfig.enabled;
110
+ if (isRateLimitError) {
111
+ const headers = ((_e = err.response) === null || _e === void 0 ? void 0 : _e.headers) || err.headers || {};
112
+ const retryAfterHeader = (_f = headers['retry-after']) === null || _f === void 0 ? void 0 : _f.toLowerCase();
113
+ const parsedDelay = RequestWrapper.parseRetryAfter(retryAfterHeader);
114
+ if (!parsedDelay) {
115
+ log(env, {
116
+ level: 'ERROR',
117
+ message: `Rate limit error occurred, but no retry-after header found. Retrying in ${retryConfig.delayMs}ms.`,
118
+ });
119
+ throw err;
120
+ }
121
+ log(env, {
122
+ level: 'INFO',
123
+ message: `Rate limit error occurred. Retrying in ${parsedDelay}ms.`,
124
+ });
125
+ await delay(parsedDelay);
53
126
  }
54
- const jsonKeys = this.apiCall.jsonKeys;
55
- let data = this.apiCall.isJsonRequest
56
- ? JSON.stringify(params)
57
- : encodeParams(params, undefined, undefined, undefined, jsonKeys);
58
- if (data.length) {
59
- extend(true, this.httpHeaders, {
60
- 'Content-Length': data.length,
127
+ else {
128
+ const canRetry = retryConfig.enabled &&
129
+ retryConfig.retryOn.includes(statusCode) &&
130
+ retryCount < retryConfig.maxRetries;
131
+ if (!canRetry) {
132
+ log(env, {
133
+ level: 'ERROR',
134
+ message: `Request failed after ${retryCount} retries: ${err.message}`,
135
+ });
136
+ throw err;
137
+ }
138
+ let retryDelayMs = retryConfig.delayMs * Math.pow(2, retryCount) +
139
+ Math.random() * 100;
140
+ log(env, {
141
+ level: 'INFO',
142
+ message: `Retrying request [${retryCount + 1}/${retryConfig.maxRetries}] in ${retryDelayMs}ms due to status code ${statusCode}.`,
61
143
  });
144
+ await delay(retryDelayMs);
62
145
  }
63
- const contentType = this.apiCall.isJsonRequest
64
- ? 'application/json;charset=UTF-8'
65
- : 'application/x-www-form-urlencoded; charset=utf-8';
66
- extend(true, this.httpHeaders, {
67
- Authorization: 'Basic ' + Buffer.from(env.apiKey + ':').toString('base64'),
68
- Accept: 'application/json',
69
- 'Content-Type': contentType,
70
- 'User-Agent': 'Chargebee-NodeJs-Client ' + env.clientVersion,
71
- 'Lang-Version': typeof process === 'undefined' ? '' : process.version,
72
- });
73
- const resp = await this.envArg.httpClient.makeApiRequest({
74
- host: getHost(env, this.apiCall.subDomain),
75
- port: env.port,
76
- path,
77
- method: this.apiCall.httpMethod,
78
- protocol: env.protocol,
79
- headers: this.httpHeaders,
80
- data: data,
81
- timeout: env.timeout,
82
- });
83
- handleResponse(callBackWrapper, resp);
146
+ return await withRetry(retryCount + 1, startTime);
84
147
  }
85
- catch (err) {
86
- callBackWrapper(err, null);
87
- }
88
- });
148
+ };
149
+ const promise = withRetry(0, Date.now());
89
150
  return callbackifyPromise(promise);
90
151
  }
91
152
  }
@@ -1,4 +1,4 @@
1
- import { isFunction, extend, callbackifyPromise } from './util.js';
1
+ import { isFunction, extend, callbackifyPromise } from './util';
2
2
  export const waitForProcessToComplete = (retrieveHandling, env) => {
3
3
  if (typeof retrieveHandling == 'undefined' || !isFunction(retrieveHandling)) {
4
4
  throw new Error('The handling parameter should be a method.');
package/esm/coreCommon.js CHANGED
@@ -1,5 +1,5 @@
1
- import { ChargebeeError } from './chargebeeError.js';
2
- import { isNotUndefinedNEmpty } from './util.js';
1
+ import { ChargebeeError } from './chargebeeError';
2
+ import { isNotUndefinedNEmpty } from './util';
3
3
  const IDEMPOTENCY_REPLAYED_HEADER = 'chargebee-idempotency-replayed';
4
4
  export function throwError(callBack, rawError, headers) {
5
5
  const error = new ChargebeeError({
@@ -1,8 +1,8 @@
1
- import { RequestWrapper } from './RequestWrapper.js';
2
- import { Environment } from './environment.js';
3
- import { Endpoints } from './resources/api_endpoints.js';
4
- import { extend, sleep } from './util.js';
5
- import { waitForProcessToComplete } from './asyncApiSupport.js';
1
+ import { RequestWrapper } from './RequestWrapper';
2
+ import { Environment } from './environment';
3
+ import { Endpoints } from './resources/api_endpoints';
4
+ import { extend, sleep } from './util';
5
+ import { waitForProcessToComplete } from './asyncApiSupport';
6
6
  export const CreateChargebee = (httpClient) => {
7
7
  const Chargebee = function (conf) {
8
8
  this._env = Object.assign({}, Environment);
@@ -78,6 +78,7 @@ export const CreateChargebee = (httpClient) => {
78
78
  subDomain: metaArr[5],
79
79
  isJsonRequest: metaArr[6],
80
80
  jsonKeys: metaArr[7],
81
+ options: metaArr[8],
81
82
  };
82
83
  this[res][apiCall.methodName] = this._createApiFunc(apiCall, this._env);
83
84
  }
@@ -8,7 +8,7 @@ export const Environment = {
8
8
  hostSuffix: '.chargebee.com',
9
9
  apiPath: '/api/v2',
10
10
  timeout: DEFAULT_TIME_OUT,
11
- clientVersion: 'v3.6.1',
11
+ clientVersion: 'v3.8.0-beta.1',
12
12
  port: DEFAULT_PORT,
13
13
  timemachineWaitInMillis: DEFAULT_TIME_MACHINE_WAIT,
14
14
  exportWaitInMillis: DEFAULT_EXPORT_WAIT,
@@ -1,4 +1,4 @@
1
- import { ChargebeeError } from '../chargebeeError.js';
1
+ import { ChargebeeError } from '../chargebeeError';
2
2
  export class HttpClient {
3
3
  async makeApiRequest(props) {
4
4
  throw new Error('makeApiRequest is not implemented');
@@ -1,4 +1,4 @@
1
- import { HttpClient, HttpClientResponse, } from './ClientInterface.js';
1
+ import { HttpClient, HttpClientResponse, } from './ClientInterface';
2
2
  export class FetchHttpClient extends HttpClient {
3
3
  async makeApiRequest(props) {
4
4
  const headers = this._createHeaders(props.headers);
@@ -1,4 +1,4 @@
1
- import { HttpClient, HttpClientResponse, } from './ClientInterface.js';
1
+ import { HttpClient, HttpClientResponse, } from './ClientInterface';
2
2
  import * as http from 'http';
3
3
  import * as https from 'https';
4
4
  export class NodeHttpClient extends HttpClient {