@doany-ai/sdk 0.2.9-alpha.0 → 0.3.0-alpha.0

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.
@@ -1,3 +1,4 @@
1
+ import type { Page, PageParams } from "./project.types";
1
2
  /**
2
3
  * One thing being sold in a checkout.
3
4
  *
@@ -409,4 +410,149 @@ export interface PaymentsModule {
409
410
  createBillingPortalSession(params: BillingPortalParams): Promise<{
410
411
  url: string;
411
412
  }>;
413
+ /**
414
+ * The way to pay for an order: an embedded Stripe checkout (`ui_mode`
415
+ * `embedded`, the default) or a hosted payment page (`hosted`, returns
416
+ * `url` — e.g. a payment link to send). One checkout per order at a time:
417
+ * asking again with the same options answers with the open one.
418
+ *
419
+ * Sends the order's access token kept by `orders.create`. Rejects with 404
420
+ * when the caller may not see the order, 409 `ENVIRONMENT_MISMATCH` when the
421
+ * page's environment (preview / published) is not the order's, 409
422
+ * `PAYMENT_UNRESOLVED` while an earlier payment is still being confirmed,
423
+ * 409 `INVALID_STATE` once the order no longer takes payment.
424
+ *
425
+ * @example
426
+ * ```typescript
427
+ * const { client_secret, publishable_key, stripe_account } =
428
+ * await doany.payments.checkout(order.id, { success_path: `/orders/${order.id}` });
429
+ * ```
430
+ */
431
+ checkout(orderId: string, params?: OrderCheckoutParams, options?: {
432
+ accessToken?: string;
433
+ }): Promise<OrderCheckout>;
434
+ /**
435
+ * Where an order's payment stands — what a "thank you" page polls: show
436
+ * success once `order_status` is `placed` (or `completed`), "confirming"
437
+ * while it is `pending`, and "not paid" when `expired` or `canceled`.
438
+ * Usually readable 1–3 seconds after paying. Sends the order's access token.
439
+ */
440
+ getForOrder(orderId: string, options?: {
441
+ accessToken?: string;
442
+ }): Promise<OrderPayments>;
443
+ /** Payments of the business. The site's admin and service callers only. */
444
+ list(params?: PaymentListParams): Promise<Page<Payment>>;
445
+ /** One payment, with its refunds. The site's admin and service callers only. */
446
+ get(paymentId: string): Promise<Payment>;
447
+ }
448
+ /** How to open an order's checkout. */
449
+ export interface OrderCheckoutParams {
450
+ /** `embedded` (default): Stripe's form inside the page. `hosted`: Stripe's page, see `url`. */
451
+ ui_mode?: "embedded" | "hosted";
452
+ /** The site to come back to; defaults to the one the order was placed on. */
453
+ app_id?: string;
454
+ /** A path on the site (`/…`, no `#`, no access token), default `/`. */
455
+ success_path?: string;
456
+ cancel_path?: string;
457
+ }
458
+ /** An order's open checkout. */
459
+ export interface OrderCheckout {
460
+ /** The payment this checkout is. */
461
+ id: string;
462
+ status: string;
463
+ expires_at: string | null;
464
+ amount_total: number;
465
+ currency: string;
466
+ ui_mode: "embedded" | "hosted";
467
+ /** `embedded`: to load Stripe's form in the page. */
468
+ client_secret: string | null;
469
+ publishable_key: string | null;
470
+ stripe_account: string | null;
471
+ /** `hosted`: the payment page. */
472
+ url: string | null;
473
+ mode: "test" | "live";
474
+ }
475
+ /** The money of one order. */
476
+ export interface OrderPayments {
477
+ order_id: string;
478
+ order_status: string;
479
+ currency: string;
480
+ total_amount: number;
481
+ /** What successful payments took. */
482
+ amount_received: number;
483
+ /** What successful refunds gave back — kept apart, never netted. */
484
+ amount_refunded: number;
485
+ payments: Array<{
486
+ id: string;
487
+ status: PaymentStatus;
488
+ requested_amount: number;
489
+ received_amount: number;
490
+ payment_method_type: string | null;
491
+ paid_at: string | null;
492
+ }>;
493
+ refunds: Array<{
494
+ id: string;
495
+ payment_id: string;
496
+ status: RefundStatus;
497
+ amount: number;
498
+ succeeded_at: string | null;
499
+ }>;
500
+ }
501
+ /** `failed` is not final: the same checkout can be paid with another card. */
502
+ export type PaymentStatus = "pending" | "processing" | "requires_action" | "authorized" | "succeeded" | "failed" | "canceled";
503
+ export type RefundStatus = "requested" | "pending" | "succeeded" | "failed" | "canceled";
504
+ export interface Refund {
505
+ id: string;
506
+ payment_id: string;
507
+ amount: number;
508
+ currency: string;
509
+ status: RefundStatus;
510
+ /** Refunds are made in the payment dashboard and synced here (`external`). */
511
+ origin: "doany" | "external";
512
+ reason: string | null;
513
+ failure_code: string | null;
514
+ provider_refund_id: string | null;
515
+ provider_status: string | null;
516
+ succeeded_at: string | null;
517
+ last_synced_at: string | null;
518
+ created_at: string | null;
519
+ version: number;
520
+ }
521
+ export interface Payment {
522
+ id: string;
523
+ order_id: string | null;
524
+ contact_id: string | null;
525
+ status: PaymentStatus;
526
+ requested_amount: number;
527
+ received_amount: number;
528
+ currency: string;
529
+ livemode: boolean;
530
+ /** How it was collected; `null` for a payment recorded by hand. */
531
+ ui_mode: "embedded" | "hosted" | "direct" | "terminal" | null;
532
+ /** The site the customer returns to. */
533
+ app_id: string | null;
534
+ payment_method_type: string | null;
535
+ has_dispute: boolean;
536
+ /** Taken back by a lost dispute. */
537
+ amount_disputed: number;
538
+ provider: string | null;
539
+ provider_account_id: string | null;
540
+ provider_payment_id: string | null;
541
+ provider_charge_id: string | null;
542
+ provider_status: string | null;
543
+ last_error_code: string | null;
544
+ paid_at: string | null;
545
+ last_synced_at: string | null;
546
+ created_at: string | null;
547
+ version: number;
548
+ /** Only in `payments.get`. */
549
+ refunds?: Refund[];
550
+ }
551
+ export interface PaymentListParams extends PageParams {
552
+ order_id?: string;
553
+ contact_id?: string;
554
+ status?: PaymentStatus;
555
+ has_dispute?: boolean;
556
+ created_from?: string;
557
+ created_to?: string;
412
558
  }
