@qelos/sdk 4.0.0 → 4.1.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/dist/administrator/payments.d.ts +47 -1
- package/dist/administrator/payments.js +34 -0
- package/dist/administrator/payments.js.map +1 -1
- package/dist/authentication.d.ts +11 -0
- package/dist/authentication.js +6 -3
- package/dist/authentication.js.map +1 -1
- package/dist/payments.d.ts +28 -2
- package/dist/payments.js +24 -0
- package/dist/payments.js.map +1 -1
- package/dist/services/decode-uri-fields.d.ts +4 -0
- package/dist/services/decode-uri-fields.js +42 -0
- package/dist/services/decode-uri-fields.js.map +1 -0
- package/docs/payments.md +301 -0
- package/package.json +2 -2
- package/src/administrator/payments.ts +74 -1
- package/src/authentication.ts +16 -3
- package/src/payments.ts +38 -2
- package/src/services/decode-uri-fields.ts +48 -0
- package/test/authentication-me.test.ts +109 -0
- package/test/authentication-social.test.ts +24 -0
|
@@ -1,6 +1,36 @@
|
|
|
1
1
|
import { QelosSDKOptions } from '../types';
|
|
2
2
|
import BaseSDK from '../base-sdk';
|
|
3
|
-
import { IPlan, ISubscription, IInvoice, ICoupon, BillableEntityType, SubscriptionStatus, InvoiceStatus } from '@qelos/global-types';
|
|
3
|
+
import { IPlan, ISubscription, IInvoice, ICoupon, BillableEntityType, SubscriptionStatus, InvoiceStatus, BillingCycle } from '@qelos/global-types';
|
|
4
|
+
export interface AdminCheckoutRequest {
|
|
5
|
+
planId: string;
|
|
6
|
+
billingCycle: BillingCycle;
|
|
7
|
+
/** When omitted, the authenticated user / default billable entity is used (same as public checkout). */
|
|
8
|
+
billableEntityType?: BillableEntityType;
|
|
9
|
+
billableEntityId?: string;
|
|
10
|
+
couponCode?: string;
|
|
11
|
+
successUrl?: string;
|
|
12
|
+
cancelUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Admin-only. For plans with `dynamic: true`, sets the `dynamicAmount` on the
|
|
15
|
+
* pending subscription before initiating checkout. Regular users cannot set this;
|
|
16
|
+
* use `setSubscriptionDynamicAmount()` + a separate `checkout()` call instead.
|
|
17
|
+
*/
|
|
18
|
+
amount?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface CreateSubscriptionData {
|
|
21
|
+
planId: string;
|
|
22
|
+
billingCycle: BillingCycle;
|
|
23
|
+
billableEntityType: BillableEntityType;
|
|
24
|
+
billableEntityId: string;
|
|
25
|
+
/** Required for plans with `dynamic: true` before checkout can be initiated. */
|
|
26
|
+
dynamicAmount?: number;
|
|
27
|
+
couponCode?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface AdminCheckoutResponse {
|
|
30
|
+
subscriptionId: string;
|
|
31
|
+
checkoutUrl?: string;
|
|
32
|
+
clientToken?: string;
|
|
33
|
+
}
|
|
4
34
|
export default class QlPaymentsAdmin extends BaseSDK {
|
|
5
35
|
private options;
|
|
6
36
|
constructor(options: QelosSDKOptions);
|
|
@@ -11,13 +41,29 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
11
41
|
createPlan(data: Omit<IPlan, '_id' | 'tenant' | 'created'>): Promise<IPlan>;
|
|
12
42
|
updatePlan(planId: string, data: Partial<IPlan>): Promise<IPlan>;
|
|
13
43
|
deletePlan(planId: string): Promise<IPlan>;
|
|
44
|
+
/**
|
|
45
|
+
* Start subscription checkout (same as `sdk.payments.checkout`, plus optional billable-entity overrides for admins).
|
|
46
|
+
* For plans with `dynamic: true`, `amount` is required.
|
|
47
|
+
*/
|
|
48
|
+
checkout(params: AdminCheckoutRequest): Promise<AdminCheckoutResponse>;
|
|
14
49
|
getSubscriptions(query?: {
|
|
15
50
|
billableEntityType?: BillableEntityType;
|
|
16
51
|
billableEntityId?: string;
|
|
17
52
|
status?: SubscriptionStatus;
|
|
18
53
|
}): Promise<ISubscription[]>;
|
|
19
54
|
getSubscription(subscriptionId: string): Promise<ISubscription>;
|
|
55
|
+
/**
|
|
56
|
+
* Creates a pending subscription on behalf of any billable entity. Admins can
|
|
57
|
+
* also set `dynamicAmount` here for dynamic plans, making the subscription
|
|
58
|
+
* immediately ready for checkout.
|
|
59
|
+
*/
|
|
60
|
+
createSubscription(data: CreateSubscriptionData): Promise<ISubscription>;
|
|
20
61
|
cancelSubscription(subscriptionId: string): Promise<ISubscription>;
|
|
62
|
+
/**
|
|
63
|
+
* Sets or updates the dynamic amount for a pending subscription. Only admins
|
|
64
|
+
* can call this. Must be done before a user can complete checkout on a dynamic plan.
|
|
65
|
+
*/
|
|
66
|
+
setSubscriptionDynamicAmount(subscriptionId: string, amount: number): Promise<ISubscription>;
|
|
21
67
|
getInvoices(query?: {
|
|
22
68
|
billableEntityType?: BillableEntityType;
|
|
23
69
|
billableEntityId?: string;
|
|
@@ -30,6 +30,17 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
30
30
|
deletePlan(planId) {
|
|
31
31
|
return this.callJsonApi(`/api/plans/${planId}`, { method: 'delete' });
|
|
32
32
|
}
|
|
33
|
+
/**
|
|
34
|
+
* Start subscription checkout (same as `sdk.payments.checkout`, plus optional billable-entity overrides for admins).
|
|
35
|
+
* For plans with `dynamic: true`, `amount` is required.
|
|
36
|
+
*/
|
|
37
|
+
checkout(params) {
|
|
38
|
+
return this.callJsonApi('/api/checkout', {
|
|
39
|
+
method: 'post',
|
|
40
|
+
headers: { 'content-type': 'application/json' },
|
|
41
|
+
body: JSON.stringify(params),
|
|
42
|
+
});
|
|
43
|
+
}
|
|
33
44
|
// --- Subscriptions ---
|
|
34
45
|
getSubscriptions(query) {
|
|
35
46
|
const qs = query ? `?${new URLSearchParams(query)}` : '';
|
|
@@ -38,9 +49,32 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
38
49
|
getSubscription(subscriptionId) {
|
|
39
50
|
return this.callJsonApi(`/api/subscriptions/${subscriptionId}`);
|
|
40
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* Creates a pending subscription on behalf of any billable entity. Admins can
|
|
54
|
+
* also set `dynamicAmount` here for dynamic plans, making the subscription
|
|
55
|
+
* immediately ready for checkout.
|
|
56
|
+
*/
|
|
57
|
+
createSubscription(data) {
|
|
58
|
+
return this.callJsonApi('/api/subscriptions', {
|
|
59
|
+
method: 'post',
|
|
60
|
+
headers: { 'content-type': 'application/json' },
|
|
61
|
+
body: JSON.stringify(data),
|
|
62
|
+
});
|
|
63
|
+
}
|
|
41
64
|
cancelSubscription(subscriptionId) {
|
|
42
65
|
return this.callJsonApi(`/api/subscriptions/${subscriptionId}/cancel`, { method: 'put' });
|
|
43
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Sets or updates the dynamic amount for a pending subscription. Only admins
|
|
69
|
+
* can call this. Must be done before a user can complete checkout on a dynamic plan.
|
|
70
|
+
*/
|
|
71
|
+
setSubscriptionDynamicAmount(subscriptionId, amount) {
|
|
72
|
+
return this.callJsonApi(`/api/subscriptions/${subscriptionId}/dynamic-amount`, {
|
|
73
|
+
method: 'put',
|
|
74
|
+
headers: { 'content-type': 'application/json' },
|
|
75
|
+
body: JSON.stringify({ amount }),
|
|
76
|
+
});
|
|
77
|
+
}
|
|
44
78
|
// --- Invoices ---
|
|
45
79
|
getInvoices(query) {
|
|
46
80
|
const qs = query ? `?${new URLSearchParams(query)}` : '';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payments.js","sourceRoot":"","sources":["../../src/administrator/payments.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"payments.js","sourceRoot":"","sources":["../../src/administrator/payments.ts"],"names":[],"mappings":"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"}
|
package/dist/authentication.d.ts
CHANGED
|
@@ -49,7 +49,18 @@ export interface IApiToken {
|
|
|
49
49
|
export type SocialProvider = 'linkedin' | 'facebook' | 'google' | 'github';
|
|
50
50
|
export interface SocialLoginOptions {
|
|
51
51
|
state?: string;
|
|
52
|
+
/**
|
|
53
|
+
* Where Qelos redirects after OAuth with `?rt=<refreshToken>` (SDK flow).
|
|
54
|
+
* Absolute URLs must use a host listed in app-configuration `metadata.websiteUrls`.
|
|
55
|
+
*/
|
|
52
56
|
returnUrl?: string;
|
|
57
|
+
/**
|
|
58
|
+
* Overrides the OAuth provider callback origin when its host is listed in
|
|
59
|
+
* app-configuration `metadata.websiteUrls`. The callback path stays
|
|
60
|
+
* `/api/auth/<provider>/callback` on that origin. When omitted, Qelos uses
|
|
61
|
+
* the request tenant host (`tenanthost`).
|
|
62
|
+
*/
|
|
63
|
+
redirectUrl?: string;
|
|
53
64
|
}
|
|
54
65
|
export interface SocialAuthCallbackPayload {
|
|
55
66
|
payload: BasicPayload & {
|
package/dist/authentication.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import BaseSDK from './base-sdk';
|
|
2
|
+
import { decodeMeUser } from './services/decode-uri-fields';
|
|
2
3
|
function collectSetCookieHeaders(res) {
|
|
3
4
|
const h = res.headers;
|
|
4
5
|
if (typeof h.getSetCookie === 'function') {
|
|
@@ -166,7 +167,7 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
166
167
|
async apiTokenSignin(apiToken) {
|
|
167
168
|
this.#apiToken = apiToken;
|
|
168
169
|
this.qlOptions.apiToken = apiToken;
|
|
169
|
-
return this.callJsonApi('/api/me');
|
|
170
|
+
return this.callJsonApi('/api/me').then(decodeMeUser);
|
|
170
171
|
}
|
|
171
172
|
listApiTokens() {
|
|
172
173
|
return this.callJsonApi('/api/me/api-tokens');
|
|
@@ -192,6 +193,8 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
192
193
|
params.state = options.state;
|
|
193
194
|
if (options.returnUrl)
|
|
194
195
|
params.returnUrl = options.returnUrl;
|
|
196
|
+
if (options.redirectUrl)
|
|
197
|
+
params.redirectUrl = options.redirectUrl;
|
|
195
198
|
const qs = Object.keys(params).length ? `?${new URLSearchParams(params)}` : '';
|
|
196
199
|
return this.buildUrl(`/api/auth/${provider}${qs}`);
|
|
197
200
|
}
|
|
@@ -249,14 +252,14 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
249
252
|
return this.exchangeAuthCallback(refreshToken);
|
|
250
253
|
}
|
|
251
254
|
getLoggedInUser() {
|
|
252
|
-
return this.callJsonApi('/api/me');
|
|
255
|
+
return this.callJsonApi('/api/me').then(decodeMeUser);
|
|
253
256
|
}
|
|
254
257
|
updateLoggedInUser(changes) {
|
|
255
258
|
return this.callJsonApi('/api/me', {
|
|
256
259
|
method: 'post',
|
|
257
260
|
headers: { 'content-type': 'application/json' },
|
|
258
261
|
body: JSON.stringify(changes)
|
|
259
|
-
});
|
|
262
|
+
}).then(decodeMeUser);
|
|
260
263
|
}
|
|
261
264
|
}
|
|
262
265
|
//# sourceMappingURL=authentication.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"authentication.js","sourceRoot":"","sources":["../src/authentication.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"authentication.js","sourceRoot":"","sources":["../src/authentication.ts"],"names":[],"mappings":"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"}
|
package/dist/payments.d.ts
CHANGED
|
@@ -2,11 +2,19 @@ import { QelosSDKOptions } from './types';
|
|
|
2
2
|
import BaseSDK from './base-sdk';
|
|
3
3
|
import { IPlan, ISubscription, IInvoice, ICoupon, BillingCycle } from '@qelos/global-types';
|
|
4
4
|
export interface CheckoutRequest {
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
/** Required when not providing `subscriptionId`. Must be a non-dynamic plan. */
|
|
6
|
+
planId?: string;
|
|
7
|
+
/** Required when not providing `subscriptionId`. */
|
|
8
|
+
billingCycle?: BillingCycle;
|
|
7
9
|
couponCode?: string;
|
|
8
10
|
successUrl?: string;
|
|
9
11
|
cancelUrl?: string;
|
|
12
|
+
/**
|
|
13
|
+
* Pre-created subscription ID. Required for dynamic plans — the admin must set
|
|
14
|
+
* `dynamicAmount` on the subscription before this checkout call will succeed.
|
|
15
|
+
* When provided, `planId` and `billingCycle` are ignored.
|
|
16
|
+
*/
|
|
17
|
+
subscriptionId?: string;
|
|
10
18
|
}
|
|
11
19
|
export interface CheckoutResponse {
|
|
12
20
|
subscriptionId: string;
|
|
@@ -22,6 +30,24 @@ export default class QlPayments extends BaseSDK {
|
|
|
22
30
|
isActive?: boolean;
|
|
23
31
|
}): Promise<IPlan[]>;
|
|
24
32
|
getPlan(planId: string): Promise<IPlan>;
|
|
33
|
+
/**
|
|
34
|
+
* Creates a pending subscription for the authenticated user without initiating
|
|
35
|
+
* payment. Use this as the first step for any plan, and the only step before
|
|
36
|
+
* `checkout()` for dynamic plans (the admin must call `setSubscriptionDynamicAmount`
|
|
37
|
+
* before the user can complete checkout).
|
|
38
|
+
*/
|
|
39
|
+
subscribeToPlan(planId: string, billingCycle: BillingCycle, couponCode?: string): Promise<ISubscription>;
|
|
40
|
+
/**
|
|
41
|
+
* Initiates a provider checkout session.
|
|
42
|
+
*
|
|
43
|
+
* **Static plans**: pass `planId` + `billingCycle` (a subscription is created
|
|
44
|
+
* inline).
|
|
45
|
+
*
|
|
46
|
+
* **Dynamic plans**: the user must first call `subscribeToPlan()` to create a
|
|
47
|
+
* pending subscription, then an admin must set the dynamic amount via
|
|
48
|
+
* `setSubscriptionDynamicAmount()`. Only then should this method be called with
|
|
49
|
+
* `subscriptionId` pointing to that pending subscription.
|
|
50
|
+
*/
|
|
25
51
|
checkout(params: CheckoutRequest): Promise<CheckoutResponse>;
|
|
26
52
|
getMySubscription(): Promise<ISubscription>;
|
|
27
53
|
cancelSubscription(subscriptionId: string): Promise<ISubscription>;
|
package/dist/payments.js
CHANGED
|
@@ -11,6 +11,30 @@ export default class QlPayments extends BaseSDK {
|
|
|
11
11
|
getPlan(planId) {
|
|
12
12
|
return this.callJsonApi(`${this.relativePath}/plans/${planId}`);
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* Creates a pending subscription for the authenticated user without initiating
|
|
16
|
+
* payment. Use this as the first step for any plan, and the only step before
|
|
17
|
+
* `checkout()` for dynamic plans (the admin must call `setSubscriptionDynamicAmount`
|
|
18
|
+
* before the user can complete checkout).
|
|
19
|
+
*/
|
|
20
|
+
subscribeToPlan(planId, billingCycle, couponCode) {
|
|
21
|
+
return this.callJsonApi(`${this.relativePath}/subscriptions`, {
|
|
22
|
+
method: 'post',
|
|
23
|
+
headers: { 'content-type': 'application/json' },
|
|
24
|
+
body: JSON.stringify({ planId, billingCycle, couponCode }),
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Initiates a provider checkout session.
|
|
29
|
+
*
|
|
30
|
+
* **Static plans**: pass `planId` + `billingCycle` (a subscription is created
|
|
31
|
+
* inline).
|
|
32
|
+
*
|
|
33
|
+
* **Dynamic plans**: the user must first call `subscribeToPlan()` to create a
|
|
34
|
+
* pending subscription, then an admin must set the dynamic amount via
|
|
35
|
+
* `setSubscriptionDynamicAmount()`. Only then should this method be called with
|
|
36
|
+
* `subscriptionId` pointing to that pending subscription.
|
|
37
|
+
*/
|
|
14
38
|
checkout(params) {
|
|
15
39
|
return this.callJsonApi(`${this.relativePath}/checkout`, {
|
|
16
40
|
method: 'post',
|
package/dist/payments.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"payments.js","sourceRoot":"","sources":["../src/payments.ts"],"names":[],"mappings":"AACA,OAAO,OAAO,MAAM,YAAY,CAAC;
|
|
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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Mirrors auth `encodeIfNeeded` — only decodes values that are URI-encoded. */
|
|
2
|
+
export declare function decodeIfNeeded(value?: string): string | undefined;
|
|
3
|
+
/** Decodes URI-encoded user fields returned by `GET/POST /api/me`. */
|
|
4
|
+
export declare function decodeMeUser<T extends Record<string, unknown>>(user: T): T;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** Mirrors auth `encodeIfNeeded` — only decodes values that are URI-encoded. */
|
|
2
|
+
export function decodeIfNeeded(value) {
|
|
3
|
+
if (value === undefined || value === null || value === '') {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
try {
|
|
7
|
+
const decoded = decodeURIComponent(value);
|
|
8
|
+
if (encodeURIComponent(decoded) === value) {
|
|
9
|
+
return decoded;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
// not a valid encoded URI component
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
const ME_USER_STRING_FIELDS = ['name', 'fullName', 'firstName', 'lastName'];
|
|
18
|
+
/** Decodes URI-encoded user fields returned by `GET/POST /api/me`. */
|
|
19
|
+
export function decodeMeUser(user) {
|
|
20
|
+
if (!user || typeof user !== 'object') {
|
|
21
|
+
return user;
|
|
22
|
+
}
|
|
23
|
+
const result = { ...user };
|
|
24
|
+
for (const field of ME_USER_STRING_FIELDS) {
|
|
25
|
+
const value = result[field];
|
|
26
|
+
if (typeof value === 'string') {
|
|
27
|
+
result[field] = decodeIfNeeded(value);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const workspace = result.workspace;
|
|
31
|
+
if (workspace && typeof workspace === 'object' && !Array.isArray(workspace)) {
|
|
32
|
+
const ws = workspace;
|
|
33
|
+
if (typeof ws.name === 'string') {
|
|
34
|
+
result.workspace = {
|
|
35
|
+
...ws,
|
|
36
|
+
name: decodeIfNeeded(ws.name),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=decode-uri-fields.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"decode-uri-fields.js","sourceRoot":"","sources":["../../src/services/decode-uri-fields.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,MAAM,UAAU,cAAc,CAAC,KAAc;IAC3C,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,EAAE,EAAE;QACzD,OAAO,KAAK,CAAC;KACd;IAED,IAAI;QACF,MAAM,OAAO,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,kBAAkB,CAAC,OAAO,CAAC,KAAK,KAAK,EAAE;YACzC,OAAO,OAAO,CAAC;SAChB;KACF;IAAC,MAAM;QACN,oCAAoC;KACrC;IAED,OAAO,KAAK,CAAC;AACf,CAAC;AAED,MAAM,qBAAqB,GAAG,CAAC,MAAM,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,CAAU,CAAC;AAErF,sEAAsE;AACtE,MAAM,UAAU,YAAY,CAAoC,IAAO;IACrE,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE;QACrC,OAAO,IAAI,CAAC;KACb;IAED,MAAM,MAAM,GAAG,EAAE,GAAG,IAAI,EAAE,CAAC;IAE3B,KAAK,MAAM,KAAK,IAAI,qBAAqB,EAAE;QACzC,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC5B,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE;YAC5B,MAAkC,CAAC,KAAK,CAAC,GAAG,cAAc,CAAC,KAAK,CAAC,CAAC;SACpE;KACF;IAED,MAAM,SAAS,GAAG,MAAM,CAAC,SAAS,CAAC;IACnC,IAAI,SAAS,IAAI,OAAO,SAAS,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,SAAS,CAAC,EAAE;QAC3E,MAAM,EAAE,GAAG,SAAoC,CAAC;QAChD,IAAI,OAAO,EAAE,CAAC,IAAI,KAAK,QAAQ,EAAE;YAC9B,MAAkC,CAAC,SAAS,GAAG;gBAC9C,GAAG,EAAE;gBACL,IAAI,EAAE,cAAc,CAAC,EAAE,CAAC,IAAI,CAAC;aAC9B,CAAC;SACH;KACF;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/docs/payments.md
ADDED
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
# Payments SDK
|
|
2
|
+
|
|
3
|
+
The payments SDK is split into two classes:
|
|
4
|
+
|
|
5
|
+
- **`QlPayments`** (`sdk.payments`) — available to any authenticated user. Covers subscribing, checkout, invoices, and coupon validation. Users cannot set prices.
|
|
6
|
+
- **`QlPaymentsAdmin`** (`adminSdk.payments`) — available to privileged callers only. Extends the user surface with plan/coupon management, cross-entity subscription control, and the ability to set dynamic amounts.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Setup
|
|
11
|
+
|
|
12
|
+
```typescript
|
|
13
|
+
import { QelosSDK } from '@qelos/sdk';
|
|
14
|
+
import { QelosAdminSDK } from '@qelos/sdk/administrator';
|
|
15
|
+
|
|
16
|
+
const sdk = new QelosSDK({ baseUrl: 'https://your-qelos-instance.com' });
|
|
17
|
+
const adminSdk = new QelosAdminSDK({ baseUrl: 'https://your-qelos-instance.com' });
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Static Plan Checkout (One Step)
|
|
23
|
+
|
|
24
|
+
For plans where the price is fixed, the user can subscribe and pay in a single `checkout()` call. Qelos creates the pending subscription internally.
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// List available plans
|
|
28
|
+
const plans = await sdk.payments.getPlans({ isActive: true });
|
|
29
|
+
const plan = plans.find(p => p.name === 'Pro');
|
|
30
|
+
|
|
31
|
+
// Validate an optional coupon
|
|
32
|
+
const coupon = await sdk.payments.validateCoupon('SUMMER20', plan._id);
|
|
33
|
+
|
|
34
|
+
// Initiate checkout — subscription is created inline
|
|
35
|
+
const result = await sdk.payments.checkout({
|
|
36
|
+
planId: plan._id,
|
|
37
|
+
billingCycle: 'monthly',
|
|
38
|
+
couponCode: 'SUMMER20',
|
|
39
|
+
successUrl: 'https://myapp.com/success',
|
|
40
|
+
cancelUrl: 'https://myapp.com/cancel',
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
// Redirect the user
|
|
44
|
+
window.location.href = result.checkoutUrl;
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
49
|
+
## Dynamic Plan Two-Phase Flow
|
|
50
|
+
|
|
51
|
+
Dynamic plans have variable pricing (e.g., usage-based). The user cannot set the amount — only an admin can. The flow is:
|
|
52
|
+
|
|
53
|
+
1. User creates a pending subscription.
|
|
54
|
+
2. Admin reviews usage / context and sets the amount.
|
|
55
|
+
3. User initiates checkout with the subscription ID.
|
|
56
|
+
|
|
57
|
+
### Step 1 — User subscribes
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const subscription = await sdk.payments.subscribeToPlan(
|
|
61
|
+
'dynamic-plan-id',
|
|
62
|
+
'monthly',
|
|
63
|
+
);
|
|
64
|
+
// subscription.status === 'pending'
|
|
65
|
+
// Store subscription._id and show the user a "waiting for quote" state
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Step 2 — Admin sets the amount
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
// Admin SDK — run server-side or in the admin dashboard
|
|
72
|
+
await adminSdk.payments.setSubscriptionDynamicAmount(
|
|
73
|
+
subscription._id,
|
|
74
|
+
149.00, // amount in the plan's currency
|
|
75
|
+
);
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Step 3 — User completes checkout
|
|
79
|
+
|
|
80
|
+
```typescript
|
|
81
|
+
const result = await sdk.payments.checkout({
|
|
82
|
+
subscriptionId: subscription._id,
|
|
83
|
+
successUrl: 'https://myapp.com/success',
|
|
84
|
+
cancelUrl: 'https://myapp.com/cancel',
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
window.location.href = result.checkoutUrl;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Admin convenience shortcut
|
|
91
|
+
|
|
92
|
+
Admins can collapse steps 1–3 into a single call. This creates a pending subscription with the given amount and immediately initiates checkout:
|
|
93
|
+
|
|
94
|
+
```typescript
|
|
95
|
+
const result = await adminSdk.payments.checkout({
|
|
96
|
+
planId: 'dynamic-plan-id',
|
|
97
|
+
billingCycle: 'monthly',
|
|
98
|
+
amount: 149.00,
|
|
99
|
+
billableEntityType: 'workspace',
|
|
100
|
+
billableEntityId: 'workspace-id',
|
|
101
|
+
successUrl: 'https://myapp.com/success',
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Listing Plans and Subscriptions
|
|
108
|
+
|
|
109
|
+
### User SDK
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// Public plan listing (unauthenticated)
|
|
113
|
+
const plans = await sdk.payments.getPlans({ isActive: true });
|
|
114
|
+
|
|
115
|
+
// Single plan
|
|
116
|
+
const plan = await sdk.payments.getPlan('plan-id');
|
|
117
|
+
|
|
118
|
+
// Current user's active subscription
|
|
119
|
+
const subscription = await sdk.payments.getMySubscription();
|
|
120
|
+
|
|
121
|
+
// User's invoices
|
|
122
|
+
const invoices = await sdk.payments.getInvoices();
|
|
123
|
+
const invoice = await sdk.payments.getInvoice('invoice-id');
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Admin SDK
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
// All plans (including inactive)
|
|
130
|
+
const allPlans = await adminSdk.payments.getPlans();
|
|
131
|
+
|
|
132
|
+
// All subscriptions with optional filters
|
|
133
|
+
const workspaceSubs = await adminSdk.payments.getSubscriptions({
|
|
134
|
+
billableEntityType: 'workspace',
|
|
135
|
+
billableEntityId: 'workspace-id',
|
|
136
|
+
status: 'active',
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// All invoices across entities
|
|
140
|
+
const invoices = await adminSdk.payments.getInvoices({
|
|
141
|
+
billableEntityType: 'user',
|
|
142
|
+
billableEntityId: 'user-id',
|
|
143
|
+
status: 'paid',
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## Coupon Validation
|
|
150
|
+
|
|
151
|
+
Validate a coupon before showing it applied in the UI:
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
try {
|
|
155
|
+
const coupon = await sdk.payments.validateCoupon('PROMO10', 'plan-id');
|
|
156
|
+
console.log(coupon.discountType); // 'percentage' | 'fixed'
|
|
157
|
+
console.log(coupon.discountValue); // e.g. 10 (%)
|
|
158
|
+
} catch (err) {
|
|
159
|
+
// err.code: COUPON_NOT_FOUND | COUPON_EXPIRED | COUPON_NOT_APPLICABLE | ...
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Invoice Retrieval
|
|
166
|
+
|
|
167
|
+
```typescript
|
|
168
|
+
// User's own invoices
|
|
169
|
+
const invoices = await sdk.payments.getInvoices({ status: 'paid' });
|
|
170
|
+
|
|
171
|
+
// Single invoice (e.g. for download link)
|
|
172
|
+
const invoice = await sdk.payments.getInvoice('invoice-id');
|
|
173
|
+
console.log(invoice.invoiceUrl);
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Cancellation
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// User cancels their own subscription
|
|
182
|
+
await sdk.payments.cancelSubscription('subscription-id');
|
|
183
|
+
|
|
184
|
+
// Admin cancels any subscription
|
|
185
|
+
await adminSdk.payments.cancelSubscription('subscription-id');
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
---
|
|
189
|
+
|
|
190
|
+
## Admin Plan Management
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
// Create a plan
|
|
194
|
+
const plan = await adminSdk.payments.createPlan({
|
|
195
|
+
name: 'Enterprise',
|
|
196
|
+
monthlyPrice: 299,
|
|
197
|
+
yearlyPrice: 2990,
|
|
198
|
+
currency: 'USD',
|
|
199
|
+
dynamic: false,
|
|
200
|
+
isActive: true,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Create a dynamic plan
|
|
204
|
+
const dynamicPlan = await adminSdk.payments.createPlan({
|
|
205
|
+
name: 'Usage-Based',
|
|
206
|
+
currency: 'USD',
|
|
207
|
+
dynamic: true,
|
|
208
|
+
isActive: true,
|
|
209
|
+
monthlyPrice: 0,
|
|
210
|
+
yearlyPrice: 0,
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Update
|
|
214
|
+
await adminSdk.payments.updatePlan(plan._id, { monthlyPrice: 349 });
|
|
215
|
+
|
|
216
|
+
// Delete
|
|
217
|
+
await adminSdk.payments.deletePlan(plan._id);
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
## Admin Coupon Management
|
|
223
|
+
|
|
224
|
+
```typescript
|
|
225
|
+
// Create a 20% off coupon
|
|
226
|
+
const coupon = await adminSdk.payments.createCoupon({
|
|
227
|
+
code: 'SUMMER20',
|
|
228
|
+
discountType: 'percentage',
|
|
229
|
+
discountValue: 20,
|
|
230
|
+
isActive: true,
|
|
231
|
+
applicablePlanIds: ['plan-id'],
|
|
232
|
+
maxRedemptions: 100,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Update
|
|
236
|
+
await adminSdk.payments.updateCoupon(coupon._id, { isActive: false });
|
|
237
|
+
|
|
238
|
+
// Delete
|
|
239
|
+
await adminSdk.payments.deleteCoupon(coupon._id);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## `QlPayments` Reference (User SDK)
|
|
245
|
+
|
|
246
|
+
| Method | Description |
|
|
247
|
+
|---|---|
|
|
248
|
+
| `getPlans(query?)` | List public plans |
|
|
249
|
+
| `getPlan(planId)` | Get a single plan |
|
|
250
|
+
| `subscribeToPlan(planId, billingCycle, couponCode?)` | Create a pending subscription (first step for dynamic plans) |
|
|
251
|
+
| `checkout(params)` | Initiate a checkout session |
|
|
252
|
+
| `getMySubscription()` | Get the current user's active subscription |
|
|
253
|
+
| `cancelSubscription(subscriptionId)` | Cancel a subscription |
|
|
254
|
+
| `getInvoices(query?)` | List the current user's invoices |
|
|
255
|
+
| `getInvoice(invoiceId)` | Get a single invoice |
|
|
256
|
+
| `validateCoupon(code, planId?)` | Validate a coupon code |
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## `QlPaymentsAdmin` Reference (Admin SDK)
|
|
261
|
+
|
|
262
|
+
| Method | Description |
|
|
263
|
+
|---|---|
|
|
264
|
+
| `getPlans(query?)` | List all plans |
|
|
265
|
+
| `getPlan(planId)` | Get a single plan |
|
|
266
|
+
| `createPlan(data)` | Create a plan |
|
|
267
|
+
| `updatePlan(planId, data)` | Update a plan |
|
|
268
|
+
| `deletePlan(planId)` | Delete a plan |
|
|
269
|
+
| `checkout(params)` | Initiate checkout with entity overrides and optional `amount` |
|
|
270
|
+
| `getSubscriptions(query?)` | List subscriptions across all entities |
|
|
271
|
+
| `getSubscription(subscriptionId)` | Get a single subscription |
|
|
272
|
+
| `createSubscription(data)` | Create a pending subscription on behalf of any entity |
|
|
273
|
+
| `cancelSubscription(subscriptionId)` | Cancel any subscription |
|
|
274
|
+
| `setSubscriptionDynamicAmount(subscriptionId, amount)` | Set or update the dynamic amount on a pending subscription |
|
|
275
|
+
| `getInvoices(query?)` | List invoices across all entities |
|
|
276
|
+
| `getInvoice(invoiceId)` | Get a single invoice |
|
|
277
|
+
| `getCoupons(query?)` | List coupons |
|
|
278
|
+
| `getCoupon(couponId)` | Get a single coupon |
|
|
279
|
+
| `createCoupon(data)` | Create a coupon |
|
|
280
|
+
| `updateCoupon(couponId, data)` | Update a coupon |
|
|
281
|
+
| `deleteCoupon(couponId)` | Delete a coupon |
|
|
282
|
+
|
|
283
|
+
---
|
|
284
|
+
|
|
285
|
+
## Permissions Summary
|
|
286
|
+
|
|
287
|
+
| Action | User SDK | Admin SDK |
|
|
288
|
+
|---|---|---|
|
|
289
|
+
| List/get plans | public plans only | all plans |
|
|
290
|
+
| Create/update/delete plans | no | yes |
|
|
291
|
+
| Create subscription (own entity) | yes | yes |
|
|
292
|
+
| Create subscription (any entity) | no | yes |
|
|
293
|
+
| Set `dynamicAmount` | no | yes |
|
|
294
|
+
| Initiate checkout | yes | yes, with entity override |
|
|
295
|
+
| Set checkout `amount` directly | no | yes (dynamic plans) |
|
|
296
|
+
| List own subscriptions/invoices | yes | yes |
|
|
297
|
+
| List all subscriptions/invoices | no | yes |
|
|
298
|
+
| Cancel own subscription | yes | yes |
|
|
299
|
+
| Cancel any subscription | no | yes |
|
|
300
|
+
| Validate coupon | yes | yes |
|
|
301
|
+
| Manage coupons | no | yes |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qelos/sdk",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"description": "SDK for Qelos API",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -102,7 +102,7 @@
|
|
|
102
102
|
"access": "public"
|
|
103
103
|
},
|
|
104
104
|
"dependencies": {
|
|
105
|
-
"@qelos/global-types": "^4.
|
|
105
|
+
"@qelos/global-types": "^4.1.1"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
108
|
"tsx": "^4.21.0",
|
|
@@ -2,9 +2,42 @@ import { QelosSDKOptions } from '../types';
|
|
|
2
2
|
import BaseSDK from '../base-sdk';
|
|
3
3
|
import {
|
|
4
4
|
IPlan, ISubscription, IInvoice, ICoupon,
|
|
5
|
-
BillableEntityType, SubscriptionStatus, InvoiceStatus,
|
|
5
|
+
BillableEntityType, SubscriptionStatus, InvoiceStatus, BillingCycle,
|
|
6
6
|
} from '@qelos/global-types';
|
|
7
7
|
|
|
8
|
+
export interface AdminCheckoutRequest {
|
|
9
|
+
planId: string;
|
|
10
|
+
billingCycle: BillingCycle;
|
|
11
|
+
/** When omitted, the authenticated user / default billable entity is used (same as public checkout). */
|
|
12
|
+
billableEntityType?: BillableEntityType;
|
|
13
|
+
billableEntityId?: string;
|
|
14
|
+
couponCode?: string;
|
|
15
|
+
successUrl?: string;
|
|
16
|
+
cancelUrl?: string;
|
|
17
|
+
/**
|
|
18
|
+
* Admin-only. For plans with `dynamic: true`, sets the `dynamicAmount` on the
|
|
19
|
+
* pending subscription before initiating checkout. Regular users cannot set this;
|
|
20
|
+
* use `setSubscriptionDynamicAmount()` + a separate `checkout()` call instead.
|
|
21
|
+
*/
|
|
22
|
+
amount?: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface CreateSubscriptionData {
|
|
26
|
+
planId: string;
|
|
27
|
+
billingCycle: BillingCycle;
|
|
28
|
+
billableEntityType: BillableEntityType;
|
|
29
|
+
billableEntityId: string;
|
|
30
|
+
/** Required for plans with `dynamic: true` before checkout can be initiated. */
|
|
31
|
+
dynamicAmount?: number;
|
|
32
|
+
couponCode?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface AdminCheckoutResponse {
|
|
36
|
+
subscriptionId: string;
|
|
37
|
+
checkoutUrl?: string;
|
|
38
|
+
clientToken?: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
8
41
|
export default class QlPaymentsAdmin extends BaseSDK {
|
|
9
42
|
constructor(private options: QelosSDKOptions) {
|
|
10
43
|
super(options);
|
|
@@ -41,6 +74,18 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
41
74
|
return this.callJsonApi<IPlan>(`/api/plans/${planId}`, { method: 'delete' });
|
|
42
75
|
}
|
|
43
76
|
|
|
77
|
+
/**
|
|
78
|
+
* Start subscription checkout (same as `sdk.payments.checkout`, plus optional billable-entity overrides for admins).
|
|
79
|
+
* For plans with `dynamic: true`, `amount` is required.
|
|
80
|
+
*/
|
|
81
|
+
checkout(params: AdminCheckoutRequest) {
|
|
82
|
+
return this.callJsonApi<AdminCheckoutResponse>('/api/checkout', {
|
|
83
|
+
method: 'post',
|
|
84
|
+
headers: { 'content-type': 'application/json' },
|
|
85
|
+
body: JSON.stringify(params),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
|
|
44
89
|
// --- Subscriptions ---
|
|
45
90
|
|
|
46
91
|
getSubscriptions(query?: {
|
|
@@ -56,6 +101,19 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
56
101
|
return this.callJsonApi<ISubscription>(`/api/subscriptions/${subscriptionId}`);
|
|
57
102
|
}
|
|
58
103
|
|
|
104
|
+
/**
|
|
105
|
+
* Creates a pending subscription on behalf of any billable entity. Admins can
|
|
106
|
+
* also set `dynamicAmount` here for dynamic plans, making the subscription
|
|
107
|
+
* immediately ready for checkout.
|
|
108
|
+
*/
|
|
109
|
+
createSubscription(data: CreateSubscriptionData) {
|
|
110
|
+
return this.callJsonApi<ISubscription>('/api/subscriptions', {
|
|
111
|
+
method: 'post',
|
|
112
|
+
headers: { 'content-type': 'application/json' },
|
|
113
|
+
body: JSON.stringify(data),
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
|
|
59
117
|
cancelSubscription(subscriptionId: string) {
|
|
60
118
|
return this.callJsonApi<ISubscription>(
|
|
61
119
|
`/api/subscriptions/${subscriptionId}/cancel`,
|
|
@@ -63,6 +121,21 @@ export default class QlPaymentsAdmin extends BaseSDK {
|
|
|
63
121
|
);
|
|
64
122
|
}
|
|
65
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Sets or updates the dynamic amount for a pending subscription. Only admins
|
|
126
|
+
* can call this. Must be done before a user can complete checkout on a dynamic plan.
|
|
127
|
+
*/
|
|
128
|
+
setSubscriptionDynamicAmount(subscriptionId: string, amount: number) {
|
|
129
|
+
return this.callJsonApi<ISubscription>(
|
|
130
|
+
`/api/subscriptions/${subscriptionId}/dynamic-amount`,
|
|
131
|
+
{
|
|
132
|
+
method: 'put',
|
|
133
|
+
headers: { 'content-type': 'application/json' },
|
|
134
|
+
body: JSON.stringify({ amount }),
|
|
135
|
+
},
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
66
139
|
// --- Invoices ---
|
|
67
140
|
|
|
68
141
|
getInvoices(query?: {
|
package/src/authentication.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { QelosSDKOptions } from './types';
|
|
2
2
|
import BaseSDK from './base-sdk';
|
|
3
|
+
import { decodeMeUser } from './services/decode-uri-fields';
|
|
3
4
|
|
|
4
5
|
export interface IUser {
|
|
5
6
|
_id: string;
|
|
@@ -54,7 +55,18 @@ export type SocialProvider = 'linkedin' | 'facebook' | 'google' | 'github';
|
|
|
54
55
|
|
|
55
56
|
export interface SocialLoginOptions {
|
|
56
57
|
state?: string;
|
|
58
|
+
/**
|
|
59
|
+
* Where Qelos redirects after OAuth with `?rt=<refreshToken>` (SDK flow).
|
|
60
|
+
* Absolute URLs must use a host listed in app-configuration `metadata.websiteUrls`.
|
|
61
|
+
*/
|
|
57
62
|
returnUrl?: string;
|
|
63
|
+
/**
|
|
64
|
+
* Overrides the OAuth provider callback origin when its host is listed in
|
|
65
|
+
* app-configuration `metadata.websiteUrls`. The callback path stays
|
|
66
|
+
* `/api/auth/<provider>/callback` on that origin. When omitted, Qelos uses
|
|
67
|
+
* the request tenant host (`tenanthost`).
|
|
68
|
+
*/
|
|
69
|
+
redirectUrl?: string;
|
|
58
70
|
}
|
|
59
71
|
|
|
60
72
|
export interface SocialAuthCallbackPayload {
|
|
@@ -271,7 +283,7 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
271
283
|
async apiTokenSignin(apiToken: string): Promise<IUser> {
|
|
272
284
|
this.#apiToken = apiToken;
|
|
273
285
|
this.qlOptions.apiToken = apiToken;
|
|
274
|
-
return this.callJsonApi<IUser>('/api/me');
|
|
286
|
+
return this.callJsonApi<IUser>('/api/me').then(decodeMeUser);
|
|
275
287
|
}
|
|
276
288
|
|
|
277
289
|
listApiTokens(): Promise<IApiToken[]> {
|
|
@@ -300,6 +312,7 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
300
312
|
const params: Record<string, string> = {};
|
|
301
313
|
if (options.state) params.state = options.state;
|
|
302
314
|
if (options.returnUrl) params.returnUrl = options.returnUrl;
|
|
315
|
+
if (options.redirectUrl) params.redirectUrl = options.redirectUrl;
|
|
303
316
|
const qs = Object.keys(params).length ? `?${new URLSearchParams(params)}` : '';
|
|
304
317
|
return this.buildUrl(`/api/auth/${provider}${qs}`);
|
|
305
318
|
}
|
|
@@ -364,7 +377,7 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
364
377
|
}
|
|
365
378
|
|
|
366
379
|
getLoggedInUser() {
|
|
367
|
-
return this.callJsonApi<IUser>('/api/me')
|
|
380
|
+
return this.callJsonApi<IUser>('/api/me').then(decodeMeUser);
|
|
368
381
|
}
|
|
369
382
|
|
|
370
383
|
updateLoggedInUser(changes: Partial<IUser & { password?: string }>): Promise<IUser> {
|
|
@@ -372,6 +385,6 @@ export default class QlAuthentication extends BaseSDK {
|
|
|
372
385
|
method: 'post',
|
|
373
386
|
headers: { 'content-type': 'application/json' },
|
|
374
387
|
body: JSON.stringify(changes)
|
|
375
|
-
})
|
|
388
|
+
}).then(decodeMeUser);
|
|
376
389
|
}
|
|
377
390
|
}
|
package/src/payments.ts
CHANGED
|
@@ -3,11 +3,19 @@ import BaseSDK from './base-sdk';
|
|
|
3
3
|
import { IPlan, ISubscription, IInvoice, ICoupon, BillingCycle } from '@qelos/global-types';
|
|
4
4
|
|
|
5
5
|
export interface CheckoutRequest {
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
/** Required when not providing `subscriptionId`. Must be a non-dynamic plan. */
|
|
7
|
+
planId?: string;
|
|
8
|
+
/** Required when not providing `subscriptionId`. */
|
|
9
|
+
billingCycle?: BillingCycle;
|
|
8
10
|
couponCode?: string;
|
|
9
11
|
successUrl?: string;
|
|
10
12
|
cancelUrl?: string;
|
|
13
|
+
/**
|
|
14
|
+
* Pre-created subscription ID. Required for dynamic plans — the admin must set
|
|
15
|
+
* `dynamicAmount` on the subscription before this checkout call will succeed.
|
|
16
|
+
* When provided, `planId` and `billingCycle` are ignored.
|
|
17
|
+
*/
|
|
18
|
+
subscriptionId?: string;
|
|
11
19
|
}
|
|
12
20
|
|
|
13
21
|
export interface CheckoutResponse {
|
|
@@ -34,6 +42,34 @@ export default class QlPayments extends BaseSDK {
|
|
|
34
42
|
return this.callJsonApi<IPlan>(`${this.relativePath}/plans/${planId}`);
|
|
35
43
|
}
|
|
36
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Creates a pending subscription for the authenticated user without initiating
|
|
47
|
+
* payment. Use this as the first step for any plan, and the only step before
|
|
48
|
+
* `checkout()` for dynamic plans (the admin must call `setSubscriptionDynamicAmount`
|
|
49
|
+
* before the user can complete checkout).
|
|
50
|
+
*/
|
|
51
|
+
subscribeToPlan(planId: string, billingCycle: BillingCycle, couponCode?: string) {
|
|
52
|
+
return this.callJsonApi<ISubscription>(
|
|
53
|
+
`${this.relativePath}/subscriptions`,
|
|
54
|
+
{
|
|
55
|
+
method: 'post',
|
|
56
|
+
headers: { 'content-type': 'application/json' },
|
|
57
|
+
body: JSON.stringify({ planId, billingCycle, couponCode }),
|
|
58
|
+
},
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Initiates a provider checkout session.
|
|
64
|
+
*
|
|
65
|
+
* **Static plans**: pass `planId` + `billingCycle` (a subscription is created
|
|
66
|
+
* inline).
|
|
67
|
+
*
|
|
68
|
+
* **Dynamic plans**: the user must first call `subscribeToPlan()` to create a
|
|
69
|
+
* pending subscription, then an admin must set the dynamic amount via
|
|
70
|
+
* `setSubscriptionDynamicAmount()`. Only then should this method be called with
|
|
71
|
+
* `subscriptionId` pointing to that pending subscription.
|
|
72
|
+
*/
|
|
37
73
|
checkout(params: CheckoutRequest) {
|
|
38
74
|
return this.callJsonApi<CheckoutResponse>(
|
|
39
75
|
`${this.relativePath}/checkout`,
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** Mirrors auth `encodeIfNeeded` — only decodes values that are URI-encoded. */
|
|
2
|
+
export function decodeIfNeeded(value?: string): string | undefined {
|
|
3
|
+
if (value === undefined || value === null || value === '') {
|
|
4
|
+
return value;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
try {
|
|
8
|
+
const decoded = decodeURIComponent(value);
|
|
9
|
+
if (encodeURIComponent(decoded) === value) {
|
|
10
|
+
return decoded;
|
|
11
|
+
}
|
|
12
|
+
} catch {
|
|
13
|
+
// not a valid encoded URI component
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return value;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const ME_USER_STRING_FIELDS = ['name', 'fullName', 'firstName', 'lastName'] as const;
|
|
20
|
+
|
|
21
|
+
/** Decodes URI-encoded user fields returned by `GET/POST /api/me`. */
|
|
22
|
+
export function decodeMeUser<T extends Record<string, unknown>>(user: T): T {
|
|
23
|
+
if (!user || typeof user !== 'object') {
|
|
24
|
+
return user;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const result = { ...user };
|
|
28
|
+
|
|
29
|
+
for (const field of ME_USER_STRING_FIELDS) {
|
|
30
|
+
const value = result[field];
|
|
31
|
+
if (typeof value === 'string') {
|
|
32
|
+
(result as Record<string, unknown>)[field] = decodeIfNeeded(value);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const workspace = result.workspace;
|
|
37
|
+
if (workspace && typeof workspace === 'object' && !Array.isArray(workspace)) {
|
|
38
|
+
const ws = workspace as Record<string, unknown>;
|
|
39
|
+
if (typeof ws.name === 'string') {
|
|
40
|
+
(result as Record<string, unknown>).workspace = {
|
|
41
|
+
...ws,
|
|
42
|
+
name: decodeIfNeeded(ws.name),
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import * as assert from 'node:assert/strict';
|
|
2
|
+
import { test } from 'node:test';
|
|
3
|
+
import { decodeIfNeeded, decodeMeUser } from '../src/services/decode-uri-fields';
|
|
4
|
+
|
|
5
|
+
test('decodeIfNeeded', async (t) => {
|
|
6
|
+
await t.test('decodes URI-encoded values', () => {
|
|
7
|
+
assert.equal(decodeIfNeeded('David%20Meir-Levy'), 'David Meir-Levy');
|
|
8
|
+
assert.equal(
|
|
9
|
+
decodeIfNeeded(encodeURIComponent('My Workspace / Special & Name')),
|
|
10
|
+
'My Workspace / Special & Name',
|
|
11
|
+
);
|
|
12
|
+
assert.equal(decodeIfNeeded(encodeURIComponent('José García')), 'José García');
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
await t.test('leaves plain strings unchanged', () => {
|
|
16
|
+
assert.equal(decodeIfNeeded('David Meir-Levy'), 'David Meir-Levy');
|
|
17
|
+
assert.equal(decodeIfNeeded('user@example.com'), 'user@example.com');
|
|
18
|
+
assert.equal(decodeIfNeeded('100% legit'), '100% legit');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
await t.test('passes through empty and undefined values', () => {
|
|
22
|
+
assert.equal(decodeIfNeeded(''), '');
|
|
23
|
+
assert.equal(decodeIfNeeded(undefined), undefined);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('decodeMeUser', async (t) => {
|
|
28
|
+
await t.test('decodes user and workspace name fields', () => {
|
|
29
|
+
const encoded = {
|
|
30
|
+
_id: 'user-1',
|
|
31
|
+
name: 'David%20Meir-Levy',
|
|
32
|
+
fullName: 'David%20Meir-Levy',
|
|
33
|
+
firstName: 'David',
|
|
34
|
+
lastName: 'Meir-Levy',
|
|
35
|
+
workspace: {
|
|
36
|
+
_id: 'ws-1',
|
|
37
|
+
name: encodeURIComponent('My Workspace / Special & Name'),
|
|
38
|
+
roles: ['admin'],
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const decoded = decodeMeUser(encoded);
|
|
43
|
+
|
|
44
|
+
assert.equal(decoded.name, 'David Meir-Levy');
|
|
45
|
+
assert.equal(decoded.fullName, 'David Meir-Levy');
|
|
46
|
+
assert.equal(decoded.firstName, 'David');
|
|
47
|
+
assert.equal(decoded.lastName, 'Meir-Levy');
|
|
48
|
+
assert.equal(decoded.workspace.name, 'My Workspace / Special & Name');
|
|
49
|
+
assert.equal(decoded._id, 'user-1');
|
|
50
|
+
assert.deepEqual(decoded.workspace.roles, ['admin']);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
await t.test('leaves non-encoded fields unchanged', () => {
|
|
54
|
+
const user = {
|
|
55
|
+
_id: 'user-1',
|
|
56
|
+
username: 'david@example.com',
|
|
57
|
+
email: 'david@example.com',
|
|
58
|
+
name: 'David Meir-Levy',
|
|
59
|
+
fullName: 'David Meir-Levy',
|
|
60
|
+
firstName: 'David',
|
|
61
|
+
lastName: 'Meir-Levy',
|
|
62
|
+
roles: ['user'],
|
|
63
|
+
metadata: {},
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
assert.deepEqual(decodeMeUser(user), user);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('getLoggedInUser decodes /api/me response', async (t) => {
|
|
71
|
+
const { default: QelosSDK } = await import('../src/index');
|
|
72
|
+
|
|
73
|
+
await t.test('returns decoded user fields', async () => {
|
|
74
|
+
const sdk = new QelosSDK({
|
|
75
|
+
appUrl: 'http://localhost:3000',
|
|
76
|
+
accessToken: 'test-token',
|
|
77
|
+
fetch: async () => {
|
|
78
|
+
const res = new Response(
|
|
79
|
+
JSON.stringify({
|
|
80
|
+
_id: '6a33dcba2c798875d68aa8ab',
|
|
81
|
+
tenant: '6a280e991bbd4078edefe097',
|
|
82
|
+
username: 'david@davidlevy.co.il',
|
|
83
|
+
email: 'david@davidlevy.co.il',
|
|
84
|
+
name: 'David%20Meir-Levy',
|
|
85
|
+
fullName: 'David%20Meir-Levy',
|
|
86
|
+
firstName: 'David',
|
|
87
|
+
lastName: 'Meir-Levy',
|
|
88
|
+
roles: ['user'],
|
|
89
|
+
metadata: {},
|
|
90
|
+
workspace: {
|
|
91
|
+
_id: 'ws-1',
|
|
92
|
+
name: encodeURIComponent('Acme / Dev'),
|
|
93
|
+
roles: ['admin'],
|
|
94
|
+
},
|
|
95
|
+
}),
|
|
96
|
+
);
|
|
97
|
+
res.headers.set('Content-Type', 'application/json');
|
|
98
|
+
return res;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const user = await sdk.authentication.getLoggedInUser();
|
|
103
|
+
|
|
104
|
+
assert.equal(user.name, 'David Meir-Levy');
|
|
105
|
+
assert.equal(user.fullName, 'David Meir-Levy');
|
|
106
|
+
assert.equal(user.workspace.name, 'Acme / Dev');
|
|
107
|
+
assert.equal(user.username, 'david@davidlevy.co.il');
|
|
108
|
+
});
|
|
109
|
+
});
|
|
@@ -28,6 +28,30 @@ test('parseSocialCallbackRefreshToken', async (t) => {
|
|
|
28
28
|
});
|
|
29
29
|
});
|
|
30
30
|
|
|
31
|
+
test('getSocialLoginUrl', async (t) => {
|
|
32
|
+
const { default: QelosSDK } = await import('../src/index');
|
|
33
|
+
|
|
34
|
+
await t.test('builds provider URL with state, returnUrl, and redirectUrl', () => {
|
|
35
|
+
const sdk = new QelosSDK({ appUrl: 'https://gateway.example.com' });
|
|
36
|
+
const url = sdk.authentication.getSocialLoginUrl('google', {
|
|
37
|
+
state: 'csrf-1',
|
|
38
|
+
returnUrl: 'https://app.example.com/auth/finish',
|
|
39
|
+
redirectUrl: 'https://app.example.com',
|
|
40
|
+
});
|
|
41
|
+
const parsed = new URL(url);
|
|
42
|
+
assert.equal(parsed.origin, 'https://gateway.example.com');
|
|
43
|
+
assert.equal(parsed.pathname, '/api/auth/google');
|
|
44
|
+
assert.equal(parsed.searchParams.get('state'), 'csrf-1');
|
|
45
|
+
assert.equal(parsed.searchParams.get('returnUrl'), 'https://app.example.com/auth/finish');
|
|
46
|
+
assert.equal(parsed.searchParams.get('redirectUrl'), 'https://app.example.com');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
await t.test('omits query string when no options', () => {
|
|
50
|
+
const sdk = new QelosSDK({ appUrl: 'http://localhost:3000' });
|
|
51
|
+
assert.equal(sdk.authentication.getSocialLoginUrl('github'), 'http://localhost:3000/api/auth/github');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
31
55
|
test('getSocialAuthSetCookieParts', async (t) => {
|
|
32
56
|
await t.test('prefers setCookieHeaders array', () => {
|
|
33
57
|
const r = {
|