@doany-ai/sdk 0.2.1 → 0.2.3
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/README.md +1 -0
- package/dist/client.js +2 -0
- package/dist/client.types.d.ts +3 -0
- package/dist/index.d.ts +1 -0
- package/dist/modules/payments.d.ts +13 -0
- package/dist/modules/payments.js +47 -0
- package/dist/modules/payments.types.d.ts +228 -0
- package/dist/modules/payments.types.js +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -46,6 +46,7 @@ const client = createClient({
|
|
|
46
46
|
- `auth` — login, tokens, current user
|
|
47
47
|
- `users` — user management
|
|
48
48
|
- `integrations` — built-in integrations
|
|
49
|
+
- `payments` — take card payments through Stripe
|
|
49
50
|
- `connectors` — third-party connectors
|
|
50
51
|
- `functions` — invoke backend functions
|
|
51
52
|
- `agents` — agent conversations
|
package/dist/client.js
CHANGED
|
@@ -6,6 +6,7 @@ import { createSsoModule } from "./modules/sso.js";
|
|
|
6
6
|
import { createConnectorsModule, createUserConnectorsModule, } from "./modules/connectors.js";
|
|
7
7
|
import { getAccessToken } from "./utils/auth-utils.js";
|
|
8
8
|
import { createFunctionsModule } from "./modules/functions.js";
|
|
9
|
+
import { createPaymentsModule } from "./modules/payments.js";
|
|
9
10
|
import { createAgentsModule } from "./modules/agents.js";
|
|
10
11
|
import { createAiGatewayModule } from "./modules/ai-gateway.js";
|
|
11
12
|
import { createAppLogsModule } from "./modules/app-logs.js";
|
|
@@ -138,6 +139,7 @@ export function createClient(config) {
|
|
|
138
139
|
getSocket,
|
|
139
140
|
}),
|
|
140
141
|
integrations: createIntegrationsModule(axiosClient, appId),
|
|
142
|
+
payments: createPaymentsModule(axiosClient, appId),
|
|
141
143
|
connectors: createUserConnectorsModule(axiosClient, appId),
|
|
142
144
|
auth: userAuthModule,
|
|
143
145
|
functions: createFunctionsModule(functionsAxiosClient, appId, {
|
package/dist/client.types.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AuthModule } from "./modules/auth.types.js";
|
|
|
4
4
|
import type { SsoModule } from "./modules/sso.types.js";
|
|
5
5
|
import type { ConnectorsModule, UserConnectorsModule } from "./modules/connectors.types.js";
|
|
6
6
|
import type { FunctionsModule } from "./modules/functions.types.js";
|
|
7
|
+
import type { PaymentsModule } from "./modules/payments.types.js";
|
|
7
8
|
import type { AgentsModule } from "./modules/agents.types.js";
|
|
8
9
|
import type { AiGatewayModule } from "./modules/ai-gateway.types.js";
|
|
9
10
|
import type { AppLogsModule } from "./modules/app-logs.types.js";
|
|
@@ -98,6 +99,8 @@ export interface DoanyClient {
|
|
|
98
99
|
functions: FunctionsModule;
|
|
99
100
|
/** {@link IntegrationsModule | Integrations module} for calling pre-built integration endpoints. */
|
|
100
101
|
integrations: IntegrationsModule;
|
|
102
|
+
/** {@link PaymentsModule | Payments module} for taking card payments through Stripe. */
|
|
103
|
+
payments: PaymentsModule;
|
|
101
104
|
/** Cleanup function to disconnect WebSocket connections. Call when you're done with the client. */
|
|
102
105
|
cleanup: () => void;
|
|
103
106
|
/**
|
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export type { FunctionsModule, FunctionName, FunctionNameRegistry, } from "./mod
|
|
|
11
11
|
export type { AgentsModule, AgentName, AgentNameRegistry, AgentConversation, AgentMessage, AgentMessageReasoning, AgentMessageToolCall, AgentMessageUsage, AgentMessageCustomContext, AgentMessageMetadata, CreateConversationParams, } from "./modules/agents.types.js";
|
|
12
12
|
export type { AiGatewayModule, AiGatewayConnection, } from "./modules/ai-gateway.types.js";
|
|
13
13
|
export type { AppLogsModule } from "./modules/app-logs.types.js";
|
|
14
|
+
export type { PaymentsModule, CheckoutLineItem, CreateCheckoutParams, CreateCheckoutResult, CheckoutSession, SubscriptionState, BillingPortalParams, } from "./modules/payments.types.js";
|
|
14
15
|
export type { SsoModule, SsoAccessTokenResponse } from "./modules/sso.types.js";
|
|
15
16
|
export type { ConnectorsModule, UserConnectorsModule, } from "./modules/connectors.types.js";
|
|
16
17
|
export type { CustomIntegrationsModule, CustomIntegrationCallParams, CustomIntegrationCallResponse, } from "./modules/custom-integrations.types.js";
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { AxiosInstance } from "axios";
|
|
2
|
+
import { PaymentsModule } from "./payments.types";
|
|
3
|
+
/**
|
|
4
|
+
* Creates the payments module for the Doany SDK.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately thin: every decision that could go wrong — which Stripe
|
|
7
|
+
* environment, which account, where the customer is sent back to — is made by
|
|
8
|
+
* the backend from the request's own origin. Nothing here can override it, so
|
|
9
|
+
* generated app code cannot accidentally charge a real card from a preview.
|
|
10
|
+
*
|
|
11
|
+
* @internal
|
|
12
|
+
*/
|
|
13
|
+
export declare function createPaymentsModule(axios: AxiosInstance, appId: string): PaymentsModule;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates the payments module for the Doany SDK.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately thin: every decision that could go wrong — which Stripe
|
|
5
|
+
* environment, which account, where the customer is sent back to — is made by
|
|
6
|
+
* the backend from the request's own origin. Nothing here can override it, so
|
|
7
|
+
* generated app code cannot accidentally charge a real card from a preview.
|
|
8
|
+
*
|
|
9
|
+
* @internal
|
|
10
|
+
*/
|
|
11
|
+
export function createPaymentsModule(axios, appId) {
|
|
12
|
+
// This client's response interceptor resolves requests to the response body.
|
|
13
|
+
// Axios's declared return type does not reflect that, so the results below
|
|
14
|
+
// are cast through `unknown`.
|
|
15
|
+
return {
|
|
16
|
+
async createCheckoutSession(params) {
|
|
17
|
+
const data = await axios.request({
|
|
18
|
+
method: "POST",
|
|
19
|
+
url: `/apps/${appId}/payments/checkout-session`,
|
|
20
|
+
data: params,
|
|
21
|
+
});
|
|
22
|
+
return data;
|
|
23
|
+
},
|
|
24
|
+
async getSubscription(subscriptionId) {
|
|
25
|
+
const data = await axios.request({
|
|
26
|
+
method: "GET",
|
|
27
|
+
url: `/apps/${appId}/payments/subscription/${encodeURIComponent(subscriptionId)}`,
|
|
28
|
+
});
|
|
29
|
+
return data;
|
|
30
|
+
},
|
|
31
|
+
async createBillingPortalSession(params) {
|
|
32
|
+
const data = await axios.request({
|
|
33
|
+
method: "POST",
|
|
34
|
+
url: `/apps/${appId}/payments/billing-portal`,
|
|
35
|
+
data: params,
|
|
36
|
+
});
|
|
37
|
+
return data;
|
|
38
|
+
},
|
|
39
|
+
async getCheckoutSession(sessionId) {
|
|
40
|
+
const data = await axios.request({
|
|
41
|
+
method: "GET",
|
|
42
|
+
url: `/apps/${appId}/payments/checkout-session/${encodeURIComponent(sessionId)}`,
|
|
43
|
+
});
|
|
44
|
+
return data;
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One thing being sold in a checkout.
|
|
3
|
+
*
|
|
4
|
+
* A line item names a record in your own app data — doany keeps your catalog
|
|
5
|
+
* there rather than as objects inside Stripe, which is why switching from test
|
|
6
|
+
* to real payments needs no migration.
|
|
7
|
+
*/
|
|
8
|
+
export type CheckoutLineItem = {
|
|
9
|
+
/**
|
|
10
|
+
* The `id` of a record in this app's `Product` entity.
|
|
11
|
+
*
|
|
12
|
+
* The price, name and currency all come from that record — you cannot pass
|
|
13
|
+
* a price. This endpoint is reachable by anyone (a shop has to sell to
|
|
14
|
+
* visitors who never signed in), so an amount taken from the caller would
|
|
15
|
+
* let a buyer set their own.
|
|
16
|
+
*/
|
|
17
|
+
product_id: string;
|
|
18
|
+
/** Defaults to 1. */
|
|
19
|
+
quantity?: number;
|
|
20
|
+
};
|
|
21
|
+
export type CreateCheckoutParams = {
|
|
22
|
+
line_items: CheckoutLineItem[];
|
|
23
|
+
/**
|
|
24
|
+
* Three-letter code, lowercase. Defaults to `usd`.
|
|
25
|
+
*
|
|
26
|
+
* A `currency` on the `Product` record wins over this, and a product without
|
|
27
|
+
* one takes this value. All line items in one checkout must end up on the
|
|
28
|
+
* same currency — Stripe puts it on the session, not the line item — or the
|
|
29
|
+
* call is refused with `MIXED_CURRENCY_BASKET`.
|
|
30
|
+
*/
|
|
31
|
+
currency?: string;
|
|
32
|
+
/**
|
|
33
|
+
* Where the customer lands after paying, as a path on your own site.
|
|
34
|
+
*
|
|
35
|
+
* Must be a path, not a full URL — the origin is added for you, which is
|
|
36
|
+
* what guarantees a checkout started in the preview comes back to the
|
|
37
|
+
* preview rather than to your published site.
|
|
38
|
+
*
|
|
39
|
+
* `session_id` is appended so the page can confirm the payment.
|
|
40
|
+
*/
|
|
41
|
+
success_path?: string;
|
|
42
|
+
/** Where the customer lands if they abandon checkout. Also a path. */
|
|
43
|
+
cancel_path?: string;
|
|
44
|
+
/** Prefills the email field. Skip it and Stripe asks. */
|
|
45
|
+
customer_email?: string;
|
|
46
|
+
/** Your own data, returned with the order. Up to 20 keys. */
|
|
47
|
+
metadata?: Record<string, string>;
|
|
48
|
+
};
|
|
49
|
+
export type CreateCheckoutResult = {
|
|
50
|
+
id: string;
|
|
51
|
+
/** Send the customer here. */
|
|
52
|
+
url: string;
|
|
53
|
+
/** `test` in the preview, `live` on a published site that has gone live. */
|
|
54
|
+
mode: "test" | "live";
|
|
55
|
+
/**
|
|
56
|
+
* What the customer is about to agree to, decided by the catalog.
|
|
57
|
+
*
|
|
58
|
+
* `subscription` when the products carry a `recurring_interval`, `payment`
|
|
59
|
+
* otherwise. Say the right word on the button: "Subscribe" over a one-off
|
|
60
|
+
* charge, or "Buy" over a recurring one, is a complaint waiting to happen.
|
|
61
|
+
*/
|
|
62
|
+
billing: "payment" | "subscription";
|
|
63
|
+
/** `month` / `year` / `week` for a subscription, `null` for a one-off. */
|
|
64
|
+
interval: string | null;
|
|
65
|
+
};
|
|
66
|
+
export type SubscriptionState = {
|
|
67
|
+
id: string;
|
|
68
|
+
mode: "test" | "live";
|
|
69
|
+
/** Stripe's own vocabulary: active / trialing / past_due / canceled / unpaid /
|
|
70
|
+
* incomplete / incomplete_expired / paused. */
|
|
71
|
+
status: string;
|
|
72
|
+
/** THE field to gate access on — true for `active` and `trialing`. */
|
|
73
|
+
active: boolean;
|
|
74
|
+
/** Cancelled, but the period they already paid for is still running.
|
|
75
|
+
* Keep access until `current_period_end`. */
|
|
76
|
+
cancel_at_period_end: boolean;
|
|
77
|
+
current_period_end: number | null;
|
|
78
|
+
canceled_at: number | null;
|
|
79
|
+
customer_id: string | null;
|
|
80
|
+
interval: string | null;
|
|
81
|
+
amount: number | null;
|
|
82
|
+
currency: string | null;
|
|
83
|
+
metadata: Record<string, string>;
|
|
84
|
+
};
|
|
85
|
+
export type BillingPortalParams = {
|
|
86
|
+
/** From `getCheckoutSession(...).customer_id`. Store it when they subscribe. */
|
|
87
|
+
customer_id: string;
|
|
88
|
+
/** Where Stripe returns them, as a path on your own site. */
|
|
89
|
+
return_path?: string;
|
|
90
|
+
};
|
|
91
|
+
export type CheckoutSession = {
|
|
92
|
+
id: string;
|
|
93
|
+
mode: "test" | "live";
|
|
94
|
+
/** The one field a success page should branch on. */
|
|
95
|
+
paid: boolean;
|
|
96
|
+
payment_status: string;
|
|
97
|
+
amount_total: number | null;
|
|
98
|
+
currency: string | null;
|
|
99
|
+
customer_email: string | null;
|
|
100
|
+
/**
|
|
101
|
+
* Whether the money came back, in three honest states.
|
|
102
|
+
*
|
|
103
|
+
* Computed from the amounts rather than from Stripe's own `refunded` flag,
|
|
104
|
+
* which is only true for a FULL refund — an app reading that flag keeps
|
|
105
|
+
* serving someone who was refunded most of their money.
|
|
106
|
+
*
|
|
107
|
+
* Re-read this before granting access to anything valuable; a checkout that
|
|
108
|
+
* was `paid` in March can be `full` in April.
|
|
109
|
+
*/
|
|
110
|
+
refund_status: "none" | "partial" | "full";
|
|
111
|
+
/** Minor units, same scale as `amount_total`. */
|
|
112
|
+
amount_refunded: number;
|
|
113
|
+
/** The customer went to their bank. Money is already gone, whatever the
|
|
114
|
+
* refund status says. */
|
|
115
|
+
disputed: boolean;
|
|
116
|
+
/** `subscription` when this checkout started a plan. */
|
|
117
|
+
billing: "payment" | "subscription";
|
|
118
|
+
/** Present for a subscription checkout. Store it — it is what reads the
|
|
119
|
+
* plan's state on every later page load. */
|
|
120
|
+
subscription_id: string | null;
|
|
121
|
+
/** Store this too: it is what opens the billing portal so the customer can
|
|
122
|
+
* cancel, without the app being given any power over subscriptions. */
|
|
123
|
+
customer_id: string | null;
|
|
124
|
+
line_items: Array<{
|
|
125
|
+
name: string | null;
|
|
126
|
+
quantity: number | null;
|
|
127
|
+
amount_total: number | null;
|
|
128
|
+
}>;
|
|
129
|
+
metadata: Record<string, string>;
|
|
130
|
+
};
|
|
131
|
+
/**
|
|
132
|
+
* Take card payments on your site.
|
|
133
|
+
*
|
|
134
|
+
* Money goes to the app owner's own Stripe account — doany never holds it and
|
|
135
|
+
* takes no cut. Stripe's usual per-transaction fee applies.
|
|
136
|
+
*
|
|
137
|
+
* ## Prices live in your data, not in your code
|
|
138
|
+
*
|
|
139
|
+
* Sellable things are records in this app's `Product` entity, with the price in
|
|
140
|
+
* an integer `price_cents` field. A checkout names the product; the server
|
|
141
|
+
* looks up what it costs.
|
|
142
|
+
*
|
|
143
|
+
* ## Test and real payments
|
|
144
|
+
*
|
|
145
|
+
* Which one you get is decided by the address the site is served from, not by
|
|
146
|
+
* anything in your code:
|
|
147
|
+
*
|
|
148
|
+
* - **Preview** — test payments. Card `4242 4242 4242 4242` with any future
|
|
149
|
+
* expiry runs the whole flow and charges nothing.
|
|
150
|
+
* - **Published site** — real payments, once the owner has gone live from the
|
|
151
|
+
* Payments panel. Before that, {@linkcode PaymentsModule.createCheckoutSession}
|
|
152
|
+
* fails with `PAYMENTS_NOT_LIVE` rather than quietly taking no money.
|
|
153
|
+
*
|
|
154
|
+
* The same code covers both. There is no key to configure and no mode to set.
|
|
155
|
+
*/
|
|
156
|
+
export interface PaymentsModule {
|
|
157
|
+
/**
|
|
158
|
+
* Opens a Stripe checkout and returns the URL to send the customer to.
|
|
159
|
+
*
|
|
160
|
+
* @example Sell one item
|
|
161
|
+
* ```typescript
|
|
162
|
+
* // The price comes from the Product record, not from this call.
|
|
163
|
+
* const { url } = await doany.payments.createCheckoutSession({
|
|
164
|
+
* line_items: [{ product_id: product.id, quantity: 1 }],
|
|
165
|
+
* success_path: '/thanks',
|
|
166
|
+
* cancel_path: '/shop',
|
|
167
|
+
* metadata: { order_id: order.id },
|
|
168
|
+
* });
|
|
169
|
+
* window.location.href = url;
|
|
170
|
+
* ```
|
|
171
|
+
*/
|
|
172
|
+
createCheckoutSession(params: CreateCheckoutParams): Promise<CreateCheckoutResult>;
|
|
173
|
+
/**
|
|
174
|
+
* Reads a checkout back — what a success page calls to confirm the payment.
|
|
175
|
+
*
|
|
176
|
+
* Confirm here rather than waiting for a webhook: the customer is already
|
|
177
|
+
* looking at your thank-you page, and the webhook may not have arrived.
|
|
178
|
+
*
|
|
179
|
+
* @example Confirm on the success page, once
|
|
180
|
+
* ```typescript
|
|
181
|
+
* const sessionId = new URLSearchParams(location.search).get('session_id');
|
|
182
|
+
* const session = await doany.payments.getCheckoutSession(sessionId);
|
|
183
|
+
* if (session.paid) {
|
|
184
|
+
* // Key the fulfilment on session.id so a refresh cannot deliver twice.
|
|
185
|
+
* await Order.update(session.metadata.order_id, { status: 'paid' });
|
|
186
|
+
* }
|
|
187
|
+
* ```
|
|
188
|
+
*/
|
|
189
|
+
getCheckoutSession(sessionId: string): Promise<CheckoutSession>;
|
|
190
|
+
/**
|
|
191
|
+
* Is this plan still active?
|
|
192
|
+
*
|
|
193
|
+
* Call it on every page that gates something behind a plan. A subscription
|
|
194
|
+
* outlives the checkout that created it and changes without your app doing
|
|
195
|
+
* anything — renewals, a failed card, a cancellation made in the portal — so
|
|
196
|
+
* a value stored at signup goes stale on its own.
|
|
197
|
+
*
|
|
198
|
+
* @example Gate a page
|
|
199
|
+
* ```typescript
|
|
200
|
+
* const plan = await doany.payments.getSubscription(user.subscription_id);
|
|
201
|
+
* if (!plan.active) return <Upgrade />;
|
|
202
|
+
* if (plan.cancel_at_period_end) {
|
|
203
|
+
* // still paid up — keep access, but say when it ends
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
getSubscription(subscriptionId: string): Promise<SubscriptionState>;
|
|
208
|
+
/**
|
|
209
|
+
* Opens Stripe's own page for cancelling, switching plan, or fixing a card.
|
|
210
|
+
*
|
|
211
|
+
* This is why there is no `cancelSubscription` here: cancelling happens on
|
|
212
|
+
* Stripe's hosted page, so no endpoint in your app can modify a
|
|
213
|
+
* subscription, and a bug in your code cannot cancel somebody's plan. What
|
|
214
|
+
* the page offers is configured by the app owner in their Stripe dashboard.
|
|
215
|
+
*
|
|
216
|
+
* @example A "Manage billing" button
|
|
217
|
+
* ```typescript
|
|
218
|
+
* const { url } = await doany.payments.createBillingPortalSession({
|
|
219
|
+
* customer_id: user.stripe_customer_id,
|
|
220
|
+
* return_path: '/account',
|
|
221
|
+
* });
|
|
222
|
+
* window.location.href = url;
|
|
223
|
+
* ```
|
|
224
|
+
*/
|
|
225
|
+
createBillingPortalSession(params: BillingPortalParams): Promise<{
|
|
226
|
+
url: string;
|
|
227
|
+
}>;
|
|
228
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|