@@ -0,0 +1,31 @@
1
+ import { AxiosInstance } from "axios";
2
+ import { AppModule } from "./project.types";
3
+ /**
4
+ * Where a site's business data lives: `/projects/{project_id}/...`.
5
+ *
6
+ * A site knows its app id; the project it belongs to comes from its public
7
+ * settings, read the first time a business module needs it and then kept.
8
+ * `createClient({ projectId })` skips the read.
9
+ *
10
+ * @internal
11
+ */
12
+ export interface ProjectScope {
13
+ /** The project id, as given or as read from the public settings. */
14
+ projectId(): Promise<string>;
15
+ /** `/projects/{project_id}` + `path`. */
16
+ path(path: string): Promise<string>;
17
+ app: AppModule;
18
+ }
19
+ /** @internal */
20
+ export declare function createProjectScope(axios: AxiosInstance, appId: string, projectId?: string): ProjectScope;
21
+ /** `encodeURIComponent` for one path segment. @internal */
22
+ export declare const seg: (value: string) => string;
23
+ /**
24
+ * A query string from the params that were actually given: `undefined` and
25
+ * `null` are left out, booleans become `"true"` / `"false"`.
26
+ *
27
+ * @internal
28
+ */
29
+ export declare function queryOf(params: object | undefined): Record<string, string | number>;
30
+ /** The Idempotency-Key header, when one was given. @internal */
31
+ export declare function idempotencyHeaders(key?: string): Record<string, string>;
@@ -0,0 +1,52 @@
1
+ /** @internal */
2
+ export function createProjectScope(axios, appId, projectId) {
3
+ let settings = null;
4
+ function getPublicSettings() {
5
+ if (!settings) {
6
+ const reading = axios.get(`/apps/public/prod/public-settings/by-id/${encodeURIComponent(appId)}`);
7
+ // A failed read is not kept: the next call tries again.
8
+ settings = reading.catch((error) => {
9
+ settings = null;
10
+ throw error;
11
+ });
12
+ }
13
+ return settings;
14
+ }
15
+ async function resolveProjectId() {
16
+ if (projectId)
17
+ return projectId;
18
+ const read = await getPublicSettings();
19
+ if (!(read === null || read === void 0 ? void 0 : read.project_id)) {
20
+ throw new Error("This app's public settings carry no project_id; pass projectId to createClient()");
21
+ }
22
+ return read.project_id;
23
+ }
24
+ return {
25
+ projectId: resolveProjectId,
26
+ async path(path) {
27
+ return `/projects/${encodeURIComponent(await resolveProjectId())}${path}`;
28
+ },
29
+ app: { getPublicSettings },
30
+ };
31
+ }
32
+ /** `encodeURIComponent` for one path segment. @internal */
33
+ export const seg = (value) => encodeURIComponent(value);
34
+ /**
35
+ * A query string from the params that were actually given: `undefined` and
36
+ * `null` are left out, booleans become `"true"` / `"false"`.
37
+ *
38
+ * @internal
39
+ */
40
+ export function queryOf(params) {
41
+ const out = {};
42
+ for (const [key, value] of Object.entries(params !== null && params !== void 0 ? params : {})) {
43
+ if (value === undefined || value === null || value === "")
44
+ continue;
45
+ out[key] = typeof value === "boolean" ? String(value) : value;
46
+ }
47
+ return out;
48
+ }
49
+ /** The Idempotency-Key header, when one was given. @internal */
50
+ export function idempotencyHeaders(key) {
51
+ return key ? { "Idempotency-Key": key } : {};
52
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * One page of a list. Every `/projects/...` list answers in this shape.
3
+ *
4
+ * Pass `next_cursor` back as `cursor` to get the next page; it is `null` on the
5
+ * last one. Change the sort and you start again from the first page.
6
+ */
7
+ export interface Page<T> {
8
+ data: T[];
9
+ has_more: boolean;
10
+ next_cursor: string | null;
11
+ }
12
+ /** What every `/projects/...` list accepts, besides its own filters. */
13
+ export interface PageParams {
14
+ /** 1–200, default 50. */
15
+ limit?: number;
16
+ /** The `next_cursor` of the previous page. */
17
+ cursor?: string;
18
+ /** A field name, `-` in front for descending. Each list documents its fields. */
19
+ sort?: string;
20
+ }
21
+ /** The version a change is based on: send back the `version` you last read. */
22
+ export interface VersionInput {
23
+ version: number;
24
+ }
25
+ export interface CreateOptions {
26
+ /**
27
+ * Makes a retry of the same create answer with the first record instead of
28
+ * making a second one (16–255 characters). Optional.
29
+ */
30
+ idempotencyKey?: string;
31
+ }
32
+ /** An address: every part may be empty; `country` is two upper-case letters. */
33
+ export interface Address {
34
+ line: string | null;
35
+ city: string | null;
36
+ region: string | null;
37
+ postal_code: string | null;
38
+ country: string | null;
39
+ }
40
+ /** What a site reads about itself before anything else. */
41
+ export interface AppPublicSettings {
42
+ /** The app (site) id. */
43
+ id: string;
44
+ /** The business the site belongs to: the `/projects/{project_id}/...` APIs are under it. */
45
+ project_id: string;
46
+ /** Whatever the site stored; `null` when it stored nothing. */
47
+ public_settings: unknown;
48
+ }
49
+ /**
50
+ * The site itself.
51
+ */
52
+ export interface AppModule {
53
+ /**
54
+ * The site's public settings, including the `project_id` its business data
55
+ * lives under. Read once per client and kept.
56
+ */
57
+ getPublicSettings(): Promise<AppPublicSettings>;
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,16 +1,10 @@
1
1
  import { AxiosInstance } from "axios";
2
+ import { ProjectScope } from "./project.js";
3
+ import { UsersModule } from "./users.types";
2
4
  /**
3
- * Creates the users module for the Doany SDK
4
- * @param {AxiosInstance} axios - Axios instance
5
- * @param {string} appId - Application ID
6
- * @returns {Object} Users module
5
+ * Creates the users module: inviting (`/apps/{app_id}/...`) and account
6
+ * management (`/projects/{project_id}/users/...`).
7
+ *
8
+ * @internal
7
9
  */
8
- export declare function createUsersModule(axios: AxiosInstance, appId: string): {
9
- /**
10
- * Invite a user to the application
11
- * @param {string} user_email - User's email address
12
- * @param {'user'|'admin'} role - User's role (user or admin)
13
- * @returns {Promise<any>}
14
- */
15
- inviteUser(user_email: string, role: "user" | "admin"): Promise<any>;
16
- };
10
+ export declare function createUsersModule(axios: AxiosInstance, appId: string, project: ProjectScope): UsersModule;
@@ -1,17 +1,13 @@
1
+ import { queryOf, seg } from "./project.js";
1
2
  /**
2
- * Creates the users module for the Doany SDK
3
- * @param {AxiosInstance} axios - Axios instance
4
- * @param {string} appId - Application ID
5
- * @returns {Object} Users module
3
+ * Creates the users module: inviting (`/apps/{app_id}/...`) and account
4
+ * management (`/projects/{project_id}/users/...`).
5
+ *
6
+ * @internal
6
7
  */
7
- export function createUsersModule(axios, appId) {
8
+ export function createUsersModule(axios, appId, project) {
9
+ const users = (suffix = "") => project.path(`/users${suffix}`);
8
10
  return {
9
- /**
10
- * Invite a user to the application
11
- * @param {string} user_email - User's email address
12
- * @param {'user'|'admin'} role - User's role (user or admin)
13
- * @returns {Promise<any>}
14
- */
15
11
  async inviteUser(user_email, role) {
16
12
  if (role !== "user" && role !== "admin") {
17
13
  throw new Error(`Invalid role: "${role}". Role must be either "user" or "admin".`);
@@ -19,5 +15,22 @@ export function createUsersModule(axios, appId) {
19
15
  const response = await axios.post(`/apps/${appId}/runtime/users/invite-user`, { user_email, role });
20
16
  return response;
21
17
  },
18
+ async list(params = {}) {
19
+ return (await axios.get(await users(), { params: queryOf(params) }));
20
+ },
21
+ async get(userId) {
22
+ return (await axios.get(await users(`/${seg(userId)}`)));
23
+ },
24
+ async disable(userId) {
25
+ return (await axios.request({ method: "POST", url: await users(`/${seg(userId)}/disable`) }));
26
+ },
27
+ async enable(userId) {
28
+ return (await axios.request({ method: "POST", url: await users(`/${seg(userId)}/enable`) }));
29
+ },
30
+ async setContact(userId, contactId) {
31
+ return (await axios.put(await users(`/${seg(userId)}/contact`), {
32
+ contact_id: contactId,
33
+ }));
34
+ },
22
35
  };
23
36
  }
@@ -0,0 +1,62 @@
1
+ import type { Page, PageParams } from "./project.types";
2
+ /**
3
+ * An account of the site's business, as account management shows it: the
4
+ * account's public fields (plus any custom fields saved with `auth.updateMe`)
5
+ * and how it signs in.
6
+ */
7
+ export interface UserRecord {
8
+ id: string;
9
+ email: string;
10
+ full_name: string | null;
11
+ /** `user`, `admin`, or a role the site defines. */
12
+ role: string;
13
+ /** Whether the email is verified. */
14
+ verified: boolean;
15
+ created_date: string | null;
16
+ updated_date: string | null;
17
+ status: "active" | "disabled";
18
+ /** `unknown`: an account migrated without a password. */
19
+ created_via: "email" | "oauth" | "invite" | "preview" | "unknown";
20
+ /** The account's own contact; may be empty until its email is verified. */
21
+ contact_id: string | null;
22
+ /** The site it signed up on. */
23
+ app_id: string | null;
24
+ last_signed_in_at: string | null;
25
+ /** e.g. `["email", "google"]`. */
26
+ login_methods: string[];
27
+ /** Custom fields saved with `auth.updateMe`. */
28
+ [field: string]: unknown;
29
+ }
30
+ export interface UserListParams extends PageParams {
31
+ /** Part of the email or name. */
32
+ q?: string;
33
+ status?: "active" | "disabled";
34
+ /** Accounts linked to this contact. */
35
+ contact_id?: string;
36
+ /** Accounts that signed up on this site. */
37
+ app_id?: string;
38
+ }
39
+ /**
40
+ * The site's accounts.
41
+ *
42
+ * `list` / `get` show a signed-in account only itself; the site's admin and
43
+ * service callers see every account. Disabling, enabling and setting the
44
+ * contact take the site's admin account or a service credential.
45
+ */
46
+ export interface UsersModule {
47
+ /**
48
+ * Invites someone by email; they set a password from the link they get.
49
+ * Only the site's admin (or a service caller) may invite an admin.
50
+ */
51
+ inviteUser(user_email: string, role: "user" | "admin"): Promise<any>;
52
+ list(params?: UserListParams): Promise<Page<UserRecord>>;
53
+ get(userId: string): Promise<UserRecord>;
54
+ /** The account can no longer sign in, and the tokens it holds stop working. */
55
+ disable(userId: string): Promise<UserRecord>;
56
+ enable(userId: string): Promise<UserRecord>;
57
+ /**
58
+ * Makes a contact the account's own (it sees that contact's orders and buys
59
+ * as it); `null` unlinks. For correcting an automatic link that went wrong.
60
+ */
61
+ setContact(userId: string, contactId: string | null): Promise<UserRecord>;
62
+ }
@@ -0,0 +1 @@
1
+ export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doany-ai/sdk",
3
- "version": "0.2.9-alpha.0",
3
+ "version": "0.3.0-alpha.0",
4
4
  "description": "JavaScript SDK for the doany app platform (API-compatible fork of @base44/sdk)",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",