@qelos/sdk 4.1.1 → 4.2.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.
Files changed (35) hide show
  1. package/README.md +81 -0
  2. package/dist/administrator/integration-sources.d.ts +3 -1
  3. package/dist/administrator/integration-sources.js +14 -0
  4. package/dist/administrator/integration-sources.js.map +1 -1
  5. package/dist/administrator/integrations.d.ts +2 -0
  6. package/dist/administrator/integrations.js.map +1 -1
  7. package/dist/administrator/payments.d.ts +17 -1
  8. package/dist/administrator/payments.js +12 -0
  9. package/dist/administrator/payments.js.map +1 -1
  10. package/dist/administrator/tests/integration-sources-status.test.d.ts +1 -0
  11. package/dist/administrator/tests/integration-sources-status.test.js +105 -0
  12. package/dist/administrator/tests/integration-sources-status.test.js.map +1 -0
  13. package/dist/authentication.d.ts +5 -0
  14. package/dist/authentication.js +39 -0
  15. package/dist/authentication.js.map +1 -1
  16. package/dist/index.d.ts +3 -2
  17. package/dist/index.js +3 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/mcp-auth.d.ts +76 -0
  20. package/dist/mcp-auth.js +216 -0
  21. package/dist/mcp-auth.js.map +1 -0
  22. package/dist/payments.d.ts +12 -1
  23. package/dist/payments.js.map +1 -1
  24. package/docs/payments.md +54 -0
  25. package/package.json +10 -10
  26. package/src/administrator/integration-sources.ts +30 -1
  27. package/src/administrator/integrations.ts +2 -0
  28. package/src/administrator/payments.ts +28 -0
  29. package/src/administrator/tests/integration-sources-status.test.ts +121 -0
  30. package/src/authentication.ts +64 -0
  31. package/src/index.ts +20 -0
  32. package/src/mcp-auth.ts +293 -0
  33. package/src/payments.ts +12 -1
  34. package/test/mcp-auth.test.ts +255 -0
  35. package/test/payments.test.ts +65 -0
@@ -0,0 +1,216 @@
1
+ function readQueryValue(input, key) {
2
+ const raw = input[key];
3
+ if (Array.isArray(raw)) {
4
+ return raw[0] && typeof raw[0] === 'string' ? raw[0] : undefined;
5
+ }
6
+ return typeof raw === 'string' ? raw : undefined;
7
+ }
8
+ function searchParamsFromString(input) {
9
+ try {
10
+ const url = new URL(input);
11
+ if (url.search) {
12
+ return url.searchParams;
13
+ }
14
+ if (url.hash.length > 1) {
15
+ return new URLSearchParams(url.hash.slice(1));
16
+ }
17
+ return url.searchParams;
18
+ }
19
+ catch {
20
+ const trimmed = input.trim();
21
+ if (!trimmed) {
22
+ return undefined;
23
+ }
24
+ const queryPart = trimmed.startsWith('?')
25
+ ? trimmed.slice(1)
26
+ : trimmed.startsWith('#')
27
+ ? trimmed.slice(1)
28
+ : trimmed.includes('=')
29
+ ? trimmed
30
+ : undefined;
31
+ return queryPart ? new URLSearchParams(queryPart) : undefined;
32
+ }
33
+ }
34
+ function searchParamsFromInput(input) {
35
+ if (input instanceof URLSearchParams) {
36
+ return input;
37
+ }
38
+ if (input instanceof URL) {
39
+ if (input.search.length > 1) {
40
+ return input.searchParams;
41
+ }
42
+ if (input.hash.length > 1) {
43
+ return new URLSearchParams(input.hash.slice(1));
44
+ }
45
+ return input.searchParams;
46
+ }
47
+ if (typeof input === 'string') {
48
+ return searchParamsFromString(input);
49
+ }
50
+ const params = new URLSearchParams();
51
+ const code = readQueryValue(input, 'code');
52
+ const state = readQueryValue(input, 'state');
53
+ const error = readQueryValue(input, 'error');
54
+ const errorDescription = readQueryValue(input, 'error_description');
55
+ if (code)
56
+ params.set('code', code);
57
+ if (state)
58
+ params.set('state', state);
59
+ if (error)
60
+ params.set('error', error);
61
+ if (errorDescription)
62
+ params.set('error_description', errorDescription);
63
+ return params.toString() ? params : undefined;
64
+ }
65
+ /**
66
+ * Parse authorization callback parameters from an MCP OAuth redirect.
67
+ * Accepts a full URL, {@link URL}, `URLSearchParams`, a query/fragment string,
68
+ * or a query object such as `req.query` from Next.js or Express.
69
+ */
70
+ export function parseMcpCallbackParams(input) {
71
+ const params = searchParamsFromInput(input);
72
+ if (!params) {
73
+ return {};
74
+ }
75
+ const result = {};
76
+ const code = params.get('code');
77
+ const state = params.get('state');
78
+ const error = params.get('error');
79
+ const errorDescription = params.get('error_description');
80
+ if (code)
81
+ result.code = code;
82
+ if (state)
83
+ result.state = state;
84
+ if (error)
85
+ result.error = error;
86
+ if (errorDescription)
87
+ result.errorDescription = errorDescription;
88
+ return result;
89
+ }
90
+ /** Relative authorize path with query string for {@link QlAuthentication.getMcpAuthorizeUrl}. */
91
+ export function buildMcpAuthorizePath(options) {
92
+ const search = new URLSearchParams({ redirect_uri: options.redirectUri });
93
+ if (options.state)
94
+ search.set('state', options.state);
95
+ if (options.codeChallenge)
96
+ search.set('code_challenge', options.codeChallenge);
97
+ if (options.codeChallengeMethod)
98
+ search.set('code_challenge_method', options.codeChallengeMethod);
99
+ if (options.clientId)
100
+ search.set('client_id', options.clientId);
101
+ return `/api/auth/mcp/authorize?${search.toString()}`;
102
+ }
103
+ /**
104
+ * Build `/api/auth/mcp/authorize?…` from OAuth authorize query params
105
+ * (e.g. when `/mcp/authorize` receives a direct client redirect).
106
+ */
107
+ export function buildMcpAuthorizePathFromQuery(query) {
108
+ const redirectUri = readQueryValue(query, 'redirect_uri');
109
+ if (!redirectUri) {
110
+ return null;
111
+ }
112
+ const options = { redirectUri };
113
+ const state = readQueryValue(query, 'state');
114
+ const codeChallenge = readQueryValue(query, 'code_challenge');
115
+ const codeChallengeMethod = readQueryValue(query, 'code_challenge_method');
116
+ const clientId = readQueryValue(query, 'client_id');
117
+ if (state)
118
+ options.state = state;
119
+ if (codeChallenge)
120
+ options.codeChallenge = codeChallenge;
121
+ if (codeChallengeMethod)
122
+ options.codeChallengeMethod = codeChallengeMethod;
123
+ if (clientId)
124
+ options.clientId = clientId;
125
+ return buildMcpAuthorizePath(options);
126
+ }
127
+ /** Login redirect used by Qelos after an unauthenticated MCP authorize request. */
128
+ export function buildMcpLoginRedirectUrl(authorizePath, options) {
129
+ const loginPath = options?.loginPath ?? '/login';
130
+ return `${loginPath}?redirect=${encodeURIComponent(authorizePath)}`;
131
+ }
132
+ /** Consent page URL issued after the user signs in during MCP OAuth. */
133
+ export function buildMcpConsentPageUrl(mcpState, options) {
134
+ const consentPath = options?.consentPath ?? '/mcp/authorize';
135
+ return `${consentPath}?mcp_state=${encodeURIComponent(mcpState)}`;
136
+ }
137
+ /**
138
+ * Resolve the post-login redirect target from login-page query params.
139
+ * Matches the resume logic used by the Qelos admin login flow.
140
+ */
141
+ export function resolveMcpLoginRedirect(query, options) {
142
+ const redirect = readQueryValue(query, 'redirect');
143
+ if (redirect) {
144
+ return redirect;
145
+ }
146
+ const mcpState = readQueryValue(query, 'mcp_state');
147
+ if (mcpState) {
148
+ return buildMcpConsentPageUrl(mcpState, options);
149
+ }
150
+ return buildMcpAuthorizePathFromQuery(query);
151
+ }
152
+ /** Redirect URI with `error=access_denied` for MCP consent denial. */
153
+ export function appendMcpAccessDeniedRedirect(redirectUri, oauthState) {
154
+ try {
155
+ const url = new URL(redirectUri);
156
+ url.searchParams.set('error', 'access_denied');
157
+ if (oauthState) {
158
+ url.searchParams.set('state', oauthState);
159
+ }
160
+ return url.toString();
161
+ }
162
+ catch {
163
+ const separator = redirectUri.includes('?') ? '&' : '?';
164
+ let result = `${redirectUri}${separator}error=access_denied`;
165
+ if (oauthState) {
166
+ result += `&state=${encodeURIComponent(oauthState)}`;
167
+ }
168
+ return result;
169
+ }
170
+ }
171
+ /**
172
+ * Decode the signed `mcp_state` JWT payload for display on a consent page.
173
+ * Does not verify the signature — use only for UI labels, not authorization.
174
+ */
175
+ export function decodeMcpOAuthStatePayload(mcpState) {
176
+ if (!mcpState) {
177
+ return null;
178
+ }
179
+ try {
180
+ const parts = mcpState.split('.');
181
+ if (parts.length !== 3 || !parts[1]) {
182
+ return null;
183
+ }
184
+ const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
185
+ const json = typeof globalThis.atob === 'function'
186
+ ? globalThis.atob(base64)
187
+ : Buffer.from(base64, 'base64').toString('utf8');
188
+ const decoded = JSON.parse(json);
189
+ const payload = {};
190
+ if (decoded.ru)
191
+ payload.redirectUri = decoded.ru;
192
+ if (decoded.s)
193
+ payload.state = decoded.s;
194
+ if (decoded.cc)
195
+ payload.codeChallenge = decoded.cc;
196
+ if (decoded.ccm)
197
+ payload.codeChallengeMethod = decoded.ccm;
198
+ if (decoded.t)
199
+ payload.tenant = decoded.t;
200
+ if (decoded.cid)
201
+ payload.clientId = decoded.cid;
202
+ return payload.redirectUri ? payload : null;
203
+ }
204
+ catch {
205
+ return null;
206
+ }
207
+ }
208
+ export function buildMcpTokenExchangeBody(options) {
209
+ return new URLSearchParams({
210
+ grant_type: 'authorization_code',
211
+ code: options.code,
212
+ redirect_uri: options.redirectUri,
213
+ code_verifier: options.codeVerifier,
214
+ });
215
+ }
216
+ //# sourceMappingURL=mcp-auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-auth.js","sourceRoot":"","sources":["../src/mcp-auth.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { QelosSDKOptions } from './types';
2
2
  import BaseSDK from './base-sdk';
3
- import { IPlan, ISubscription, IInvoice, ICoupon, BillingCycle } from '@qelos/global-types';
3
+ import { IPlan, ISubscription, IInvoice, ICoupon, BillingCycle, CheckoutCustomer } from '@qelos/global-types';
4
4
  export interface CheckoutRequest {
5
5
  /** Required when not providing `subscriptionId`. Must be a non-dynamic plan. */
6
6
  planId?: string;
@@ -9,6 +9,17 @@ export interface CheckoutRequest {
9
9
  couponCode?: string;
10
10
  successUrl?: string;
11
11
  cancelUrl?: string;
12
+ /**
13
+ * Real-world identity of the billable entity. Passed through to the payment
14
+ * provider (e.g. Sumit) so it can create a properly-named customer record
15
+ * instead of falling back to the raw billable entity ID.
16
+ */
17
+ customer?: CheckoutCustomer;
18
+ /**
19
+ * When true, cancels any existing active or trialing subscription for the
20
+ * authenticated billable entity before starting checkout (e.g. plan change).
21
+ */
22
+ reset?: boolean;
12
23
  /**
13
24
  * Pre-created subscription ID. Required for dynamic plans — the admin must set
14
25
  * `dynamicAmount` on the subscription before this checkout call will succeed.
@@ -1 +1 @@
1
- {"version":3,"file":"payments.js","sourceRoot":"","sources":["../src/payments.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,YAAY,CAAC;AA2BjC,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IACrC,YAAY,GAAG,MAAM,CAAC;IAE9B,YAAY,OAAwB;QAClC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED,QAAQ,CAAC,KAA8B;QACrC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,KAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,WAAW,CAAU,GAAG,IAAI,CAAC,YAAY,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,WAAW,CAAQ,GAAG,IAAI,CAAC,YAAY,UAAU,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,MAAc,EAAE,YAA0B,EAAE,UAAmB;QAC7E,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,gBAAgB,EACpC;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;SAC3D,CACF,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAuB;QAC9B,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,WAAW,EAC/B;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SAC7B,CACF,CAAC;IACJ,CAAC;IAED,iBAAiB;QACf,OAAO,IAAI,CAAC,WAAW,CAAgB,GAAG,IAAI,CAAC,YAAY,mBAAmB,CAAC,CAAC;IAClF,CAAC;IAED,kBAAkB,CAAC,cAAsB;QACvC,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,kBAAkB,cAAc,SAAS,EAC7D,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,KAA2B;QACrC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,KAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,WAAW,CAAa,GAAG,IAAI,CAAC,YAAY,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,WAAW,CAAW,GAAG,IAAI,CAAC,YAAY,aAAa,SAAS,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,cAAc,CAAC,IAAY,EAAE,MAAe;QAC1C,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,mBAAmB,EACvC;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SACvC,CACF,CAAC;IACJ,CAAC;CACF"}
1
+ {"version":3,"file":"payments.js","sourceRoot":"","sources":["../src/payments.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,YAAY,CAAC;AAsCjC,MAAM,CAAC,OAAO,OAAO,UAAW,SAAQ,OAAO;IACrC,YAAY,GAAG,MAAM,CAAC;IAE9B,YAAY,OAAwB;QAClC,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;IAED,QAAQ,CAAC,KAA8B;QACrC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,KAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,WAAW,CAAU,GAAG,IAAI,CAAC,YAAY,gBAAgB,EAAE,EAAE,CAAC,CAAC;IAC7E,CAAC;IAED,OAAO,CAAC,MAAc;QACpB,OAAO,IAAI,CAAC,WAAW,CAAQ,GAAG,IAAI,CAAC,YAAY,UAAU,MAAM,EAAE,CAAC,CAAC;IACzE,CAAC;IAED;;;;;OAKG;IACH,eAAe,CAAC,MAAc,EAAE,YAA0B,EAAE,UAAmB;QAC7E,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,gBAAgB,EACpC;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,EAAE,YAAY,EAAE,UAAU,EAAE,CAAC;SAC3D,CACF,CAAC;IACJ,CAAC;IAED;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,MAAuB;QAC9B,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,WAAW,EAC/B;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC;SAC7B,CACF,CAAC;IACJ,CAAC;IAED,iBAAiB;QACf,OAAO,IAAI,CAAC,WAAW,CAAgB,GAAG,IAAI,CAAC,YAAY,mBAAmB,CAAC,CAAC;IAClF,CAAC;IAED,kBAAkB,CAAC,cAAsB;QACvC,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,kBAAkB,cAAc,SAAS,EAC7D,EAAE,MAAM,EAAE,KAAK,EAAE,CAClB,CAAC;IACJ,CAAC;IAED,WAAW,CAAC,KAA2B;QACrC,MAAM,EAAE,GAAG,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,eAAe,CAAC,KAAY,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAChE,OAAO,IAAI,CAAC,WAAW,CAAa,GAAG,IAAI,CAAC,YAAY,YAAY,EAAE,EAAE,CAAC,CAAC;IAC5E,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,WAAW,CAAW,GAAG,IAAI,CAAC,YAAY,aAAa,SAAS,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,cAAc,CAAC,IAAY,EAAE,MAAe;QAC1C,OAAO,IAAI,CAAC,WAAW,CACrB,GAAG,IAAI,CAAC,YAAY,mBAAmB,EACvC;YACE,MAAM,EAAE,MAAM;YACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;YAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;SACvC,CACF,CAAC;IACJ,CAAC;CACF"}
package/docs/payments.md CHANGED
@@ -104,6 +104,60 @@ const result = await adminSdk.payments.checkout({
104
104
 
105
105
  ---
106
106
 
107
+ ## Payments configuration
108
+
109
+ Admin-only tenant settings stored in `payments-configuration`.
110
+
111
+ ### `GET /api/payments/configuration`
112
+
113
+ Returns the current payments configuration metadata. **Auth**: privileged.
114
+
115
+ **Response `200`**
116
+
117
+ ```json
118
+ {
119
+ "key": "payments-configuration",
120
+ "metadata": {
121
+ "isEnabled": true,
122
+ "paymentSourceId": "integration-source-id",
123
+ "defaultCurrency": "ILS",
124
+ "gracePeriodDays": 3,
125
+ "successUrl": "https://your-app.com/billing/return?status=success",
126
+ "cancelUrl": "https://your-app.com/billing/return?status=cancel"
127
+ }
128
+ }
129
+ ```
130
+
131
+ ### `PUT /api/payments/configuration`
132
+
133
+ Creates or updates payments configuration. Partial `metadata` is merged with the existing record. **Auth**: privileged.
134
+
135
+ **Body**
136
+
137
+ ```json
138
+ {
139
+ "metadata": {
140
+ "isEnabled": true,
141
+ "paymentSourceId": "integration-source-id",
142
+ "successUrl": "https://your-app.com/billing/return?status=success",
143
+ "cancelUrl": "https://your-app.com/billing/return?status=cancel"
144
+ }
145
+ }
146
+ ```
147
+
148
+ | Field | Type | Description |
149
+ |---|---|---|
150
+ | `isEnabled` | boolean | Enable payments for the tenant |
151
+ | `paymentSourceId` | string | Integration source `_id` for the payment provider |
152
+ | `defaultCurrency` | string | Default ISO 4217 currency code |
153
+ | `gracePeriodDays` | number | Grace period after failed payment |
154
+ | `successUrl` | string | Default redirect URL after successful checkout |
155
+ | `cancelUrl` | string | Default redirect URL when checkout is canceled |
156
+
157
+ > **SDK:** `adminSdk.payments.getPaymentsConfiguration()` · `adminSdk.payments.updatePaymentsConfiguration(metadata)`
158
+
159
+ ---
160
+
107
161
  ## Listing Plans and Subscriptions
108
162
 
109
163
  ### User SDK
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@qelos/sdk",
3
- "version": "4.1.1",
3
+ "version": "4.2.1",
4
4
  "description": "SDK for Qelos API",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -96,23 +96,23 @@
96
96
  },
97
97
  "./package.json": "./package.json"
98
98
  },
99
+ "scripts": {
100
+ "test": "node --import tsx --test **/**/*.test.ts",
101
+ "type-check": "tsc --noEmit",
102
+ "build": "npm run type-check && tsc",
103
+ "pre-build": "pnpm run type-check && tsc"
104
+ },
99
105
  "author": "David Meir-Levy <davidmeirlevy@gmail.com>",
100
106
  "license": "MIT",
101
107
  "publishConfig": {
102
108
  "access": "public"
103
109
  },
104
110
  "dependencies": {
105
- "@qelos/global-types": "^4.1.1"
111
+ "@qelos/global-types": "workspace:^"
106
112
  },
107
113
  "devDependencies": {
108
114
  "tsx": "^4.21.0",
109
115
  "typescript": "^4.9.5"
110
116
  },
111
- "gitHead": "13468bc51557291935b96b1aeaa837b8b52861e8",
112
- "scripts": {
113
- "test": "node --import tsx --test **/**/*.test.ts",
114
- "type-check": "tsc --noEmit",
115
- "build": "npm run type-check && tsc",
116
- "pre-build": "pnpm run type-check && tsc"
117
- }
118
- }
117
+ "gitHead": "13468bc51557291935b96b1aeaa837b8b52861e8"
118
+ }
@@ -1,6 +1,10 @@
1
1
  import { QelosSDKOptions, RequestExtra } from '../types';
2
2
  import BaseSDK from '../base-sdk';
3
- import { IIntegrationSource } from '@qelos/global-types';
3
+ import {
4
+ IIntegrationSource,
5
+ IIntegrationSourceStatusRequest,
6
+ IIntegrationSourceStatusResult,
7
+ } from '@qelos/global-types';
4
8
 
5
9
  export default class QlIntegrationSources extends BaseSDK {
6
10
  private relativePath = '/api/integration-sources';
@@ -40,4 +44,29 @@ export default class QlIntegrationSources extends BaseSDK {
40
44
  })
41
45
  }
42
46
 
47
+ checkStatus(
48
+ sourceId: string,
49
+ overrides?: Partial<Pick<IIntegrationSource, 'metadata' | 'authentication'>>,
50
+ ): Promise<IIntegrationSourceStatusResult> {
51
+ return this.callJsonApi<IIntegrationSourceStatusResult>(
52
+ `${this.relativePath}/${sourceId}/status`,
53
+ {
54
+ method: 'post',
55
+ headers: { 'content-type': 'application/json' },
56
+ body: JSON.stringify(overrides ?? {}),
57
+ },
58
+ );
59
+ }
60
+
61
+ checkDraftStatus(source: IIntegrationSourceStatusRequest): Promise<IIntegrationSourceStatusResult> {
62
+ return this.callJsonApi<IIntegrationSourceStatusResult>(
63
+ `${this.relativePath}/status`,
64
+ {
65
+ method: 'post',
66
+ headers: { 'content-type': 'application/json' },
67
+ body: JSON.stringify(source),
68
+ },
69
+ );
70
+ }
71
+
43
72
  }
@@ -38,6 +38,7 @@ export default class QlIntegrations extends BaseSDK {
38
38
  trigger?: any;
39
39
  target?: any;
40
40
  dataManipulation?: any;
41
+ targetManipulation?: any;
41
42
  }): Promise<IIntegration> {
42
43
  return this.callJsonApi<IIntegration>(
43
44
  `${this.relativePath}/${integrationId}`,
@@ -53,6 +54,7 @@ export default class QlIntegrations extends BaseSDK {
53
54
  trigger: any;
54
55
  target: any;
55
56
  dataManipulation?: any;
57
+ targetManipulation?: any;
56
58
  active?: boolean;
57
59
  }): Promise<IIntegration> {
58
60
  return this.callJsonApi<IIntegration>(this.relativePath, {
@@ -3,8 +3,14 @@ import BaseSDK from '../base-sdk';
3
3
  import {
4
4
  IPlan, ISubscription, IInvoice, ICoupon,
5
5
  BillableEntityType, SubscriptionStatus, InvoiceStatus, BillingCycle,
6
+ IPaymentsConfigurationMetadata, CheckoutCustomer,
6
7
  } from '@qelos/global-types';
7
8
 
9
+ export interface PaymentsConfigurationRecord {
10
+ key: string;
11
+ metadata: IPaymentsConfigurationMetadata;
12
+ }
13
+
8
14
  export interface AdminCheckoutRequest {
9
15
  planId: string;
10
16
  billingCycle: BillingCycle;
@@ -14,12 +20,20 @@ export interface AdminCheckoutRequest {
14
20
  couponCode?: string;
15
21
  successUrl?: string;
16
22
  cancelUrl?: string;
23
+ /** When true, cancels any existing active/trialing subscription before checkout. */
24
+ reset?: boolean;
17
25
  /**
18
26
  * Admin-only. For plans with `dynamic: true`, sets the `dynamicAmount` on the
19
27
  * pending subscription before initiating checkout. Regular users cannot set this;
20
28
  * use `setSubscriptionDynamicAmount()` + a separate `checkout()` call instead.
21
29
  */
22
30
  amount?: number;
31
+ /**
32
+ * Real-world identity of the billable entity. Passed through to the payment
33
+ * provider (e.g. Sumit) so it can create a properly-named customer record
34
+ * instead of falling back to the raw billable entity ID.
35
+ */
36
+ customer?: CheckoutCustomer;
23
37
  }
24
38
 
25
39
  export interface CreateSubscriptionData {
@@ -181,4 +195,18 @@ export default class QlPaymentsAdmin extends BaseSDK {
181
195
  deleteCoupon(couponId: string) {
182
196
  return this.callJsonApi<ICoupon>(`/api/coupons/${couponId}`, { method: 'delete' });
183
197
  }
198
+
199
+ /** Load tenant payments settings (`payments-configuration`). */
200
+ getPaymentsConfiguration() {
201
+ return this.callJsonApi<PaymentsConfigurationRecord>('/api/payments/configuration');
202
+ }
203
+
204
+ /** Update tenant payments settings. Partial metadata is merged with the existing record. */
205
+ updatePaymentsConfiguration(metadata: Partial<IPaymentsConfigurationMetadata>) {
206
+ return this.callJsonApi<PaymentsConfigurationRecord>('/api/payments/configuration', {
207
+ method: 'put',
208
+ headers: { 'content-type': 'application/json' },
209
+ body: JSON.stringify({ metadata }),
210
+ });
211
+ }
184
212
  }
@@ -0,0 +1,121 @@
1
+ import * as assert from 'node:assert/strict';
2
+ import { test } from 'node:test';
3
+ import { IntegrationSourceKind } from '@qelos/global-types';
4
+ import { QelosSDKOptions } from '../../types';
5
+ import QlIntegrationSources from '../integration-sources';
6
+
7
+ function jsonResponse(body: unknown) {
8
+ const res = new Response(JSON.stringify(body));
9
+ res.headers.set('Content-Type', 'application/json');
10
+ return res;
11
+ }
12
+
13
+ const connectedResult = {
14
+ status: 'connected' as const,
15
+ message: 'Connection verified',
16
+ kind: IntegrationSourceKind.Sumit,
17
+ checkedAt: '2026-07-23T05:00:00.000Z',
18
+ };
19
+
20
+ test('QlIntegrationSources status methods', async (t) => {
21
+ await t.test('checkStatus sends POST to saved source status path', async () => {
22
+ let url = '';
23
+ let init: RequestInit | undefined;
24
+ const options: QelosSDKOptions = {
25
+ appUrl: 'http://localhost:3000',
26
+ fetch: async (requestUrl, requestInit) => {
27
+ url = requestUrl.toString();
28
+ init = requestInit;
29
+ return jsonResponse(connectedResult);
30
+ },
31
+ };
32
+
33
+ const sdk = new QlIntegrationSources(options);
34
+ const result = await sdk.checkStatus('src-123');
35
+
36
+ assert.equal(url, 'http://localhost:3000/api/integration-sources/src-123/status');
37
+ assert.equal(init?.method, 'post');
38
+ assert.equal((init?.headers as Record<string, string>)['content-type'], 'application/json');
39
+ assert.deepEqual(JSON.parse(init?.body as string), {});
40
+ assert.deepEqual(result, connectedResult);
41
+ });
42
+
43
+ await t.test('checkStatus sends metadata and authentication overrides', async () => {
44
+ let init: RequestInit | undefined;
45
+ const options: QelosSDKOptions = {
46
+ appUrl: 'http://localhost:3000',
47
+ fetch: async (_url, requestInit) => {
48
+ init = requestInit;
49
+ return jsonResponse(connectedResult);
50
+ },
51
+ };
52
+
53
+ const sdk = new QlIntegrationSources(options);
54
+ await sdk.checkStatus('src-123', {
55
+ metadata: { companyId: '42' },
56
+ authentication: { apiKey: 'secret' },
57
+ });
58
+
59
+ assert.deepEqual(JSON.parse(init?.body as string), {
60
+ metadata: { companyId: '42' },
61
+ authentication: { apiKey: 'secret' },
62
+ });
63
+ });
64
+
65
+ await t.test('checkDraftStatus sends POST to draft status path', async () => {
66
+ let url = '';
67
+ let init: RequestInit | undefined;
68
+ const options: QelosSDKOptions = {
69
+ appUrl: 'http://localhost:3000',
70
+ fetch: async (requestUrl, requestInit) => {
71
+ url = requestUrl.toString();
72
+ init = requestInit;
73
+ return jsonResponse(connectedResult);
74
+ },
75
+ };
76
+
77
+ const draft = {
78
+ kind: IntegrationSourceKind.PayPal,
79
+ metadata: { environment: 'sandbox' },
80
+ authentication: { clientId: 'id', clientSecret: 'secret' },
81
+ };
82
+
83
+ const sdk = new QlIntegrationSources(options);
84
+ const result = await sdk.checkDraftStatus(draft);
85
+
86
+ assert.equal(url, 'http://localhost:3000/api/integration-sources/status');
87
+ assert.equal(init?.method, 'post');
88
+ assert.equal((init?.headers as Record<string, string>)['content-type'], 'application/json');
89
+ assert.deepEqual(JSON.parse(init?.body as string), draft);
90
+ assert.deepEqual(result, connectedResult);
91
+ });
92
+
93
+ await t.test('checkDraftStatus is kind-agnostic and works for non-payment sources too', async () => {
94
+ let init: RequestInit | undefined;
95
+ const emailConnectedResult = {
96
+ status: 'connected' as const,
97
+ message: 'SMTP connection verified',
98
+ kind: IntegrationSourceKind.Email,
99
+ checkedAt: '2026-07-23T05:00:00.000Z',
100
+ };
101
+ const options: QelosSDKOptions = {
102
+ appUrl: 'http://localhost:3000',
103
+ fetch: async (_url, requestInit) => {
104
+ init = requestInit;
105
+ return jsonResponse(emailConnectedResult);
106
+ },
107
+ };
108
+
109
+ const draft = {
110
+ kind: IntegrationSourceKind.Email,
111
+ metadata: { smtp: 'smtp.example.com', username: 'bot@example.com', email: 'bot@example.com', pop3: 'pop3.example.com', senderName: 'Bot' },
112
+ authentication: { password: 'secret' },
113
+ };
114
+
115
+ const sdk = new QlIntegrationSources(options);
116
+ const result = await sdk.checkDraftStatus(draft);
117
+
118
+ assert.deepEqual(JSON.parse(init?.body as string), draft);
119
+ assert.deepEqual(result, emailConnectedResult);
120
+ });
121
+ });
@@ -1,6 +1,26 @@
1
1
  import { QelosSDKOptions } from './types';
2
2
  import BaseSDK from './base-sdk';
3
3
  import { decodeMeUser } from './services/decode-uri-fields';
4
+ import {
5
+ buildMcpAuthorizePath,
6
+ buildMcpTokenExchangeBody,
7
+ type McpAuthorizeOptions,
8
+ type McpOAuthTokenResponse,
9
+ type McpTokenExchangeOptions,
10
+ type McpTokenExchangeResult,
11
+ } from './mcp-auth';
12
+
13
+ export type {
14
+ McpAuthorizeOptions,
15
+ McpCallbackInput,
16
+ McpCallbackParams,
17
+ McpTokenExchangeOptions,
18
+ McpTokenExchangeResult,
19
+ DecodedMcpOAuthStatePayload,
20
+ McpPagePathOptions,
21
+ } from './mcp-auth';
22
+
23
+ export { parseMcpCallbackParams } from './mcp-auth';
4
24
 
5
25
  export interface IUser {
6
26
  _id: string;
@@ -376,6 +396,50 @@ export default class QlAuthentication extends BaseSDK {
376
396
  return this.exchangeAuthCallback(refreshToken);
377
397
  }
378
398
 
399
+ getMcpAuthorizeUrl(options: McpAuthorizeOptions): string {
400
+ if (!options.redirectUri) {
401
+ throw new Error('redirectUri is required');
402
+ }
403
+ return this.buildUrl(buildMcpAuthorizePath(options));
404
+ }
405
+
406
+ async exchangeMcpAuthorizationCode(options: McpTokenExchangeOptions): Promise<McpTokenExchangeResult> {
407
+ const { code, redirectUri, codeVerifier } = options;
408
+ if (!code || !redirectUri || !codeVerifier) {
409
+ throw new Error('code, redirectUri, and codeVerifier are required');
410
+ }
411
+
412
+ const res = await this.callApi('/api/auth/mcp/token', {
413
+ method: 'post',
414
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
415
+ body: buildMcpTokenExchangeBody(options).toString(),
416
+ });
417
+
418
+ if (!res.ok) {
419
+ let detail = '';
420
+ try {
421
+ detail = (await res.text()).slice(0, 512);
422
+ } catch {
423
+ /* ignore */
424
+ }
425
+ throw new Error(
426
+ `failed to exchange MCP authorization code: HTTP ${res.status}${detail ? ` — ${detail}` : ''}`,
427
+ );
428
+ }
429
+
430
+ const body = (await res.json()) as McpOAuthTokenResponse;
431
+ this.#accessToken = body.access_token;
432
+ this.#refreshToken = body.refresh_token;
433
+
434
+ const user = await this.getLoggedInUser();
435
+ return {
436
+ accessToken: body.access_token,
437
+ refreshToken: body.refresh_token,
438
+ expiresIn: body.expires_in,
439
+ user,
440
+ };
441
+ }
442
+
379
443
  getLoggedInUser() {
380
444
  return this.callJsonApi<IUser>('/api/me').then(decodeMeUser);
381
445
  }